From c69cb8cc094cc916adbc516b09e944cd3d137c01 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 13:08:07 +0200 Subject: Adding upstream version 1.29.3. Signed-off-by: Daniel Baumann --- web/Makefile.am | 22 + web/README.md | 236 + web/api/Makefile.am | 21 + web/api/README.md | 14 + web/api/badges/Makefile.am | 8 + web/api/badges/README.md | 363 + web/api/badges/web_buffer_svg.c | 1140 ++ web/api/badges/web_buffer_svg.h | 16 + web/api/exporters/Makefile.am | 13 + web/api/exporters/README.md | 10 + web/api/exporters/allmetrics.c | 127 + web/api/exporters/allmetrics.h | 12 + web/api/exporters/prometheus/Makefile.am | 8 + web/api/exporters/prometheus/README.md | 10 + web/api/exporters/shell/Makefile.am | 8 + web/api/exporters/shell/README.md | 71 + web/api/exporters/shell/allmetrics_shell.c | 161 + web/api/exporters/shell/allmetrics_shell.h | 21 + web/api/formatters/Makefile.am | 15 + web/api/formatters/README.md | 78 + web/api/formatters/charts2json.c | 192 + web/api/formatters/charts2json.h | 12 + web/api/formatters/csv/Makefile.am | 8 + web/api/formatters/csv/README.md | 144 + web/api/formatters/csv/csv.c | 143 + web/api/formatters/csv/csv.h | 12 + web/api/formatters/json/Makefile.am | 8 + web/api/formatters/json/README.md | 156 + web/api/formatters/json/json.c | 248 + web/api/formatters/json/json.h | 10 + web/api/formatters/json_wrapper.c | 295 + web/api/formatters/json_wrapper.h | 11 + web/api/formatters/rrd2json.c | 401 + web/api/formatters/rrd2json.h | 92 + web/api/formatters/rrdset2json.c | 161 + web/api/formatters/rrdset2json.h | 10 + web/api/formatters/ssv/Makefile.am | 8 + web/api/formatters/ssv/README.md | 59 + web/api/formatters/ssv/ssv.c | 45 + web/api/formatters/ssv/ssv.h | 10 + web/api/formatters/value/Makefile.am | 8 + web/api/formatters/value/README.md | 24 + web/api/formatters/value/value.c | 94 + web/api/formatters/value/value.h | 10 + web/api/health/Makefile.am | 8 + web/api/health/README.md | 225 + web/api/health/health_cmdapi.c | 205 + web/api/health/health_cmdapi.h | 31 + web/api/netdata-swagger.json | 2065 ++++ web/api/netdata-swagger.yaml | 1611 +++ web/api/queries/Makefile.am | 20 + web/api/queries/README.md | 176 + web/api/queries/average/Makefile.am | 8 + web/api/queries/average/README.md | 46 + web/api/queries/average/average.c | 62 + web/api/queries/average/average.h | 15 + web/api/queries/des/Makefile.am | 8 + web/api/queries/des/README.md | 73 + web/api/queries/des/des.c | 139 + web/api/queries/des/des.h | 17 + web/api/queries/incremental_sum/Makefile.am | 8 + web/api/queries/incremental_sum/README.md | 41 + web/api/queries/incremental_sum/incremental_sum.c | 69 + web/api/queries/incremental_sum/incremental_sum.h | 15 + web/api/queries/max/Makefile.am | 8 + web/api/queries/max/README.md | 38 + web/api/queries/max/max.c | 60 + web/api/queries/max/max.h | 15 + web/api/queries/median/Makefile.am | 8 + web/api/queries/median/README.md | 45 + web/api/queries/median/median.c | 79 + web/api/queries/median/median.h | 15 + web/api/queries/min/Makefile.am | 8 + web/api/queries/min/README.md | 38 + web/api/queries/min/min.c | 60 + web/api/queries/min/min.h | 15 + web/api/queries/query.c | 1636 +++ web/api/queries/query.h | 24 + web/api/queries/rrdr.c | 144 + web/api/queries/rrdr.h | 115 + web/api/queries/ses/Makefile.am | 8 + web/api/queries/ses/README.md | 61 + web/api/queries/ses/ses.c | 92 + web/api/queries/ses/ses.h | 17 + web/api/queries/stddev/Makefile.am | 8 + web/api/queries/stddev/README.md | 93 + web/api/queries/stddev/stddev.c | 176 + web/api/queries/stddev/stddev.h | 18 + web/api/queries/sum/Makefile.am | 8 + web/api/queries/sum/README.md | 41 + web/api/queries/sum/sum.c | 58 + web/api/queries/sum/sum.h | 15 + web/api/tests/valid_urls.c | 790 ++ web/api/tests/web_api.c | 474 + web/api/web_api_v1.c | 1063 ++ web/api/web_api_v1.h | 36 + web/gui/.well-known/dnt/cookies | 14 + web/gui/Makefile.am | 189 + web/gui/README.md | 169 + web/gui/browserconfig.xml | 2 + web/gui/confluence/README.md | 1019 ++ web/gui/console.html | 72 + web/gui/css/bootstrap-3.3.7.css | 6758 ++++++++++++ web/gui/css/bootstrap-slate-flat-3.3.7.css | 7101 +++++++++++++ web/gui/css/bootstrap-slider-10.0.0.min.css | 22 + web/gui/css/bootstrap-theme-3.3.7.min.css | 7 + web/gui/css/bootstrap-toggle-2.2.2.min.css | 29 + web/gui/css/c3-0.4.18.min.css | 2 + web/gui/css/morris-0.5.1.css | 3 + web/gui/custom/README.md | 663 ++ web/gui/dash-example.html | 1028 ++ web/gui/dashboard.css | 757 ++ web/gui/dashboard.html | 699 ++ web/gui/dashboard.js | 10377 +++++++++++++++++++ web/gui/dashboard.slate.css | 775 ++ web/gui/dashboard_info.js | 3781 +++++++ web/gui/dashboard_info_custom_example.js | 62 + web/gui/demo.html | 51 + web/gui/demo2.html | 143 + web/gui/demosites.html | 1469 +++ web/gui/demosites2.html | 1112 ++ web/gui/favicon.ico | Bin 0 -> 1150 bytes web/gui/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes web/gui/fonts/glyphicons-halflings-regular.svg | 289 + web/gui/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes web/gui/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes web/gui/fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes web/gui/goto-host-from-alarm.html | 251 + web/gui/images/alert-128-orange.png | Bin 0 -> 3477 bytes web/gui/images/alert-128-red.png | Bin 0 -> 3743 bytes web/gui/images/alert-multi-size-orange.ico | Bin 0 -> 112374 bytes web/gui/images/alert-multi-size-red.ico | Bin 0 -> 112458 bytes web/gui/images/android-icon-144x144.png | Bin 0 -> 2721 bytes web/gui/images/android-icon-192x192.png | Bin 0 -> 3923 bytes web/gui/images/android-icon-36x36.png | Bin 0 -> 539 bytes web/gui/images/android-icon-48x48.png | Bin 0 -> 762 bytes web/gui/images/android-icon-72x72.png | Bin 0 -> 1153 bytes web/gui/images/android-icon-96x96.png | Bin 0 -> 1747 bytes web/gui/images/animated.gif | Bin 0 -> 389597 bytes web/gui/images/apple-icon-114x114.png | Bin 0 -> 3651 bytes web/gui/images/apple-icon-120x120.png | Bin 0 -> 2315 bytes web/gui/images/apple-icon-144x144.png | Bin 0 -> 4001 bytes web/gui/images/apple-icon-152x152.png | Bin 0 -> 5026 bytes web/gui/images/apple-icon-180x180.png | Bin 0 -> 3645 bytes web/gui/images/apple-icon-57x57.png | Bin 0 -> 1250 bytes web/gui/images/apple-icon-60x60.png | Bin 0 -> 1052 bytes web/gui/images/apple-icon-72x72.png | Bin 0 -> 1427 bytes web/gui/images/apple-icon-76x76.png | Bin 0 -> 1711 bytes web/gui/images/apple-icon-precomposed.png | Bin 0 -> 3926 bytes web/gui/images/apple-icon.png | Bin 0 -> 3926 bytes web/gui/images/banner-icon-144x144.png | Bin 0 -> 2724 bytes web/gui/images/check-mark-2-128-green.png | Bin 0 -> 3771 bytes web/gui/images/check-mark-2-multi-size-green.ico | Bin 0 -> 111893 bytes web/gui/images/favicon-128.png | Bin 0 -> 2436 bytes web/gui/images/favicon-16x16.png | Bin 0 -> 285 bytes web/gui/images/favicon-196x196.png | Bin 0 -> 10025 bytes web/gui/images/favicon-32x32.png | Bin 0 -> 454 bytes web/gui/images/favicon-96x96.png | Bin 0 -> 1925 bytes web/gui/images/favicon.ico | Bin 0 -> 1150 bytes web/gui/images/ms-icon-144x144.png | Bin 0 -> 4001 bytes web/gui/images/ms-icon-150x150.png | Bin 0 -> 2867 bytes web/gui/images/ms-icon-310x150.png | Bin 0 -> 3632 bytes web/gui/images/ms-icon-310x310.png | Bin 0 -> 7215 bytes web/gui/images/ms-icon-36x36.png | Bin 0 -> 536 bytes web/gui/images/ms-icon-70x70.png | Bin 0 -> 2436 bytes web/gui/images/netdata-logomark.svg | 8 + web/gui/images/netdata.svg | 18 + web/gui/images/packaging-beta-tag.svg | 42 + web/gui/images/post.png | Bin 0 -> 9043 bytes web/gui/images/seo-performance-128.png | Bin 0 -> 1828 bytes web/gui/index.html | 1322 +++ web/gui/infographic.html | 171 + web/gui/lib/bootstrap-3.3.7.min.js | 8 + web/gui/lib/bootstrap-slider-10.0.0.min.js | 6 + web/gui/lib/bootstrap-table-1.11.0.min.js | 9 + web/gui/lib/bootstrap-table-export-1.11.0.min.js | 8 + web/gui/lib/bootstrap-toggle-2.2.2.min.js | 10 + web/gui/lib/clipboard-polyfill-be05dad.js | 9 + web/gui/lib/d3-4.12.2.min.js | 3 + web/gui/lib/d3pie-0.2.1-netdata-3.js | 2124 ++++ web/gui/lib/dygraph-c91c859.min.js | 7 + web/gui/lib/dygraph-smooth-plotter-c91c859.js | 141 + web/gui/lib/fontawesome-all-5.0.1.min.js | 6 + web/gui/lib/gauge-1.3.2.min.js | 2 + web/gui/lib/jquery-2.2.4.min.js | 5 + web/gui/lib/jquery.easypiechart-97b5824.min.js | 10 + web/gui/lib/jquery.peity-3.2.0.min.js | 14 + web/gui/lib/jquery.sparkline-2.1.2.min.js | 6 + web/gui/lib/lz-string-1.4.4.min.js | 2 + web/gui/lib/pako-1.0.6.min.js | 2 + web/gui/lib/perfect-scrollbar-0.6.15.min.js | 3 + web/gui/lib/tableExport-1.6.0.min.js | 55 + web/gui/main.css | 764 ++ web/gui/main.js | 5155 +++++++++ web/gui/manifest.json | 41 + web/gui/old/index.html | 1321 +++ web/gui/refresh-badges.js | 98 + web/gui/robots.txt | 7 + web/gui/sitemap.xml | 9 + web/gui/src/dashboard.js/alarms.js | 422 + web/gui/src/dashboard.js/boot.js | 142 + web/gui/src/dashboard.js/chart-registry.js | 94 + web/gui/src/dashboard.js/charting.js | 492 + web/gui/src/dashboard.js/charting/_c3.js | 114 + web/gui/src/dashboard.js/charting/_morris.js | 81 + web/gui/src/dashboard.js/charting/_raphael.js | 48 + web/gui/src/dashboard.js/charting/d3.js | 43 + web/gui/src/dashboard.js/charting/d3pie.js | 341 + web/gui/src/dashboard.js/charting/dygraph.js | 1065 ++ .../src/dashboard.js/charting/easy-pie-chart.js | 281 + web/gui/src/dashboard.js/charting/gauge.js | 406 + web/gui/src/dashboard.js/charting/google-charts.js | 132 + web/gui/src/dashboard.js/charting/peity.js | 62 + web/gui/src/dashboard.js/charting/sparkline.js | 155 + web/gui/src/dashboard.js/charting/textonly.js | 18 + web/gui/src/dashboard.js/colors.js | 34 + web/gui/src/dashboard.js/common.js | 249 + web/gui/src/dashboard.js/compatibility.js | 31 + web/gui/src/dashboard.js/dependencies.js | 21 + web/gui/src/dashboard.js/epilogue.js.inc | 1 + web/gui/src/dashboard.js/error-handling.js | 52 + web/gui/src/dashboard.js/localstorage.js | 173 + web/gui/src/dashboard.js/main.js | 4279 ++++++++ web/gui/src/dashboard.js/options.js | 225 + web/gui/src/dashboard.js/prologue.js.inc | 84 + web/gui/src/dashboard.js/registry.js | 311 + web/gui/src/dashboard.js/server-detection.js | 29 + web/gui/src/dashboard.js/themes.js | 92 + web/gui/src/dashboard.js/timeout.js | 100 + web/gui/src/dashboard.js/units-conversion.js | 528 + web/gui/src/dashboard.js/utils.js | 432 + web/gui/src/dashboard.js/xss.js | 84 + web/gui/static/img/netdata-logomark.svg | 3 + web/gui/tv.html | 279 + web/server/Makefile.am | 12 + web/server/README.md | 245 + web/server/static/Makefile.am | 11 + web/server/static/README.md | 17 + web/server/static/static-threaded.c | 503 + web/server/static/static-threaded.h | 10 + web/server/web_client.c | 2035 ++++ web/server/web_client.h | 217 + web/server/web_client_cache.c | 269 + web/server/web_client_cache.h | 31 + web/server/web_server.c | 159 + web/server/web_server.h | 64 + 246 files changed, 77329 insertions(+) create mode 100644 web/Makefile.am create mode 100644 web/README.md create mode 100644 web/api/Makefile.am create mode 100644 web/api/README.md create mode 100644 web/api/badges/Makefile.am create mode 100644 web/api/badges/README.md create mode 100644 web/api/badges/web_buffer_svg.c create mode 100644 web/api/badges/web_buffer_svg.h create mode 100644 web/api/exporters/Makefile.am create mode 100644 web/api/exporters/README.md create mode 100644 web/api/exporters/allmetrics.c create mode 100644 web/api/exporters/allmetrics.h create mode 100644 web/api/exporters/prometheus/Makefile.am create mode 100644 web/api/exporters/prometheus/README.md create mode 100644 web/api/exporters/shell/Makefile.am create mode 100644 web/api/exporters/shell/README.md create mode 100644 web/api/exporters/shell/allmetrics_shell.c create mode 100644 web/api/exporters/shell/allmetrics_shell.h create mode 100644 web/api/formatters/Makefile.am create mode 100644 web/api/formatters/README.md create mode 100644 web/api/formatters/charts2json.c create mode 100644 web/api/formatters/charts2json.h create mode 100644 web/api/formatters/csv/Makefile.am create mode 100644 web/api/formatters/csv/README.md create mode 100644 web/api/formatters/csv/csv.c create mode 100644 web/api/formatters/csv/csv.h create mode 100644 web/api/formatters/json/Makefile.am create mode 100644 web/api/formatters/json/README.md create mode 100644 web/api/formatters/json/json.c create mode 100644 web/api/formatters/json/json.h create mode 100644 web/api/formatters/json_wrapper.c create mode 100644 web/api/formatters/json_wrapper.h create mode 100644 web/api/formatters/rrd2json.c create mode 100644 web/api/formatters/rrd2json.h create mode 100644 web/api/formatters/rrdset2json.c create mode 100644 web/api/formatters/rrdset2json.h create mode 100644 web/api/formatters/ssv/Makefile.am create mode 100644 web/api/formatters/ssv/README.md create mode 100644 web/api/formatters/ssv/ssv.c create mode 100644 web/api/formatters/ssv/ssv.h create mode 100644 web/api/formatters/value/Makefile.am create mode 100644 web/api/formatters/value/README.md create mode 100644 web/api/formatters/value/value.c create mode 100644 web/api/formatters/value/value.h create mode 100644 web/api/health/Makefile.am create mode 100644 web/api/health/README.md create mode 100644 web/api/health/health_cmdapi.c create mode 100644 web/api/health/health_cmdapi.h create mode 100644 web/api/netdata-swagger.json create mode 100644 web/api/netdata-swagger.yaml create mode 100644 web/api/queries/Makefile.am create mode 100644 web/api/queries/README.md create mode 100644 web/api/queries/average/Makefile.am create mode 100644 web/api/queries/average/README.md create mode 100644 web/api/queries/average/average.c create mode 100644 web/api/queries/average/average.h create mode 100644 web/api/queries/des/Makefile.am create mode 100644 web/api/queries/des/README.md create mode 100644 web/api/queries/des/des.c create mode 100644 web/api/queries/des/des.h create mode 100644 web/api/queries/incremental_sum/Makefile.am create mode 100644 web/api/queries/incremental_sum/README.md create mode 100644 web/api/queries/incremental_sum/incremental_sum.c create mode 100644 web/api/queries/incremental_sum/incremental_sum.h create mode 100644 web/api/queries/max/Makefile.am create mode 100644 web/api/queries/max/README.md create mode 100644 web/api/queries/max/max.c create mode 100644 web/api/queries/max/max.h create mode 100644 web/api/queries/median/Makefile.am create mode 100644 web/api/queries/median/README.md create mode 100644 web/api/queries/median/median.c create mode 100644 web/api/queries/median/median.h create mode 100644 web/api/queries/min/Makefile.am create mode 100644 web/api/queries/min/README.md create mode 100644 web/api/queries/min/min.c create mode 100644 web/api/queries/min/min.h create mode 100644 web/api/queries/query.c create mode 100644 web/api/queries/query.h create mode 100644 web/api/queries/rrdr.c create mode 100644 web/api/queries/rrdr.h create mode 100644 web/api/queries/ses/Makefile.am create mode 100644 web/api/queries/ses/README.md create mode 100644 web/api/queries/ses/ses.c create mode 100644 web/api/queries/ses/ses.h create mode 100644 web/api/queries/stddev/Makefile.am create mode 100644 web/api/queries/stddev/README.md create mode 100644 web/api/queries/stddev/stddev.c create mode 100644 web/api/queries/stddev/stddev.h create mode 100644 web/api/queries/sum/Makefile.am create mode 100644 web/api/queries/sum/README.md create mode 100644 web/api/queries/sum/sum.c create mode 100644 web/api/queries/sum/sum.h create mode 100644 web/api/tests/valid_urls.c create mode 100644 web/api/tests/web_api.c create mode 100644 web/api/web_api_v1.c create mode 100644 web/api/web_api_v1.h create mode 100644 web/gui/.well-known/dnt/cookies create mode 100644 web/gui/Makefile.am create mode 100644 web/gui/README.md create mode 100644 web/gui/browserconfig.xml create mode 100644 web/gui/confluence/README.md create mode 100644 web/gui/console.html create mode 100644 web/gui/css/bootstrap-3.3.7.css create mode 100644 web/gui/css/bootstrap-slate-flat-3.3.7.css create mode 100644 web/gui/css/bootstrap-slider-10.0.0.min.css create mode 100644 web/gui/css/bootstrap-theme-3.3.7.min.css create mode 100644 web/gui/css/bootstrap-toggle-2.2.2.min.css create mode 100644 web/gui/css/c3-0.4.18.min.css create mode 100644 web/gui/css/morris-0.5.1.css create mode 100644 web/gui/custom/README.md create mode 100644 web/gui/dash-example.html create mode 100644 web/gui/dashboard.css create mode 100644 web/gui/dashboard.html create mode 100644 web/gui/dashboard.js create mode 100644 web/gui/dashboard.slate.css create mode 100644 web/gui/dashboard_info.js create mode 100644 web/gui/dashboard_info_custom_example.js create mode 100644 web/gui/demo.html create mode 100644 web/gui/demo2.html create mode 100644 web/gui/demosites.html create mode 100644 web/gui/demosites2.html create mode 100644 web/gui/favicon.ico create mode 100644 web/gui/fonts/glyphicons-halflings-regular.eot create mode 100644 web/gui/fonts/glyphicons-halflings-regular.svg create mode 100644 web/gui/fonts/glyphicons-halflings-regular.ttf create mode 100644 web/gui/fonts/glyphicons-halflings-regular.woff create mode 100644 web/gui/fonts/glyphicons-halflings-regular.woff2 create mode 100644 web/gui/goto-host-from-alarm.html create mode 100644 web/gui/images/alert-128-orange.png create mode 100644 web/gui/images/alert-128-red.png create mode 100644 web/gui/images/alert-multi-size-orange.ico create mode 100644 web/gui/images/alert-multi-size-red.ico create mode 100644 web/gui/images/android-icon-144x144.png create mode 100644 web/gui/images/android-icon-192x192.png create mode 100644 web/gui/images/android-icon-36x36.png create mode 100644 web/gui/images/android-icon-48x48.png create mode 100644 web/gui/images/android-icon-72x72.png create mode 100644 web/gui/images/android-icon-96x96.png create mode 100644 web/gui/images/animated.gif create mode 100644 web/gui/images/apple-icon-114x114.png create mode 100644 web/gui/images/apple-icon-120x120.png create mode 100644 web/gui/images/apple-icon-144x144.png create mode 100644 web/gui/images/apple-icon-152x152.png create mode 100644 web/gui/images/apple-icon-180x180.png create mode 100644 web/gui/images/apple-icon-57x57.png create mode 100644 web/gui/images/apple-icon-60x60.png create mode 100644 web/gui/images/apple-icon-72x72.png create mode 100644 web/gui/images/apple-icon-76x76.png create mode 100644 web/gui/images/apple-icon-precomposed.png create mode 100644 web/gui/images/apple-icon.png create mode 100644 web/gui/images/banner-icon-144x144.png create mode 100644 web/gui/images/check-mark-2-128-green.png create mode 100644 web/gui/images/check-mark-2-multi-size-green.ico create mode 100644 web/gui/images/favicon-128.png create mode 100644 web/gui/images/favicon-16x16.png create mode 100644 web/gui/images/favicon-196x196.png create mode 100644 web/gui/images/favicon-32x32.png create mode 100644 web/gui/images/favicon-96x96.png create mode 100644 web/gui/images/favicon.ico create mode 100644 web/gui/images/ms-icon-144x144.png create mode 100644 web/gui/images/ms-icon-150x150.png create mode 100644 web/gui/images/ms-icon-310x150.png create mode 100644 web/gui/images/ms-icon-310x310.png create mode 100644 web/gui/images/ms-icon-36x36.png create mode 100644 web/gui/images/ms-icon-70x70.png create mode 100644 web/gui/images/netdata-logomark.svg create mode 100644 web/gui/images/netdata.svg create mode 100644 web/gui/images/packaging-beta-tag.svg create mode 100644 web/gui/images/post.png create mode 100644 web/gui/images/seo-performance-128.png create mode 100644 web/gui/index.html create mode 100644 web/gui/infographic.html create mode 100644 web/gui/lib/bootstrap-3.3.7.min.js create mode 100644 web/gui/lib/bootstrap-slider-10.0.0.min.js create mode 100644 web/gui/lib/bootstrap-table-1.11.0.min.js create mode 100644 web/gui/lib/bootstrap-table-export-1.11.0.min.js create mode 100644 web/gui/lib/bootstrap-toggle-2.2.2.min.js create mode 100644 web/gui/lib/clipboard-polyfill-be05dad.js create mode 100644 web/gui/lib/d3-4.12.2.min.js create mode 100644 web/gui/lib/d3pie-0.2.1-netdata-3.js create mode 100644 web/gui/lib/dygraph-c91c859.min.js create mode 100644 web/gui/lib/dygraph-smooth-plotter-c91c859.js create mode 100644 web/gui/lib/fontawesome-all-5.0.1.min.js create mode 100644 web/gui/lib/gauge-1.3.2.min.js create mode 100644 web/gui/lib/jquery-2.2.4.min.js create mode 100644 web/gui/lib/jquery.easypiechart-97b5824.min.js create mode 100644 web/gui/lib/jquery.peity-3.2.0.min.js create mode 100644 web/gui/lib/jquery.sparkline-2.1.2.min.js create mode 100644 web/gui/lib/lz-string-1.4.4.min.js create mode 100644 web/gui/lib/pako-1.0.6.min.js create mode 100644 web/gui/lib/perfect-scrollbar-0.6.15.min.js create mode 100644 web/gui/lib/tableExport-1.6.0.min.js create mode 100644 web/gui/main.css create mode 100644 web/gui/main.js create mode 100644 web/gui/manifest.json create mode 100644 web/gui/old/index.html create mode 100644 web/gui/refresh-badges.js create mode 100644 web/gui/robots.txt create mode 100644 web/gui/sitemap.xml create mode 100644 web/gui/src/dashboard.js/alarms.js create mode 100644 web/gui/src/dashboard.js/boot.js create mode 100644 web/gui/src/dashboard.js/chart-registry.js create mode 100644 web/gui/src/dashboard.js/charting.js create mode 100644 web/gui/src/dashboard.js/charting/_c3.js create mode 100644 web/gui/src/dashboard.js/charting/_morris.js create mode 100644 web/gui/src/dashboard.js/charting/_raphael.js create mode 100644 web/gui/src/dashboard.js/charting/d3.js create mode 100644 web/gui/src/dashboard.js/charting/d3pie.js create mode 100644 web/gui/src/dashboard.js/charting/dygraph.js create mode 100644 web/gui/src/dashboard.js/charting/easy-pie-chart.js create mode 100644 web/gui/src/dashboard.js/charting/gauge.js create mode 100644 web/gui/src/dashboard.js/charting/google-charts.js create mode 100644 web/gui/src/dashboard.js/charting/peity.js create mode 100644 web/gui/src/dashboard.js/charting/sparkline.js create mode 100644 web/gui/src/dashboard.js/charting/textonly.js create mode 100644 web/gui/src/dashboard.js/colors.js create mode 100644 web/gui/src/dashboard.js/common.js create mode 100644 web/gui/src/dashboard.js/compatibility.js create mode 100644 web/gui/src/dashboard.js/dependencies.js create mode 100644 web/gui/src/dashboard.js/epilogue.js.inc create mode 100644 web/gui/src/dashboard.js/error-handling.js create mode 100644 web/gui/src/dashboard.js/localstorage.js create mode 100644 web/gui/src/dashboard.js/main.js create mode 100644 web/gui/src/dashboard.js/options.js create mode 100644 web/gui/src/dashboard.js/prologue.js.inc create mode 100644 web/gui/src/dashboard.js/registry.js create mode 100644 web/gui/src/dashboard.js/server-detection.js create mode 100644 web/gui/src/dashboard.js/themes.js create mode 100644 web/gui/src/dashboard.js/timeout.js create mode 100644 web/gui/src/dashboard.js/units-conversion.js create mode 100644 web/gui/src/dashboard.js/utils.js create mode 100644 web/gui/src/dashboard.js/xss.js create mode 100644 web/gui/static/img/netdata-logomark.svg create mode 100644 web/gui/tv.html create mode 100644 web/server/Makefile.am create mode 100644 web/server/README.md create mode 100644 web/server/static/Makefile.am create mode 100644 web/server/static/README.md create mode 100644 web/server/static/static-threaded.c create mode 100644 web/server/static/static-threaded.h create mode 100644 web/server/web_client.c create mode 100644 web/server/web_client.h create mode 100644 web/server/web_client_cache.c create mode 100644 web/server/web_client_cache.h create mode 100644 web/server/web_server.c create mode 100644 web/server/web_server.h (limited to 'web') diff --git a/web/Makefile.am b/web/Makefile.am new file mode 100644 index 0000000..ccaccd7 --- /dev/null +++ b/web/Makefile.am @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + api \ + gui \ + server \ + $(NULL) + +usersslconfigdir=$(configdir)/ssl + +# Explicitly install directories to avoid permission issues due to umask +install-exec-local: + $(INSTALL) -d $(DESTDIR)$(usersslconfigdir) + +dist_noinst_DATA = \ + README.md \ + gui/confluence/README.md \ + gui/custom/README.md \ + $(NULL) diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..fc1d371 --- /dev/null +++ b/web/README.md @@ -0,0 +1,236 @@ + + +# Dashboards + +Because Netdata is a health monitoring and _performance troubleshooting_ system, +we put a lot of emphasis on real-time, meaningful, and context-aware charts. + +We bundle Netdata with a dashboard and hundreds of charts, designed by both our +team and the community, but you can also customize them yourself. + +There are two primary ways to view Netdata's dashboards: + +1. The [local Agent dashboard](/web/gui/README.md) that comes pre-configured with every Netdata installation. You can + see it at `http://NODE:19999`, replacing `NODE` with `localhost`, the hostname of your node, or its IP address. You + can customize the contents and colors of the standard dashboard [using + JavaScript](/web/gui/README.md#customizing-the-standard-dashboard). + +2. The [`dashboard.js` JavaScript library](#dashboardjs), which helps you + [customize the standard dashboards](/web/gui/README.md#customizing-the-standard-dashboard) + using JavaScript, or create entirely new [custom dashboards](/web/gui/custom/README.md) or + [Atlassian Confluence dashboards](/web/gui/confluence/README.md). + +You can also view all the data Netdata collects through the [REST API v1](/web/api/). + +No matter where you use Netdata's charts, you'll want to know how to [use](#using-charts) them. You'll also want to +understand how Netdata defines [charts](#charts), [dimensions](#dimensions), [families](#families), and +[contexts](#contexts). + +## Using charts + +Netdata's charts are far from static. They are interactive, real-time, and work +with your mouse, touchpad, or touchscreen! + +Hover over any chart to temporarily pause it and see the exact values presented +as different [dimensions](#dimensions). Click or tap stop the chart from automatically updating with new metrics, thereby locking it to a single timeframe. + +![Animated GIF of hovering over a chart to see +values](https://user-images.githubusercontent.com/1153921/62968279-9227dd00-bdbf-11e9-9112-1d21444d0f31.gif) + +You can change how charts show their metrics by zooming in or out, moving +forward or backward in time, or selecting a specific timeframe for more in-depth +analysis. + +Whenever you use a chart in this way, Netdata synchronizes all the other charts +to match it. + +You can change how charts show their metrics in a few different ways, each of +which have a few methods: + +| Manipulation | Method #1 | Method #2 | Method #3 | +|--- |--- |--- |--- | +| **Reset** charts to default auto-refreshing state  | `double click` | `double tap` (touchpad/touchscreen)  |  | +| **Select** a certain timeframe | `ALT` + `mouse selection`  | `⌘` + `mouse selection` (macOS)  |  | +| **Pan** forward or back in time  | `click and drag` | `touch and drag` (touchpad/touchscreen)  |  | +| **Zoom** to a specific timeframe | `SHIFT` + `mouse selection`  |  |  | +| **Zoom** in/out  | `SHIFT`/`ALT` + `mouse scrollwheel`  | `SHIFT`/`ALT` + `two-finger pinch` (touchpad/touchscreen)  | `SHIFT`/`ALT` + `two-finger scroll` (touchpad/touchscreen) | + +Here's how chart synchronization looks while zooming and panning: + +![Animated GIF of the standard Netdata dashboard being manipulated and synchronizing +charts](https://user-images.githubusercontent.com/1153921/80839230-b034a800-8baf-11ea-9cb2-99c1e10f0f85.gif) + +You can also perform all these actions using the small +rewind/play/fast-forward/zoom-in/zoom-out buttons that appear in the +bottom-right corner of each chart. + +Additionally, resize charts by clicking-and-dragging the icon on the bottom-right corner of any chart. To restore the +chart to its original height, double-click the same icon. + +![Animated GIF of resizing a chart and resetting it to the default +height](https://user-images.githubusercontent.com/1153921/80842459-7d41e280-8bb6-11ea-9488-1bc29f94d7f2.gif) + +## Charts, contexts, families + +Before customizing the standard web dashboard, creating a custom dashboard, +configuring an alarm, or writing a collector, it's crucial to understand how +Netdata organizes metrics into charts, dimensions, families, and contexts. + +### Charts + +A **chart** is an individual, interactive, always-updating graphic displaying +one or more collected/calculated metrics. Charts are generated by +[collectors](/collectors/README.md). + +Here's the system CPU chart, the first chart displayed on the standard +dashboard: + +![Screenshot of the system CPU chart in the Netdata +dashboard](https://user-images.githubusercontent.com/1153921/62720972-0b8a8e80-b9c0-11e9-930b-4829f7b17cfd.png) + +Netdata displays a chart's name in parentheses above the chart. For example, if +you navigate to the system CPU chart, you'll see the label: **Total CPU +utilization (system.cpu)**. In this case, the chart's name is `system.cpu`. +Netdata derives the name from the chart's [context](#contexts). + +### Dimensions + +A **dimension** is a value that gets shown on a chart. The value can be raw data +or calculated values, such as percentages, aggregates, and more. + +Charts are capable of showing more than one dimension. Netdata shows these +dimensions on the right side of the chart, beneath the date and time. Again, the +`system.cpu` chart will serve as a good example. + +![Screenshot of the dimensions shown in the system CPU chart in the Netdata +dashboard](https://user-images.githubusercontent.com/1153921/62721031-2bba4d80-b9c0-11e9-9dca-32403617ce72.png) + +Here, the `system.cpu` chart is showing many dimensions, such as `user`, +`system`, `softirq`, `irq`, and more. + +Note that other applications sometimes use the word _series_ instead of +_dimension_. + +### Families + +A **family** is _one_ instance of a monitored hardware or software resource that +needs to be monitored and displayed separately from similar instances. + +For example, if your system has multiple disk drives at `sda` and `sdb`, Netdata +will put each interface into their own family. Same goes for software resources, +like multiple MySQL instances. We call these instances "families" because the +charts associated with a single disk instance, for example, are often related to +each other. Relatives, family... get it? + +When relevant, Netdata prefers to organize charts by family. When you visit the +**Disks** section, you will see your disk drives organized into families, and +each family will have one or more charts: `disk`, `disk_ops`, `disk_backlog`, +`disk_util`, `disk_await`, `disk_avgsz`, `disk_svctm`, `disk_mops`, and +`disk_iotime`. + +In the screenshot below, the disk family `sdb` shows a few gauges, followed by a +few of the associated charts: + +![Screenshot of a disk drive family and associated charts in the Netdata +dashboard](https://user-images.githubusercontent.com/1153921/62721362-e34f5f80-b9c0-11e9-8d2e-9a3bec48e920.png) + +Netdata also creates separate submenu entries for each family in the right +navigation page so you can easily navigate to the instance you're interested in. +Here, Netdata has made several submenus under the **Disk** menu. + +![Screenshot of the disks menu and +submenus](https://user-images.githubusercontent.com/1153921/62721531-3cb78e80-b9c1-11e9-89c2-fdd736aec7d4.png) + +### Contexts + +A **context** is a way of grouping charts by the types of metrics collected and +dimensions displayed. Different charts with the same context will show the same +dimensions, but for different instances (families) of hardware/software +resources. + +For example, the **Disks** section will often use many contexts (`disk.io`, +`disk.ops`, `disk.backlog`, `disk.util`, and so on). Netdata then creates an +individual chart for each context, and groups them by family. + +Netdata names charts according to their context according to the following +structure: `[context].[family]`. A chart with the `disk.util` context, in the +`sdb` family, gets the name `disk_util.sdb`. Netdata shows that name in the +top-left corner of a chart. + +Given the four example contexts, and two families of `sdb` and `sdd`, Netdata +will create the following charts and their names: + +Context | `sdb` family | `sdd` family +--- | --- | --- +`disk.io` | `disk_io.sdb` | `disk_io.sdd` +`disk.ops` | `disk_ops.sdb` | `disk_ops.sdd` +`disk.backlog` | `disk_backlog.sdb` | `disk_backlog.sdd` +`disk.util` | `disk_util.sdb` | `disk_util.sdd` + +And here's what two of those charts in the `disk.io` context look like under +`sdb` and `sdd` families: + +![context_01](https://user-images.githubusercontent.com/1153921/62728232-177e4c80-b9d0-11e9-9e29-2a6c59d4d873.png) +![context_02](https://user-images.githubusercontent.com/1153921/62728234-1b11d380-b9d0-11e9-8904-07befd8ac592.png) + +As you can see in the screenshot, you can view the context of a chart if you +hover over the date above the list of dimensions. A tooltip will appear that +shows you two pieces of information: the collector that produces the chart, and +the chart's context. + +Netdata also uses [contexts for alarm templates](/health/REFERENCE.md#alarm-line-on). You can create an alarm for the +`net.packets` context to receive alerts for any chart with that context, no matter which family it's attached to. + +## Positive and negative values on charts + +To improve clarity on charts, Netdata dashboards present **positive** values for +metrics representing `read`, `input`, `inbound`, `received` and **negative** +values for metrics representing `write`, `output`, `outbound`, `sent`. + +![Screenshot showing positive and negative +values](https://user-images.githubusercontent.com/1153921/81870401-9d649080-952a-11ea-80e3-4a7b480252ee.gif) + +_Netdata charts showing the bandwidth and packets of a network interface. +`received` is positive and `sent` is negative._ + +## Autoscaled y-axis + +Netdata charts automatically zoom vertically, to visualize the variation of each +metric within the visible timeframe. + +![Animated GIF showing the auso-scaling Y +axis](https://user-images.githubusercontent.com/1153921/80838276-8084a080-8bad-11ea-8167-8d5ab2fb1be1.gif) + +_A zero-based `stacked` chart, automatically switches to an auto-scaled `area` +chart when a single dimension is selected._ + +## dashboard.js + +Netdata uses the `dashboards.js` file to define, configure, create, and update +all the charts and other visualizations that appear on any Netdata dashboard. +You need to put `dashboard.js` on any HTML page that's going to render Netdata +charts. + +The [custom dashboards documentation](/web/gui/custom/README.md) contains examples of such +custom HTML pages. + +### Generating dashboard.js + +We build the `dashboards.js` file by concatenating all the source files located +in the `web/gui/src/dashboard.js/` directory. That's done using the provided +build script: + +```sh +cd web/gui +make +``` + +If you make any changes to the `src` directory when developing Netdata, you +should regenerate the `dashboard.js` file before you commit to the Netdata +repository. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)]() diff --git a/web/api/Makefile.am b/web/api/Makefile.am new file mode 100644 index 0000000..7255ac8 --- /dev/null +++ b/web/api/Makefile.am @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + badges \ + queries \ + exporters \ + formatters \ + health \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(NULL) + +dist_web_DATA = \ + netdata-swagger.yaml \ + netdata-swagger.json \ + $(NULL) diff --git a/web/api/README.md b/web/api/README.md new file mode 100644 index 0000000..1cc3439 --- /dev/null +++ b/web/api/README.md @@ -0,0 +1,14 @@ + + +# API + +## Netdata REST API + +The complete documentation of the Netdata API is available at the **[Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml)**. + +If your prefer it over the Swagger Editor, you can also use **[Swagger UI](https://registry.my-netdata.io/swagger/#!/default/get_data)**. This however does not provide all the information available. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/badges/Makefile.am b/web/api/badges/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/badges/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/badges/README.md b/web/api/badges/README.md new file mode 100644 index 0000000..b5fc534 --- /dev/null +++ b/web/api/badges/README.md @@ -0,0 +1,363 @@ + + +# Netdata badges + +**Badges are cool!** + +Netdata can generate badges for any chart and any dimension at any time-frame. Badges come in `SVG` and can be added to any web page using an `` HTML tag. + +**Netdata badges are powerful**! + +Given that Netdata collects from **1.000** to **5.000** metrics per server (depending on the number of network interfaces, disks, cpu cores, applications running, users logged in, containers running, etc) and that Netdata already has data reduction/aggregation functions embedded, the badges can be quite powerful. + +For each metric/dimension and for arbitrary time-frames badges can show **min**, **max** or **average** value, but also **sum** or **incremental-sum** to have their **volume**. + +For example, there is [a chart in Netdata that shows the current requests/s of nginx](http://london.my-netdata.io/#nginx_local_nginx). Using this chart alone we can show the following badges (we could add more time-frames, like **today**, **yesterday**, etc): + + + +Similarly, there is [a chart that shows outbound bandwidth per class](http://london.my-netdata.io/#tc_eth0), using QoS data. So it shows `kilobits/s` per class. Using this chart we can show: + + + +The right one is a **volume** calculation. Netdata calculated the total of the last 86.400 seconds (a day) which gives `kilobits`, then divided it by 8 to make it KB, then by 1024 to make it MB and then by 1024 to make it GB. Calculations like this are quite accurate, since for every value collected, every second, Netdata interpolates it to second boundary using microsecond calculations. + +Let's see a few more badge examples (they come from the [Netdata registry](/registry/README.md)): + +- **cpu usage of user `root`** (you can pick any user; 100% = 1 core). This will be `green <10%`, `yellow <20%`, `orange <50%`, `blue <100%` (1 core), `red` otherwise (you define thresholds and colors on the URL). + + + +- **mysql queries per second** + + + + niche ones: **mysql SELECT statements with JOIN, which did full table scans**: + + + +--- + +> So, every single line on the charts of a [Netdata dashboard](http://london.my-netdata.io/), can become a badge and this badge can calculate **average**, **min**, **max**, or **volume** for any time-frame! And you can also vary the badge color using conditions on the calculated value. + +--- + +## How to create badges + +The basic URL is `http://your.netdata:19999/api/v1/badge.svg?option1&option2&option3&...`. + +Here is what you can put for `options` (these are standard Netdata API options): + +- `chart=CHART.NAME` + + The chart to get the values from. + + **This is the only parameter required** and with just this parameter, Netdata will return the sum of the latest values of all chart dimensions. + + Example: + +```html + + + +``` + + Which produces this: + + + + + +- `alarm=NAME` + + Render the current value and status of an alarm linked to the chart. This option can be ignored if the badge to be generated is not related to an alarm. + + The current value of the alarm will be rendered. The color of the badge will indicate the status of the alarm. + + For alarm badges, **both `chart` and `alarm` parameters are required**. + +- `dimensions=DIMENSION1|DIMENSION2|...` + + The dimensions of the chart to use. If you don't set any dimension, all will be used. When multiple dimensions are used, Netdata will sum their values. You can append `options=absolute` if you want this sum to convert all values to positive before adding them. + + Pipes in HTML have to escaped with `%7C`. + + Example: + +```html + + + +``` + + Which produces this: + + + + + +- `before=SECONDS` and `after=SECONDS` + + The timeframe. These can be absolute unix timestamps, or relative to now, number of seconds. By default `before=0` and `after=-1` (1 second in the past). + + To get the last minute set `after=-60`. This will give the average of the last complete minute (XX:XX:00 - XX:XX:59). + + To get the max of the last hour set `after=-3600&group=max`. This will give the maximum value of the last complete hour (XX:00:00 - XX:59:59) + + Example: + +```html + + + +``` + + Which produces the average of last complete minute (XX:XX:00 - XX:XX:59): + + + + + + While this is the previous minute (one minute before the last one, again aligned XX:XX:00 - XX:XX:59): + +```html + + + +``` + + It produces this: + + + + + +- `group=min` or `group=max` or `group=average` (the default) or `group=sum` or `group=incremental-sum` + + If Netdata will have to reduce (aggregate) the data to calculate the value, which aggregation method to use. + + - `max` will find the max value for the timeframe. This works on both positive and negative dimensions. It will find the most extreme value. + + - `min` will find the min value for the timeframe. This works on both positive and negative dimensions. It will find the number closest to zero. + + - `average` will calculate the average value for the timeframe. + + - `sum` will sum all the values for the timeframe. This is nice for finding the volume of dimensions for a timeframe. So if you have a dimension that reports `X per second`, you can find the volume of the dimension in a timeframe, by adding its values in that timeframe. + + - `incremental-sum` will sum the difference of each value to its next. Let's assume you have a dimension that does not measure the rate of something, but the absolute value of it. So it has values like this "1, 5, 3, 7, 4". `incremental-sum` will calculate the difference of adjacent values. In this example, they will be `(5 - 1) + (3 - 5) + (7 - 3) + (4 - 7) = 3` (which is equal to the last value minus the first = 4 - 1). + +- `options=opt1|opt2|opt3|...` + + These fine tune various options of the API. Here is what you can use for badges (the API has more option, but only these are useful for badges): + + - `percentage`, instead of returning the value, calculate the percentage of the sum of the selected dimensions, versus the sum of all the dimensions of the chart. This also sets the units to `%`. + + - `absolute` or `abs`, turn all values positive and then sum them. + + - `display_absolute` or `display-absolute`, to use the signed value during color calculation, but display the absolute value on the badge. + + - `min2max`, when multiple dimensions are given, do not sum them, but take their `max - min`. + + - `unaligned`, when data are reduced / aggregated (e.g. the request is about the average of the last minute, or hour), Netdata by default aligns them so that the charts will have a constant shape (so average per minute returns always XX:XX:00 - XX:XX:59). Setting the `unaligned` option, Netdata will aggregate data without any alignment, so if the request is for 60 seconds, it will aggregate the latest 60 seconds of collected data. + +These are options dedicated to badges: + +- `label=TEXT` + + The label of the badge. + +- `units=TEXT` + + The units of the badge. If you want to put a `/`, please put a `\`. This is because Netdata allows badges parameters to be given as path in URL, instead of query string. You can also use `null` or `empty` to show it without any units. + + The units `seconds`, `minutes` and `hours` trigger special formatting. The value has to be in this unit, and Netdata will automatically change it to show a more pretty duration. + +- `multiply=NUMBER` + + Multiply the value with this number. The default is `1`. + +- `divide=NUMBER` + + Divide the value with this number. The default is `1`. + +- Color customization parameters + + The following parameters specify colors of each individual part of the badge. Each parameter is documented in detail + below. + + | Area of badge | Background color parameter | Text color parameter | + | ---: | :------------------------: | :------------------: | + | Label (left) part | `label_color` | `text_color_lbl` | + | Value (right) part | `value_color` | `text_color_val` | + + - `label_color=COLOR` + + The color of the label (the left part). You can use any HTML color in `RGB` or `RRGGBB` hex notation (without + the `#` character at the beginning). Additionally, you can use one of the following predefined colors (and you + can use them by their name): + + - `green` + - `brightgreen` + - `yellow` + - `yellowgreen` + - `orange` + - `red` + - `blue` + - `grey` + - `gray` + - `lightgrey` + - `lightgray` + + These colors are taken from , which makes them compatible with standard + badges. + + - `value_color=COLOR:null|COLORVALUE|COLOR>=VALUE|COLOR<=VALUE|...` + + You can add a pipe delimited list of conditions to pick the value color. The first matching (left to right) will + be used. + + Example: `value_color=grey:null|green<10|yellow<100|orange<1000|blue<10000|red` + + The above will set `grey` if no value exists (not collected within the `gap when lost iterations above` in + `netdata.conf` for the chart), `green` if the value is less than 10, `yellow` if the value is less than 100, and + so on. Netdata will use `red` if no other conditions match. Only integers are supported as values. + + The supported operators are `<`, `>`, `<=`, `>=`, `=` (or `:`), and `!=` (or `<>`). + + You can also use the same syntax as the `label_color` parameter to define each of these colors. You can + reference a predefined color by name or `RGB`/`RRGGBB` hex notation. + + - `text_color_lbl=RGB` or `text_color_lbl=RRGGBB` or `text_color_lbl=color_by_name` + + This value specifies the font color for the font of left/label side of the badge. The syntax is the same as the + `label_color` parameter. If not given, or given with an empty value, Netdata will use the default color. + + - `text_color_val=RGB` or `text_color_val=RRGGBB` or `text_color_lbl=color_by_name` + + This value specifies the font color for the font of right/value side of the badge. The syntax is the same as the + `label_color` parameter. If not given, or given with an empty value, Netdata will use the default color. + +- `precision=NUMBER` + + The number of decimal digits of the value. By default Netdata will add: + + - no decimal digits for values > 1000 + - 1 decimal digit for values > 100 + - 2 decimal digits for values > 1 + - 3 decimal digits for values > 0.1 + - 4 decimal digits for values \<= 0.1 + + Using the `precision=NUMBER` you can set your preference per badge. + +- `scale=XXX` + + This option scales the svg image. It accepts values above or equal to 100 (100% is the default scale). For example, lets get a few different sizes: + + original
+ `scale=125`
+ `scale=150`
+ `scale=175`
+ `scale=200` + +- `fixed_width_lbl=NUMBER` and `fixed_width_val=NUMBER` + + This parameter overrides auto-sizing of badges and displays them at fixed widths. `fixed_width_lbl` determines the size of the label's left side (label/name). `fixed_width_val` determines the size of the the label's right side (value). You must set both parameters together, or they will be ignored. + + You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. + + The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`. + +- `refresh=auto` or `refresh=SECONDS` + + This option enables auto-refreshing of images. Netdata will send the HTTP header `Refresh: SECONDS` to the web browser, thus requesting automatic refresh of the images at regular intervals. + + `auto` will calculate the proper `SECONDS` to avoid unnecessary refreshes. If `SECONDS` is zero, this feature is disabled (it is also disabled by default). + + Auto-refreshing like this, works only if you access the badge directly. So, you may have to put it an `embed` or `iframe` for it to be auto-refreshed. Use something like this: + +```html + +``` + + Another way is to use javascript to auto-refresh them. You can auto-refresh all the Netdata badges on a page using javascript. You have to add a class to all the Netdata badges, like this ``. Then add this javascript code to your page (it requires jquery): + +```html + +``` + +A more advanced badges refresh method is to include `http://your.netdata.ip:19999/refresh-badges.js` in your page. + +--- + +## Escaping URLs + +Keep in mind that if you add badge URLs to your HTML pages you have to escape the special characters: + +|character|name|escape sequence| +|:-------:|:--:|:-------------:| +|``|space (in labels and units)|`%20`| +|`#`|hash (for colors)|`%23`| +|`%`|percent (in units)|`%25`| +|`<`|less than|`%3C`| +|`>`|greater than|`%3E`| +|`\`|backslash (when you need a `/`)|`%5C`| +|`\|`|pipe (delimiting parameters)|`%7C`| + +## FAQ + +#### Is it fast? + +On modern hardware, Netdata can generate about **2.000 badges per second per core**, before noticing any delays. It generates a badge in about half a millisecond! + +Of course these timing are for badges that use recent data. If you need badges that do calculations over long durations (a day, or more), timing will differ. Netdata logs its timings at its `access.log`, so take a look there before adding a heavy badge on a busy web site. Of course, you can cache such badges or have a cron job get them from Netdata and save them at your web server at regular intervals. + +#### Embedding badges in github + +You have 2 options a) SVG images with markdown and b) SVG images with HTML (directly in .md files). + +For example, this is the cpu badge shown above: + +- Markdown example: + +```md +[![A nice name](https://registry.my-netdata.io/api/v1/badge.svg?chart=users.cpu&dimensions=root&value_color=grey:null%7Cgreen%3C10%7Cyellow%3C20%7Corange%3C50%7Cblue%3C100%7Cred&label=root%20user%20cpu%20now&units=%25)](https://registry.my-netdata.io/#apps_cpu) +``` + +- HTML example: + +```html + + + +``` + +Both produce this: + + + + + +#### auto-refreshing badges in github + +Unfortunately it cannot be done. Github fetches all the images using a proxy and rewrites all the URLs to be served by the proxy. + +You can refresh them from your browser console though. Press F12 to open the web browser console (switch to the console too), paste the following and press enter. They will refresh: + +```js +var len = document.images.length; while(len--) { document.images[len].src = document.images[len].src.replace(/\?cacheBuster=\d*/, "") + "?cacheBuster=" + new Date().getTime().toString(); }; +``` + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fbadges%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c new file mode 100644 index 0000000..b5a1e03 --- /dev/null +++ b/web/api/badges/web_buffer_svg.c @@ -0,0 +1,1140 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "web_buffer_svg.h" + +#define BADGE_HORIZONTAL_PADDING 4 +#define VERDANA_KERNING 0.2 +#define VERDANA_PADDING 1.0 + +/* + * verdana11_widths[] has been generated with this method: + * https://github.com/badges/shields/blob/master/measure-text.js +*/ + +static double verdana11_widths[128] = { + [0] = 0.0, + [1] = 0.0, + [2] = 0.0, + [3] = 0.0, + [4] = 0.0, + [5] = 0.0, + [6] = 0.0, + [7] = 0.0, + [8] = 0.0, + [9] = 0.0, + [10] = 0.0, + [11] = 0.0, + [12] = 0.0, + [13] = 0.0, + [14] = 0.0, + [15] = 0.0, + [16] = 0.0, + [17] = 0.0, + [18] = 0.0, + [19] = 0.0, + [20] = 0.0, + [21] = 0.0, + [22] = 0.0, + [23] = 0.0, + [24] = 0.0, + [25] = 0.0, + [26] = 0.0, + [27] = 0.0, + [28] = 0.0, + [29] = 0.0, + [30] = 0.0, + [31] = 0.0, + [32] = 3.8671874999999996, // + [33] = 4.3291015625, // ! + [34] = 5.048828125, // " + [35] = 9.001953125, // # + [36] = 6.9931640625, // $ + [37] = 11.837890625, // % + [38] = 7.992187499999999, // & + [39] = 2.9541015625, // ' + [40] = 4.9951171875, // ( + [41] = 4.9951171875, // ) + [42] = 6.9931640625, // * + [43] = 9.001953125, // + + [44] = 4.00146484375, // , + [45] = 4.9951171875, // - + [46] = 4.00146484375, // . + [47] = 4.9951171875, // / + [48] = 6.9931640625, // 0 + [49] = 6.9931640625, // 1 + [50] = 6.9931640625, // 2 + [51] = 6.9931640625, // 3 + [52] = 6.9931640625, // 4 + [53] = 6.9931640625, // 5 + [54] = 6.9931640625, // 6 + [55] = 6.9931640625, // 7 + [56] = 6.9931640625, // 8 + [57] = 6.9931640625, // 9 + [58] = 4.9951171875, // : + [59] = 4.9951171875, // ; + [60] = 9.001953125, // < + [61] = 9.001953125, // = + [62] = 9.001953125, // > + [63] = 5.99951171875, // ? + [64] = 11.0, // @ + [65] = 7.51953125, // A + [66] = 7.541015625, // B + [67] = 7.680664062499999, // C + [68] = 8.4755859375, // D + [69] = 6.95556640625, // E + [70] = 6.32177734375, // F + [71] = 8.529296875, // G + [72] = 8.26611328125, // H + [73] = 4.6298828125, // I + [74] = 5.00048828125, // J + [75] = 7.62158203125, // K + [76] = 6.123046875, // L + [77] = 9.2705078125, // M + [78] = 8.228515625, // N + [79] = 8.658203125, // O + [80] = 6.63330078125, // P + [81] = 8.658203125, // Q + [82] = 7.6484375, // R + [83] = 7.51953125, // S + [84] = 6.7783203125, // T + [85] = 8.05126953125, // U + [86] = 7.51953125, // V + [87] = 10.87646484375, // W + [88] = 7.53564453125, // X + [89] = 6.767578125, // Y + [90] = 7.53564453125, // Z + [91] = 4.9951171875, // [ + [92] = 4.9951171875, // backslash + [93] = 4.9951171875, // ] + [94] = 9.001953125, // ^ + [95] = 6.9931640625, // _ + [96] = 6.9931640625, // ` + [97] = 6.6064453125, // a + [98] = 6.853515625, // b + [99] = 5.73095703125, // c + [100] = 6.853515625, // d + [101] = 6.552734375, // e + [102] = 3.8671874999999996, // f + [103] = 6.853515625, // g + [104] = 6.9609375, // h + [105] = 3.0185546875, // i + [106] = 3.78662109375, // j + [107] = 6.509765625, // k + [108] = 3.0185546875, // l + [109] = 10.69921875, // m + [110] = 6.9609375, // n + [111] = 6.67626953125, // o + [112] = 6.853515625, // p + [113] = 6.853515625, // q + [114] = 4.6943359375, // r + [115] = 5.73095703125, // s + [116] = 4.33447265625, // t + [117] = 6.9609375, // u + [118] = 6.509765625, // v + [119] = 9.001953125, // w + [120] = 6.509765625, // x + [121] = 6.509765625, // y + [122] = 5.779296875, // z + [123] = 6.982421875, // { + [124] = 4.9951171875, // | + [125] = 6.982421875, // } + [126] = 9.001953125, // ~ + [127] = 0.0 +}; + +// find the width of the string using the verdana 11points font +static inline double verdana11_width(const char *s, float em_size) { + double w = 0.0; + + while(*s) { + // 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 { + 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++; + } + } + + w -= VERDANA_KERNING; + w += VERDANA_PADDING; + return w; +} + +static inline size_t escape_xmlz(char *dst, const char *src, size_t len) { + size_t i = len; + + // required escapes from + // https://github.com/badges/shields/blob/master/badge.js + while(*src && i) { + switch(*src) { + case '\\': + *dst++ = '/'; + src++; + i--; + break; + + case '&': + if(i > 5) { + strcpy(dst, "&"); + i -= 5; + dst += 5; + src++; + } + else goto cleanup; + break; + + case '<': + if(i > 4) { + strcpy(dst, "<"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '>': + if(i > 4) { + strcpy(dst, ">"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '"': + if(i > 6) { + strcpy(dst, """); + i -= 6; + dst += 6; + src++; + } + else goto cleanup; + break; + + case '\'': + if(i > 6) { + strcpy(dst, "'"); + i -= 6; + dst += 6; + src++; + } + else goto cleanup; + break; + + default: + i--; + *dst++ = *src++; + break; + } + } + +cleanup: + *dst = '\0'; + return len - i; +} + +static inline char *format_value_with_precision_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) { + if(unlikely(isnan(value) || isinf(value))) + value = 0.0; + + char *separator = ""; + if(unlikely(isalnum(*units))) + separator = " "; + + if(precision < 0) { + int len, lstop = 0, trim_zeros = 1; + + calculated_number abs = value; + if(isless(value, 0)) { + lstop = 1; + abs = calculated_number_fabs(value); + } + + if(isgreaterequal(abs, 1000)) { + len = snprintfz(value_string, value_string_len, "%0.0" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + trim_zeros = 0; + } + else if(isgreaterequal(abs, 10)) len = snprintfz(value_string, value_string_len, "%0.1" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else if(isgreaterequal(abs, 1)) len = snprintfz(value_string, value_string_len, "%0.2" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, value_string_len, "%0.2" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else if(isgreaterequal(abs, 0.01)) len = snprintfz(value_string, value_string_len, "%0.4" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else if(isgreaterequal(abs, 0.001)) len = snprintfz(value_string, value_string_len, "%0.5" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else if(isgreaterequal(abs, 0.0001)) len = snprintfz(value_string, value_string_len, "%0.6" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + else len = snprintfz(value_string, value_string_len, "%0.7" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE) value); + + if(unlikely(trim_zeros)) { + int l; + // remove trailing zeros from the decimal part + for(l = len - 1; l > lstop; l--) { + if(likely(value_string[l] == '0')) { + value_string[l] = '\0'; + len--; + } + + else if(unlikely(value_string[l] == '.')) { + value_string[l] = '\0'; + len--; + break; + } + + else + break; + } + } + + if(unlikely(len <= 0)) len = 1; + snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units); + } + else { + if(precision > 50) precision = 50; + snprintfz(value_string, value_string_len, "%0.*" LONG_DOUBLE_MODIFIER "%s%s", precision, (LONG_DOUBLE) value, separator, units); + } + + return value_string; +} + +typedef enum badge_units_format { + UNITS_FORMAT_NONE, + UNITS_FORMAT_SECONDS, + UNITS_FORMAT_SECONDS_AGO, + UNITS_FORMAT_MINUTES, + UNITS_FORMAT_MINUTES_AGO, + UNITS_FORMAT_HOURS, + UNITS_FORMAT_HOURS_AGO, + UNITS_FORMAT_ONOFF, + UNITS_FORMAT_UPDOWN, + UNITS_FORMAT_OKERROR, + UNITS_FORMAT_OKFAILED, + UNITS_FORMAT_EMPTY, + UNITS_FORMAT_PERCENT +} UNITS_FORMAT; + + +static struct units_formatter { + const char *units; + uint32_t hash; + UNITS_FORMAT format; +} badge_units_formatters[] = { + { "seconds", 0, UNITS_FORMAT_SECONDS }, + { "seconds ago", 0, UNITS_FORMAT_SECONDS_AGO }, + { "minutes", 0, UNITS_FORMAT_MINUTES }, + { "minutes ago", 0, UNITS_FORMAT_MINUTES_AGO }, + { "hours", 0, UNITS_FORMAT_HOURS }, + { "hours ago", 0, UNITS_FORMAT_HOURS_AGO }, + { "on/off", 0, UNITS_FORMAT_ONOFF }, + { "on-off", 0, UNITS_FORMAT_ONOFF }, + { "onoff", 0, UNITS_FORMAT_ONOFF }, + { "up/down", 0, UNITS_FORMAT_UPDOWN }, + { "up-down", 0, UNITS_FORMAT_UPDOWN }, + { "updown", 0, UNITS_FORMAT_UPDOWN }, + { "ok/error", 0, UNITS_FORMAT_OKERROR }, + { "ok-error", 0, UNITS_FORMAT_OKERROR }, + { "okerror", 0, UNITS_FORMAT_OKERROR }, + { "ok/failed", 0, UNITS_FORMAT_OKFAILED }, + { "ok-failed", 0, UNITS_FORMAT_OKFAILED }, + { "okfailed", 0, UNITS_FORMAT_OKFAILED }, + { "empty", 0, UNITS_FORMAT_EMPTY }, + { "null", 0, UNITS_FORMAT_EMPTY }, + { "percentage", 0, UNITS_FORMAT_PERCENT }, + { "percent", 0, UNITS_FORMAT_PERCENT }, + { "pcent", 0, UNITS_FORMAT_PERCENT }, + + // terminator + { NULL, 0, UNITS_FORMAT_NONE } +}; + +inline char *format_value_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) { + static int max = -1; + int i; + + if(unlikely(max == -1)) { + for(i = 0; badge_units_formatters[i].units; i++) + badge_units_formatters[i].hash = simple_hash(badge_units_formatters[i].units); + + max = i; + } + + if(unlikely(!units)) units = ""; + uint32_t hash_units = simple_hash(units); + + UNITS_FORMAT format = UNITS_FORMAT_NONE; + for(i = 0; i < max; i++) { + struct units_formatter *ptr = &badge_units_formatters[i]; + + if(hash_units == ptr->hash && !strcmp(units, ptr->units)) { + format = ptr->format; + break; + } + } + + if(unlikely(format == UNITS_FORMAT_SECONDS || format == UNITS_FORMAT_SECONDS_AGO)) { + if(value == 0.0) { + snprintfz(value_string, value_string_len, "%s", "now"); + return value_string; + } + else if(isnan(value) || isinf(value)) { + snprintfz(value_string, value_string_len, "%s", "undefined"); + return value_string; + } + + const char *suffix = (format == UNITS_FORMAT_SECONDS_AGO)?" ago":""; + + size_t s = (size_t)value; + size_t d = s / 86400; + s = s % 86400; + + size_t h = s / 3600; + s = s % 3600; + + size_t m = s / 60; + s = s % 60; + + if(d) + snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix); + else + snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix); + + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_MINUTES || format == UNITS_FORMAT_MINUTES_AGO)) { + if(value == 0.0) { + snprintfz(value_string, value_string_len, "%s", "now"); + return value_string; + } + else if(isnan(value) || isinf(value)) { + snprintfz(value_string, value_string_len, "%s", "undefined"); + return value_string; + } + + const char *suffix = (format == UNITS_FORMAT_MINUTES_AGO)?" ago":""; + + size_t m = (size_t)value; + size_t d = m / (60 * 24); + m = m % (60 * 24); + + size_t h = m / 60; + m = m % 60; + + if(d) + snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix); + else + snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix); + + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_HOURS || format == UNITS_FORMAT_HOURS_AGO)) { + if(value == 0.0) { + snprintfz(value_string, value_string_len, "%s", "now"); + return value_string; + } + else if(isnan(value) || isinf(value)) { + snprintfz(value_string, value_string_len, "%s", "undefined"); + return value_string; + } + + const char *suffix = (format == UNITS_FORMAT_HOURS_AGO)?" ago":""; + + size_t h = (size_t)value; + size_t d = h / 24; + h = h % 24; + + if(d) + snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix); + else + snprintfz(value_string, value_string_len, "%zuh%s", h, suffix); + + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_ONOFF)) { + snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"on":"off"); + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_UPDOWN)) { + snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"up":"down"); + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_OKERROR)) { + snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"error"); + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_OKFAILED)) { + snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"failed"); + return value_string; + } + + else if(unlikely(format == UNITS_FORMAT_EMPTY)) + units = ""; + + else if(unlikely(format == UNITS_FORMAT_PERCENT)) + units = "%"; + + if(unlikely(isnan(value) || isinf(value))) { + strcpy(value_string, "-"); + return value_string; + } + + return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision); +} + +static struct badge_color { + const char *name; + uint32_t hash; + const char *color; +} badge_colors[] = { + + // colors from: + // https://github.com/badges/shields/blob/master/colorscheme.json + + { "brightgreen", 0, "4c1" }, + { "green", 0, "97CA00" }, + { "yellow", 0, "dfb317" }, + { "yellowgreen", 0, "a4a61d" }, + { "orange", 0, "fe7d37" }, + { "red", 0, "e05d44" }, + { "blue", 0, "007ec6" }, + { "grey", 0, "555" }, + { "gray", 0, "555" }, + { "lightgrey", 0, "9f9f9f" }, + { "lightgray", 0, "9f9f9f" }, + + // terminator + { NULL, 0, NULL } +}; + +static inline const char *color_map(const char *color, const char *def) { + static int max = -1; + int i; + + if(unlikely(max == -1)) { + for(i = 0; badge_colors[i].name ;i++) + badge_colors[i].hash = simple_hash(badge_colors[i].name); + + max = i; + } + + uint32_t hash = simple_hash(color); + + for(i = 0; i < max; i++) { + struct badge_color *ptr = &badge_colors[i]; + + if(hash == ptr->hash && !strcmp(color, ptr->name)) + return ptr->color; + } + + return def; +} + +typedef enum color_comparison { + COLOR_COMPARE_EQUAL, + COLOR_COMPARE_NOTEQUAL, + COLOR_COMPARE_LESS, + COLOR_COMPARE_LESSEQUAL, + COLOR_COMPARE_GREATER, + COLOR_COMPARE_GREATEREQUAL, +} BADGE_COLOR_COMPARISON; + +static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value) { + if(isnan(value) || isinf(value)) + value = NAN; + + char color_buffer[256 + 1] = ""; + char value_buffer[256 + 1] = ""; + BADGE_COLOR_COMPARISON comparison = COLOR_COMPARE_GREATER; + + // example input: + // colormin|color:null... + + const char *c = color; + while(*c) { + char *dc = color_buffer, *dv = NULL; + size_t ci = 0, vi = 0; + + const char *t = c; + + while(*t && *t != '|') { + switch(*t) { + case '!': + if(t[1] == '=') t++; + comparison = COLOR_COMPARE_NOTEQUAL; + dv = value_buffer; + break; + + case '=': + case ':': + comparison = COLOR_COMPARE_EQUAL; + dv = value_buffer; + break; + + case '}': + case ')': + case '>': + if(t[1] == '=') { + comparison = COLOR_COMPARE_GREATEREQUAL; + t++; + } + else + comparison = COLOR_COMPARE_GREATER; + dv = value_buffer; + break; + + case '{': + case '(': + case '<': + if(t[1] == '=') { + comparison = COLOR_COMPARE_LESSEQUAL; + t++; + } + else if(t[1] == '>' || t[1] == ')' || t[1] == '}') { + comparison = COLOR_COMPARE_NOTEQUAL; + t++; + } + else + comparison = COLOR_COMPARE_LESS; + dv = value_buffer; + break; + + default: + if(dv) { + if(vi < 256) { + vi++; + *dv++ = *t; + } + } + else { + if(ci < 256) { + ci++; + *dc++ = *t; + } + } + break; + } + + t++; + } + + // prepare for next iteration + if(*t == '|') t++; + c = t; + + // do the math + *dc = '\0'; + if(dv) { + *dv = '\0'; + calculated_number v; + + if(!*value_buffer || !strcmp(value_buffer, "null")) { + v = NAN; + } + else { + v = str2l(value_buffer); + if(isnan(v) || isinf(v)) + v = NAN; + } + + if(unlikely(isnan(value) || isnan(v))) { + if(isnan(value) && isnan(v)) + break; + } + else { + if (unlikely(comparison == COLOR_COMPARE_LESS && isless(value, v))) break; + else if (unlikely(comparison == COLOR_COMPARE_LESSEQUAL && islessequal(value, v))) break; + else if (unlikely(comparison == COLOR_COMPARE_GREATER && isgreater(value, v))) break; + else if (unlikely(comparison == COLOR_COMPARE_GREATEREQUAL && isgreaterequal(value, v))) break; + else if (unlikely(comparison == COLOR_COMPARE_EQUAL && !islessgreater(value, v))) break; + else if (unlikely(comparison == COLOR_COMPARE_NOTEQUAL && islessgreater(value, v))) break; + } + } + else + break; + } + + const char *b; + if(color_buffer[0]) + b = color_buffer; + else + b = color; + + strncpyz(final, b, len); +} + +// value + units +#define VALUE_STRING_SIZE 100 + +// label +#define LABEL_STRING_SIZE 200 + +// colors +#define COLOR_STRING_SIZE 100 + +static inline int allowed_hexa_char(char x) { + return ( (x >= '0' && x <= '9') || + (x >= 'a' && x <= 'f') || + (x >= 'A' && x <= 'F') + ); +} + +static int html_color_check(const char *str) { + int i = 0; + while(str[i]) { + if(!allowed_hexa_char(str[i])) + return 0; + if(unlikely(i >= 6)) + return 0; + i++; + } + // want to allow either RGB or RRGGBB + return ( i == 6 || i == 3 ); +} + +// Will parse color arg as #RRGGBB or #RGB or one of the colors +// from color_map hash table +// if parsing fails (argument error) it will return default color +// given as default parameter (def) +// in any case it will return either color in "RRGGBB" or "RGB" format as string +// or whatever is given as def (without checking - caller responsible to give sensible +// safely escaped default) as default if it fails +// in any case this function must always return something we can put directly in XML +// so no escaping is necessary anymore (with excpetion of default where caller is responsible) +// to give sensible default +#define BADGE_SVG_COLOR_ARG_MAXLEN 20 + +static const char *parse_color_argument(const char *arg, const char *def) +{ + if( !arg ) + return def; + size_t len = strnlen(arg, BADGE_SVG_COLOR_ARG_MAXLEN); + if( len < 2 || len >= BADGE_SVG_COLOR_ARG_MAXLEN ) + return def; + if( html_color_check(arg) ) + return arg; + return color_map(arg, def); +} + +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, 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] + , label_escaped[LABEL_STRING_SIZE + 1] + , value_escaped[VALUE_STRING_SIZE + 1]; + + const char *label_color_parsed; + const char *value_color_parsed; + + double label_width = (double)fixed_width_lbl, value_width = (double)fixed_width_val, total_width; + double height = 20.0, font_size = 11.0, text_offset = 5.8, round_corner = 3.0; + + if(scale < 100) scale = 100; + + if(unlikely(!value_color || !*value_color)) + value_color = (isnan(value) || isinf(value))?"999":"4c1"; + + 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); + + if(fixed_width_lbl <= 0 || fixed_width_val <= 0) { + 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, LABEL_STRING_SIZE); + escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE); + + label_color_parsed = parse_color_argument(label_color, "555"); + value_color_parsed = parse_color_argument(value_color_buffer, "555"); + + wb->contenttype = CT_IMAGE_SVG_XML; + + total_width = total_width * scale / 100.0; + height = height * scale / 100.0; + font_size = font_size * scale / 100.0; + text_offset = text_offset * scale / 100.0; + label_width = label_width * scale / 100.0; + value_width = value_width * scale / 100.0; + round_corner = round_corner * scale / 100.0; + + // svg template from: + // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg + buffer_sprintf(wb, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + total_width, height, + total_width, height, round_corner, + label_width, height, label_color_parsed); // 0 && fixed_width_val > 0) { + buffer_sprintf(wb, + "" + "" + "", + label_width, height); // ", + label_width, value_width, height, value_color_parsed); + + if(fixed_width_lbl > 0 && fixed_width_val > 0) { + buffer_sprintf(wb, + "" + "" + "", + label_width, value_width, height); + } + + buffer_sprintf(wb, + "" + "" + "" + "%s" + "%s" + "%s" + "%s" + "", + total_width, height, + font_size, + label_width / 2, ceil(height - text_offset), label_escaped, + label_width / 2, ceil(height - text_offset - 1.0), parse_color_argument(text_color_lbl, "fff"), 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), parse_color_argument(text_color_val, "fff"), value_escaped); + + if(fixed_width_lbl <= 0 || fixed_width_val <= 0){ + buffer_sprintf(wb, + "", + BADGE_HORIZONTAL_PADDING); + } + buffer_sprintf(wb, ""); +} + +#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 ret = HTTP_RESP_BAD_REQUEST; + buffer_flush(w->response.data); + + BUFFER *dimensions = NULL; + + const char *chart = NULL + , *before_str = NULL + , *after_str = NULL + , *points_str = NULL + , *multiply_str = NULL + , *divide_str = NULL + , *label = NULL + , *units = NULL + , *label_color = NULL + , *value_color = NULL + , *refresh_str = NULL + , *precision_str = NULL + , *scale_str = NULL + , *alarm = NULL + , *fixed_width_lbl_str = NULL + , *fixed_width_val_str = NULL + , *text_color_lbl_str = NULL + , *text_color_val_str = NULL; + + int group = RRDR_GROUPING_AVERAGE; + uint32_t options = 0x00000000; + + while(url) { + char *value = mystrsep(&url, "&"); + if(!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + debug(D_WEB_CLIENT, "%llu: API v1 badge.svg 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, "chart")) chart = value; + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!dimensions) + dimensions = buffer_create(100); + + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, 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, "group")) { + group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + } + else if(!strcmp(name, "options")) { + options |= web_client_api_request_v1_data_options(value); + } + else if(!strcmp(name, "label")) label = value; + else if(!strcmp(name, "units")) units = value; + else if(!strcmp(name, "label_color")) label_color = value; + else if(!strcmp(name, "value_color")) value_color = value; + else if(!strcmp(name, "multiply")) multiply_str = value; + else if(!strcmp(name, "divide")) divide_str = value; + else if(!strcmp(name, "refresh")) refresh_str = value; + else if(!strcmp(name, "precision")) precision_str = value; + else if(!strcmp(name, "scale")) scale_str = value; + else if(!strcmp(name, "fixed_width_lbl")) fixed_width_lbl_str = value; + else if(!strcmp(name, "fixed_width_val")) fixed_width_val_str = value; + else if(!strcmp(name, "alarm")) alarm = value; + else if(!strcmp(name, BADGE_URL_ARG_LBL_COLOR)) text_color_lbl_str = value; + else if(!strcmp(name, BADGE_URL_ARG_VAL_COLOR)) text_color_val_str = value; + } + + int fixed_width_lbl = -1; + int fixed_width_val = -1; + + if(fixed_width_lbl_str && *fixed_width_lbl_str + && fixed_width_val_str && *fixed_width_val_str) { + fixed_width_lbl = str2i(fixed_width_lbl_str); + fixed_width_val = str2i(fixed_width_val_str); + } + + if(!chart || !*chart) { + buffer_no_cacheable(w->response.data); + buffer_sprintf(w->response.data, "No chart id is given at the request."); + goto cleanup; + } + + int scale = (scale_str && *scale_str)?str2i(scale_str):100; + + RRDSET *st = rrdset_find(host, chart); + if(!st) st = rrdset_find_byname(host, chart); + if(!st) { + buffer_no_cacheable(w->response.data); + buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL); + ret = HTTP_RESP_OK; + goto cleanup; + } + st->last_accessed_time = now_realtime_sec(); + + RRDCALC *rc = NULL; + if(alarm) { + rc = rrdcalc_find(st, alarm); + if (!rc) { + buffer_no_cacheable(w->response.data); + buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL); + ret = HTTP_RESP_OK; + goto cleanup; + } + } + + long long multiply = (multiply_str && *multiply_str )?str2l(multiply_str):1; + long long divide = (divide_str && *divide_str )?str2l(divide_str):1; + long long before = (before_str && *before_str )?str2l(before_str):0; + long long after = (after_str && *after_str )?str2l(after_str):-st->update_every; + int points = (points_str && *points_str )?str2i(points_str):1; + int precision = (precision_str && *precision_str)?str2i(precision_str):-1; + + if(!multiply) multiply = 1; + if(!divide) divide = 1; + + int refresh = 0; + if(refresh_str && *refresh_str) { + if(!strcmp(refresh_str, "auto")) { + if(rc) refresh = rc->update_every; + else if(options & RRDR_OPTION_NOT_ALIGNED) + refresh = st->update_every; + else { + refresh = (int)(before - after); + if(refresh < 0) refresh = -refresh; + } + } + else { + refresh = str2i(refresh_str); + if(refresh < 0) refresh = -refresh; + } + } + + if(!label) { + if(alarm) { + char *s = (char *)alarm; + while(*s) { + if(*s == '_') *s = ' '; + s++; + } + label = alarm; + } + else if(dimensions) { + const char *dim = buffer_tostring(dimensions); + if(*dim == '|') dim++; + label = dim; + } + else + label = st->name; + } + if(!units) { + if(alarm) { + if(rc->units) + units = rc->units; + else + units = ""; + } + else if(options & RRDR_OPTION_PERCENTAGE) + units = "%"; + else + units = st->units; + } + + debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'" + , w->id + , chart + , alarm?alarm:"" + , (dimensions)?buffer_tostring(dimensions):"" + , after + , before + , points + , group + , options + ); + + if(rc) { + if (refresh > 0) { + buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + w->response.data->expires = now_realtime_sec() + refresh; + } + else buffer_no_cacheable(w->response.data); + + if(!value_color) { + switch(rc->status) { + case RRDCALC_STATUS_CRITICAL: + value_color = "red"; + break; + + case RRDCALC_STATUS_WARNING: + value_color = "orange"; + break; + + case RRDCALC_STATUS_CLEAR: + value_color = "brightgreen"; + break; + + case RRDCALC_STATUS_UNDEFINED: + value_color = "lightgrey"; + break; + + case RRDCALC_STATUS_UNINITIALIZED: + value_color = "#000"; + break; + + default: + value_color = "grey"; + break; + } + } + + buffer_svg(w->response.data, + label, + (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide, + units, + label_color, + value_color, + precision, + scale, + options, + fixed_width_lbl, + fixed_width_val, + text_color_lbl_str, + text_color_val_str + ); + ret = HTTP_RESP_OK; + } + else { + time_t latest_timestamp = 0; + int value_is_null = 1; + calculated_number n = NAN; + ret = HTTP_RESP_INTERNAL_SERVER_ERROR; + + // if the collected value is too old, don't calculate its value + if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above))) + ret = rrdset2value_api_v1(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL + , points, after, before, group, 0, options, NULL, &latest_timestamp, &value_is_null); + + // if the value cannot be calculated, show empty badge + if (ret != HTTP_RESP_OK) { + buffer_no_cacheable(w->response.data); + value_is_null = 1; + n = 0; + ret = HTTP_RESP_OK; + } + else if (refresh > 0) { + buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + w->response.data->expires = now_realtime_sec() + refresh; + } + else buffer_no_cacheable(w->response.data); + + // render the badge + buffer_svg(w->response.data, + label, + (value_is_null)?NAN:(n * multiply / divide), + units, + label_color, + value_color, + precision, + scale, + options, + fixed_width_lbl, + fixed_width_val, + text_color_lbl_str, + text_color_val_str + ); + } + + cleanup: + buffer_free(dimensions); + return ret; +} diff --git a/web/api/badges/web_buffer_svg.h b/web/api/badges/web_buffer_svg.h new file mode 100644 index 0000000..1cf69e2 --- /dev/null +++ b/web/api/badges/web_buffer_svg.h @@ -0,0 +1,16 @@ +// 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" + +extern 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, int fixed_width_lbl, int fixed_width_val, const char* text_color_lbl, const char* text_color_val); +extern char *format_value_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision); + +extern 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/web/api/exporters/Makefile.am b/web/api/exporters/Makefile.am new file mode 100644 index 0000000..06fda51 --- /dev/null +++ b/web/api/exporters/Makefile.am @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + shell \ + prometheus \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/exporters/README.md b/web/api/exporters/README.md new file mode 100644 index 0000000..4019647 --- /dev/null +++ b/web/api/exporters/README.md @@ -0,0 +1,10 @@ + + +# Exporters + +TBD + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fexporters%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/exporters/allmetrics.c b/web/api/exporters/allmetrics.c new file mode 100644 index 0000000..d10de3d --- /dev/null +++ b/web/api/exporters/allmetrics.c @@ -0,0 +1,127 @@ +// 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[] = { + { "help", PROMETHEUS_OUTPUT_HELP }, + { "types", PROMETHEUS_OUTPUT_TYPES }, + { "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 *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_backend_options; + + PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options = + PROMETHEUS_OUTPUT_TIMESTAMPS | + ((prometheus_exporting_options & BACKEND_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_backend_prefix; + + while(url) { + char *value = mystrsep(&url, "&"); + if (!value || !*value) continue; + + char *name = mystrsep(&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, "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 = backend_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->contenttype = CT_APPLICATION_JSON; + rrd_stats_api_v1_charts_allmetrics_json(host, w->response.data); + return HTTP_RESP_OK; + + case ALLMETRICS_SHELL: + w->response.data->contenttype = CT_TEXT_PLAIN; + rrd_stats_api_v1_charts_allmetrics_shell(host, w->response.data); + return HTTP_RESP_OK; + + case ALLMETRICS_PROMETHEUS: + w->response.data->contenttype = CT_PROMETHEUS; + rrd_stats_api_v1_charts_allmetrics_prometheus_single_host( + host + , 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->contenttype = CT_PROMETHEUS; + rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts( + host + , w->response.data + , prometheus_server + , prometheus_prefix + , prometheus_exporting_options + , prometheus_output_options + ); + return HTTP_RESP_OK; + + default: + w->response.data->contenttype = 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/web/api/exporters/allmetrics.h b/web/api/exporters/allmetrics.h new file mode 100644 index 0000000..f076ff0 --- /dev/null +++ b/web/api/exporters/allmetrics.h @@ -0,0 +1,12 @@ +// 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" + +extern int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url); + +#endif //NETDATA_API_ALLMETRICS_H diff --git a/web/api/exporters/prometheus/Makefile.am b/web/api/exporters/prometheus/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/exporters/prometheus/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/exporters/prometheus/README.md b/web/api/exporters/prometheus/README.md new file mode 100644 index 0000000..d26c6e4 --- /dev/null +++ b/web/api/exporters/prometheus/README.md @@ -0,0 +1,10 @@ + + +# prometheus exporter + +The prometheus exporter for Netdata is located at the [backends section for prometheus](/backends/prometheus/README.md). + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fexporters%2Fprometheus%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/exporters/shell/Makefile.am b/web/api/exporters/shell/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/exporters/shell/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/exporters/shell/README.md b/web/api/exporters/shell/README.md new file mode 100644 index 0000000..b919045 --- /dev/null +++ b/web/api/exporters/shell/README.md @@ -0,0 +1,71 @@ + + +# shell exporter + +Shell scripts can now query Netdata: + +```sh +eval "$(curl -s 'http://localhost:19999/api/v1/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')" + +# let's see if there are variables exposed by Netdata for system.cpu +set | grep "^NETDATA_SYSTEM_CPU" + +NETDATA_SYSTEM_CPU_GUEST=0 +NETDATA_SYSTEM_CPU_GUEST_NICE=0 +NETDATA_SYSTEM_CPU_IDLE=95 +NETDATA_SYSTEM_CPU_IOWAIT=0 +NETDATA_SYSTEM_CPU_IRQ=0 +NETDATA_SYSTEM_CPU_NICE=0 +NETDATA_SYSTEM_CPU_SOFTIRQ=0 +NETDATA_SYSTEM_CPU_STEAL=0 +NETDATA_SYSTEM_CPU_SYSTEM=1 +NETDATA_SYSTEM_CPU_USER=4 +NETDATA_SYSTEM_CPU_VISIBLETOTAL=5 + +# let's see the total cpu utilization of the system +echo ${NETDATA_SYSTEM_CPU_VISIBLETOTAL} +5 + +# what about alarms? +set | grep "^NETDATA_ALARM_SYSTEM_SWAP_" +NETDATA_ALARM_SYSTEM_SWAP_RAM_IN_SWAP_STATUS=CRITICAL +NETDATA_ALARM_SYSTEM_SWAP_RAM_IN_SWAP_VALUE=53 +NETDATA_ALARM_SYSTEM_SWAP_USED_SWAP_STATUS=CLEAR +NETDATA_ALARM_SYSTEM_SWAP_USED_SWAP_VALUE=51 + +# let's get the current status of the alarm 'ram in swap' +echo ${NETDATA_ALARM_SYSTEM_SWAP_RAM_IN_SWAP_STATUS} +CRITICAL + +# is it fast? +time curl -s 'http://localhost:19999/api/v1/allmetrics' >/dev/null + +real 0m0,070s +user 0m0,000s +sys 0m0,007s + +# it is... +# 0.07 seconds for curl to be loaded, connect to Netdata and fetch the response back... +``` + +The `_VISIBLETOTAL` variable sums up all the dimensions of each chart. + +The format of the variables is: + +```sh +NETDATA_${chart_id^^}_${dimension_id^^}="${value}" +``` + +The value is rounded to the closest integer, since shell script cannot process decimal numbers. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fexporters%2Fshell%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/exporters/shell/allmetrics_shell.c b/web/api/exporters/shell/allmetrics_shell.c new file mode 100644 index 0000000..daa0049 --- /dev/null +++ b/web/api/exporters/shell/allmetrics_shell.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "allmetrics_shell.h" + +// ---------------------------------------------------------------------------- +// BASH +// /api/v1/allmetrics?format=bash + +static inline size_t shell_name_copy(char *d, const char *s, size_t usable) { + size_t n; + + for(n = 0; *s && n < usable ; d++, s++, n++) { + register char c = *s; + + if(unlikely(!isalnum(c))) *d = '_'; + else *d = (char)toupper(c); + } + *d = '\0'; + + return n; +} + +#define SHELL_ELEMENT_MAX 100 + +void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, BUFFER *wb) { + rrdhost_rdlock(host); + + // for each chart + RRDSET *st; + rrdset_foreach_read(st, host) { + calculated_number total = 0.0; + char chart[SHELL_ELEMENT_MAX + 1]; + shell_name_copy(chart, st->name?st->name:st->id, SHELL_ELEMENT_MAX); + + buffer_sprintf(wb, "\n# chart: %s (name: %s)\n", st->id, st->name); + if(rrdset_is_available_for_viewers(st)) { + rrdset_rdlock(st); + + // for each dimension + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) { + char dimension[SHELL_ELEMENT_MAX + 1]; + shell_name_copy(dimension, rd->name?rd->name:rd->id, SHELL_ELEMENT_MAX); + + calculated_number n = rd->last_stored_value; + + if(isnan(n) || isinf(n)) + buffer_sprintf(wb, "NETDATA_%s_%s=\"\" # %s\n", chart, dimension, st->units); + else { + if(rd->multiplier < 0 || rd->divisor < 0) n = -n; + n = calculated_number_round(n); + if(!rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN)) total += n; + buffer_sprintf(wb, "NETDATA_%s_%s=\"" CALCULATED_NUMBER_FORMAT_ZERO "\" # %s\n", chart, dimension, n, st->units); + } + } + } + + total = calculated_number_round(total); + buffer_sprintf(wb, "NETDATA_%s_VISIBLETOTAL=\"" CALCULATED_NUMBER_FORMAT_ZERO "\" # %s\n", chart, total, st->units); + rrdset_unlock(st); + } + } + + buffer_strcat(wb, "\n# NETDATA ALARMS RUNNING\n"); + + RRDCALC *rc; + for(rc = host->alarms; rc ;rc = rc->next) { + if(!rc->rrdset) continue; + + char chart[SHELL_ELEMENT_MAX + 1]; + shell_name_copy(chart, rc->rrdset->name?rc->rrdset->name:rc->rrdset->id, SHELL_ELEMENT_MAX); + + char alarm[SHELL_ELEMENT_MAX + 1]; + shell_name_copy(alarm, rc->name, SHELL_ELEMENT_MAX); + + calculated_number n = rc->value; + + if(isnan(n) || isinf(n)) + buffer_sprintf(wb, "NETDATA_ALARM_%s_%s_VALUE=\"\" # %s\n", chart, alarm, rc->units); + else { + n = calculated_number_round(n); + buffer_sprintf(wb, "NETDATA_ALARM_%s_%s_VALUE=\"" CALCULATED_NUMBER_FORMAT_ZERO "\" # %s\n", chart, alarm, n, rc->units); + } + + buffer_sprintf(wb, "NETDATA_ALARM_%s_%s_STATUS=\"%s\"\n", chart, alarm, rrdcalc_status2string(rc->status)); + } + + rrdhost_unlock(host); +} + +// ---------------------------------------------------------------------------- + +void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, BUFFER *wb) { + rrdhost_rdlock(host); + + buffer_strcat(wb, "{"); + + size_t chart_counter = 0; + size_t dimension_counter = 0; + + // for each chart + RRDSET *st; + rrdset_foreach_read(st, host) { + if(rrdset_is_available_for_viewers(st)) { + rrdset_rdlock(st); + + buffer_sprintf(wb, "%s\n" + "\t\"%s\": {\n" + "\t\t\"name\":\"%s\",\n" + "\t\t\"family\":\"%s\",\n" + "\t\t\"context\":\"%s\",\n" + "\t\t\"units\":\"%s\",\n" + "\t\t\"last_updated\": %ld,\n" + "\t\t\"dimensions\": {" + , chart_counter?",":"" + , st->id + , st->name + , st->family + , st->context + , st->units + , rrdset_last_entry_t_nolock(st) + ); + + chart_counter++; + dimension_counter = 0; + + // for each dimension + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) { + + buffer_sprintf(wb, "%s\n" + "\t\t\t\"%s\": {\n" + "\t\t\t\t\"name\": \"%s\",\n" + "\t\t\t\t\"value\": " + , dimension_counter?",":"" + , rd->id + , rd->name + ); + + if(isnan(rd->last_stored_value)) + buffer_strcat(wb, "null"); + else + buffer_sprintf(wb, CALCULATED_NUMBER_FORMAT, rd->last_stored_value); + + buffer_strcat(wb, "\n\t\t\t}"); + + dimension_counter++; + } + } + + buffer_strcat(wb, "\n\t\t}\n\t}"); + rrdset_unlock(st); + } + } + + buffer_strcat(wb, "\n}"); + rrdhost_unlock(host); +} + diff --git a/web/api/exporters/shell/allmetrics_shell.h b/web/api/exporters/shell/allmetrics_shell.h new file mode 100644 index 0000000..1d7611a --- /dev/null +++ b/web/api/exporters/shell/allmetrics_shell.h @@ -0,0 +1,21 @@ +// 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 + +extern void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, BUFFER *wb); +extern void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, BUFFER *wb); + +#endif //NETDATA_API_ALLMETRICS_SHELL_H diff --git a/web/api/formatters/Makefile.am b/web/api/formatters/Makefile.am new file mode 100644 index 0000000..11f239c --- /dev/null +++ b/web/api/formatters/Makefile.am @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + csv \ + json \ + ssv \ + value \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/formatters/README.md b/web/api/formatters/README.md new file mode 100644 index 0000000..1fd2b30 --- /dev/null +++ b/web/api/formatters/README.md @@ -0,0 +1,78 @@ + + +# Query formatting + +API data queries need to be formatted before returned to the caller. +Using API parameters, the caller may define the format he/she wishes to get back. + +The following formats are supported: + +| format|module|content type|description| +|:----:|:----:|:----------:|:----------| +| `array`|[ssv](/web/api/formatters/ssv/README.md)|application/json|a JSON array| +| `csv`|[csv](/web/api/formatters/csv/README.md)|text/plain|a text table, comma separated, with a header line (dimension names) and `\r\n` at the end of the lines| +| `csvjsonarray`|[csv](/web/api/formatters/csv/README.md)|application/json|a JSON array, with each row as another array (the first row has the dimension names)| +| `datasource`|[json](/web/api/formatters/json/README.md)|application/json|a Google Visualization Provider `datasource` javascript callback| +| `datatable`|[json](/web/api/formatters/json/README.md)|application/json|a Google `datatable`| +| `html`|[csv](/web/api/formatters/csv/README.md)|text/html|an html table| +| `json`|[json](/web/api/formatters/json/README.md)|application/json|a JSON object| +| `jsonp`|[json](/web/api/formatters/json/README.md)|application/json|a JSONP javascript callback| +| `markdown`|[csv](/web/api/formatters/csv/README.md)|text/plain|a markdown table| +| `ssv`|[ssv](/web/api/formatters/ssv/README.md)|text/plain|a space separated list of values| +| `ssvcomma`|[ssv](/web/api/formatters/ssv/README.md)|text/plain|a comma separated list of values| +| `tsv`|[csv](/web/api/formatters/csv/README.md)|text/plain|a TAB delimited `csv` (MS Excel flavor)| + +For examples of each format, check the relative module documentation. + +## Metadata with the `jsonwrap` option + +All data queries can be encapsulated to JSON object having metadata about the query and the results. + +This is done by adding the `options=jsonwrap` to the API URL (if there are other `options` append +`,jsonwrap` to the existing ones). + +This is such an object: + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=system.cpu&after=-3600&points=6&group=average&format=csv&options=nonzero,jsonwrap' +{ + "api": 1, + "id": "system.cpu", + "name": "system.cpu", + "view_update_every": 600, + "update_every": 1, + "first_entry": 1540387074, + "last_entry": 1540647070, + "before": 1540647000, + "after": 1540644000, + "dimension_names": ["steal", "softirq", "user", "system", "iowait"], + "dimension_ids": ["steal", "softirq", "user", "system", "iowait"], + "latest_values": [0, 0.2493766, 1.745636, 0.4987531, 0], + "view_latest_values": [0.0158314, 0.0516506, 0.866549, 0.7196127, 0.0050002], + "dimensions": 5, + "points": 6, + "format": "csv", + "result": "time,steal,softirq,user,system,iowait\n2018-10-27 13:30:00,0.0158314,0.0516506,0.866549,0.7196127,0.0050002\n2018-10-27 13:20:00,0.0149856,0.0529183,0.8673155,0.7121144,0.0049979\n2018-10-27 13:10:00,0.0137501,0.053315,0.8578097,0.7197613,0.0054209\n2018-10-27 13:00:00,0.0154252,0.0554688,0.899432,0.7200638,0.0067252\n2018-10-27 12:50:00,0.0145866,0.0495922,0.8404341,0.7011141,0.0041688\n2018-10-27 12:40:00,0.0162366,0.0595954,0.8827475,0.7020573,0.0041636\n", + "min": 0, + "max": 0 +} +``` + +## Downloading data query result files + +Following the [Google Visualization Provider guidelines](https://developers.google.com/chart/interactive/docs/dev/implementing_data_source), +Netdata supports parsing `tqx` options. + +Using these options, any Netdata data query can instruct the web browser to download +the result and save it under a given filename. + +For example, to download a CSV file with CPU utilization of the last hour, +[click here](https://registry.my-netdata.io/api/v1/data?chart=system.cpu&after=-3600&format=csv&options=nonzero&tqx=outFileName:system+cpu+utilization+of+the+last_hour.csv). + +This is done by appending `&tqx=outFileName:FILENAME` to any data query. +The output will be in the format given with `&format=`. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fformatters%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/formatters/charts2json.c b/web/api/formatters/charts2json.c new file mode 100644 index 0000000..856ffb5 --- /dev/null +++ b/web/api/formatters/charts2json.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "charts2json.h" + +// generate JSON for the /api/v1/charts API call + +const char* get_release_channel() { + static int use_stable = -1; + + if (use_stable == -1) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/.environment", netdata_configured_user_config_dir); + procfile *ff = procfile_open(filename, "=", PROCFILE_FLAG_DEFAULT); + if(!ff) { + use_stable=1; + } else { + procfile_set_quotes(ff, "'\""); + ff = procfile_readall(ff); + if(!ff) { + use_stable=1; + } else { + unsigned int i; + for(i = 0; i < procfile_lines(ff); i++) { + if (!procfile_linewords(ff, i)) continue; + + if (!strcmp(procfile_lineword(ff, i, 0), "RELEASE_CHANNEL") && !strcmp(procfile_lineword(ff, i, 1), "stable")) { + use_stable = 1; + break; + } + } + procfile_close(ff); + if (use_stable == -1) use_stable = 0; + } + } + } + return (use_stable)?"stable":"nightly"; +} + +void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived) { + static char *custom_dashboard_info_js_filename = NULL; + size_t c, dimensions = 0, memory = 0, alarms = 0; + RRDSET *st; + + time_t now = now_realtime_sec(); + + if(unlikely(!custom_dashboard_info_js_filename)) + custom_dashboard_info_js_filename = config_get(CONFIG_SECTION_WEB, "custom dashboard_info.js", ""); + + buffer_sprintf(wb, "{\n" + "\t\"hostname\": \"%s\"" + ",\n\t\"version\": \"%s\"" + ",\n\t\"release_channel\": \"%s\"" + ",\n\t\"os\": \"%s\"" + ",\n\t\"timezone\": \"%s\"" + ",\n\t\"update_every\": %d" + ",\n\t\"history\": %ld" + ",\n\t\"memory_mode\": \"%s\"" + ",\n\t\"custom_info\": \"%s\"" + ",\n\t\"charts\": {" + , host->hostname + , host->program_version + , get_release_channel() + , host->os + , host->timezone + , host->rrd_update_every + , host->rrd_history_entries + , rrd_memory_mode_name(host->rrd_memory_mode) + , custom_dashboard_info_js_filename + ); + + c = 0; + rrdhost_rdlock(host); + rrdset_foreach_read(st, host) { + if ((!show_archived && rrdset_is_available_for_viewers(st)) || (show_archived && rrdset_is_archived(st))) { + if(c) buffer_strcat(wb, ","); + buffer_strcat(wb, "\n\t\t\""); + buffer_strcat(wb, st->id); + buffer_strcat(wb, "\": "); + rrdset2json(st, wb, &dimensions, &memory, skip_volatile); + + c++; + st->last_accessed_time = now; + } + } + + RRDCALC *rc; + for(rc = host->alarms; rc ; rc = rc->next) { + if(rc->rrdset) + alarms++; + } + rrdhost_unlock(host); + + buffer_sprintf(wb + , "\n\t}" + ",\n\t\"charts_count\": %zu" + ",\n\t\"dimensions_count\": %zu" + ",\n\t\"alarms_count\": %zu" + ",\n\t\"rrd_memory_bytes\": %zu" + ",\n\t\"hosts_count\": %zu" + ",\n\t\"hosts\": [" + , c + , dimensions + , alarms + , memory + , rrd_hosts_available + ); + + if(unlikely(rrd_hosts_available > 1)) { + rrd_rdlock(); + + size_t found = 0; + RRDHOST *h; + rrdhost_foreach_read(h) { + if(!rrdhost_should_be_removed(h, host, now) && !rrdhost_flag_check(h, RRDHOST_FLAG_ARCHIVED)) { + buffer_sprintf(wb + , "%s\n\t\t{" + "\n\t\t\t\"hostname\": \"%s\"" + "\n\t\t}" + , (found > 0) ? "," : "" + , h->hostname + ); + + found++; + } + } + + rrd_unlock(); + } + else { + buffer_sprintf(wb + , "\n\t\t{" + "\n\t\t\t\"hostname\": \"%s\"" + "\n\t\t}" + , host->hostname + ); + } + + buffer_sprintf(wb, "\n\t]\n}\n"); +} + +// generate collectors list for the api/v1/info call + +struct collector { + char *plugin; + char *module; +}; + +struct array_printer { + int c; + BUFFER *wb; +}; + +int print_collector(void *entry, void *data) { + struct array_printer *ap = (struct array_printer *)data; + BUFFER *wb = ap->wb; + struct collector *col=(struct collector *) entry; + if(ap->c) buffer_strcat(wb, ","); + buffer_strcat(wb, "\n\t\t{\n\t\t\t\"plugin\": \""); + buffer_strcat(wb, col->plugin); + buffer_strcat(wb, "\",\n\t\t\t\"module\": \""); + buffer_strcat(wb, col->module); + buffer_strcat(wb, "\"\n\t\t}"); + (ap->c)++; + return 0; +} + +void chartcollectors2json(RRDHOST *host, BUFFER *wb) { + DICTIONARY *dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED); + RRDSET *st; + char name[500]; + + time_t now = now_realtime_sec(); + rrdhost_rdlock(host); + rrdset_foreach_read(st, host) { + if (rrdset_is_available_for_viewers(st)) { + struct collector col = { + .plugin = st->plugin_name ? st->plugin_name : "", + .module = st->module_name ? st->module_name : "" + }; + sprintf(name, "%s:%s", col.plugin, col.module); + dictionary_set(dict, name, &col, sizeof(struct collector)); + st->last_accessed_time = now; + } + } + rrdhost_unlock(host); + struct array_printer ap = { + .c = 0, + .wb = wb + }; + dictionary_get_all(dict, print_collector, &ap); + dictionary_destroy(dict); +} diff --git a/web/api/formatters/charts2json.h b/web/api/formatters/charts2json.h new file mode 100644 index 0000000..2d8cce3 --- /dev/null +++ b/web/api/formatters/charts2json.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_CHARTS2JSON_H +#define NETDATA_API_FORMATTER_CHARTS2JSON_H + +#include "rrd2json.h" + +extern void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived); +extern void chartcollectors2json(RRDHOST *host, BUFFER *wb); +extern const char* get_release_channel(); + +#endif //NETDATA_API_FORMATTER_CHARTS2JSON_H diff --git a/web/api/formatters/csv/Makefile.am b/web/api/formatters/csv/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/formatters/csv/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/formatters/csv/README.md b/web/api/formatters/csv/README.md new file mode 100644 index 0000000..2a859e2 --- /dev/null +++ b/web/api/formatters/csv/README.md @@ -0,0 +1,144 @@ + + +# CSV formatter + +The CSV formatter presents [results of database queries](/web/api/queries/README.md) in the following formats: + +| format|content type|description| +| :----:|:----------:|:----------| +| `csv`|text/plain|a text table, comma separated, with a header line (dimension names) and `\r\n` at the end of the lines| +| `csvjsonarray`|application/json|a JSON array, with each row as another array (the first row has the dimension names)| +| `tsv`|text/plain|like `csv` but TAB is used instead of comma to separate values (MS Excel flavor)| +| `html`|text/html|an html table| +| `markdown`|text/plain|markdown table| + +In all formats the date and time is the first column. + +The CSV formatter respects the following API `&options=`: + +| option|supported|description| +|:----:|:-------:|:----------| +| `nonzero`|yes|to return only the dimensions that have at least a non-zero value| +| `flip`|yes|to return the rows older to newer (the default is newer to older)| +| `seconds`|yes|to return the date and time in unix timestamp| +| `ms`|yes|to return the date and time in unit timestamp as milliseconds| +| `percent`|yes|to replace all values with their percentage over the row total| +| `abs`|yes|to turn all values positive| +| `null2zero`|yes|to replace gaps with zeros (the default prints the string `null`| + +## Examples + +Get the system total bandwidth for all physical network interfaces, over the last hour, +in 6 rows (one for every 10 minutes), in `csv` format: + +Netdata always returns bandwidth in `kilobits`. + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=system.net&format=csv&after=-3600&group=sum&points=6&options=abs' +time,received,sent +2018-10-26 23:50:00,90214.67847,215137.79762 +2018-10-26 23:40:00,90126.32286,238587.57522 +2018-10-26 23:30:00,86061.22688,213389.23526 +2018-10-26 23:20:00,85590.75164,206129.01608 +2018-10-26 23:10:00,83163.30691,194311.77384 +2018-10-26 23:00:00,85167.29657,197538.07773 +``` + +--- + +Get the max RAM used by the SQL server and any cron jobs, over the last hour, in 2 rows (one for every 30 +minutes), in `tsv` format, and format the date and time as unix timestamp: + +Netdata always returns memory in `MB`. + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=apps.mem&format=tsv&after=-3600&group=max&points=2&options=nonzero,seconds&dimensions=sql,cron' +time sql cron +1540598400 61.95703 0.25 +1540596600 61.95703 0.25 +``` + +--- + +Get an HTML table of the last 4 values (4 seconds) of system CPU utilization: + +Netdata always returns CPU utilization as `%`. + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=system.cpu&format=html&after=-4&options=nonzero' + +
+ + + + + + +
timesoftirqusersystem
2018-10-27 00:16:070.2510.75
2018-10-27 00:16:0601.00250630.5012531
2018-10-27 00:16:05010.75
2018-10-27 00:16:0401.00250630.7518797
+
+ +``` + +This is how it looks when rendered by a web browser: + +![image](https://user-images.githubusercontent.com/2662304/47597887-bafbf480-d99c-11e8-864a-d880bb8d2e5b.png) + +--- + +Get a JSON array with the average bandwidth rate of the mysql server, over the last hour, in 6 values +(one every 10 minutes), and return the date and time in milliseconds: + +Netdata always returns bandwidth rates in `kilobits/s`. + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=mysql_local.net&format=csvjsonarray&after=-3600&points=6&group=average&options=abs,ms' +[ +["time","in","out"], +[1540599600000,0.7499986,120.2810185], +[1540599000000,0.7500019,120.2815509], +[1540598400000,0.7499999,120.2812319], +[1540597800000,0.7500044,120.2819634], +[1540597200000,0.7499968,120.2807337], +[1540596600000,0.7499988,120.2810527] +] +``` + +--- + +Get the number of processes started per minute, for the last 10 minutes, in `markdown` format: + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=system.forks&format=markdown&after=-600&points=10&group=sum' +time | started +:---: |:---: +2018-10-27 03:52:00| 245.1706149 +2018-10-27 03:51:00| 152.6654636 +2018-10-27 03:50:00| 163.1755789 +2018-10-27 03:49:00| 176.1574766 +2018-10-27 03:48:00| 178.0137076 +2018-10-27 03:47:00| 183.8306543 +2018-10-27 03:46:00| 264.1635621 +2018-10-27 03:45:00| 205.001551 +2018-10-27 03:44:00| 7026.9852167 +2018-10-27 03:43:00| 205.9904794 +``` + +And this is how it looks when formatted: + +| time | started | +|:--:|:-----:| +| 2018-10-27 03:52:00 | 245.1706149 | +| 2018-10-27 03:51:00 | 152.6654636 | +| 2018-10-27 03:50:00 | 163.1755789 | +| 2018-10-27 03:49:00 | 176.1574766 | +| 2018-10-27 03:48:00 | 178.0137076 | +| 2018-10-27 03:47:00 | 183.8306543 | +| 2018-10-27 03:46:00 | 264.1635621 | +| 2018-10-27 03:45:00 | 205.001551 | +| 2018-10-27 03:44:00 | 7026.9852167 | +| 2018-10-27 03:43:00 | 205.9904794 | + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fformatters%2Fcsv%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/formatters/csv/csv.c b/web/api/formatters/csv/csv.c new file mode 100644 index 0000000..da0a6b5 --- /dev/null +++ b/web/api/formatters/csv/csv.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" +#include "csv.h" + +void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const char *startline, const char *separator, const char *endline, const char *betweenlines, RRDDIM *temp_rd) { + rrdset_check_rdlock(r->st); + + //info("RRD2CSV(): %s: BEGIN", r->st->id); + long c, i; + RRDDIM *d; + + // print the csv header + for(c = 0, i = 0, d = temp_rd?temp_rd:r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(!i) { + buffer_strcat(wb, startline); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, "time"); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + } + buffer_strcat(wb, separator); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, d->name); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + i++; + } + buffer_strcat(wb, endline); + + if(format == DATASOURCE_CSV_MARKDOWN) { + // print the --- line after header + for(c = 0, i = 0, d = temp_rd?temp_rd:r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(!i) { + buffer_strcat(wb, startline); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, ":---:"); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + } + buffer_strcat(wb, separator); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, ":---:"); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + i++; + } + buffer_strcat(wb, endline); + } + + if(!i) { + // no dimensions present + return; + } + + long start = 0, end = rrdr_rows(r), step = 1; + if(!(options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + calculated_number total = 1; + for(i = start; i != end ;i += step) { + calculated_number *cn = &r->v[ i * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; + + buffer_strcat(wb, betweenlines); + buffer_strcat(wb, startline); + + time_t now = r->t[i]; + + if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { + // print the timestamp of the line + buffer_rrd_value(wb, (calculated_number)now); + // in ms + if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); + } + else { + // generate the local date time + struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); + if(!tm) { error("localtime() failed."); continue; } + buffer_date(wb, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + } + + int set_min_max = 0; + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, d = temp_rd?temp_rd:r->st->dimensions; d && c < r->d ;c++, d = d->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + set_min_max = 1; + } + + // for each dimension + for(c = 0, d = temp_rd?temp_rd:r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + buffer_strcat(wb, separator); + + calculated_number n = cn[c]; + + if(co[c] & RRDR_VALUE_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else { + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + n = n * 100 / total; + + if(unlikely(set_min_max)) { + r->min = r->max = n; + set_min_max = 0; + } + + if(n < r->min) r->min = n; + if(n > r->max) r->max = n; + } + + buffer_rrd_value(wb, n); + } + } + + buffer_strcat(wb, endline); + } + //info("RRD2CSV(): %s: END", r->st->id); +} diff --git a/web/api/formatters/csv/csv.h b/web/api/formatters/csv/csv.h new file mode 100644 index 0000000..cf6020d --- /dev/null +++ b/web/api/formatters/csv/csv.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_CSV_H +#define NETDATA_API_FORMATTER_CSV_H + +#include "web/api/queries/rrdr.h" + +extern void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const char *startline, const char *separator, const char *endline, const char *betweenlines, RRDDIM *temp_rd); + +#include "../rrd2json.h" + +#endif //NETDATA_API_FORMATTER_CSV_H diff --git a/web/api/formatters/json/Makefile.am b/web/api/formatters/json/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/formatters/json/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/formatters/json/README.md b/web/api/formatters/json/README.md new file mode 100644 index 0000000..685a3f2 --- /dev/null +++ b/web/api/formatters/json/README.md @@ -0,0 +1,156 @@ + + +# JSON formatter + +The CSV formatter presents [results of database queries](/web/api/queries/README.md) in the following formats: + +| format | content type | description| +|:----:|:----------:|:----------| +| `json` | application/json | return the query result as a json object| +| `jsonp` | application/json | return the query result as a JSONP javascript callback| +| `datatable` | application/json | return the query result as a Google `datatable`| +| `datasource` | application/json | return the query result as a Google Visualization Provider `datasource` javascript callback| + +The CSV formatter respects the following API `&options=`: + +| option | supported | description| +|:----:|:-------:|:----------| +| `google_json` | yes | enable the Google flavor of JSON (using double quotes for strings and `Date()` function for dates| +| `objectrows` | yes | return each row as an object, instead of an array| +| `nonzero` | yes | to return only the dimensions that have at least a non-zero value| +| `flip` | yes | to return the rows older to newer (the default is newer to older)| +| `seconds` | yes | to return the date and time in unix timestamp| +| `ms` | yes | to return the date and time in unit timestamp as milliseconds| +| `percent` | yes | to replace all values with their percentage over the row total| +| `abs` | yes | to turn all values positive| +| `null2zero` | yes | to replace gaps with zeros (the default prints the string `null`| + +## Examples + +To show the differences between each format, in the following examples we query the same +chart (having just one dimension called `active`), changing only the query `format` and its `options`. + +> Using `format=json` and `options=` + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&format=json&options=' +{ + "labels": ["time", "active"], + "data": + [ + [ 1540644600, 224.2516667], + [ 1540644000, 229.29], + [ 1540643400, 222.41], + [ 1540642800, 226.6816667], + [ 1540642200, 246.4083333], + [ 1540641600, 241.0966667] + ] +} +``` + +> Using `format=json` and `options=objectrows` + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&format=json&options=objectrows' +{ + "labels": ["time", "active"], + "data": + [ + { "time": 1540644600, "active": 224.2516667}, + { "time": 1540644000, "active": 229.29}, + { "time": 1540643400, "active": 222.41}, + { "time": 1540642800, "active": 226.6816667}, + { "time": 1540642200, "active": 246.4083333}, + { "time": 1540641600, "active": 241.0966667} + ] +} +``` + +> Using `format=json` and `options=objectrows,google_json` + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&formatjson&options=objectrows,google_json' +{ + "labels": ["time", "active"], + "data": + [ + { "time": new Date(2018,9,27,12,50,0), "active": 224.2516667}, + { "time": new Date(2018,9,27,12,40,0), "active": 229.29}, + { "time": new Date(2018,9,27,12,30,0), "active": 222.41}, + { "time": new Date(2018,9,27,12,20,0), "active": 226.6816667}, + { "time": new Date(2018,9,27,12,10,0), "active": 246.4083333}, + { "time": new Date(2018,9,27,12,0,0), "active": 241.0966667} + ] +} +``` + +> Using `format=jsonp` and `options=` + +```bash +curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&formjsonp&options=' +callback({ + "labels": ["time", "active"], + "data": + [ + [ 1540645200, 235.885], + [ 1540644600, 224.2516667], + [ 1540644000, 229.29], + [ 1540643400, 222.41], + [ 1540642800, 226.6816667], + [ 1540642200, 246.4083333] + ] +}); +``` + +> Using `format=datatable` and `options=` + +```bash +curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&formdatatable&options=' +{ + "cols": + [ + {"id":"","label":"time","pattern":"","type":"datetime"}, + {"id":"","label":"","pattern":"","type":"string","p":{"role":"annotation"}}, + {"id":"","label":"","pattern":"","type":"string","p":{"role":"annotationText"}}, + {"id":"","label":"active","pattern":"","type":"number"} + ], + "rows": + [ + {"c":[{"v":"Date(2018,9,27,13,0,0)"},{"v":null},{"v":null},{"v":235.885}]}, + {"c":[{"v":"Date(2018,9,27,12,50,0)"},{"v":null},{"v":null},{"v":224.2516667}]}, + {"c":[{"v":"Date(2018,9,27,12,40,0)"},{"v":null},{"v":null},{"v":229.29}]}, + {"c":[{"v":"Date(2018,9,27,12,30,0)"},{"v":null},{"v":null},{"v":222.41}]}, + {"c":[{"v":"Date(2018,9,27,12,20,0)"},{"v":null},{"v":null},{"v":226.6816667}]}, + {"c":[{"v":"Date(2018,9,27,12,10,0)"},{"v":null},{"v":null},{"v":246.4083333}]} + ] +} +``` + +> Using `format=datasource` and `options=` + +```bash +curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&after=-3600&points=6&group=average&format=datasource&options=' +google.visualization.Query.setResponse({version:'0.6',reqId:'0',status:'ok',sig:'1540645368',table:{ + "cols": + [ + {"id":"","label":"time","pattern":"","type":"datetime"}, + {"id":"","label":"","pattern":"","type":"string","p":{"role":"annotation"}}, + {"id":"","label":"","pattern":"","type":"string","p":{"role":"annotationText"}}, + {"id":"","label":"active","pattern":"","type":"number"} + ], + "rows": + [ + {"c":[{"v":"Date(2018,9,27,13,0,0)"},{"v":null},{"v":null},{"v":235.885}]}, + {"c":[{"v":"Date(2018,9,27,12,50,0)"},{"v":null},{"v":null},{"v":224.2516667}]}, + {"c":[{"v":"Date(2018,9,27,12,40,0)"},{"v":null},{"v":null},{"v":229.29}]}, + {"c":[{"v":"Date(2018,9,27,12,30,0)"},{"v":null},{"v":null},{"v":222.41}]}, + {"c":[{"v":"Date(2018,9,27,12,20,0)"},{"v":null},{"v":null},{"v":226.6816667}]}, + {"c":[{"v":"Date(2018,9,27,12,10,0)"},{"v":null},{"v":null},{"v":246.4083333}]} + ] +}}); +``` + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fformatters%2Fjson%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/formatters/json/json.c b/web/api/formatters/json/json.c new file mode 100644 index 0000000..f28eb57 --- /dev/null +++ b/web/api/formatters/json/json.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "json.h" + +#define JSON_DATES_JS 1 +#define JSON_DATES_TIMESTAMP 2 + +void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable, RRDDIM *temp_rd) { + rrdset_check_rdlock(r->st); + + //info("RRD2JSON(): %s: BEGIN", r->st->id); + int row_annotations = 0, dates, dates_with_new = 0; + char kq[2] = "", // key quote + sq[2] = "", // string quote + pre_label[101] = "", // before each label + post_label[101] = "", // after each label + pre_date[101] = "", // the beginning of line, to the date + post_date[101] = "", // closing the date + pre_value[101] = "", // before each value + post_value[101] = "", // after each value + post_line[101] = "", // at the end of each row + normal_annotation[201] = "", // default row annotation + overflow_annotation[201] = "", // overflow row annotation + data_begin[101] = "", // between labels and values + finish[101] = ""; // at the end of everything + + if(datatable) { + dates = JSON_DATES_JS; + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + row_annotations = 1; + snprintfz(pre_date, 100, " {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq); + snprintfz(post_date, 100, "%s}", sq); + snprintfz(pre_label, 100, ",\n {%sid%s:%s%s,%slabel%s:%s", kq, kq, sq, sq, kq, kq, sq); + snprintfz(post_label, 100, "%s,%spattern%s:%s%s,%stype%s:%snumber%s}", sq, kq, kq, sq, sq, kq, kq, sq, sq); + snprintfz(pre_value, 100, ",{%sv%s:", kq, kq); + strcpy(post_value, "}"); + strcpy(post_line, "]}"); + snprintfz(data_begin, 100, "\n ],\n %srows%s:\n [\n", kq, kq); + strcpy(finish, "\n ]\n}"); + + snprintfz(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq); + snprintfz(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq); + + buffer_sprintf(wb, "{\n %scols%s:\n [\n", kq, kq); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%stime%s,%spattern%s:%s%s,%stype%s:%sdatetime%s},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + + // remove the valueobjects flag + // google wants its own keys + if(options & RRDR_OPTION_OBJECTSROWS) + options &= ~RRDR_OPTION_OBJECTSROWS; + } + else { + kq[0] = '"'; + sq[0] = '"'; + if(options & RRDR_OPTION_GOOGLE_JSON) { + dates = JSON_DATES_JS; + dates_with_new = 1; + } + else { + dates = JSON_DATES_TIMESTAMP; + dates_with_new = 0; + } + if( options & RRDR_OPTION_OBJECTSROWS ) + strcpy(pre_date, " { "); + else + strcpy(pre_date, " [ "); + strcpy(pre_label, ", \""); + strcpy(post_label, "\""); + strcpy(pre_value, ", "); + if( options & RRDR_OPTION_OBJECTSROWS ) + strcpy(post_line, "}"); + else + strcpy(post_line, "]"); + snprintfz(data_begin, 100, "],\n %sdata%s:\n [\n", kq, kq); + strcpy(finish, "\n ]\n}"); + + buffer_sprintf(wb, "{\n %slabels%s: [", kq, kq); + buffer_sprintf(wb, "%stime%s", sq, sq); + } + + // ------------------------------------------------------------------------- + // print the JSON header + + long c, i; + RRDDIM *rd; + + // print the header lines + for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + buffer_strcat(wb, pre_label); + buffer_strcat(wb, rd->name); +// buffer_strcat(wb, "."); +// buffer_strcat(wb, rd->rrdset->name); + buffer_strcat(wb, post_label); + i++; + } + if(!i) { + buffer_strcat(wb, pre_label); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, post_label); + } + + // print the begin of row data + buffer_strcat(wb, data_begin); + + // if all dimensions are hidden, print a null + if(!i) { + buffer_strcat(wb, finish); + return; + } + + long start = 0, end = rrdr_rows(r), step = 1; + if(!(options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + calculated_number total = 1; + for(i = start; i != end ;i += step) { + calculated_number *cn = &r->v[ i * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; + + time_t now = r->t[i]; + + if(dates == JSON_DATES_JS) { + // generate the local date time + struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); + if(!tm) { error("localtime_r() failed."); continue; } + + if(likely(i != start)) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, pre_date); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%stime%s: ", kq, kq); + + if(dates_with_new) + buffer_strcat(wb, "new "); + + buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + buffer_strcat(wb, post_date); + + if(row_annotations) { + // google supports one annotation per row + int annotation_found = 0; + for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd ;c++, rd = rd->next) { + if(unlikely(!(r->od[c] & RRDR_DIMENSION_SELECTED))) continue; + + if(co[c] & RRDR_VALUE_RESET) { + buffer_strcat(wb, overflow_annotation); + annotation_found = 1; + break; + } + } + if(!annotation_found) + buffer_strcat(wb, normal_annotation); + } + } + else { + // print the timestamp of the line + if(likely(i != start)) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, pre_date); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%stime%s: ", kq, kq); + + buffer_rrd_value(wb, (calculated_number)r->t[i]); + // in ms + if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); + + buffer_strcat(wb, post_date); + } + + int set_min_max = 0; + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + set_min_max = 1; + } + + // for each dimension + for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + calculated_number n = cn[c]; + + buffer_strcat(wb, pre_value); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%s%s%s: ", kq, rd->name, kq); + + if(co[c] & RRDR_VALUE_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else { + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + n = n * 100 / total; + + if(unlikely(set_min_max)) { + r->min = r->max = n; + set_min_max = 0; + } + + if(n < r->min) r->min = n; + if(n > r->max) r->max = n; + } + + buffer_rrd_value(wb, n); + } + + buffer_strcat(wb, post_value); + } + + buffer_strcat(wb, post_line); + } + + buffer_strcat(wb, finish); + //info("RRD2JSON(): %s: END", r->st->id); +} diff --git a/web/api/formatters/json/json.h b/web/api/formatters/json/json.h new file mode 100644 index 0000000..6d73a3f --- /dev/null +++ b/web/api/formatters/json/json.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_JSON_H +#define NETDATA_API_FORMATTER_JSON_H + +#include "../rrd2json.h" + +extern void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable, RRDDIM *temp_rd); + +#endif //NETDATA_API_FORMATTER_JSON_H diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c new file mode 100644 index 0000000..cf4f109 --- /dev/null +++ b/web/api/formatters/json_wrapper.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "json_wrapper.h" + +void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd, char *chart_label_key) { + rrdset_check_rdlock(r->st); + + long rows = rrdr_rows(r); + long c, i; + RRDDIM *rd; + + //info("JSONWRAPPER(): %s: BEGIN", r->st->id); + char kq[2] = "", // key quote + sq[2] = ""; // string quote + + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + + rrdset_rdlock(r->st); + buffer_sprintf(wb, "{\n" + " %sapi%s: 1,\n" + " %sid%s: %s%s%s,\n" + " %sname%s: %s%s%s,\n" + " %sview_update_every%s: %d,\n" + " %supdate_every%s: %d,\n" + " %sfirst_entry%s: %u,\n" + " %slast_entry%s: %u,\n" + " %sbefore%s: %u,\n" + " %safter%s: %u,\n" + " %sdimension_names%s: [" + , kq, kq + , kq, kq, sq, temp_rd?r->st->context:r->st->id, sq + , kq, kq, sq, temp_rd?r->st->context:r->st->name, sq + , kq, kq, r->update_every + , kq, kq, r->st->update_every + , kq, kq, (uint32_t)rrdset_first_entry_t_nolock(r->st) + , kq, kq, (uint32_t)rrdset_last_entry_t_nolock(r->st) + , kq, kq, (uint32_t)r->before + , kq, kq, (uint32_t)r->after + , kq, kq); + rrdset_unlock(r->st); + + for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + buffer_strcat(wb, sq); + buffer_strcat(wb, rd->name); + buffer_strcat(wb, sq); + i++; + } + if(!i) { +#ifdef NETDATA_INTERNAL_CHECKS + error("RRDR is empty for %s (RRDR has %d dimensions, options is 0x%08x)", r->st->id, r->d, options); +#endif + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + + buffer_sprintf(wb, "],\n" + " %sdimension_ids%s: [" + , kq, kq); + + for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + buffer_strcat(wb, sq); + buffer_strcat(wb, rd->id); + buffer_strcat(wb, sq); + i++; + } + if(!i) { + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + buffer_strcat(wb, "],\n"); + + // Composite charts + if (temp_rd) { + buffer_sprintf( + wb, + " %schart_ids%s: [", + kq, kq); + + for (c = 0, i = 0, rd = temp_rd ; rd && c < r->d; c++, rd = rd->next) { + if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) + continue; + if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) + continue; + + if (i) + buffer_strcat(wb, ", "); + buffer_strcat(wb, sq); + buffer_strcat(wb, rd->rrdset->name); + buffer_strcat(wb, sq); + i++; + } + if (!i) { + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + buffer_strcat(wb, "],\n"); + if (chart_label_key) { + buffer_sprintf(wb, " %schart_labels%s: { ", kq, kq); + + SIMPLE_PATTERN *pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *original_pattern = pattern; + char *label_key = NULL; + int keys = 0; + while (pattern && (label_key = simple_pattern_iterate(&pattern))) { + uint32_t key_hash = simple_hash(label_key); + struct label *current_label; + + if (keys) + buffer_strcat(wb, ", "); + buffer_sprintf(wb, "%s%s%s : [", kq, label_key, kq); + keys++; + + for (c = 0, i = 0, rd = temp_rd; rd && c < r->d; c++, rd = rd->next) { + if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) + continue; + if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) + continue; + if (i) + buffer_strcat(wb, ", "); + + current_label = rrdset_lookup_label_key(rd->rrdset, label_key, key_hash); + if (current_label) { + buffer_strcat(wb, sq); + buffer_strcat(wb, current_label->value); + buffer_strcat(wb, sq); + } else + buffer_strcat(wb, "null"); + i++; + } + if (!i) { + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + buffer_strcat(wb, "]"); + } + buffer_strcat(wb, "},\n"); + simple_pattern_free(original_pattern); + } + } + + buffer_sprintf(wb, " %slatest_values%s: [" + , kq, kq); + + for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + i++; + + calculated_number value = rd->last_stored_value; + if (NAN == value) + buffer_strcat(wb, "null"); + else + buffer_rrd_value(wb, value); + /* + storage_number n = rd->values[rrdset_last_slot(r->st)]; + + if(!does_storage_number_exist(n)) + buffer_strcat(wb, "null"); + else + buffer_rrd_value(wb, unpack_storage_number(n)); + */ + } + if(!i) { + rows = 0; + buffer_strcat(wb, "null"); + } + + buffer_sprintf(wb, "],\n" + " %sview_latest_values%s: [" + , kq, kq); + + i = 0; + if(rows) { + calculated_number total = 1; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + calculated_number *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + } + + for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + i++; + + calculated_number *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ (rrdr_rows(r) - 1) * r->d ]; + calculated_number n = cn[c]; + + if(co[c] & RRDR_VALUE_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else { + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) + n = n * 100 / total; + + buffer_rrd_value(wb, n); + } + } + } + if(!i) { + rows = 0; + buffer_strcat(wb, "null"); + } + + buffer_sprintf(wb, "],\n" + " %sdimensions%s: %ld,\n" + " %spoints%s: %ld,\n" + " %sformat%s: %s" + , kq, kq, i + , kq, kq, rows + , kq, kq, sq + ); + + rrdr_buffer_print_format(wb, format); + + if((options & RRDR_OPTION_CUSTOM_VARS) && (options & RRDR_OPTION_JSON_WRAP)) { + buffer_sprintf(wb, "%s,\n %schart_variables%s: ", sq, kq, kq); + health_api_v1_chart_custom_variables2json(r->st, wb); + } + else + buffer_sprintf(wb, "%s", sq); + + buffer_sprintf(wb, ",\n %sresult%s: ", kq, kq); + + if(string_value) buffer_strcat(wb, sq); + //info("JSONWRAPPER(): %s: END", r->st->id); +} + +void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { + (void)format; + + char kq[2] = "", // key quote + sq[2] = ""; // string quote + + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + + if(string_value) buffer_strcat(wb, sq); + + buffer_sprintf(wb, ",\n %smin%s: ", kq, kq); + buffer_rrd_value(wb, r->min); + buffer_sprintf(wb, ",\n %smax%s: ", kq, kq); + buffer_rrd_value(wb, r->max); + buffer_strcat(wb, "\n}\n"); +} diff --git a/web/api/formatters/json_wrapper.h b/web/api/formatters/json_wrapper.h new file mode 100644 index 0000000..d48d5d1 --- /dev/null +++ b/web/api/formatters/json_wrapper.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_JSON_WRAPPER_H +#define NETDATA_API_FORMATTER_JSON_WRAPPER_H + +#include "rrd2json.h" + +extern void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd, char *chart_key); +extern void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value); + +#endif //NETDATA_API_FORMATTER_JSON_WRAPPER_H diff --git a/web/api/formatters/rrd2json.c b/web/api/formatters/rrd2json.c new file mode 100644 index 0000000..d8e2480 --- /dev/null +++ b/web/api/formatters/rrd2json.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "web/api/web_api_v1.h" + +static inline void free_temp_rrddim(RRDDIM *temp_rd) +{ + if (unlikely(!temp_rd)) + return; + + RRDDIM *t; + while (temp_rd) { + t = temp_rd->next; + freez((char *)temp_rd->id); + freez((char *)temp_rd->name); +#ifdef ENABLE_DBENGINE + if (temp_rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) + freez(temp_rd->state->metric_uuid); +#endif + freez(temp_rd->state); + freez(temp_rd); + temp_rd = t; + } +} + +void free_context_param_list(struct context_param **param_list) +{ + if (unlikely(!param_list || !*param_list)) + return; + + free_temp_rrddim(((*param_list)->rd)); + freez((*param_list)); + *param_list = NULL; +} + +void rebuild_context_param_list(struct context_param *context_param_list, time_t after_requested) +{ + RRDDIM *temp_rd = context_param_list->rd; + RRDDIM *new_rd_list = NULL, *t; + while (temp_rd) { + t = temp_rd->next; + if (rrdset_last_entry_t(temp_rd->rrdset) >= after_requested) { + temp_rd->next = new_rd_list; + new_rd_list = temp_rd; + } else { + freez((char *)temp_rd->id); + freez((char *)temp_rd->name); +#ifdef ENABLE_DBENGINE + if (temp_rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) + freez(temp_rd->state->metric_uuid); +#endif + freez(temp_rd->state); + freez(temp_rd); + } + temp_rd = t; + } + context_param_list->rd = new_rd_list; +}; + +void build_context_param_list(struct context_param **param_list, RRDSET *st) +{ + if (unlikely(!param_list || !st)) + return; + + if (unlikely(!(*param_list))) { + *param_list = mallocz(sizeof(struct context_param)); + (*param_list)->first_entry_t = LONG_MAX; + (*param_list)->last_entry_t = 0; + (*param_list)->rd = NULL; + } + + RRDDIM *rd1; + st->last_accessed_time = now_realtime_sec(); + rrdset_rdlock(st); + + (*param_list)->first_entry_t = MIN((*param_list)->first_entry_t, rrdset_first_entry_t_nolock(st)); + (*param_list)->last_entry_t = MAX((*param_list)->last_entry_t, rrdset_last_entry_t_nolock(st)); + + rrddim_foreach_read(rd1, st) { + RRDDIM *rd = mallocz(rd1->memsize); + memcpy(rd, rd1, rd1->memsize); + rd->id = strdupz(rd1->id); + rd->name = strdupz(rd1->name); + rd->state = mallocz(sizeof(*rd->state)); + memcpy(rd->state, rd1->state, sizeof(*rd->state)); + memcpy(&rd->state->collect_ops, &rd1->state->collect_ops, sizeof(struct rrddim_collect_ops)); + memcpy(&rd->state->query_ops, &rd1->state->query_ops, sizeof(struct rrddim_query_ops)); +#ifdef ENABLE_DBENGINE + if (rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + rd->state->metric_uuid = mallocz(sizeof(uuid_t)); + uuid_copy(*rd->state->metric_uuid, *rd1->state->metric_uuid); + } +#endif + rd->next = (*param_list)->rd; + (*param_list)->rd = rd; + } + + rrdset_unlock(st); +} + +void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb) { + rrdset2json(st, wb, NULL, NULL, 0); +} + +void rrdr_buffer_print_format(BUFFER *wb, uint32_t format) { + switch(format) { + case DATASOURCE_JSON: + buffer_strcat(wb, DATASOURCE_FORMAT_JSON); + break; + + case DATASOURCE_DATATABLE_JSON: + buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSON); + break; + + case DATASOURCE_DATATABLE_JSONP: + buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSONP); + break; + + case DATASOURCE_JSONP: + buffer_strcat(wb, DATASOURCE_FORMAT_JSONP); + break; + + case DATASOURCE_SSV: + buffer_strcat(wb, DATASOURCE_FORMAT_SSV); + break; + + case DATASOURCE_CSV: + buffer_strcat(wb, DATASOURCE_FORMAT_CSV); + break; + + case DATASOURCE_TSV: + buffer_strcat(wb, DATASOURCE_FORMAT_TSV); + break; + + case DATASOURCE_HTML: + buffer_strcat(wb, DATASOURCE_FORMAT_HTML); + break; + + case DATASOURCE_JS_ARRAY: + buffer_strcat(wb, DATASOURCE_FORMAT_JS_ARRAY); + break; + + case DATASOURCE_SSV_COMMA: + buffer_strcat(wb, DATASOURCE_FORMAT_SSV_COMMA); + break; + + default: + buffer_strcat(wb, "unknown"); + break; + } +} + +int rrdset2value_api_v1( + RRDSET *st + , BUFFER *wb + , calculated_number *n + , const char *dimensions + , long points + , long long after + , long long before + , int group_method + , long group_time + , uint32_t options + , time_t *db_after + , time_t *db_before + , int *value_is_null +) { + + RRDR *r = rrd2rrdr(st, points, after, before, group_method, group_time, options, dimensions, NULL); + + if(!r) { + if(value_is_null) *value_is_null = 1; + return HTTP_RESP_INTERNAL_SERVER_ERROR; + } + + if(rrdr_rows(r) == 0) { + rrdr_free(r); + + if(db_after) *db_after = 0; + if(db_before) *db_before = 0; + if(value_is_null) *value_is_null = 1; + + return HTTP_RESP_BAD_REQUEST; + } + + if(wb) { + if (r->result_options & RRDR_RESULT_OPTION_RELATIVE) + buffer_no_cacheable(wb); + else if (r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + buffer_cacheable(wb); + } + + if(db_after) *db_after = r->after; + if(db_before) *db_before = r->before; + + long i = (!(options & RRDR_OPTION_REVERSED))?rrdr_rows(r) - 1:0; + *n = rrdr2value(r, i, options, value_is_null); + + rrdr_free(r); + return HTTP_RESP_OK; +} + +int rrdset2anything_api_v1( + RRDSET *st + , BUFFER *wb + , BUFFER *dimensions + , uint32_t format + , long points + , long long after + , long long before + , int group_method + , long group_time + , uint32_t options + , time_t *latest_timestamp + , struct context_param *context_param_list + , char *chart_label_key +) { + time_t last_accessed_time = now_realtime_sec(); + st->last_accessed_time = last_accessed_time; + + + RRDR *r = rrd2rrdr(st, points, after, before, group_method, group_time, options, dimensions?buffer_tostring(dimensions):NULL, context_param_list); + if(!r) { + buffer_strcat(wb, "Cannot generate output with these parameters on this chart."); + return HTTP_RESP_INTERNAL_SERVER_ERROR; + } + + RRDDIM *temp_rd = context_param_list ? context_param_list->rd : NULL; + + if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) + buffer_no_cacheable(wb); + else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + buffer_cacheable(wb); + + if(latest_timestamp && rrdr_rows(r) > 0) + *latest_timestamp = r->before; + + switch(format) { + case DATASOURCE_SSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + rrdr2ssv(r, wb, options, "", " ", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2ssv(r, wb, options, "", " ", ""); + } + break; + + case DATASOURCE_SSV_COMMA: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + rrdr2ssv(r, wb, options, "", ",", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2ssv(r, wb, options, "", ",", ""); + } + break; + + case DATASOURCE_JS_ARRAY: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + rrdr2ssv(r, wb, options, "[", ",", "]"); + rrdr_json_wrapper_end(r, wb, format, options, 0); + } + else { + wb->contenttype = CT_APPLICATION_JSON; + rrdr2ssv(r, wb, options, "[", ",", "]"); + } + break; + + case DATASOURCE_CSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + rrdr2csv(r, wb, format, options, "", ",", "\\n", "", temp_rd); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2csv(r, wb, format, options, "", ",", "\r\n", "", temp_rd); + } + break; + + case DATASOURCE_CSV_MARKDOWN: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + rrdr2csv(r, wb, format, options, "", "|", "\\n", "", temp_rd); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2csv(r, wb, format, options, "", "|", "\r\n", "", temp_rd); + } + break; + + case DATASOURCE_CSV_JSON_ARRAY: + wb->contenttype = CT_APPLICATION_JSON; + if(options & RRDR_OPTION_JSON_WRAP) { + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + buffer_strcat(wb, "[\n"); + rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n", temp_rd); + buffer_strcat(wb, "\n]"); + rrdr_json_wrapper_end(r, wb, format, options, 0); + } + else { + wb->contenttype = CT_APPLICATION_JSON; + buffer_strcat(wb, "[\n"); + rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n", temp_rd); + buffer_strcat(wb, "\n]"); + } + break; + + case DATASOURCE_TSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + rrdr2csv(r, wb, format, options, "", "\t", "\\n", "", temp_rd); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2csv(r, wb, format, options, "", "\t", "\r\n", "", temp_rd); + } + break; + + case DATASOURCE_HTML: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); + buffer_strcat(wb, "\\n
\\n\\n"); + rrdr2csv(r, wb, format, options, "\\n", "", temp_rd); + buffer_strcat(wb, "
", "", "
\\n
\\n\\n"); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_HTML; + buffer_strcat(wb, "\n
\n\n"); + rrdr2csv(r, wb, format, options, "\n", "", temp_rd); + buffer_strcat(wb, "
", "", "
\n
\n\n"); + } + break; + + case DATASOURCE_DATATABLE_JSONP: + wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + + rrdr2json(r, wb, options, 1, temp_rd); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_DATATABLE_JSON: + wb->contenttype = CT_APPLICATION_JSON; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + + rrdr2json(r, wb, options, 1, temp_rd); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_JSONP: + wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + + rrdr2json(r, wb, options, 0, temp_rd); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_JSON: + default: + wb->contenttype = CT_APPLICATION_JSON; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); + + rrdr2json(r, wb, options, 0, temp_rd); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + } + + rrdr_free(r); + return HTTP_RESP_OK; +} diff --git a/web/api/formatters/rrd2json.h b/web/api/formatters/rrd2json.h new file mode 100644 index 0000000..3dc5989 --- /dev/null +++ b/web/api/formatters/rrd2json.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RRD2JSON_H +#define NETDATA_RRD2JSON_H 1 + +#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" +#include "web/api/formatters/ssv/ssv.h" +#include "web/api/formatters/json/json.h" +#include "web/api/formatters/value/value.h" + +#include "web/api/formatters/rrdset2json.h" +#include "web/api/formatters/charts2json.h" +#include "web/api/formatters/json_wrapper.h" + +#include "web/server/web_client.h" + +#define HOSTNAME_MAX 1024 + +#define API_RELATIVE_TIME_MAX (3 * 365 * 86400) + +// type of JSON generations +#define DATASOURCE_INVALID (-1) +#define DATASOURCE_JSON 0 +#define DATASOURCE_DATATABLE_JSON 1 +#define DATASOURCE_DATATABLE_JSONP 2 +#define DATASOURCE_SSV 3 +#define DATASOURCE_CSV 4 +#define DATASOURCE_JSONP 5 +#define DATASOURCE_TSV 6 +#define DATASOURCE_HTML 7 +#define DATASOURCE_JS_ARRAY 8 +#define DATASOURCE_SSV_COMMA 9 +#define DATASOURCE_CSV_JSON_ARRAY 10 +#define DATASOURCE_CSV_MARKDOWN 11 + +#define DATASOURCE_FORMAT_JSON "json" +#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" + +extern void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb); +extern void rrdr_buffer_print_format(BUFFER *wb, uint32_t format); + +extern int rrdset2anything_api_v1( + RRDSET *st + , BUFFER *wb + , BUFFER *dimensions + , uint32_t format + , long points + , long long after + , long long before + , int group_method + , long group_time + , uint32_t options + , time_t *latest_timestamp + , struct context_param *context_param_list + , char *chart_label_key +); + +extern int rrdset2value_api_v1( + RRDSET *st + , BUFFER *wb + , calculated_number *n + , const char *dimensions + , long points + , long long after + , long long before + , int group_method + , long group_time + , uint32_t options + , time_t *db_after + , time_t *db_before + , int *value_is_null +); + +extern void build_context_param_list(struct context_param **param_list, RRDSET *st); +extern void rebuild_context_param_list(struct context_param *context_param_list, time_t after_requested); +extern void free_context_param_list(struct context_param **param_list); + +#endif /* NETDATA_RRD2JSON_H */ diff --git a/web/api/formatters/rrdset2json.c b/web/api/formatters/rrdset2json.c new file mode 100644 index 0000000..5482881 --- /dev/null +++ b/web/api/formatters/rrdset2json.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rrdset2json.h" + +void chart_labels2json(RRDSET *st, BUFFER *wb, size_t indentation) +{ + char tabs[11]; + struct label_index *labels = &st->state->labels; + + if (indentation > 10) + indentation = 10; + + tabs[0] = '\0'; + while (indentation) { + strcat(tabs, "\t"); + indentation--; + } + + int count = 0; + netdata_rwlock_rdlock(&labels->labels_rwlock); + for (struct label *label = labels->head; label; label = label->next) { + if(count > 0) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, tabs); + + char value[CONFIG_MAX_VALUE * 2 + 1]; + sanitize_json_string(value, label->value, CONFIG_MAX_VALUE * 2); + buffer_sprintf(wb, "\"%s\": \"%s\"", label->key, value); + + count++; + } + buffer_strcat(wb, "\n"); + netdata_rwlock_unlock(&labels->labels_rwlock); +} + +// generate JSON for the /api/v1/chart API call + +void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memory_used, int skip_volatile) { + rrdset_rdlock(st); + + time_t first_entry_t = rrdset_first_entry_t_nolock(st); + time_t last_entry_t = rrdset_last_entry_t_nolock(st); + + buffer_sprintf(wb, + "\t\t{\n" + "\t\t\t\"id\": \"%s\",\n" + "\t\t\t\"name\": \"%s\",\n" + "\t\t\t\"type\": \"%s\",\n" + "\t\t\t\"family\": \"%s\",\n" + "\t\t\t\"context\": \"%s\",\n" + "\t\t\t\"title\": \"%s (%s)\",\n" + "\t\t\t\"priority\": %ld,\n" + "\t\t\t\"plugin\": \"%s\",\n" + "\t\t\t\"module\": \"%s\",\n" + "\t\t\t\"enabled\": %s,\n" + "\t\t\t\"units\": \"%s\",\n" + "\t\t\t\"data_url\": \"/api/v1/data?chart=%s\",\n" + "\t\t\t\"chart_type\": \"%s\",\n" + , st->id + , st->name + , st->type + , st->family + , st->context + , st->title, st->name + , st->priority + , st->plugin_name?st->plugin_name:"" + , st->module_name?st->module_name:"" + , rrdset_flag_check(st, RRDSET_FLAG_ENABLED)?"true":"false" + , st->units + , st->name + , rrdset_type_name(st->chart_type) + ); + + if (likely(!skip_volatile)) + buffer_sprintf(wb, + "\t\t\t\"duration\": %ld,\n" + , last_entry_t - first_entry_t + st->update_every//st->entries * st->update_every + ); + + buffer_sprintf(wb, + "\t\t\t\"first_entry\": %ld,\n" + , first_entry_t //rrdset_first_entry_t(st) + ); + + if (likely(!skip_volatile)) + buffer_sprintf(wb, + "\t\t\t\"last_entry\": %ld,\n" + , last_entry_t//rrdset_last_entry_t(st) + ); + + buffer_sprintf(wb, + "\t\t\t\"update_every\": %d,\n" + "\t\t\t\"dimensions\": {\n" + , st->update_every + ); + + unsigned long memory = st->memsize; + + size_t dimensions = 0; + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + if(rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN) || rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) continue; + + memory += rd->memsize; + + if (dimensions) + buffer_strcat(wb, ",\n\t\t\t\t\""); + else + buffer_strcat(wb, "\t\t\t\t\""); + buffer_strcat_jsonescape(wb, rd->id); + buffer_strcat(wb, "\": { \"name\": \""); + buffer_strcat_jsonescape(wb, rd->name); + buffer_strcat(wb, "\" }"); + + dimensions++; + } + + if(dimensions_count) *dimensions_count += dimensions; + if(memory_used) *memory_used += memory; + + buffer_sprintf(wb, "\n\t\t\t},\n\t\t\t\"chart_variables\": "); + health_api_v1_chart_custom_variables2json(st, wb); + + buffer_strcat(wb, ",\n\t\t\t\"green\": "); + buffer_rrd_value(wb, st->green); + buffer_strcat(wb, ",\n\t\t\t\"red\": "); + buffer_rrd_value(wb, st->red); + + if (likely(!skip_volatile)) { + buffer_strcat(wb, ",\n\t\t\t\"alarms\": {\n"); + size_t alarms = 0; + RRDCALC *rc; + for (rc = st->alarms; rc; rc = rc->rrdset_next) { + buffer_sprintf( + wb, + "%s" + "\t\t\t\t\"%s\": {\n" + "\t\t\t\t\t\"id\": %u,\n" + "\t\t\t\t\t\"status\": \"%s\",\n" + "\t\t\t\t\t\"units\": \"%s\",\n" + "\t\t\t\t\t\"update_every\": %d\n" + "\t\t\t\t}", + (alarms) ? ",\n" : "", rc->name, rc->id, rrdcalc_status2string(rc->status), rc->units, + rc->update_every); + + alarms++; + } + buffer_sprintf(wb, + "\n\t\t\t}" + ); + } + buffer_strcat(wb, ",\n\t\t\t\"chart_labels\": {\n"); + chart_labels2json(st, wb, 2); + buffer_strcat(wb, "\t\t\t}\n"); + + + buffer_sprintf(wb, + "\n\t\t}" + ); + + rrdset_unlock(st); +} diff --git a/web/api/formatters/rrdset2json.h b/web/api/formatters/rrdset2json.h new file mode 100644 index 0000000..697c846 --- /dev/null +++ b/web/api/formatters/rrdset2json.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_RRDSET2JSON_H +#define NETDATA_API_FORMATTER_RRDSET2JSON_H + +#include "rrd2json.h" + +extern void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memory_used, int skip_volatile); + +#endif //NETDATA_API_FORMATTER_RRDSET2JSON_H diff --git a/web/api/formatters/ssv/Makefile.am b/web/api/formatters/ssv/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/formatters/ssv/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/formatters/ssv/README.md b/web/api/formatters/ssv/README.md new file mode 100644 index 0000000..d439949 --- /dev/null +++ b/web/api/formatters/ssv/README.md @@ -0,0 +1,59 @@ + + +# SSV formatter + +The SSV formatter sums all dimensions in [results of database queries](/web/api/queries/README.md) +to a single value and returns a list of such values showing how it changes through time. + +It supports the following formats: + +| format | content type | description | +|:----:|:----------:|:----------| +| `ssv` | text/plain | a space separated list of values | +| `ssvcomma` | text/plain | a comma separated list of values | +| `array` | application/json | a JSON array | + +The SSV formatter respects the following API `&options=`: + +| option | supported | description | +| :----:|:-------:|:----------| +| `nonzero` | yes | to return only the dimensions that have at least a non-zero value | +| `flip` | yes | to return the numbers older to newer (the default is newer to older) | +| `percent` | yes | to replace all values with their percentage over the row total | +| `abs` | yes | to turn all values positive, before using them | +| `min2max` | yes | to return the delta from the minimum value to the maximum value (across dimensions) | + +## Examples + +Get the average system CPU utilization of the last hour, in 6 values (one every 10 minutes): + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=system.cpu&format=ssv&after=-3600&points=6&group=average' +1.741352 1.6800467 1.769411 1.6761112 1.629862 1.6807968 +``` + +--- + +Get the total mysql bandwidth (in + out) for the last hour, in 6 values (one every 10 minutes): + +Netdata returns bandwidth in `kilobits`. + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=mysql_local.net&format=ssvcomma&after=-3600&points=6&group=sum&options=abs' +72618.7936215,72618.778889,72618.788084,72618.9195918,72618.7760612,72618.6712421 +``` + +--- + +Get the web server max connections for the last hour, in 12 values (one every 5 minutes) +in a JSON array: + +```bash +# curl -Ss 'https://registry.my-netdata.io/api/v1/data?chart=nginx_local.connections&format=array&after=-3600&points=12&group=max' +[278,258,268,239,259,260,243,266,278,318,264,258] +``` + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fformatters%2Fssv%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/formatters/ssv/ssv.c b/web/api/formatters/ssv/ssv.c new file mode 100644 index 0000000..eeba028 --- /dev/null +++ b/web/api/formatters/ssv/ssv.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ssv.h" + +void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, const char *separator, const char *suffix) { + //info("RRD2SSV(): %s: BEGIN", r->st->id); + long i; + + buffer_strcat(wb, prefix); + long start = 0, end = rrdr_rows(r), step = 1; + if(!(options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + for(i = start; i != end ;i += step) { + int all_values_are_null = 0; + calculated_number v = rrdr2value(r, i, options, &all_values_are_null); + + if(likely(i != start)) { + if(r->min > v) r->min = v; + if(r->max < v) r->max = v; + } + else { + r->min = v; + r->max = v; + } + + if(likely(i != start)) + buffer_strcat(wb, separator); + + if(all_values_are_null) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else + buffer_rrd_value(wb, v); + } + buffer_strcat(wb, suffix); + //info("RRD2SSV(): %s: END", r->st->id); +} diff --git a/web/api/formatters/ssv/ssv.h b/web/api/formatters/ssv/ssv.h new file mode 100644 index 0000000..6963dcf --- /dev/null +++ b/web/api/formatters/ssv/ssv.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_SSV_H +#define NETDATA_API_FORMATTER_SSV_H + +#include "../rrd2json.h" + +extern void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, const char *separator, const char *suffix); + +#endif //NETDATA_API_FORMATTER_SSV_H diff --git a/web/api/formatters/value/Makefile.am b/web/api/formatters/value/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/formatters/value/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/formatters/value/README.md b/web/api/formatters/value/README.md new file mode 100644 index 0000000..21c9370 --- /dev/null +++ b/web/api/formatters/value/README.md @@ -0,0 +1,24 @@ + + +# Value formatter + +The Value formatter presents [results of database queries](/web/api/queries/README.md) as a single value. + +To calculate the single value to be returned, it sums the values of all dimensions. + +The Value formatter respects the following API `&options=`: + +| option | supported | description | +|:----: |:-------: |:---------- | +| `percent` | yes | to replace all values with their percentage over the row total| +| `abs` | yes | to turn all values positive, before using them | +| `min2max` | yes | to return the delta from the minimum value to the maximum value (across dimensions)| + +The Value formatter is not exposed by the API by itself. +Instead it is used by the [`ssv`](/web/api/formatters/ssv/README.md) formatter +and [health monitoring queries](/health/README.md). + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fformatters%2Fvalue%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/formatters/value/value.c b/web/api/formatters/value/value.c new file mode 100644 index 0000000..aea6c16 --- /dev/null +++ b/web/api/formatters/value/value.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "value.h" + + +inline calculated_number rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null) { + rrdset_check_rdlock(r->st); + + long c; + RRDDIM *d; + + calculated_number *cn = &r->v[ i * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; + + calculated_number sum = 0, min = 0, max = 0, v; + int all_null = 1, init = 1; + + calculated_number total = 1; + int set_min_max = 0; + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + set_min_max = 1; + } + + // for each dimension + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + n = n * 100 / total; + + if(unlikely(set_min_max)) { + r->min = r->max = n; + set_min_max = 0; + } + + if(n < r->min) r->min = n; + if(n > r->max) r->max = n; + } + + if(unlikely(init)) { + if(n > 0) { + min = 0; + max = n; + } + else { + min = n; + max = 0; + } + init = 0; + } + + if(likely(!(co[c] & RRDR_VALUE_EMPTY))) { + all_null = 0; + sum += n; + } + + if(n < min) min = n; + if(n > max) max = n; + } + + if(unlikely(all_null)) { + if(likely(all_values_are_null)) + *all_values_are_null = 1; + return 0; + } + else { + if(likely(all_values_are_null)) + *all_values_are_null = 0; + } + + if(options & RRDR_OPTION_MIN2MAX) + v = max - min; + else + v = sum; + + return v; +} diff --git a/web/api/formatters/value/value.h b/web/api/formatters/value/value.h new file mode 100644 index 0000000..d9e981f --- /dev/null +++ b/web/api/formatters/value/value.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_FORMATTER_VALUE_H +#define NETDATA_API_FORMATTER_VALUE_H + +#include "../rrd2json.h" + +extern calculated_number rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null); + +#endif //NETDATA_API_FORMATTER_VALUE_H diff --git a/web/api/health/Makefile.am b/web/api/health/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/health/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/health/README.md b/web/api/health/README.md new file mode 100644 index 0000000..b6e8b5c --- /dev/null +++ b/web/api/health/README.md @@ -0,0 +1,225 @@ + + +# Health API Calls + +## Health Read API + +### Enabled Alarms + +Netdata enables alarms on demand, i.e. when the chart they should be linked to starts collecting data. So, although many +more alarms are configured, only the useful ones are enabled. + +To get the list of all enabled alarms, open your browser and navigate to `http://NODE:19999/api/v1/alarms?all`, +replacing `NODE` with the IP address or hostname for your Agent dashboard. + +### Raised Alarms + +This API call will return the alarms currently in WARNING or CRITICAL state. + +`http://NODE:19999/api/v1/alarms` + +### Event Log + +The size of the alarm log is configured in `netdata.conf`. There are 2 settings: the rotation of the alarm log file and the in memory size of the alarm log. + +``` +[health] + in memory max health log entries = 1000 + rotate log every lines = 2000 +``` + +The API call retrieves all entries of the alarm log: + +`http://NODE:19999/api/v1/alarm_log` + +### Alarm Log Incremental Updates + +`http://NODE:19999/api/v1/alarm_log?after=UNIQUEID` + +The above returns all the events in the alarm log that occurred after UNIQUEID (you poll it once without `after=`, remember the last UNIQUEID of the returned set, which you give back to get incrementally the next events). + +### Alarm badges + +The following will return an SVG badge of the alarm named `NAME`, attached to the chart named `CHART`. + +`http://NODE:19999/api/v1/badge.svg?alarm=NAME&chart=CHART` + +## Health Management API + +Netdata v1.12 and beyond provides a command API to control health checks and notifications at runtime. The feature is especially useful for maintenance periods, during which you receive meaningless alarms. +From Netdata v1.16.0 and beyond, the configuration controlled via the API commands is [persisted across Netdata restarts](#persistence). + +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. + +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://NODE:19999/netdata.conf`: + +``` +[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: + +``` +curl "http://NODE:19999/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken" +``` + +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](/web/server/README.md#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 correctly, 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: + +```sh +curl "http://NODE:19999/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: + +```sh +curl "http://NODE:19999/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. + +Regardless of the option you choose, at the end of your maintenance period you revert to the normal state via the RESET command. + +```sh + curl "http://NODE:19999/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. +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. + +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. +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: + +``` +curl "http://NODE:19999/api/v1/manage/health?cmd=SILENCE&context=load" -H "X-Auth-Token: Mytoken" +``` + +#### Selection criteria + +The `selection criteria` are key/value pairs, in the format `key : value`, where value is a Netdata [simple pattern](/libnetdata/simple_pattern/README.md). 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. +- `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. + +Example 1: Disable all health checks for context = `random` + +``` +http://NODE:19999/api/v1/manage/health?cmd=DISABLE&context=random +``` + +Example 2: Silence all alarms and templates with name starting with `out_of` on host `myhost` + +``` +http://NODE:19999/api/v1/manage/health?cmd=SILENCE&alarm=out_of*&hosts=myhost +``` + +Example 2.2: Add one more selector, to also silence alarms for cpu1 and cpu2 + +``` +http://NODE:19999/api/v1/manage/health?families=cpu1 cpu2 +``` + +### List silencers + +The command `LIST` was added in Netdata v1.16.0 and returns a JSON with the current status of the silencers. + +``` + curl "http://NODE:19999/api/v1/manage/health?cmd=LIST" -H "X-Auth-Token: Mytoken" +``` + +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 +{ + "all": false, + "type": "SILENCE", + "silencers": [ + { + "alarm": "samplealarm" + }, + { + "context": "random", + "hosts": "myhost" + } + ] +} +``` + +The response below shows that we have disabled all health checks. + +``` +json +{ + "all": true, + "type": "DISABLE", + "silencers": [] +} +``` + +### Responses + +- "Auth Error" : Token authentication failed +- "All alarm notifications are silenced" : Successful response to cmd=SILENCE ALL +- "All health checks are disabled" : Successful response to cmd=DISABLE ALL +- "All health checks and notifications are enabled" : Successful response to cmd=RESET +- "Health checks disabled for alarms matching the selectors" : Added to the response for a cmd=DISABLE +- "Alarm notifications silenced for alarms matching the selectors" : Added to the response for a cmd=SILENCE +- "Alarm selector added" : Added to the response when a new selector is added +- "Invalid key. Ignoring it." : Wrong name of a parameter. Added to the response and ignored. +- "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command." : Added to the response if a selector is added without a selector-specific command. +- "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors." : Added to the response if a selector-specific command is issued without a selector. + +### Persistence + +From Netdata v1.16.0 and beyond, the silencers configuration is persisted to disk and loaded when Netdata starts. +The JSON string returned by the [LIST command](#list-silencers) is automatically saved to the `silencers file`, every time a command alters the silencers configuration. +The file's location is configurable in `netdata.conf`. The default is shown below: + +``` +[health] + # silencers file = /var/lib/netdata/health.silencers.json +``` + +### Further reading + +The test script under [tests/health_mgmtapi](/tests/health_mgmtapi/README.md) contains a series of tests that you can either run or read through to understand the various calls and responses better. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fhealth%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c new file mode 100644 index 0000000..4dd85e6 --- /dev/null +++ b/web/api/health/health_cmdapi.c @@ -0,0 +1,205 @@ +// +// Created by Christopher on 11/12/18. +// + +#include "health_cmdapi.h" + +/** + * Free Silencers + * + * Clean the silencer structure + * + * @param t is the structure that will be cleaned. + */ +void free_silencers(SILENCER *t) { + if (!t) return; + if (t->next) free_silencers(t->next); + debug(D_HEALTH, "HEALTH command API: Freeing silencer %s:%s:%s:%s:%s", t->alarms, + t->charts, t->contexts, t->hosts, t->families); + simple_pattern_free(t->alarms_pattern); + simple_pattern_free(t->charts_pattern); + simple_pattern_free(t->contexts_pattern); + simple_pattern_free(t->hosts_pattern); + simple_pattern_free(t->families_pattern); + freez(t->alarms); + freez(t->charts); + freez(t->contexts); + freez(t->hosts); + freez(t->families); + freez(t); + return; +} + +/** + * Silencers to JSON Entry + * + * Fill the buffer with the other values given. + * + * @param wb a pointer to the output buffer + * @param var the json variable + * @param val the json value + * @param hasprev has it a previous value? + * + * @return + */ +int health_silencers2json_entry(BUFFER *wb, char* var, char* val, int hasprev) { + if (val) { + buffer_sprintf(wb, "%s\n\t\t\t\"%s\": \"%s\"", (hasprev)?",":"", var, val); + return 1; + } else { + return hasprev; + } +} + +/** + * Silencer to JSON + * + * Write the silencer values using JSON format inside a buffer. + * + * @param wb is the buffer to write the silencers. + */ +void health_silencers2json(BUFFER *wb) { + buffer_sprintf(wb, "{\n\t\"all\": %s," + "\n\t\"type\": \"%s\"," + "\n\t\"silencers\": [", + (silencers->all_alarms)?"true":"false", + (silencers->stype == STYPE_NONE)?"None":((silencers->stype == STYPE_DISABLE_ALARMS)?"DISABLE":"SILENCE")); + + SILENCER *silencer; + int i = 0, j = 0; + for(silencer = silencers->silencers; silencer ; silencer = silencer->next) { + if(likely(i)) buffer_strcat(wb, ","); + buffer_strcat(wb, "\n\t\t{"); + j=health_silencers2json_entry(wb, HEALTH_ALARM_KEY, silencer->alarms, j); + j=health_silencers2json_entry(wb, HEALTH_CHART_KEY, silencer->charts, j); + j=health_silencers2json_entry(wb, HEALTH_CONTEXT_KEY, silencer->contexts, j); + j=health_silencers2json_entry(wb, HEALTH_HOST_KEY, silencer->hosts, j); + health_silencers2json_entry(wb, HEALTH_FAMILIES_KEY, silencer->families, j); + j=0; + buffer_strcat(wb, "\n\t\t}"); + i++; + } + if(likely(i)) buffer_strcat(wb, "\n\t"); + buffer_strcat(wb, "]\n}\n"); +} + +/** + * Silencer to FILE + * + * Write the sliencer buffer to a file. + * @param wb + */ +void health_silencers2file(BUFFER *wb) { + if (wb->len == 0) return; + + FILE *fd = fopen(silencers_filename, "wb"); + if(fd) { + size_t written = (size_t)fprintf(fd, "%s", wb->buffer) ; + if (written == wb->len ) { + info("Silencer changes written to %s", silencers_filename); + } + fclose(fd); + return; + } + error("Silencer changes could not be written to %s. Error %s", silencers_filename, strerror(errno)); +} + +/** + * Request V1 MGMT Health + * + * Function called by api to management the health. + * + * @param host main structure with client information! + * @param w is the structure with all information of the client request. + * @param url is the url that netdata is working + * + * @return It returns 200 on success and another code otherwise. + */ +int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url) { + int ret; + (void) host; + + BUFFER *wb = w->response.data; + buffer_flush(wb); + wb->contenttype = CT_TEXT_PLAIN; + + buffer_flush(w->response.data); + + //Local instance of the silencer + SILENCER *silencer = NULL; + int config_changed = 1; + + if (!w->auth_bearer_token) { + buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR); + ret = HTTP_RESP_FORBIDDEN; + } else { + debug(D_HEALTH, "HEALTH command API: Comparing secret '%s' to '%s'", w->auth_bearer_token, api_secret); + if (strcmp(w->auth_bearer_token, api_secret)) { + buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR); + ret = HTTP_RESP_FORBIDDEN; + } else { + while (url) { + char *value = mystrsep(&url, "&"); + if (!value || !*value) continue; + + char *key = mystrsep(&value, "="); + if (!key || !*key) continue; + if (!value || !*value) continue; + + debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value); + + // name and value are now the parameters + if (!strcmp(key, "cmd")) { + if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCEALL)) { + silencers->all_alarms = 1; + silencers->stype = STYPE_SILENCE_NOTIFICATIONS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCEALL); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLEALL)) { + silencers->all_alarms = 1; + silencers->stype = STYPE_DISABLE_ALARMS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLEALL); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCE)) { + silencers->stype = STYPE_SILENCE_NOTIFICATIONS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCE); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLE)) { + silencers->stype = STYPE_DISABLE_ALARMS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLE); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_RESET)) { + silencers->all_alarms = 0; + silencers->stype = STYPE_NONE; + free_silencers(silencers->silencers); + silencers->silencers = NULL; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_LIST)) { + w->response.data->contenttype = CT_APPLICATION_JSON; + health_silencers2json(wb); + config_changed=0; + } + } else { + silencer = health_silencers_addparam(silencer, key, value); + } + } + + if (likely(silencer)) { + health_silencers_add(silencer); + buffer_strcat(wb, HEALTH_CMDAPI_MSG_ADDED); + if (silencers->stype == STYPE_NONE) { + buffer_strcat(wb, HEALTH_CMDAPI_MSG_STYPEWARNING); + } + } + if (unlikely(silencers->stype != STYPE_NONE && !silencers->all_alarms && !silencers->silencers)) { + buffer_strcat(wb, HEALTH_CMDAPI_MSG_NOSELECTORWARNING); + } + ret = HTTP_RESP_OK; + } + } + w->response.data = wb; + buffer_no_cacheable(w->response.data); + if (ret == HTTP_RESP_OK && config_changed) { + BUFFER *jsonb = buffer_create(200); + health_silencers2json(jsonb); + health_silencers2file(jsonb); + } + + return ret; +} diff --git a/web/api/health/health_cmdapi.h b/web/api/health/health_cmdapi.h new file mode 100644 index 0000000..d8ec6aa --- /dev/null +++ b/web/api/health/health_cmdapi.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WEB_HEALTH_SVG_H +#define NETDATA_WEB_HEALTH_SVG_H 1 + +#include "libnetdata/libnetdata.h" +#include "web/server/web_client.h" +#include "health/health.h" + +#define HEALTH_CMDAPI_CMD_SILENCEALL "SILENCE ALL" +#define HEALTH_CMDAPI_CMD_DISABLEALL "DISABLE ALL" +#define HEALTH_CMDAPI_CMD_SILENCE "SILENCE" +#define HEALTH_CMDAPI_CMD_DISABLE "DISABLE" +#define HEALTH_CMDAPI_CMD_RESET "RESET" +#define HEALTH_CMDAPI_CMD_LIST "LIST" + +#define HEALTH_CMDAPI_MSG_AUTHERROR "Auth Error\n" +#define HEALTH_CMDAPI_MSG_SILENCEALL "All alarm notifications are silenced\n" +#define HEALTH_CMDAPI_MSG_DISABLEALL "All health checks are disabled\n" +#define HEALTH_CMDAPI_MSG_RESET "All health checks and notifications are enabled\n" +#define HEALTH_CMDAPI_MSG_DISABLE "Health checks disabled for alarms matching the selectors\n" +#define HEALTH_CMDAPI_MSG_SILENCE "Alarm notifications silenced for alarms matching the selectors\n" +#define HEALTH_CMDAPI_MSG_ADDED "Alarm selector added\n" +#define HEALTH_CMDAPI_MSG_STYPEWARNING "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command.\n" +#define HEALTH_CMDAPI_MSG_NOSELECTORWARNING "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors.\n" + +extern int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url); + +#include "web/api/web_api_v1.h" + +#endif /* NETDATA_WEB_HEALTH_SVG_H */ diff --git a/web/api/netdata-swagger.json b/web/api/netdata-swagger.json new file mode 100644 index 0000000..ed2555f --- /dev/null +++ b/web/api/netdata-swagger.json @@ -0,0 +1,2065 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "NetData API", + "description": "Real-time performance and health monitoring.", + "version": "1.11.1_rolling" + }, + "paths": { + "/info": { + "get": { + "summary": "Get netdata basic information", + "description": "The info endpoint returns basic information about netdata. It provides:\n* netdata version\n* netdata unique id\n* list of hosts mirrored (includes itself)\n* Operating System, Virtualization, K8s nodes and Container technology information\n* List of active collector plugins and modules\n* number of alarms in the host\n * number of alarms in normal state\n * number of alarms in warning state\n * number of alarms in critical state\n", + "responses": { + "200": { + "description": "netdata basic information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/info" + } + } + } + }, + "503": { + "description": "netdata daemon not ready (used for health checks)." + } + } + } + }, + "/charts": { + "get": { + "summary": "Get a list of all charts available at the server", + "description": "The charts endpoint returns a summary about all charts stored in the netdata server.", + "responses": { + "200": { + "description": "An array of charts.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chart_summary" + } + } + } + } + } + } + }, + "/chart": { + "get": { + "summary": "Get info about a specific chart", + "description": "The Chart endpoint returns detailed information about a chart.", + "parameters": [ + { + "name": "chart", + "in": "query", + "description": "The id of the chart as returned by the /charts call.", + "required": true, + "schema": { + "type": "string", + "format": "as returned by /charts", + "default": "system.cpu" + } + } + ], + "responses": { + "200": { + "description": "A javascript object with detailed information about the chart.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chart" + } + } + } + }, + "400": { + "description": "No chart id was supplied in the request." + }, + "404": { + "description": "No chart with the given id is found." + } + } + } + }, + "/alarm_variables": { + "get": { + "summary": "List variables available to configure alarms for a chart", + "description": "Returns the basic information of a chart and all the variables that can be used in alarm and template health configurations for the particular chart or family.", + "parameters": [ + { + "name": "chart", + "in": "query", + "description": "The id of the chart as returned by the /charts call.", + "required": true, + "schema": { + "type": "string", + "format": "as returned by /charts", + "default": "system.cpu" + } + } + ], + "responses": { + "200": { + "description": "A javascript object with information about the chart and the available variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/alarm_variables" + } + } + } + }, + "400": { + "description": "Bad request - the body will include a message stating what is wrong." + }, + "404": { + "description": "No chart with the given id is found." + }, + "500": { + "description": "Internal server error. This usually means the server is out of memory." + } + } + } + }, + "/data": { + "get": { + "summary": "Get collected data for a specific chart", + "description": "The data endpoint returns data stored in the round robin database of a chart.", + "parameters": [ + { + "name": "chart", + "in": "query", + "description": "The id of the chart as returned by the /charts call. Note chart or context must be specified", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "as returned by /charts", + "default": "system.cpu" + } + }, + { + "name": "context", + "in": "query", + "description": "The context of the chart as returned by the /charts call. Note chart or context must be specified", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "as returned by /charts" + } + }, + { + "name": "dimension", + "in": "query", + "description": "Zero, one or more dimension ids or names, as returned by the /chart call, separated with comma or pipe. Netdata simple patterns are supported.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "as returned by /charts" + } + } + }, + { + "name": "after", + "in": "query", + "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds (negative, relative to parameter: before). Netdata will assume it is a relative number if it is less that 3 years (in seconds). If not specified the default is -600 seconds. Netdata will adapt this parameter to the boundaries of the round robin database unless the allow_past option is specified.", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer", + "default": -600 + } + }, + { + "name": "before", + "in": "query", + "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is zero (i.e. the timestamp of the last value collected).", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + { + "name": "points", + "in": "query", + "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration will be returned.", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer", + "default": 20 + } + }, + { + "name": "group", + "in": "query", + "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "string", + "enum": [ + "min", + "max", + "average", + "median", + "stddev", + "sum", + "incremental-sum" + ], + "default": "average" + } + }, + { + "name": "gtime", + "in": "query", + "description": "The grouping number of seconds. This is used in conjunction with group=average to change the units of metrics (ie when the data is per-second, setting gtime=60 will turn them to per-minute).", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + { + "name": "format", + "in": "query", + "description": "The format of the data to be returned.", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "string", + "enum": [ + "json", + "jsonp", + "csv", + "tsv", + "tsv-excel", + "ssv", + "ssvcomma", + "datatable", + "datasource", + "html", + "markdown", + "array", + "csvjsonarray" + ], + "default": "json" + } + }, + { + "name": "options", + "in": "query", + "description": "Options that affect data generation.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "nonzero", + "flip", + "jsonwrap", + "min2max", + "seconds", + "milliseconds", + "abs", + "absolute", + "absolute-sum", + "null2zero", + "objectrows", + "google_json", + "percentage", + "unaligned", + "match-ids", + "match-names", + "showcustomvars", + "allow_past" + ] + }, + "default": [ + "seconds", + "jsonwrap" + ] + } + }, + { + "name": "callback", + "in": "query", + "description": "For JSONP responses, the callback function name.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string" + } + }, + { + "name": "filename", + "in": "query", + "description": "Add Content-Disposition: attachment; filename= header to the response, that will instruct the browser to save the response with the given filename.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string" + } + }, + { + "name": "tqx", + "in": "query", + "description": "[Google Visualization API](https://developers.google.com/chart/interactive/docs/dev/implementing_data_source?hl=en) formatted parameter.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The call was successful. The response includes the data in the format requested. Swagger2.0 does not process the discriminator field to show polymorphism. The response will be one of the sub-types of the data-schema according to the chosen format, e.g. json -> data_json.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/data" + } + } + } + }, + "400": { + "description": "Bad request - the body will include a message stating what is wrong." + }, + "404": { + "description": "Chart or context is not found. The supplied chart or context will be reported." + }, + "500": { + "description": "Internal server error. This usually means the server is out of memory." + } + } + } + }, + "/badge.svg": { + "get": { + "summary": "Generate a badge in form of SVG image for a chart (or dimension)", + "description": "Successful responses are SVG images.", + "parameters": [ + { + "name": "chart", + "in": "query", + "description": "The id of the chart as returned by the /charts call.", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "as returned by /charts", + "default": "system.cpu" + } + }, + { + "name": "alarm", + "in": "query", + "description": "The name of an alarm linked to the chart.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "dimension", + "in": "query", + "description": "Zero, one or more dimension ids, as returned by the /chart call.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "as returned by /charts" + } + } + }, + { + "name": "after", + "in": "query", + "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer", + "default": -600 + } + }, + { + "name": "before", + "in": "query", + "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + { + "name": "group", + "in": "query", + "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods are supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).", + "required": true, + "allowEmptyValue": false, + "schema": { + "type": "string", + "enum": [ + "min", + "max", + "average", + "median", + "stddev", + "sum", + "incremental-sum" + ], + "default": "average" + } + }, + { + "name": "options", + "in": "query", + "description": "Options that affect data generation.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "abs", + "absolute", + "display-absolute", + "absolute-sum", + "null2zero", + "percentage", + "unaligned" + ] + }, + "default": [ + "absolute" + ] + } + }, + { + "name": "label", + "in": "query", + "description": "A text to be used as the label.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "units", + "in": "query", + "description": "A text to be used as the units.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "label_color", + "in": "query", + "description": "A color to be used for the background of the label side(left side) of the badge. One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). If value wrong or not given default color will be used.", + "required": false, + "allowEmptyValue": true, + "schema": { + "oneOf": [ + { + "type": "string", + "enum": [ + "green", + "brightgreen", + "yellow", + "yellowgreen", + "orange", + "red", + "blue", + "grey", + "gray", + "lightgrey", + "lightgray" + ] + }, + { + "type": "string", + "format": "^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + } + ] + } + }, + { + "name": "value_color", + "in": "query", + "description": "A color to be used for the background of the value *(right)* part of badge. You can set multiple using a pipe with a condition each, like this: `color, <, >=, <=, =, :null (to check if no value exists). Each color can be specified in same manner as for `label_color` parameter. Currently only integers are suported as values.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "text_color_lbl", + "in": "query", + "description": "Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceeding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.", + "required": false, + "allowEmptyValue": true, + "schema": { + "oneOf": [ + { + "type": "string", + "enum": [ + "green", + "brightgreen", + "yellow", + "yellowgreen", + "orange", + "red", + "blue", + "grey", + "gray", + "lightgrey", + "lightgray" + ] + }, + { + "type": "string", + "format": "^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + } + ] + } + }, + { + "name": "text_color_val", + "in": "query", + "description": "Font color for value *(right)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceeding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.", + "required": false, + "allowEmptyValue": true, + "schema": { + "oneOf": [ + { + "type": "string", + "enum": [ + "green", + "brightgreen", + "yellow", + "yellowgreen", + "orange", + "red", + "blue", + "grey", + "gray", + "lightgrey", + "lightgray" + ] + }, + { + "type": "string", + "format": "^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + } + ] + } + }, + { + "name": "multiply", + "in": "query", + "description": "Multiply the value with this number for rendering it at the image (integer value required).", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "number", + "format": "integer" + } + }, + { + "name": "divide", + "in": "query", + "description": "Divide the value with this number for rendering it at the image (integer value required).", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "number", + "format": "integer" + } + }, + { + "name": "scale", + "in": "query", + "description": "Set the scale of the badge (greater or equal to 100).", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "number", + "format": "integer" + } + }, + { + "name": "fixed_width_lbl", + "in": "query", + "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's left side *(label/name)*. You must set this parameter together with `fixed_width_val` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer" + } + }, + { + "name": "fixed_width_val", + "in": "query", + "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's right side *(value)*. You must set this parameter together with `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer" + } + } + ], + "responses": { + "200": { + "description": "The call was successful. The response should be an SVG image." + }, + "400": { + "description": "Bad request - the body will include a message stating what is wrong." + }, + "404": { + "description": "No chart with the given id is found." + }, + "500": { + "description": "Internal server error. This usually means the server is out of memory." + } + } + } + }, + "/allmetrics": { + "get": { + "summary": "Get a value of all the metrics maintained by netdata", + "description": "The allmetrics endpoint returns the latest value of all charts and dimensions stored in the netdata server.", + "parameters": [ + { + "name": "format", + "in": "query", + "description": "The format of the response to be returned.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "shell", + "prometheus", + "prometheus_all_hosts", + "json" + ], + "default": "shell" + } + }, + { + "name": "variables", + "in": "query", + "description": "When enabled, netdata will expose various system configuration metrics.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" + } + }, + { + "name": "help", + "in": "query", + "description": "Enable or disable HELP lines in prometheus output.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" + } + }, + { + "name": "types", + "in": "query", + "description": "Enable or disable TYPE lines in prometheus output.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" + } + }, + { + "name": "timestamps", + "in": "query", + "description": "Enable or disable timestamps in prometheus output.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "names", + "in": "query", + "description": "When enabled netdata will report dimension names. When disabled netdata will report dimension IDs. The default is controlled in netdata.conf.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "oldunits", + "in": "query", + "description": "When enabled, netdata will show metric names for the default source=average as they appeared before 1.12, by using the legacy unit naming conventions.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "hideunits", + "in": "query", + "description": "When enabled, netdata will not include the units in the metric names, for the default source=average.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "server", + "in": "query", + "description": "Set a distinct name of the client querying prometheus metrics. Netdata will use the client IP if this is not set.", + "required": false, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "prefix", + "in": "query", + "description": "Prefix all prometheus metrics with this string.", + "required": false, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "data", + "in": "query", + "description": "Select the prometheus response data source. There is a setting in netdata.conf for the default.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "as-collected", + "average", + "sum" + ], + "default": "average" + } + } + ], + "responses": { + "200": { + "description": "All the metrics returned in the format requested." + }, + "400": { + "description": "The format requested is not supported." + } + } + } + }, + "/alarms": { + "get": { + "summary": "Get a list of active or raised alarms on the server", + "description": "The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing \"?all\", all the enabled alarms are returned.", + "parameters": [ + { + "name": "all", + "in": "query", + "description": "If passed, all enabled alarms are returned.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "active", + "in": "query", + "description": "If passed, the raised alarms in state WARNING or CRITICAL are returned.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "An object containing general info and a linked list of alarms.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/alarms" + } + } + } + } + } + } + }, + "/alarms_values": { + "get": { + "summary": "Get a list of active or raised alarms on the server", + "description": "The alarms_values endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing \"?all\", all the enabled alarms are returned. This option output differs from `/alarms` in the number of variables delivered. This endpoint gives to user `id`, `value` and alarm `status`.", + "parameters": [ + { + "name": "all", + "in": "query", + "description": "If passed, all enabled alarms are returned.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "active", + "in": "query", + "description": "If passed, the raised alarms in state WARNING or CRITICAL are returned.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "An object containing general info and a linked list of alarms.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/alarms_values" + } + } + } + } + } + } + }, + "/alarm_log": { + "get": { + "summary": "Retrieves the entries of the alarm log", + "description": "Returns an array of alarm_log entries, with historical information on raised and cleared alarms.", + "parameters": [ + { + "name": "after", + "in": "query", + "description": "Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events.", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "An array of alarm log entries.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/alarm_log_entry" + } + } + } + } + } + } + } + }, + "/alarm_count": { + "get": { + "summary": "Get an overall status of the chart", + "description": "Checks multiple charts with the same context and counts number of alarms with given status.", + "parameters": [ + { + "in": "query", + "name": "context", + "description": "Specify context which should be checked.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "system.cpu" + ] + } + }, + { + "in": "query", + "name": "status", + "description": "Specify alarm status to count.", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string", + "enum": [ + "REMOVED", + "UNDEFINED", + "UNINITIALIZED", + "CLEAR", + "RAISED", + "WARNING", + "CRITICAL" + ], + "default": "RAISED" + } + } + ], + "responses": { + "200": { + "description": "An object containing a count of alarms with given status for given contexts.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "500": { + "description": "Internal server error. This usually means the server is out of memory." + } + } + } + }, + "/manage/health": { + "get": { + "summary": "Accesses the health management API to control health checks and notifications at runtime.", + "description": "Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenaria, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation.", + "parameters": [ + { + "name": "cmd", + "in": "query", + "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "DISABLE ALL", + "SILENCE ALL", + "DISABLE", + "SILENCE", + "RESET", + "LIST" + ] + } + }, + { + "name": "alarm", + "in": "query", + "description": "The expression provided will match both `alarm` and `template` names.", + "schema": { + "type": "string" + } + }, + { + "name": "chart", + "in": "query", + "description": "Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`.", + "schema": { + "type": "string" + } + }, + { + "name": "context", + "in": "query", + "description": "Chart context, as shown on the dashboard. These will match the `on` entry of a configured `template`.", + "schema": { + "type": "string" + } + }, + { + "name": "hosts", + "in": "query", + "description": "The hostnames that will need to match.", + "schema": { + "type": "string" + } + }, + { + "name": "families", + "in": "query", + "description": "The alarm families.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "A plain text response based on the result of the command." + }, + "403": { + "description": "Bearer authentication error." + } + } + } + } + }, + "servers": [ + { + "url": "https://registry.my-netdata.io/api/v1" + }, + { + "url": "http://registry.my-netdata.io/api/v1" + } + ], + "components": { + "schemas": { + "info": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "netdata version of the server.", + "example": "1.11.1_rolling" + }, + "uid": { + "type": "string", + "description": "netdata unique id of the server.", + "example": "24e9fe3c-f2ac-11e8-bafc-0242ac110002" + }, + "mirrored_hosts": { + "type": "array", + "description": "List of hosts mirrored of the server (include itself).", + "items": { + "type": "string" + }, + "example": [ + "host1.example.com", + "host2.example.com" + ] + }, + "mirrored_hosts_status": { + "type": "array", + "description": "List of details of hosts mirrored to this served (including self). Indexes correspond to indexes in \"mirrored_hosts\".", + "items": { + "type": "object", + "description": "Host data", + "properties": { + "guid": { + "type": "string", + "format": "uuid", + "nullable": false, + "description": "Host unique GUID from `netdata.public.unique.id`.", + "example": "245e4bff-3b34-47c1-a6e5-5c535a9abfb2" + }, + "reachable": { + "type": "boolean", + "nullable": false, + "description": "Current state of streaming. Always true for localhost/self." + }, + "claim_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Cloud GUID/identifier in case the host is claimed. If child status unknown or unclaimed this field is set to `null`", + "example": "c3b2a66a-3052-498c-ac52-7fe9e8cccb0c" + } + } + } + }, + "os_name": { + "type": "string", + "description": "Operating System Name.", + "example": "Manjaro Linux" + }, + "os_id": { + "type": "string", + "description": "Operating System ID.", + "example": "manjaro" + }, + "os_id_like": { + "type": "string", + "description": "Known OS similar to this OS.", + "example": "arch" + }, + "os_version": { + "type": "string", + "description": "Operating System Version.", + "example": "18.0.4" + }, + "os_version_id": { + "type": "string", + "description": "Operating System Version ID.", + "example": "unknown" + }, + "os_detection": { + "type": "string", + "description": "OS parameters detection method.", + "example": "Mixed" + }, + "kernel_name": { + "type": "string", + "description": "Kernel Name.", + "example": "Linux" + }, + "kernel_version": { + "type": "string", + "description": "Kernel Version.", + "example": "4.19.32-1-MANJARO" + }, + "is_k8s_node": { + "type": "boolean", + "description": "Netdata is running on a K8s node.", + "example": false + }, + "architecture": { + "type": "string", + "description": "Kernel architecture.", + "example": "x86_64" + }, + "virtualization": { + "type": "string", + "description": "Virtualization Type.", + "example": "kvm" + }, + "virt_detection": { + "type": "string", + "description": "Virtualization detection method.", + "example": "systemd-detect-virt" + }, + "container": { + "type": "string", + "description": "Container technology.", + "example": "docker" + }, + "container_detection": { + "type": "string", + "description": "Container technology detection method.", + "example": "dockerenv" + }, + "labels": { + "type": "object", + "description": "List of host labels.", + "properties": { + "app": { + "type": "string", + "description": "Host label.", + "example": "netdata" + } + } + }, + "collectors": { + "type": "array", + "items": { + "type": "object", + "description": "Array of collector plugins and modules.", + "properties": { + "plugin": { + "type": "string", + "description": "Collector plugin.", + "example": "python.d.plugin" + }, + "module": { + "type": "string", + "description": "Module of the collector plugin.", + "example": "dockerd" + } + } + } + }, + "alarms": { + "type": "object", + "description": "Number of alarms in the server.", + "properties": { + "normal": { + "type": "integer", + "description": "Number of alarms in normal state." + }, + "warning": { + "type": "integer", + "description": "Number of alarms in warning state." + }, + "critical": { + "type": "integer", + "description": "Number of alarms in critical state." + } + } + } + } + }, + "chart_summary": { + "type": "object", + "properties": { + "hostname": { + "type": "string", + "description": "The hostname of the netdata server." + }, + "version": { + "type": "string", + "description": "netdata version of the server." + }, + "release_channel": { + "type": "string", + "description": "The release channel of the build on the server.", + "example": "nightly" + }, + "timezone": { + "type": "string", + "description": "The current timezone on the server." + }, + "os": { + "type": "string", + "description": "The netdata server host operating system.", + "enum": [ + "macos", + "linux", + "freebsd" + ] + }, + "history": { + "type": "number", + "description": "The duration, in seconds, of the round robin database maintained by netdata." + }, + "memory_mode": { + "type": "string", + "description": "The name of the database memory mode on the server." + }, + "update_every": { + "type": "number", + "description": "The default update frequency of the netdata server. All charts have an update frequency equal or bigger than this." + }, + "charts": { + "type": "object", + "description": "An object containing all the chart objects available at the netdata server. This is used as an indexed array. The key of each chart object is the id of the chart.", + "additionalProperties": { + "$ref": "#/components/schemas/chart" + } + }, + "charts_count": { + "type": "number", + "description": "The number of charts." + }, + "dimensions_count": { + "type": "number", + "description": "The total number of dimensions." + }, + "alarms_count": { + "type": "number", + "description": "The number of alarms." + }, + "rrd_memory_bytes": { + "type": "number", + "description": "The size of the round robin database in bytes." + } + } + }, + "chart": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The unique id of the chart." + }, + "name": { + "type": "string", + "description": "The name of the chart." + }, + "type": { + "type": "string", + "description": "The type of the chart. Types are not handled by netdata. You can use this field for anything you like." + }, + "family": { + "type": "string", + "description": "The family of the chart. Families are not handled by netdata. You can use this field for anything you like." + }, + "title": { + "type": "string", + "description": "The title of the chart." + }, + "priority": { + "type": "number", + "description": "The relative priority of the chart. NetData does not care about priorities. This is just an indication of importance for the chart viewers to sort charts of higher priority (lower number) closer to the top. Priority sorting should only be used among charts of the same type or family." + }, + "enabled": { + "type": "boolean", + "description": "True when the chart is enabled. Disabled charts do not currently collect values, but they may have historical values available." + }, + "units": { + "type": "string", + "description": "The unit of measurement for the values of all dimensions of the chart." + }, + "data_url": { + "type": "string", + "description": "The absolute path to get data values for this chart. You are expected to use this path as the base when constructing the URL to fetch data values for this chart." + }, + "chart_type": { + "type": "string", + "description": "The chart type.", + "enum": [ + "line", + "area", + "stacked" + ] + }, + "duration": { + "type": "number", + "description": "The duration, in seconds, of the round robin database maintained by netdata." + }, + "first_entry": { + "type": "number", + "description": "The UNIX timestamp of the first entry (the oldest) in the round robin database." + }, + "last_entry": { + "type": "number", + "description": "The UNIX timestamp of the latest entry in the round robin database." + }, + "update_every": { + "type": "number", + "description": "The update frequency of this chart, in seconds. One value every this amount of time is kept in the round robin database." + }, + "dimensions": { + "type": "object", + "description": "An object containing all the chart dimensions available for the chart. This is used as an indexed array. For each pair in the dictionary: the key is the id of the dimension and the value is a dictionary containing the name.", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the dimension" + } + } + } + }, + "chart_variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/chart_variables" + } + }, + "green": { + "type": "number", + "nullable": true, + "description": "Chart health green threshold." + }, + "red": { + "type": "number", + "nullable": true, + "description": "Chart health red threshold." + } + } + }, + "alarm_variables": { + "type": "object", + "properties": { + "chart": { + "type": "string", + "description": "The unique id of the chart." + }, + "chart_name": { + "type": "string", + "description": "The name of the chart." + }, + "cnart_context": { + "type": "string", + "description": "The context of the chart. It is shared across multiple monitored software or hardware instances and used in alarm templates." + }, + "family": { + "type": "string", + "description": "The family of the chart." + }, + "host": { + "type": "string", + "description": "The host containing the chart." + }, + "chart_variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/chart_variables" + } + }, + "family_variables": { + "type": "object", + "properties": { + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" + } + } + }, + "host_variables": { + "type": "object", + "properties": { + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" + } + } + } + } + }, + "chart_variables": { + "type": "object", + "properties": { + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" + } + } + }, + "data": { + "type": "object", + "discriminator": { + "propertyName": "format" + }, + "description": "Response will contain the appropriate subtype, e.g. data_json depending on the requested format.", + "properties": { + "api": { + "type": "number", + "description": "The API version this conforms to, currently 1." + }, + "id": { + "type": "string", + "description": "The unique id of the chart." + }, + "name": { + "type": "string", + "description": "The name of the chart." + }, + "update_every": { + "type": "number", + "description": "The update frequency of this chart, in seconds. One value every this amount of time is kept in the round robin database (indepedently of the current view)." + }, + "view_update_every": { + "type": "number", + "description": "The current view appropriate update frequency of this chart, in seconds. There is no point to request chart refreshes, using the same settings, more frequently than this." + }, + "first_entry": { + "type": "number", + "description": "The UNIX timestamp of the first entry (the oldest) in the round robin database (indepedently of the current view)." + }, + "last_entry": { + "type": "number", + "description": "The UNIX timestamp of the latest entry in the round robin database (indepedently of the current view)." + }, + "after": { + "type": "number", + "description": "The UNIX timestamp of the first entry (the oldest) returned in this response." + }, + "before": { + "type": "number", + "description": "The UNIX timestamp of the latest entry returned in this response." + }, + "min": { + "type": "number", + "description": "The minimum value returned in the current view. This can be used to size the y-series of the chart." + }, + "max": { + "type": "number", + "description": "The maximum value returned in the current view. This can be used to size the y-series of the chart." + }, + "dimension_names": { + "description": "The dimension names of the chart as returned in the current view.", + "type": "array", + "items": { + "type": "string" + } + }, + "dimension_ids": { + "description": "The dimension IDs of the chart as returned in the current view.", + "type": "array", + "items": { + "type": "string" + } + }, + "latest_values": { + "description": "The latest values collected for the chart (indepedently of the current view).", + "type": "array", + "items": { + "type": "string" + } + }, + "view_latest_values": { + "description": "The latest values returned with this response.", + "type": "array", + "items": { + "type": "string" + } + }, + "dimensions": { + "type": "number", + "description": "The number of dimensions returned." + }, + "points": { + "type": "number", + "description": "The number of rows / points returned." + }, + "format": { + "type": "string", + "description": "The format of the result returned." + }, + "chart_variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/chart_variables" + } + } + } + }, + "data_json": { + "description": "Data response in json format.", + "allOf": [ + { + "$ref": "#/components/schemas/data" + }, + { + "properties": { + "result": { + "type": "object", + "properties": { + "labels": { + "description": "The dimensions retrieved from the chart.", + "type": "array", + "items": { + "type": "string" + } + }, + "data": { + "description": "The data requested, one element per sample with each element containing the values of the dimensions described in the labels value.", + "type": "array", + "items": { + "type": "number" + } + } + }, + "description": "The result requested, in the format requested." + } + } + } + ] + }, + "data_flat": { + "description": "Data response in csv / tsv / tsv-excel / ssv / ssv-comma / markdown / html formats.", + "allOf": [ + { + "$ref": "#/components/schemas/data" + }, + { + "properties": { + "result": { + "type": "string" + } + } + } + ] + }, + "data_array": { + "description": "Data response in array format.", + "allOf": [ + { + "$ref": "#/components/schemas/data" + }, + { + "properties": { + "result": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + ] + }, + "data_csvjsonarray": { + "description": "Data response in csvjsonarray format.", + "allOf": [ + { + "$ref": "#/components/schemas/data" + }, + { + "properties": { + "result": { + "description": "The first inner array contains strings showing the labels of each column, each subsequent array contains the values for each point in time.", + "type": "array", + "items": { + "type": "array", + "items": {} + } + } + } + } + ] + }, + "data_datatable": { + "description": "Data response in datatable / datasource formats (suitable for Google Charts).", + "allOf": [ + { + "$ref": "#/components/schemas/data" + }, + { + "properties": { + "result": { + "type": "object", + "properties": { + "cols": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "Always empty - for future use." + }, + "label": { + "description": "The dimension returned from the chart." + }, + "pattern": { + "description": "Always empty - for future use." + }, + "type": { + "description": "The type of data in the column / chart-dimension." + }, + "p": { + "description": "Contains any annotations for the column." + } + }, + "required": [ + "id", + "label", + "pattern", + "type" + ] + } + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "c": { + "type": "array", + "items": { + "properties": { + "v": { + "description": "Each value in the row is represented by an object named `c` with five v fields: data, null, null, 0, the value. This format is fixed by the Google Charts API." + } + } + } + } + } + } + } + } + } + } + } + ] + }, + "alarms": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "latest_alarm_log_unique_id": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "boolean" + }, + "now": { + "type": "integer", + "format": "int32" + }, + "alarms": { + "type": "object", + "properties": { + "chart-name.alarm-name": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "description": "Full alarm name." + }, + "chart": { + "type": "string" + }, + "family": { + "type": "string" + }, + "active": { + "type": "boolean", + "description": "Will be false only if the alarm is disabled in the configuration." + }, + "disabled": { + "type": "boolean", + "description": "Whether the health check for this alarm has been disabled via a health command API DISABLE command." + }, + "silenced": { + "type": "boolean", + "description": "Whether notifications for this alarm have been silenced via a health command API SILENCE command." + }, + "exec": { + "type": "string" + }, + "recipient": { + "type": "string" + }, + "source": { + "type": "string" + }, + "units": { + "type": "string" + }, + "info": { + "type": "string" + }, + "status": { + "type": "string" + }, + "last_status_change": { + "type": "integer", + "format": "int32" + }, + "last_updated": { + "type": "integer", + "format": "int32" + }, + "next_update": { + "type": "integer", + "format": "int32" + }, + "update_every": { + "type": "integer", + "format": "int32" + }, + "delay_up_duration": { + "type": "integer", + "format": "int32" + }, + "delay_down_duration": { + "type": "integer", + "format": "int32" + }, + "delay_max_duration": { + "type": "integer", + "format": "int32" + }, + "delay_multiplier": { + "type": "integer", + "format": "int32" + }, + "delay": { + "type": "integer", + "format": "int32" + }, + "delay_up_to_timestamp": { + "type": "integer", + "format": "int32" + }, + "value_string": { + "type": "string" + }, + "no_clear_notification": { + "type": "boolean" + }, + "lookup_dimensions": { + "type": "string" + }, + "db_after": { + "type": "integer", + "format": "int32" + }, + "db_before": { + "type": "integer", + "format": "int32" + }, + "lookup_method": { + "type": "string" + }, + "lookup_after": { + "type": "integer", + "format": "int32" + }, + "lookup_before": { + "type": "integer", + "format": "int32" + }, + "lookup_options": { + "type": "string" + }, + "calc": { + "type": "string" + }, + "calc_parsed": { + "type": "string" + }, + "warn": { + "type": "string" + }, + "warn_parsed": { + "type": "string" + }, + "crit": { + "type": "string" + }, + "crit_parsed": { + "type": "string" + }, + "warn_repeat_every": { + "type": "integer", + "format": "int32" + }, + "crit_repeat_every": { + "type": "integer", + "format": "int32" + }, + "green": { + "type": "string", + "format": "nullable" + }, + "red": { + "type": "string", + "format": "nullable" + }, + "value": { + "type": "number" + } + } + } + } + } + } + }, + "alarm_log_entry": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "unique_id": { + "type": "integer", + "format": "int32" + }, + "alarm_id": { + "type": "integer", + "format": "int32" + }, + "alarm_event_id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "chart": { + "type": "string" + }, + "family": { + "type": "string" + }, + "processed": { + "type": "boolean" + }, + "updated": { + "type": "boolean" + }, + "exec_run": { + "type": "integer", + "format": "int32" + }, + "exec_failed": { + "type": "boolean" + }, + "exec": { + "type": "string" + }, + "recipient": { + "type": "string" + }, + "exec_code": { + "type": "integer", + "format": "int32" + }, + "source": { + "type": "string" + }, + "units": { + "type": "string" + }, + "when": { + "type": "integer", + "format": "int32" + }, + "duration": { + "type": "integer", + "format": "int32" + }, + "non_clear_duration": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "string" + }, + "old_status": { + "type": "string" + }, + "delay": { + "type": "integer", + "format": "int32" + }, + "delay_up_to_timestamp": { + "type": "integer", + "format": "int32" + }, + "updated_by_id": { + "type": "integer", + "format": "int32" + }, + "updates_id": { + "type": "integer", + "format": "int32" + }, + "value_string": { + "type": "string" + }, + "old_value_string": { + "type": "string" + }, + "silenced": { + "type": "string" + }, + "info": { + "type": "string" + }, + "value": { + "type": "number", + "nullable": true + }, + "old_value": { + "type": "number", + "nullable": true + } + } + }, + "alarms_values": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "alarms": { + "type": "object", + "description": "HashMap with keys being alarm names", + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "value": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "REMOVED", + "UNDEFINED", + "UNINITIALIZED", + "CLEAR", + "RAISED", + "WARNING", + "CRITICAL", + "UNKNOWN" + ] + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/web/api/netdata-swagger.yaml b/web/api/netdata-swagger.yaml new file mode 100644 index 0000000..7482742 --- /dev/null +++ b/web/api/netdata-swagger.yaml @@ -0,0 +1,1611 @@ +openapi: 3.0.0 +info: + title: NetData API + description: Real-time performance and health monitoring. + version: 1.11.1_rolling +paths: + /info: + get: + summary: Get netdata basic information + description: | + The info endpoint returns basic information about netdata. It provides: + * netdata version + * netdata unique id + * list of hosts mirrored (includes itself) + * Operating System, Virtualization, K8s nodes and Container technology information + * List of active collector plugins and modules + * number of alarms in the host + * number of alarms in normal state + * number of alarms in warning state + * number of alarms in critical state + responses: + "200": + description: netdata basic information. + content: + application/json: + schema: + $ref: "#/components/schemas/info" + "503": + description: netdata daemon not ready (used for health checks). + /charts: + get: + summary: Get a list of all charts available at the server + description: The charts endpoint returns a summary about all charts stored in the + netdata server. + responses: + "200": + description: An array of charts. + content: + application/json: + schema: + $ref: "#/components/schemas/chart_summary" + /chart: + get: + summary: Get info about a specific chart + description: The Chart endpoint returns detailed information about a chart. + parameters: + - name: chart + in: query + description: The id of the chart as returned by the /charts call. + required: true + schema: + type: string + format: as returned by /charts + default: system.cpu + responses: + "200": + description: A javascript object with detailed information about the chart. + content: + application/json: + schema: + $ref: "#/components/schemas/chart" + "400": + description: No chart id was supplied in the request. + "404": + description: No chart with the given id is found. + /alarm_variables: + get: + summary: List variables available to configure alarms for a chart + description: Returns the basic information of a chart and all the variables that can + be used in alarm and template health configurations for the particular + chart or family. + parameters: + - name: chart + in: query + description: The id of the chart as returned by the /charts call. + required: true + schema: + type: string + format: as returned by /charts + default: system.cpu + responses: + "200": + description: A javascript object with information about the chart and the + available variables. + content: + application/json: + schema: + $ref: "#/components/schemas/alarm_variables" + "400": + description: Bad request - the body will include a message stating what is wrong. + "404": + description: No chart with the given id is found. + "500": + description: Internal server error. This usually means the server is out of + memory. + /data: + get: + summary: Get collected data for a specific chart + description: The data endpoint returns data stored in the round robin database of a + chart. + parameters: + - name: chart + in: query + description: The id of the chart as returned by the /charts call. Note chart or context must be specified + required: false + allowEmptyValue: false + schema: + type: string + format: as returned by /charts + default: system.cpu + - name: context + in: query + description: The context of the chart as returned by the /charts call. Note chart or context must be specified + required: false + allowEmptyValue: false + schema: + type: string + format: as returned by /charts + - name: dimension + in: query + description: Zero, one or more dimension ids or names, as returned by the /chart + call, separated with comma or pipe. Netdata simple patterns are + supported. + required: false + allowEmptyValue: false + schema: + type: array + items: + type: string + format: as returned by /charts + - name: after + in: query + description: "This parameter can either be an absolute timestamp specifying the + starting point of the data to be returned, or a relative number of + seconds (negative, relative to parameter: before). Netdata will + assume it is a relative number if it is less that 3 years (in seconds). + If not specified the default is -600 seconds. Netdata will adapt this + parameter to the boundaries of the round robin database unless the allow_past + option is specified." + required: true + allowEmptyValue: false + schema: + type: number + format: integer + default: -600 + - name: before + in: query + description: This parameter can either be an absolute timestamp specifying the + ending point of the data to be returned, or a relative number of + seconds (negative), relative to the last collected timestamp. + Netdata will assume it is a relative number if it is less than 3 + years (in seconds). Netdata will adapt this parameter to the + boundaries of the round robin database. The default is zero (i.e. + the timestamp of the last value collected). + required: false + schema: + type: number + format: integer + default: 0 + - name: points + in: query + description: The number of points to be returned. If not given, or it is <= 0, or + it is bigger than the points stored in the round robin database for + this chart for the given duration, all the available collected + values for the given duration will be returned. + required: true + allowEmptyValue: false + schema: + type: number + format: integer + default: 20 + - name: group + in: query + description: The grouping method. If multiple collected values are to be grouped + in order to return fewer points, this parameters defines the method + of grouping. methods supported "min", "max", "average", "sum", + "incremental-sum". "max" is actually calculated on the absolute + value collected (so it works for both positive and negative + dimesions to return the most extreme value in either direction). + required: true + allowEmptyValue: false + schema: + type: string + enum: + - min + - max + - average + - median + - stddev + - sum + - incremental-sum + default: average + - name: gtime + in: query + description: The grouping number of seconds. This is used in conjunction with + group=average to change the units of metrics (ie when the data is + per-second, setting gtime=60 will turn them to per-minute). + required: false + allowEmptyValue: false + schema: + type: number + format: integer + default: 0 + - name: format + in: query + description: The format of the data to be returned. + required: true + allowEmptyValue: false + schema: + type: string + enum: + - json + - jsonp + - csv + - tsv + - tsv-excel + - ssv + - ssvcomma + - datatable + - datasource + - html + - markdown + - array + - csvjsonarray + default: json + - name: options + in: query + description: Options that affect data generation. + required: false + allowEmptyValue: false + schema: + type: array + items: + type: string + enum: + - nonzero + - flip + - jsonwrap + - min2max + - seconds + - milliseconds + - abs + - absolute + - absolute-sum + - null2zero + - objectrows + - google_json + - percentage + - unaligned + - match-ids + - match-names + - showcustomvars + - allow_past + default: + - seconds + - jsonwrap + - name: callback + in: query + description: For JSONP responses, the callback function name. + required: false + allowEmptyValue: true + schema: + type: string + - name: filename + in: query + description: "Add Content-Disposition: attachment; filename= header to + the response, that will instruct the browser to save the response + with the given filename." + required: false + allowEmptyValue: true + schema: + type: string + - name: tqx + in: query + description: "[Google Visualization + API](https://developers.google.com/chart/interactive/docs/dev/imple\ + menting_data_source?hl=en) formatted parameter." + required: false + allowEmptyValue: true + schema: + type: string + responses: + "200": + description: The call was successful. The response includes the data in the + format requested. Swagger2.0 does not process the discriminator + field to show polymorphism. The response will be one of the + sub-types of the data-schema according to the chosen format, e.g. + json -> data_json. + content: + application/json: + schema: + $ref: "#/components/schemas/data" + "400": + description: Bad request - the body will include a message stating what is wrong. + "404": + description: Chart or context is not found. The supplied chart or context will be reported. + "500": + description: Internal server error. This usually means the server is out of + memory. + /badge.svg: + get: + summary: Generate a badge in form of SVG image for a chart (or dimension) + description: Successful responses are SVG images. + parameters: + - name: chart + in: query + description: The id of the chart as returned by the /charts call. + required: true + allowEmptyValue: false + schema: + type: string + format: as returned by /charts + default: system.cpu + - name: alarm + in: query + description: The name of an alarm linked to the chart. + required: false + allowEmptyValue: true + schema: + type: string + format: any text + - name: dimension + in: query + description: Zero, one or more dimension ids, as returned by the /chart call. + required: false + allowEmptyValue: false + schema: + type: array + items: + type: string + format: as returned by /charts + - name: after + in: query + description: This parameter can either be an absolute timestamp specifying the + starting point of the data to be returned, or a relative number of + seconds, to the last collected timestamp. Netdata will assume it is + a relative number if it is smaller than the duration of the round + robin database for this chart. So, if the round robin database is + 3600 seconds, any value from -3600 to 3600 will trigger relative + arithmetics. Netdata will adapt this parameter to the boundaries of + the round robin database. + required: true + allowEmptyValue: false + schema: + type: number + format: integer + default: -600 + - name: before + in: query + description: This parameter can either be an absolute timestamp specifying the + ending point of the data to be returned, or a relative number of + seconds, to the last collected timestamp. Netdata will assume it is + a relative number if it is smaller than the duration of the round + robin database for this chart. So, if the round robin database is + 3600 seconds, any value from -3600 to 3600 will trigger relative + arithmetics. Netdata will adapt this parameter to the boundaries of + the round robin database. + required: false + schema: + type: number + format: integer + default: 0 + - name: group + in: query + description: The grouping method. If multiple collected values are to be grouped + in order to return fewer points, this parameters defines the method + of grouping. methods are supported "min", "max", "average", "sum", + "incremental-sum". "max" is actually calculated on the absolute + value collected (so it works for both positive and negative + dimesions to return the most extreme value in either direction). + required: true + allowEmptyValue: false + schema: + type: string + enum: + - min + - max + - average + - median + - stddev + - sum + - incremental-sum + default: average + - name: options + in: query + description: Options that affect data generation. + required: false + allowEmptyValue: true + schema: + type: array + items: + type: string + enum: + - abs + - absolute + - display-absolute + - absolute-sum + - null2zero + - percentage + - unaligned + default: + - absolute + - name: label + in: query + description: A text to be used as the label. + required: false + allowEmptyValue: true + schema: + type: string + format: any text + - name: units + in: query + description: A text to be used as the units. + required: false + allowEmptyValue: true + schema: + type: string + format: any text + - name: label_color + in: query + description: A color to be used for the background of the label side(left side) of the badge. One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). If value wrong or not given default color will be used. + required: false + allowEmptyValue: true + schema: + oneOf: + - type: string + enum: + - green + - brightgreen + - yellow + - yellowgreen + - orange + - red + - blue + - grey + - gray + - lightgrey + - lightgray + - type: string + format: ^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - name: value_color + in: query + description: "A color to be used for the background of the value *(right)* part of badge. You can set + multiple using a pipe with a condition each, like this: + `color, <, >=, <=, =, :null (to check if no value exists). Each color can be specified in same manner as for `label_color` parameter. + Currently only integers are suported as values." + required: false + allowEmptyValue: true + schema: + type: string + format: any text + - name: text_color_lbl + in: query + description: Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceeding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used. + required: false + allowEmptyValue: true + schema: + oneOf: + - type: string + enum: + - green + - brightgreen + - yellow + - yellowgreen + - orange + - red + - blue + - grey + - gray + - lightgrey + - lightgray + - type: string + format: ^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - name: text_color_val + in: query + description: Font color for value *(right)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceeding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used. + required: false + allowEmptyValue: true + schema: + oneOf: + - type: string + enum: + - green + - brightgreen + - yellow + - yellowgreen + - orange + - red + - blue + - grey + - gray + - lightgrey + - lightgray + - type: string + format: ^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - name: multiply + in: query + description: Multiply the value with this number for rendering it at the image + (integer value required). + required: false + allowEmptyValue: true + schema: + type: number + format: integer + - name: divide + in: query + description: Divide the value with this number for rendering it at the image + (integer value required). + required: false + allowEmptyValue: true + schema: + type: number + format: integer + - name: scale + in: query + description: Set the scale of the badge (greater or equal to 100). + required: false + allowEmptyValue: true + schema: + type: number + format: integer + - name: fixed_width_lbl + in: query + description: This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's left side *(label/name)*. You must set this parameter together with `fixed_width_val` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`. + required: false + allowEmptyValue: false + schema: + type: number + format: integer + - name: fixed_width_val + in: query + description: This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's right side *(value)*. You must set this parameter together with `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`. + required: false + allowEmptyValue: false + schema: + type: number + format: integer + responses: + "200": + description: The call was successful. The response should be an SVG image. + "400": + description: Bad request - the body will include a message stating what is wrong. + "404": + description: No chart with the given id is found. + "500": + description: Internal server error. This usually means the server is out of + memory. + /allmetrics: + get: + summary: Get a value of all the metrics maintained by netdata + description: The allmetrics endpoint returns the latest value of all charts and + dimensions stored in the netdata server. + parameters: + - name: format + in: query + description: The format of the response to be returned. + required: true + schema: + type: string + enum: + - shell + - prometheus + - prometheus_all_hosts + - json + default: shell + - name: variables + in: query + description: When enabled, netdata will expose various system + configuration metrics. + required: false + schema: + type: string + enum: + - yes + - no + default: no + - name: help + in: query + description: Enable or disable HELP lines in prometheus output. + required: false + schema: + type: string + enum: + - yes + - no + default: no + - name: types + in: query + description: Enable or disable TYPE lines in prometheus output. + required: false + schema: + type: string + enum: + - yes + - no + default: no + - name: timestamps + in: query + description: Enable or disable timestamps in prometheus output. + required: false + schema: + type: string + enum: + - yes + - no + default: yes + - name: names + in: query + description: When enabled netdata will report dimension names. When disabled + netdata will report dimension IDs. The default is controlled in + netdata.conf. + required: false + schema: + type: string + enum: + - yes + - no + default: yes + - name: oldunits + in: query + description: When enabled, netdata will show metric names for the default + source=average as they appeared before 1.12, by using the legacy + unit naming conventions. + required: false + schema: + type: string + enum: + - yes + - no + default: yes + - name: hideunits + in: query + description: When enabled, netdata will not include the units in the metric + names, for the default source=average. + required: false + schema: + type: string + enum: + - yes + - no + default: yes + - name: server + in: query + description: Set a distinct name of the client querying prometheus metrics. + Netdata will use the client IP if this is not set. + required: false + schema: + type: string + format: any text + - name: prefix + in: query + description: Prefix all prometheus metrics with this string. + required: false + schema: + type: string + format: any text + - name: data + in: query + description: Select the prometheus response data source. There is a setting in + netdata.conf for the default. + required: false + schema: + type: string + enum: + - as-collected + - average + - sum + default: average + responses: + "200": + description: All the metrics returned in the format requested. + "400": + description: The format requested is not supported. + /alarms: + get: + summary: Get a list of active or raised alarms on the server + description: The alarms endpoint returns the list of all raised or enabled alarms on + the netdata server. Called without any parameters, the raised alarms in + state WARNING or CRITICAL are returned. By passing "?all", all the + enabled alarms are returned. + parameters: + - name: all + in: query + description: If passed, all enabled alarms are returned. + required: false + allowEmptyValue: true + schema: + type: boolean + - name: active + in: query + description: If passed, the raised alarms in state WARNING or CRITICAL are returned. + required: false + allowEmptyValue: true + schema: + type: boolean + responses: + "200": + description: An object containing general info and a linked list of alarms. + content: + application/json: + schema: + $ref: "#/components/schemas/alarms" + /alarms_values: + get: + summary: Get a list of active or raised alarms on the server + description: The alarms_values endpoint returns the list of all raised or enabled alarms on + the netdata server. Called without any parameters, the raised alarms in + state WARNING or CRITICAL are returned. By passing "?all", all the + enabled alarms are returned. + This option output differs from `/alarms` in the number of variables delivered. This endpoint gives + to user `id`, `value` and alarm `status`. + parameters: + - name: all + in: query + description: If passed, all enabled alarms are returned. + required: false + allowEmptyValue: true + schema: + type: boolean + - name: active + in: query + description: If passed, the raised alarms in state WARNING or CRITICAL are returned. + required: false + allowEmptyValue: true + schema: + type: boolean + responses: + "200": + description: An object containing general info and a linked list of alarms. + content: + application/json: + schema: + $ref: "#/components/schemas/alarms_values" + /alarm_log: + get: + summary: Retrieves the entries of the alarm log + description: Returns an array of alarm_log entries, with historical information on + raised and cleared alarms. + parameters: + - name: after + in: query + description: Passing the parameter after=UNIQUEID returns all the events in the + alarm log that occurred after UNIQUEID. An automated series of calls + would call the interface once without after=, store the last + UNIQUEID of the returned set, and give it back to get incrementally + the next events. + required: false + schema: + type: integer + responses: + "200": + description: An array of alarm log entries. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/alarm_log_entry" + /alarm_count: + get: + summary: Get an overall status of the chart + description: Checks multiple charts with the same context and counts number of alarms + with given status. + parameters: + - in: query + name: context + description: Specify context which should be checked. + required: false + allowEmptyValue: true + schema: + type: array + items: + type: string + default: + - system.cpu + - in: query + name: status + description: Specify alarm status to count. + required: false + allowEmptyValue: true + schema: + type: string + enum: + - REMOVED + - UNDEFINED + - UNINITIALIZED + - CLEAR + - RAISED + - WARNING + - CRITICAL + default: RAISED + responses: + "200": + description: An object containing a count of alarms with given status for given + contexts. + content: + application/json: + schema: + type: array + items: + type: number + "500": + description: Internal server error. This usually means the server is out of + memory. + /manage/health: + get: + summary: Accesses the health management API to control health checks and + notifications at runtime. + description: Available from Netdata v1.12 and above, protected via bearer + authorization. Especially useful for maintenance periods, the API allows + you to disable health checks completely, silence alarm notifications, or + Disable/Silence specific alarms that match selectors on alarm/template + name, chart, context, host and family. For the simple disable/silence + all scenaria, only the cmd parameter is required. The other parameters + are used to define alarm selectors. For more information and examples, + refer to the netdata documentation. + parameters: + - name: cmd + in: query + description: "DISABLE ALL: No alarm criteria are evaluated, nothing is written in + the alarm log. SILENCE ALL: No notifications are sent. RESET: Return + to the default state. DISABLE/SILENCE: Set the mode to be used for + the alarms matching the criteria of the alarm selectors. LIST: Show + active configuration." + required: false + schema: + type: string + enum: + - DISABLE ALL + - SILENCE ALL + - DISABLE + - SILENCE + - RESET + - LIST + - name: alarm + in: query + description: The expression provided will match both `alarm` and `template` names. + schema: + type: string + - name: chart + in: query + description: Chart ids/names, as shown on the dashboard. These will match the + `on` entry of a configured `alarm`. + schema: + type: string + - name: context + in: query + description: Chart context, as shown on the dashboard. These will match the `on` + entry of a configured `template`. + schema: + type: string + - name: hosts + in: query + description: The hostnames that will need to match. + schema: + type: string + - name: families + in: query + description: The alarm families. + schema: + type: string + responses: + "200": + description: A plain text response based on the result of the command. + "403": + description: Bearer authentication error. +servers: + - url: https://registry.my-netdata.io/api/v1 + - url: http://registry.my-netdata.io/api/v1 +components: + schemas: + info: + type: object + properties: + version: + type: string + description: netdata version of the server. + example: 1.11.1_rolling + uid: + type: string + description: netdata unique id of the server. + example: 24e9fe3c-f2ac-11e8-bafc-0242ac110002 + mirrored_hosts: + type: array + description: List of hosts mirrored of the server (include itself). + items: + type: string + example: + - host1.example.com + - host2.example.com + mirrored_hosts_status: + type: array + description: >- + List of details of hosts mirrored to this served (including self). + Indexes correspond to indexes in "mirrored_hosts". + items: + type: object + description: Host data + properties: + guid: + type: string + format: uuid + nullable: false + description: Host unique GUID from `netdata.public.unique.id`. + example: 245e4bff-3b34-47c1-a6e5-5c535a9abfb2 + reachable: + type: boolean + nullable: false + description: Current state of streaming. Always true for localhost/self. + claim_id: + type: string + format: uuid + nullable: true + description: >- + Cloud GUID/identifier in case the host is claimed. + If child status unknown or unclaimed this field is set to `null` + example: c3b2a66a-3052-498c-ac52-7fe9e8cccb0c + os_name: + type: string + description: Operating System Name. + example: Manjaro Linux + os_id: + type: string + description: Operating System ID. + example: manjaro + os_id_like: + type: string + description: Known OS similar to this OS. + example: arch + os_version: + type: string + description: Operating System Version. + example: 18.0.4 + os_version_id: + type: string + description: Operating System Version ID. + example: unknown + os_detection: + type: string + description: OS parameters detection method. + example: Mixed + kernel_name: + type: string + description: Kernel Name. + example: Linux + kernel_version: + type: string + description: Kernel Version. + example: 4.19.32-1-MANJARO + is_k8s_node: + type: boolean + description: Netdata is running on a K8s node. + example: false + architecture: + type: string + description: Kernel architecture. + example: x86_64 + virtualization: + type: string + description: Virtualization Type. + example: kvm + virt_detection: + type: string + description: Virtualization detection method. + example: systemd-detect-virt + container: + type: string + description: Container technology. + example: docker + container_detection: + type: string + description: Container technology detection method. + example: dockerenv + labels: + type: object + description: List of host labels. + properties: + app: + type: string + description: Host label. + example: netdata + collectors: + type: array + items: + type: object + description: Array of collector plugins and modules. + properties: + plugin: + type: string + description: Collector plugin. + example: python.d.plugin + module: + type: string + description: Module of the collector plugin. + example: dockerd + alarms: + type: object + description: Number of alarms in the server. + properties: + normal: + type: integer + description: Number of alarms in normal state. + warning: + type: integer + description: Number of alarms in warning state. + critical: + type: integer + description: Number of alarms in critical state. + chart_summary: + type: object + properties: + hostname: + type: string + description: The hostname of the netdata server. + version: + type: string + description: netdata version of the server. + release_channel: + type: string + description: The release channel of the build on the server. + example: nightly + timezone: + type: string + description: The current timezone on the server. + os: + type: string + description: The netdata server host operating system. + enum: + - macos + - linux + - freebsd + history: + type: number + description: The duration, in seconds, of the round robin database maintained by + netdata. + memory_mode: + type: string + description: The name of the database memory mode on the server. + update_every: + type: number + description: The default update frequency of the netdata server. All charts have + an update frequency equal or bigger than this. + charts: + type: object + description: An object containing all the chart objects available at the netdata + server. This is used as an indexed array. The key of each chart + object is the id of the chart. + additionalProperties: + $ref: "#/components/schemas/chart" + charts_count: + type: number + description: The number of charts. + dimensions_count: + type: number + description: The total number of dimensions. + alarms_count: + type: number + description: The number of alarms. + rrd_memory_bytes: + type: number + description: The size of the round robin database in bytes. + chart: + type: object + properties: + id: + type: string + description: The unique id of the chart. + name: + type: string + description: The name of the chart. + type: + type: string + description: The type of the chart. Types are not handled by netdata. You can use + this field for anything you like. + family: + type: string + description: The family of the chart. Families are not handled by netdata. You + can use this field for anything you like. + title: + type: string + description: The title of the chart. + priority: + type: number + description: The relative priority of the chart. NetData does not care about + priorities. This is just an indication of importance for the chart + viewers to sort charts of higher priority (lower number) closer to + the top. Priority sorting should only be used among charts of the + same type or family. + enabled: + type: boolean + description: True when the chart is enabled. Disabled charts do not currently + collect values, but they may have historical values available. + units: + type: string + description: The unit of measurement for the values of all dimensions of the + chart. + data_url: + type: string + description: The absolute path to get data values for this chart. You are + expected to use this path as the base when constructing the URL to + fetch data values for this chart. + chart_type: + type: string + description: The chart type. + enum: + - line + - area + - stacked + duration: + type: number + description: The duration, in seconds, of the round robin database maintained by + netdata. + first_entry: + type: number + description: The UNIX timestamp of the first entry (the oldest) in the round + robin database. + last_entry: + type: number + description: The UNIX timestamp of the latest entry in the round robin database. + update_every: + type: number + description: The update frequency of this chart, in seconds. One value every this + amount of time is kept in the round robin database. + dimensions: + type: object + description: "An object containing all the chart dimensions available for the + chart. This is used as an indexed array. For each pair in the + dictionary: the key is the id of the dimension and the value is a + dictionary containing the name." + additionalProperties: + type: object + properties: + name: + type: string + description: The name of the dimension + chart_variables: + type: object + additionalProperties: + $ref: "#/components/schemas/chart_variables" + green: + type: number + nullable: true + description: Chart health green threshold. + red: + type: number + nullable: true + description: Chart health red threshold. + alarm_variables: + type: object + properties: + chart: + type: string + description: The unique id of the chart. + chart_name: + type: string + description: The name of the chart. + cnart_context: + type: string + description: The context of the chart. It is shared across multiple monitored + software or hardware instances and used in alarm templates. + family: + type: string + description: The family of the chart. + host: + type: string + description: The host containing the chart. + chart_variables: + type: object + additionalProperties: + $ref: "#/components/schemas/chart_variables" + family_variables: + type: object + properties: + varname1: + type: number + format: float + varname2: + type: number + format: float + host_variables: + type: object + properties: + varname1: + type: number + format: float + varname2: + type: number + format: float + chart_variables: + type: object + properties: + varname1: + type: number + format: float + varname2: + type: number + format: float + data: + type: object + discriminator: + propertyName: format + description: Response will contain the appropriate subtype, e.g. data_json depending + on the requested format. + properties: + api: + type: number + description: The API version this conforms to, currently 1. + id: + type: string + description: The unique id of the chart. + name: + type: string + description: The name of the chart. + update_every: + type: number + description: The update frequency of this chart, in seconds. One value every this + amount of time is kept in the round robin database (indepedently of + the current view). + view_update_every: + type: number + description: The current view appropriate update frequency of this chart, in + seconds. There is no point to request chart refreshes, using the + same settings, more frequently than this. + first_entry: + type: number + description: The UNIX timestamp of the first entry (the oldest) in the round + robin database (indepedently of the current view). + last_entry: + type: number + description: The UNIX timestamp of the latest entry in the round robin database + (indepedently of the current view). + after: + type: number + description: The UNIX timestamp of the first entry (the oldest) returned in this + response. + before: + type: number + description: The UNIX timestamp of the latest entry returned in this response. + min: + type: number + description: The minimum value returned in the current view. This can be used to + size the y-series of the chart. + max: + type: number + description: The maximum value returned in the current view. This can be used to + size the y-series of the chart. + dimension_names: + description: The dimension names of the chart as returned in the current view. + type: array + items: + type: string + dimension_ids: + description: The dimension IDs of the chart as returned in the current view. + type: array + items: + type: string + latest_values: + description: The latest values collected for the chart (indepedently of the + current view). + type: array + items: + type: string + view_latest_values: + description: The latest values returned with this response. + type: array + items: + type: string + dimensions: + type: number + description: The number of dimensions returned. + points: + type: number + description: The number of rows / points returned. + format: + type: string + description: The format of the result returned. + chart_variables: + type: object + additionalProperties: + $ref: "#/components/schemas/chart_variables" + data_json: + description: Data response in json format. + allOf: + - $ref: "#/components/schemas/data" + - properties: + result: + type: object + properties: + labels: + description: The dimensions retrieved from the chart. + type: array + items: + type: string + data: + description: The data requested, one element per sample with each element + containing the values of the dimensions described in the + labels value. + type: array + items: + type: number + description: The result requested, in the format requested. + data_flat: + description: Data response in csv / tsv / tsv-excel / ssv / ssv-comma / markdown / + html formats. + allOf: + - $ref: "#/components/schemas/data" + - properties: + result: + type: string + data_array: + description: Data response in array format. + allOf: + - $ref: "#/components/schemas/data" + - properties: + result: + type: array + items: + type: number + data_csvjsonarray: + description: Data response in csvjsonarray format. + allOf: + - $ref: "#/components/schemas/data" + - properties: + result: + description: The first inner array contains strings showing the labels of + each column, each subsequent array contains the values for each + point in time. + type: array + items: + type: array + items: {} + data_datatable: + description: Data response in datatable / datasource formats (suitable for Google + Charts). + allOf: + - $ref: "#/components/schemas/data" + - properties: + result: + type: object + properties: + cols: + type: array + items: + type: object + properties: + id: + description: Always empty - for future use. + label: + description: The dimension returned from the chart. + pattern: + description: Always empty - for future use. + type: + description: The type of data in the column / chart-dimension. + p: + description: Contains any annotations for the column. + required: + - id + - label + - pattern + - type + rows: + type: array + items: + type: object + properties: + c: + type: array + items: + properties: + v: + description: "Each value in the row is represented by an + object named `c` with five v fields: data, null, + null, 0, the value. This format is fixed by the + Google Charts API." + alarms: + type: object + properties: + hostname: + type: string + latest_alarm_log_unique_id: + type: integer + format: int32 + status: + type: boolean + now: + type: integer + format: int32 + alarms: + type: object + properties: + chart-name.alarm-name: + type: object + properties: + id: + type: integer + format: int32 + name: + type: string + description: Full alarm name. + chart: + type: string + family: + type: string + active: + type: boolean + description: Will be false only if the alarm is disabled in the + configuration. + disabled: + type: boolean + description: Whether the health check for this alarm has been disabled + via a health command API DISABLE command. + silenced: + type: boolean + description: Whether notifications for this alarm have been silenced via + a health command API SILENCE command. + exec: + type: string + recipient: + type: string + source: + type: string + units: + type: string + info: + type: string + status: + type: string + last_status_change: + type: integer + format: int32 + last_updated: + type: integer + format: int32 + next_update: + type: integer + format: int32 + update_every: + type: integer + format: int32 + delay_up_duration: + type: integer + format: int32 + delay_down_duration: + type: integer + format: int32 + delay_max_duration: + type: integer + format: int32 + delay_multiplier: + type: integer + format: int32 + delay: + type: integer + format: int32 + delay_up_to_timestamp: + type: integer + format: int32 + value_string: + type: string + no_clear_notification: + type: boolean + lookup_dimensions: + type: string + db_after: + type: integer + format: int32 + db_before: + type: integer + format: int32 + lookup_method: + type: string + lookup_after: + type: integer + format: int32 + lookup_before: + type: integer + format: int32 + lookup_options: + type: string + calc: + type: string + calc_parsed: + type: string + warn: + type: string + warn_parsed: + type: string + crit: + type: string + crit_parsed: + type: string + warn_repeat_every: + type: integer + format: int32 + crit_repeat_every: + type: integer + format: int32 + green: + type: string + format: nullable + red: + type: string + format: nullable + value: + type: number + alarm_log_entry: + type: object + properties: + hostname: + type: string + unique_id: + type: integer + format: int32 + alarm_id: + type: integer + format: int32 + alarm_event_id: + type: integer + format: int32 + name: + type: string + chart: + type: string + family: + type: string + processed: + type: boolean + updated: + type: boolean + exec_run: + type: integer + format: int32 + exec_failed: + type: boolean + exec: + type: string + recipient: + type: string + exec_code: + type: integer + format: int32 + source: + type: string + units: + type: string + when: + type: integer + format: int32 + duration: + type: integer + format: int32 + non_clear_duration: + type: integer + format: int32 + status: + type: string + old_status: + type: string + delay: + type: integer + format: int32 + delay_up_to_timestamp: + type: integer + format: int32 + updated_by_id: + type: integer + format: int32 + updates_id: + type: integer + format: int32 + value_string: + type: string + old_value_string: + type: string + silenced: + type: string + info: + type: string + value: + type: number + nullable: true + old_value: + type: number + nullable: true + alarms_values: + type: object + properties: + hostname: + type: string + alarms: + type: object + description: HashMap with keys being alarm names + additionalProperties: + type: object + properties: + id: + type: integer + value: + type: integer + status: + type: string + enum: + - REMOVED + - UNDEFINED + - UNINITIALIZED + - CLEAR + - RAISED + - WARNING + - CRITICAL + - UNKNOWN diff --git a/web/api/queries/Makefile.am b/web/api/queries/Makefile.am new file mode 100644 index 0000000..34bfdb8 --- /dev/null +++ b/web/api/queries/Makefile.am @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + average \ + des \ + incremental_sum \ + max \ + min \ + sum \ + median \ + ses \ + stddev \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/README.md b/web/api/queries/README.md new file mode 100644 index 0000000..31ec496 --- /dev/null +++ b/web/api/queries/README.md @@ -0,0 +1,176 @@ + + +# Database Queries + +Netdata database can be queried with `/api/v1/data` and `/api/v1/badge.svg` REST API methods. + +Every data query accepts the following parameters: + +|name|required|description| +|:--:|:------:|:----------| +|`chart`|yes|The chart to be queried.| +|`points`|no|The number of points to be returned. Netdata can reduce number of points by applying query grouping methods. If not given, the result will have the same granularity as the database (although this relates to `gtime`).| +|`before`|no|The absolute timestamp or the relative (to now) time the query should finish evaluating data. If not given, it defaults to the timestamp of the latest point in the database.| +|`after`|no|The absolute timestamp or the relative (to `before`) time the query should start evaluating data. if not given, it defaults to the timestamp of the oldest point in the database.| +|`group`|no|The grouping method to use when reducing the points the database has. If not given, it defaults to `average`.| +|`gtime`|no|A resampling period to change the units of the metrics (i.e. setting this to `60` will convert `per second` metrics to `per minute`. If not given it defaults to granularity of the database.| +|`options`|no|A bitmap of options that can affect the operation of the query. Only 2 options are used by the query engine: `unaligned` and `percentage`. All the other options are used by the output formatters. The default is to return aligned data.| +|`dimensions`|no|A simple pattern to filter the dimensions to be queried. The default is to return all the dimensions of the chart.| + +## Operation + +The query engine works as follows (in this order): + +#### Time-frame + +`after` and `before` define a time-frame, accepting: + +- **absolute timestamps** (unix timestamps, i.e. seconds since epoch). + +- **relative timestamps**: + + `before` is relative to now and `after` is relative to `before`. + + Example: `before=-60&after=-60` evaluates to the time-frame from -120 up to -60 seconds in + the past, relative to the latest entry of the database of the chart. + +The engine verifies that the time-frame requested is available at the database: + +- If the requested time-frame overlaps with the database, the excess requested + will be truncated. + +- If the requested time-frame does not overlap with the database, the engine will + return an empty data set. + +At the end of this operation, `after` and `before` are absolute timestamps. + +#### Data grouping + +Database points grouping is applied when the caller requests a time-frame to be +expressed with fewer points, compared to what is available at the database. + +There are 2 uses that enable this feature: + +- The caller requests a specific number of `points` to be returned. + + For example, for a time-frame of 10 minutes, the database has 600 points (1/sec), + while the caller requested these 10 minutes to be expressed in 200 points. + + This feature is used by Netdata dashboards when you zoom-out the charts. + The dashboard is requesting the number of points the user's screen has. + This saves bandwidth and speeds up the browser (fewer points to evaluate for drawing the charts). +- The caller requests a **re-sampling** of the database, by setting `gtime` to any value + above the granularity of the chart. + + For example, the chart's units is `requests/sec` and caller wants `requests/min`. + +Using `points` and `gtime` the query engine tries to find a best fit for **database-points** +vs **result-points** (we call this ratio `group points`). It always tries to keep `group points` +an integer. Keep in mind the query engine may shift `after` if required. See also the [example](#example). + +#### Time-frame Alignment + +Alignment is a very important aspect of Netdata queries. Without it, the animated +charts on the dashboards would constantly [change shape](#example) during incremental updates. + +To provide consistent grouping through time, the query engine (by default) aligns +`after` and `before` to be a multiple of `group points`. + +For example, if `group points` is 60 and alignment is enabled, the engine will return +each point with durations XX:XX:00 - XX:XX:59, matching whole minutes. + +To disable alignment, pass `&options=unaligned` to the query. + +#### Query Execution + +To execute the query, the engine evaluates all dimensions of the chart, one after another. + +The engine does not evaluate dimensions that do not match the [simple pattern](/libnetdata/simple_pattern/README.md) +given at the `dimensions` parameter, except when `options=percentage` is given (this option +requires all the dimensions to be evaluated to find the percentage of each dimension vs to chart +total). + +For each dimension, it starts evaluating values starting at `after` (not inclusive) towards +`before` (inclusive). + +For each value it calls the **grouping method** given with the `&group=` query parameter +(the default is `average`). + +## Grouping methods + +The following grouping methods are supported. These are given all the values in the time-frame +and they group the values every `group points`. + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min&value_color=blue) finds the minimum value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max&value_color=lightblue) finds the maximum value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average&value_color=yellow) finds the average value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=sum&after=-60&label=sum&units=requests&value_color=orange) adds all the values and returns the sum +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=median&after=-60&label=median&value_color=red) sorts the values and returns the value in the middle of the list +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=stddev&after=-60&label=stddev&value_color=green) finds the standard deviation of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=cv&after=-60&label=cv&units=pcent&value_color=yellow) finds the relative standard deviation (coefficient of variation) of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=ses&after=-60&label=ses&value_color=brown) finds the exponential weighted moving average of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=des&after=-60&label=des&value_color=blue) applies Holt-Winters double exponential smoothing +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=incremental_sum&after=-60&label=incremental_sum&value_color=red) finds the difference of the last vs the first value + +The examples shown above, are live information from the `successful` web requests of the global Netdata registry. + +## Further processing + +The result of the query engine is always a structure that has dimensions and values +for each dimension. + +Formatting modules are then used to convert this result in many different formats and return it +to the caller. + +## Performance + +The query engine is highly optimized for speed. Most of its modules implement "online" +versions of the algorithms, requiring just one pass on the database values to produce +the result. + +## Example + +When Netdata is reducing metrics, it tries to return always the same boundaries. So, if we want 10s averages, it will always return points starting at a `unix timestamp % 10 = 0`. + +Let's see why this is needed, by looking at the error case. + +Assume we have 5 points: + +|time|value| +|:--:|:---:| +|00:01|1| +|00:02|2| +|00:03|3| +|00:04|4| +|00:05|5| + +At 00:04 you ask for 2 points for 4 seconds in the past. So `group = 2`. Netdata would return: + +|point|time|value| +|:---:|:--:|:---:| +|1|00:01 - 00:02|1.5| +|2|00:03 - 00:04|3.5| + +A second later the chart is to be refreshed, and makes again the same request at 00:05. These are the points that would have been returned: + +|point|time|value| +|:---:|:--:|:---:| +|1|00:02 - 00:03|2.5| +|2|00:04 - 00:05|4.5| + +**Wait a moment!** The chart was shifted just one point and it changed value! Point 2 was 3.5 and when shifted to point 1 is 2.5! If you see this in a chart, it's a mess. The charts change shape constantly. + +For this reason, Netdata always aligns the data it returns to the `group`. + +When you request `points=1`, Netdata understands that you need 1 point for the whole database, so `group = 3600`. Then it tries to find the starting point which would be `timestamp % 3600 = 0` Within a database of 3600 seconds, there is one such point for sure. Then it tries to find the average of 3600 points. But, most probably it will not find 3600 of them (for just 1 out of 3600 seconds this query will return something). + +So, the proper way to query the database is to also set at least `after`. The following call will returns 1 point for the last complete 10-second duration (it starts at `timestamp % 10 = 0`): + + + +When you keep calling this URL, you will see that it returns one new value every 10 seconds, and the timestamp always ends with zero. Similarly, if you say `points=1&after=-5` it will always return timestamps ending with 0 or 5. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/average/Makefile.am b/web/api/queries/average/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/average/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/average/README.md b/web/api/queries/average/README.md new file mode 100644 index 0000000..f32a675 --- /dev/null +++ b/web/api/queries/average/README.md @@ -0,0 +1,46 @@ + + +# Average or Mean + +> This query is available as `average` and `mean`. + +An average is a single number taken as representative of a list of numbers. + +It is calculated as: + +``` +average = sum(numbers) / count(numbers) +``` + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: average -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`average` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=average` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Faverage%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/average/average.c b/web/api/queries/average/average.c new file mode 100644 index 0000000..2c64358 --- /dev/null +++ b/web/api/queries/average/average.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "average.h" + +// ---------------------------------------------------------------------------- +// average + +struct grouping_average { + calculated_number sum; + size_t count; +}; + +void *grouping_create_average(RRDR *r) { + (void)r; + return callocz(1, sizeof(struct grouping_average)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_average(RRDR *r) { + struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; + g->sum = 0; + g->count = 0; +} + +void grouping_free_average(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_average(RRDR *r, calculated_number value) { + if(!isnan(value)) { + struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; + g->sum += value; + g->count++; + } +} + +calculated_number grouping_flush_average(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + if(unlikely(r->internal.resampling_group != 1)) { + if (unlikely(r->result_options & RRDR_RESULT_OPTION_VARIABLE_STEP)) + value = g->sum / g->count / r->internal.resampling_divisor; + else + value = g->sum / r->internal.resampling_divisor; + } else + value = g->sum / g->count; + } + + g->sum = 0.0; + g->count = 0; + + return value; +} diff --git a/web/api/queries/average/average.h b/web/api/queries/average/average.h new file mode 100644 index 0000000..9fb7de2 --- /dev/null +++ b/web/api/queries/average/average.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERY_AVERAGE_H +#define NETDATA_API_QUERY_AVERAGE_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_average(RRDR *r); +extern void grouping_reset_average(RRDR *r); +extern void grouping_free_average(RRDR *r); +extern void grouping_add_average(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_average(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERY_AVERAGE_H diff --git a/web/api/queries/des/Makefile.am b/web/api/queries/des/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/des/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/des/README.md b/web/api/queries/des/README.md new file mode 100644 index 0000000..5505de5 --- /dev/null +++ b/web/api/queries/des/README.md @@ -0,0 +1,73 @@ + + +# double exponential smoothing + +Exponential smoothing is one of many window functions commonly applied to smooth data in signal +processing, acting as low-pass filters to remove high frequency noise. + +Simple exponential smoothing does not do well when there is a trend in the data. +In such situations, several methods were devised under the name "double exponential smoothing" +or "second-order exponential smoothing.", which is the recursive application of an exponential +filter twice, thus being termed "double exponential smoothing". + +In simple terms, this is like an average value, but more recent values are given more weight +and the trend of the values influences significantly the result. + +> **IMPORTANT** +> +> It is common for `des` to provide "average" values that far beyond the minimum or the maximum +> values found in the time-series. +> `des` estimates these values because of it takes into account the trend. + +This module implements the "Holt-Winters double exponential smoothing". + +Netdata automatically adjusts the weight (`alpha`) and the trend (`beta`) based on the number +of values processed, using the formula: + +``` +window = max(number of values, 15) +alpha = 2 / (window + 1) +beta = 2 / (window + 1) +``` + +You can change the fixed value `15` by setting in `netdata.conf`: + +``` +[web] + des max window = 15 +``` + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: des -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`des` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=des` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average&value_color=yellow) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=ses&after=-60&label=single+exponential+smoothing&value_color=yellow) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=des&after=-60&label=double+exponential+smoothing&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fdes%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/des/des.c b/web/api/queries/des/des.c new file mode 100644 index 0000000..c6236f3 --- /dev/null +++ b/web/api/queries/des/des.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "des.h" + + +// ---------------------------------------------------------------------------- +// single exponential smoothing + +struct grouping_des { + calculated_number alpha; + calculated_number alpha_other; + calculated_number beta; + calculated_number beta_other; + + calculated_number level; + calculated_number trend; + + size_t count; +}; + +static size_t max_window_size = 15; + +void grouping_init_des(void) { + long long ret = config_get_number(CONFIG_SECTION_WEB, "des max window", (long long)max_window_size); + if(ret <= 1) { + config_set_number(CONFIG_SECTION_WEB, "des max window", (long long)max_window_size); + } + else { + max_window_size = (size_t) ret; + } +} + +static inline calculated_number window(RRDR *r, struct grouping_des *g) { + (void)g; + + calculated_number points; + if(r->group == 1) { + // provide a running DES + points = r->internal.points_wanted; + } + else { + // provide a SES with flush points + points = r->group; + } + + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + return (points > max_window_size) ? max_window_size : points; +} + +static inline void set_alpha(RRDR *r, struct grouping_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + + g->alpha = 2.0 / (window(r, g) + 1.0); + g->alpha_other = 1.0 - g->alpha; + + //info("alpha for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->alpha); +} + +static inline void set_beta(RRDR *r, struct grouping_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + + g->beta = 2.0 / (window(r, g) + 1.0); + g->beta_other = 1.0 - g->beta; + + //info("beta for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->beta); +} + +void *grouping_create_des(RRDR *r) { + struct grouping_des *g = (struct grouping_des *)malloc(sizeof(struct grouping_des)); + set_alpha(r, g); + set_beta(r, g); + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + return g; +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_des(RRDR *r) { + struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + + // fprintf(stderr, "\nDES: "); + +} + +void grouping_free_des(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_des(RRDR *r, calculated_number value) { + struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; + + if(calculated_number_isnumber(value)) { + if(likely(g->count > 0)) { + // we have at least a number so far + + if(unlikely(g->count == 1)) { + // the second value we got + g->trend = value - g->trend; + g->level = value; + } + + // for the values, except the first + calculated_number last_level = g->level; + g->level = (g->alpha * value) + (g->alpha_other * (g->level + g->trend)); + g->trend = (g->beta * (g->level - last_level)) + (g->beta_other * g->trend); + } + else { + // the first value we got + g->level = g->trend = value; + } + + g->count++; + } + + //fprintf(stderr, "value: " CALCULATED_NUMBER_FORMAT ", level: " CALCULATED_NUMBER_FORMAT ", trend: " CALCULATED_NUMBER_FORMAT "\n", value, g->level, g->trend); +} + +calculated_number grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; + + if(unlikely(!g->count || !calculated_number_isnumber(g->level))) { + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + return 0.0; + } + + //fprintf(stderr, " RESULT for %zu values = " CALCULATED_NUMBER_FORMAT " \n", g->count, g->level); + + return g->level; +} diff --git a/web/api/queries/des/des.h b/web/api/queries/des/des.h new file mode 100644 index 0000000..360513e --- /dev/null +++ b/web/api/queries/des/des.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERIES_DES_H +#define NETDATA_API_QUERIES_DES_H + +#include "../query.h" +#include "../rrdr.h" + +extern void grouping_init_des(void); + +extern void *grouping_create_des(RRDR *r); +extern void grouping_reset_des(RRDR *r); +extern void grouping_free_des(RRDR *r); +extern void grouping_add_des(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERIES_DES_H diff --git a/web/api/queries/incremental_sum/Makefile.am b/web/api/queries/incremental_sum/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/incremental_sum/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/incremental_sum/README.md b/web/api/queries/incremental_sum/README.md new file mode 100644 index 0000000..e5f3dfc --- /dev/null +++ b/web/api/queries/incremental_sum/README.md @@ -0,0 +1,41 @@ + + +# Incremental Sum (`incremental_sum`) + +This modules finds the incremental sum of a period, which `last value - first value`. + +The result may be positive (rising) or negative (falling) depending on the first and last values. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: incremental_sum -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`incremental_sum` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=incremental_sum` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=incremental_sum&after=-60&label=incremental+sum&value_color=orange) + +## References + +- none + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fincremental_sum%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/incremental_sum/incremental_sum.c b/web/api/queries/incremental_sum/incremental_sum.c new file mode 100644 index 0000000..131d85d --- /dev/null +++ b/web/api/queries/incremental_sum/incremental_sum.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "incremental_sum.h" + +// ---------------------------------------------------------------------------- +// incremental sum + +struct grouping_incremental_sum { + calculated_number first; + calculated_number last; + size_t count; +}; + +void *grouping_create_incremental_sum(RRDR *r) { + (void)r; + return callocz(1, sizeof(struct grouping_incremental_sum)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_incremental_sum(RRDR *r) { + struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; + g->first = 0; + g->last = 0; + g->count = 0; +} + +void grouping_free_incremental_sum(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_incremental_sum(RRDR *r, calculated_number value) { + if(!isnan(value)) { + struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; + + if(unlikely(!g->count)) { + g->first = value; + g->count++; + } + else { + g->last = value; + g->count++; + } + } +} + +calculated_number grouping_flush_incremental_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else if(unlikely(g->count == 1)) { + value = 0.0; + } + else { + value = g->last - g->first; + } + + g->first = 0.0; + g->last = 0.0; + g->count = 0; + + return value; +} diff --git a/web/api/queries/incremental_sum/incremental_sum.h b/web/api/queries/incremental_sum/incremental_sum.h new file mode 100644 index 0000000..990a2ac --- /dev/null +++ b/web/api/queries/incremental_sum/incremental_sum.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERY_INCREMENTAL_SUM_H +#define NETDATA_API_QUERY_INCREMENTAL_SUM_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_incremental_sum(RRDR *r); +extern void grouping_reset_incremental_sum(RRDR *r); +extern void grouping_free_incremental_sum(RRDR *r); +extern void grouping_add_incremental_sum(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_incremental_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERY_INCREMENTAL_SUM_H diff --git a/web/api/queries/max/Makefile.am b/web/api/queries/max/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/max/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/max/README.md b/web/api/queries/max/README.md new file mode 100644 index 0000000..32b1d43 --- /dev/null +++ b/web/api/queries/max/README.md @@ -0,0 +1,38 @@ + + +# Max + +This module finds the max value in the time-frame given. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: max -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`max` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=max` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max&value_color=orange) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fmax%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/max/max.c b/web/api/queries/max/max.c new file mode 100644 index 0000000..a4be36a --- /dev/null +++ b/web/api/queries/max/max.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "max.h" + +// ---------------------------------------------------------------------------- +// max + +struct grouping_max { + calculated_number max; + size_t count; +}; + +void *grouping_create_max(RRDR *r) { + (void)r; + return callocz(1, sizeof(struct grouping_max)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_max(RRDR *r) { + struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; + g->max = 0; + g->count = 0; +} + +void grouping_free_max(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_max(RRDR *r, calculated_number value) { + if(!isnan(value)) { + struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; + + if(!g->count || calculated_number_fabs(value) > calculated_number_fabs(g->max)) { + g->max = value; + g->count++; + } + } +} + +calculated_number grouping_flush_max(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->max; + } + + g->max = 0.0; + g->count = 0; + + return value; +} + diff --git a/web/api/queries/max/max.h b/web/api/queries/max/max.h new file mode 100644 index 0000000..d839fe3 --- /dev/null +++ b/web/api/queries/max/max.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERY_MAX_H +#define NETDATA_API_QUERY_MAX_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_max(RRDR *r); +extern void grouping_reset_max(RRDR *r); +extern void grouping_free_max(RRDR *r); +extern void grouping_add_max(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_max(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERY_MAX_H diff --git a/web/api/queries/median/Makefile.am b/web/api/queries/median/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/median/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/median/README.md b/web/api/queries/median/README.md new file mode 100644 index 0000000..25ce8b8 --- /dev/null +++ b/web/api/queries/median/README.md @@ -0,0 +1,45 @@ + + +# Median + +The median is the value separating the higher half from the lower half of a data sample +(a population or a probability distribution). For a data set, it may be thought of as the +"middle" value. + +`median` is not an accurate average. However, it eliminates all spikes, by sorting +all the values in a period, and selecting the value in the middle of the sorted array. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: median -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`median` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=median` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=median&after=-60&label=median&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fmedian%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/median/median.c b/web/api/queries/median/median.c new file mode 100644 index 0000000..31916c5 --- /dev/null +++ b/web/api/queries/median/median.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "median.h" + + +// ---------------------------------------------------------------------------- +// median + +struct grouping_median { + size_t series_size; + size_t next_pos; + + LONG_DOUBLE series[]; +}; + +void *grouping_create_median(RRDR *r) { + long entries = r->group; + if(entries < 0) entries = 0; + + struct grouping_median *g = (struct grouping_median *)callocz(1, sizeof(struct grouping_median) + entries * sizeof(LONG_DOUBLE)); + g->series_size = (size_t)entries; + + return g; +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_median(RRDR *r) { + struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; + g->next_pos = 0; +} + +void grouping_free_median(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_median(RRDR *r, calculated_number value) { + struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; + + if(unlikely(g->next_pos >= g->series_size)) { + error("INTERNAL ERROR: median buffer overflow on chart '%s' - next_pos = %zu, series_size = %zu, r->group = %ld.", r->st->name, g->next_pos, g->series_size, r->group); + } + else { + if(calculated_number_isnumber(value)) + g->series[g->next_pos++] = (LONG_DOUBLE)value; + } +} + +calculated_number grouping_flush_median(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->next_pos)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + if(g->next_pos > 1) { + sort_series(g->series, g->next_pos); + value = (calculated_number)median_on_sorted_series(g->series, g->next_pos); + } + else + value = (calculated_number)g->series[0]; + + if(!calculated_number_isnumber(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + //log_series_to_stderr(g->series, g->next_pos, value, "median"); + } + + g->next_pos = 0; + + return value; +} + diff --git a/web/api/queries/median/median.h b/web/api/queries/median/median.h new file mode 100644 index 0000000..dd2c1ff --- /dev/null +++ b/web/api/queries/median/median.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERIES_MEDIAN_H +#define NETDATA_API_QUERIES_MEDIAN_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_median(RRDR *r); +extern void grouping_reset_median(RRDR *r); +extern void grouping_free_median(RRDR *r); +extern void grouping_add_median(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_median(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERIES_MEDIAN_H diff --git a/web/api/queries/min/Makefile.am b/web/api/queries/min/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/min/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/min/README.md b/web/api/queries/min/README.md new file mode 100644 index 0000000..69ef4ea --- /dev/null +++ b/web/api/queries/min/README.md @@ -0,0 +1,38 @@ + + +# Min + +This module finds the min value in the time-frame given. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: min -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`min` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=min` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fmin%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/min/min.c b/web/api/queries/min/min.c new file mode 100644 index 0000000..9bd7460 --- /dev/null +++ b/web/api/queries/min/min.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "min.h" + +// ---------------------------------------------------------------------------- +// min + +struct grouping_min { + calculated_number min; + size_t count; +}; + +void *grouping_create_min(RRDR *r) { + (void)r; + return callocz(1, sizeof(struct grouping_min)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_min(RRDR *r) { + struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; + g->min = 0; + g->count = 0; +} + +void grouping_free_min(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_min(RRDR *r, calculated_number value) { + if(!isnan(value)) { + struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; + + if(!g->count || calculated_number_fabs(value) < calculated_number_fabs(g->min)) { + g->min = value; + g->count++; + } + } +} + +calculated_number grouping_flush_min(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->min; + } + + g->min = 0.0; + g->count = 0; + + return value; +} + diff --git a/web/api/queries/min/min.h b/web/api/queries/min/min.h new file mode 100644 index 0000000..7470360 --- /dev/null +++ b/web/api/queries/min/min.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERY_MIN_H +#define NETDATA_API_QUERY_MIN_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_min(RRDR *r); +extern void grouping_reset_min(RRDR *r); +extern void grouping_free_min(RRDR *r); +extern void grouping_add_min(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_min(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERY_MIN_H diff --git a/web/api/queries/query.c b/web/api/queries/query.c new file mode 100644 index 0000000..663e4bd --- /dev/null +++ b/web/api/queries/query.c @@ -0,0 +1,1636 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "query.h" +#include "web/api/formatters/rrd2json.h" +#include "rrdr.h" + +#include "average/average.h" +#include "incremental_sum/incremental_sum.h" +#include "max/max.h" +#include "median/median.h" +#include "min/min.h" +#include "sum/sum.h" +#include "stddev/stddev.h" +#include "ses/ses.h" +#include "des/des.h" + +// ---------------------------------------------------------------------------- + +static struct { + const char *name; + uint32_t hash; + RRDR_GROUPING value; + + // One time initialization for the module. + // This is called once, when netdata starts. + void (*init)(void); + + // Allocate all required structures for a query. + // This is called once for each netdata query. + void *(*create)(struct rrdresult *r); + + // Cleanup collected values, but don't destroy the structures. + // This is called when the query engine switches dimensions, + // as part of the same query (so same chart, switching metric). + void (*reset)(struct rrdresult *r); + + // Free all resources allocated for the query. + void (*free)(struct rrdresult *r); + + // Add a single value into the calculation. + // The module may decide to cache it, or use it in the fly. + void (*add)(struct rrdresult *r, calculated_number value); + + // Generate a single result for the values added so far. + // More values and points may be requested later. + // It is up to the module to reset its internal structures + // when flushing it (so for a few modules it may be better to + // continue after a flush as if nothing changed, for others a + // cleanup of the internal structures may be required). + calculated_number (*flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +} api_v1_data_groups[] = { + {.name = "average", + .hash = 0, + .value = RRDR_GROUPING_AVERAGE, + .init = NULL, + .create= grouping_create_average, + .reset = grouping_reset_average, + .free = grouping_free_average, + .add = grouping_add_average, + .flush = grouping_flush_average + }, + {.name = "mean", // alias on 'average' + .hash = 0, + .value = RRDR_GROUPING_AVERAGE, + .init = NULL, + .create= grouping_create_average, + .reset = grouping_reset_average, + .free = grouping_free_average, + .add = grouping_add_average, + .flush = grouping_flush_average + }, + {.name = "incremental_sum", + .hash = 0, + .value = RRDR_GROUPING_INCREMENTAL_SUM, + .init = NULL, + .create= grouping_create_incremental_sum, + .reset = grouping_reset_incremental_sum, + .free = grouping_free_incremental_sum, + .add = grouping_add_incremental_sum, + .flush = grouping_flush_incremental_sum + }, + {.name = "incremental-sum", + .hash = 0, + .value = RRDR_GROUPING_INCREMENTAL_SUM, + .init = NULL, + .create= grouping_create_incremental_sum, + .reset = grouping_reset_incremental_sum, + .free = grouping_free_incremental_sum, + .add = grouping_add_incremental_sum, + .flush = grouping_flush_incremental_sum + }, + {.name = "median", + .hash = 0, + .value = RRDR_GROUPING_MEDIAN, + .init = NULL, + .create= grouping_create_median, + .reset = grouping_reset_median, + .free = grouping_free_median, + .add = grouping_add_median, + .flush = grouping_flush_median + }, + {.name = "min", + .hash = 0, + .value = RRDR_GROUPING_MIN, + .init = NULL, + .create= grouping_create_min, + .reset = grouping_reset_min, + .free = grouping_free_min, + .add = grouping_add_min, + .flush = grouping_flush_min + }, + {.name = "max", + .hash = 0, + .value = RRDR_GROUPING_MAX, + .init = NULL, + .create= grouping_create_max, + .reset = grouping_reset_max, + .free = grouping_free_max, + .add = grouping_add_max, + .flush = grouping_flush_max + }, + {.name = "sum", + .hash = 0, + .value = RRDR_GROUPING_SUM, + .init = NULL, + .create= grouping_create_sum, + .reset = grouping_reset_sum, + .free = grouping_free_sum, + .add = grouping_add_sum, + .flush = grouping_flush_sum + }, + + // standard deviation + {.name = "stddev", + .hash = 0, + .value = RRDR_GROUPING_STDDEV, + .init = NULL, + .create= grouping_create_stddev, + .reset = grouping_reset_stddev, + .free = grouping_free_stddev, + .add = grouping_add_stddev, + .flush = grouping_flush_stddev + }, + {.name = "cv", // coefficient variation is calculated by stddev + .hash = 0, + .value = RRDR_GROUPING_CV, + .init = NULL, + .create= grouping_create_stddev, // not an error, stddev calculates this too + .reset = grouping_reset_stddev, // not an error, stddev calculates this too + .free = grouping_free_stddev, // not an error, stddev calculates this too + .add = grouping_add_stddev, // not an error, stddev calculates this too + .flush = grouping_flush_coefficient_of_variation + }, + {.name = "rsd", // alias of 'cv' + .hash = 0, + .value = RRDR_GROUPING_CV, + .init = NULL, + .create= grouping_create_stddev, // not an error, stddev calculates this too + .reset = grouping_reset_stddev, // not an error, stddev calculates this too + .free = grouping_free_stddev, // not an error, stddev calculates this too + .add = grouping_add_stddev, // not an error, stddev calculates this too + .flush = grouping_flush_coefficient_of_variation + }, + + /* + {.name = "mean", // same as average, no need to define it again + .hash = 0, + .value = RRDR_GROUPING_MEAN, + .setup = NULL, + .create= grouping_create_stddev, + .reset = grouping_reset_stddev, + .free = grouping_free_stddev, + .add = grouping_add_stddev, + .flush = grouping_flush_mean + }, + */ + + /* + {.name = "variance", // meaningless to offer + .hash = 0, + .value = RRDR_GROUPING_VARIANCE, + .setup = NULL, + .create= grouping_create_stddev, + .reset = grouping_reset_stddev, + .free = grouping_free_stddev, + .add = grouping_add_stddev, + .flush = grouping_flush_variance + }, + */ + + // single exponential smoothing + {.name = "ses", + .hash = 0, + .value = RRDR_GROUPING_SES, + .init = grouping_init_ses, + .create= grouping_create_ses, + .reset = grouping_reset_ses, + .free = grouping_free_ses, + .add = grouping_add_ses, + .flush = grouping_flush_ses + }, + {.name = "ema", // alias for 'ses' + .hash = 0, + .value = RRDR_GROUPING_SES, + .init = NULL, + .create= grouping_create_ses, + .reset = grouping_reset_ses, + .free = grouping_free_ses, + .add = grouping_add_ses, + .flush = grouping_flush_ses + }, + {.name = "ewma", // alias for ses + .hash = 0, + .value = RRDR_GROUPING_SES, + .init = NULL, + .create= grouping_create_ses, + .reset = grouping_reset_ses, + .free = grouping_free_ses, + .add = grouping_add_ses, + .flush = grouping_flush_ses + }, + + // double exponential smoothing + {.name = "des", + .hash = 0, + .value = RRDR_GROUPING_DES, + .init = grouping_init_des, + .create= grouping_create_des, + .reset = grouping_reset_des, + .free = grouping_free_des, + .add = grouping_add_des, + .flush = grouping_flush_des + }, + + // terminator + {.name = NULL, + .hash = 0, + .value = RRDR_GROUPING_UNDEFINED, + .init = NULL, + .create= grouping_create_average, + .reset = grouping_reset_average, + .free = grouping_free_average, + .add = grouping_add_average, + .flush = grouping_flush_average + } +}; + +void web_client_api_v1_init_grouping(void) { + int i; + + for(i = 0; api_v1_data_groups[i].name ; i++) { + api_v1_data_groups[i].hash = simple_hash(api_v1_data_groups[i].name); + + if(api_v1_data_groups[i].init) + api_v1_data_groups[i].init(); + } +} + +const char *group_method2string(RRDR_GROUPING group) { + int i; + + for(i = 0; api_v1_data_groups[i].name ; i++) { + if(api_v1_data_groups[i].value == group) { + return api_v1_data_groups[i].name; + } + } + + return "unknown-group-method"; +} + +RRDR_GROUPING web_client_api_request_v1_data_group(const char *name, RRDR_GROUPING def) { + int i; + + uint32_t hash = simple_hash(name); + for(i = 0; api_v1_data_groups[i].name ; i++) + if(unlikely(hash == api_v1_data_groups[i].hash && !strcmp(name, api_v1_data_groups[i].name))) + return api_v1_data_groups[i].value; + + return def; +} + +// ---------------------------------------------------------------------------- + +static void rrdr_disable_not_selected_dimensions(RRDR *r, RRDR_OPTIONS options, const char *dims, RRDDIM *temp_rd) { + rrdset_check_rdlock(r->st); + + if(unlikely(!dims || !*dims || (dims[0] == '*' && dims[1] == '\0'))) return; + + int match_ids = 0, match_names = 0; + + if(unlikely(options & RRDR_OPTION_MATCH_IDS)) + match_ids = 1; + if(unlikely(options & RRDR_OPTION_MATCH_NAMES)) + match_names = 1; + + if(likely(!match_ids && !match_names)) + match_ids = match_names = 1; + + SIMPLE_PATTERN *pattern = simple_pattern_create(dims, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + + RRDDIM *d; + long c, dims_selected = 0, dims_not_hidden_not_zero = 0; + for(c = 0, d = temp_rd?temp_rd:r->st->dimensions; d ;c++, d = d->next) { + if( (match_ids && simple_pattern_matches(pattern, d->id)) + || (match_names && simple_pattern_matches(pattern, d->name)) + ) { + r->od[c] |= RRDR_DIMENSION_SELECTED; + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) r->od[c] &= ~RRDR_DIMENSION_HIDDEN; + dims_selected++; + + // since the user needs this dimension + // make it appear as NONZERO, to return it + // even if the dimension has only zeros + // unless option non_zero is set + if(unlikely(!(options & RRDR_OPTION_NONZERO))) + r->od[c] |= RRDR_DIMENSION_NONZERO; + + // count the visible dimensions + if(likely(r->od[c] & RRDR_DIMENSION_NONZERO)) + dims_not_hidden_not_zero++; + } + else { + r->od[c] |= RRDR_DIMENSION_HIDDEN; + if(unlikely(r->od[c] & RRDR_DIMENSION_SELECTED)) r->od[c] &= ~RRDR_DIMENSION_SELECTED; + } + } + simple_pattern_free(pattern); + + // check if all dimensions are hidden + if(unlikely(!dims_not_hidden_not_zero && dims_selected)) { + // there are a few selected dimensions + // but they are all zero + // enable the selected ones + // to avoid returning an empty chart + for(c = 0, d = temp_rd?temp_rd:r->st->dimensions; d ;c++, d = d->next) + if(unlikely(r->od[c] & RRDR_DIMENSION_SELECTED)) + r->od[c] |= RRDR_DIMENSION_NONZERO; + } +} + +// ---------------------------------------------------------------------------- +// helpers to find our way in RRDR + +static inline RRDR_VALUE_FLAGS *rrdr_line_options(RRDR *r, long rrdr_line) { + return &r->o[ rrdr_line * r->d ]; +} + +static inline calculated_number *rrdr_line_values(RRDR *r, long rrdr_line) { + return &r->v[ rrdr_line * r->d ]; +} + +static inline long rrdr_line_init(RRDR *r, time_t t, long rrdr_line) { + rrdr_line++; + + #ifdef NETDATA_INTERNAL_CHECKS + + if(unlikely(rrdr_line >= r->n)) + error("INTERNAL ERROR: requested to step above RRDR size for chart '%s'", r->st->name); + + if(unlikely(r->t[rrdr_line] != 0 && r->t[rrdr_line] != t)) + error("INTERNAL ERROR: overwriting the timestamp of RRDR line %zu from %zu to %zu, of chart '%s'", (size_t)rrdr_line, (size_t)r->t[rrdr_line], (size_t)t, r->st->name); + + #endif + + // save the time + r->t[rrdr_line] = t; + + return rrdr_line; +} + +static inline void rrdr_done(RRDR *r, long rrdr_line) { + r->rows = rrdr_line + 1; +} + + +// ---------------------------------------------------------------------------- +// fill RRDR for a single dimension + +static inline void do_dimension_variablestep( + RRDR *r + , long points_wanted + , RRDDIM *rd + , long dim_id_in_rrdr + , time_t after_wanted + , time_t before_wanted +){ +// RRDSET *st = r->st; + + time_t + now = after_wanted, + dt = r->update_every, + max_date = 0, + min_date = 0; + + long +// group_size = r->group, + points_added = 0, + values_in_group = 0, + values_in_group_non_zero = 0, + rrdr_line = -1; + + RRDR_VALUE_FLAGS + group_value_flags = RRDR_VALUE_NOTHING; + + struct rrddim_query_handle handle; + + calculated_number min = r->min, max = r->max; + size_t db_points_read = 0; + time_t db_now = now; + storage_number n_curr, n_prev = SN_EMPTY_SLOT; + calculated_number value; + + for(rd->state->query_ops.init(rd, &handle, now, before_wanted) ; points_added < points_wanted ; now += dt) { + // make sure we return data in the proper time range + if (unlikely(now > before_wanted)) { +#ifdef NETDATA_INTERNAL_CHECKS + r->internal.log = "stopped, because attempted to access the db after 'wanted before'"; +#endif + break; + } + if (unlikely(now < after_wanted)) { +#ifdef NETDATA_INTERNAL_CHECKS + r->internal.log = "skipped, because attempted to access the db before 'wanted after'"; +#endif + continue; + } + + while (now >= db_now && (!rd->state->query_ops.is_finished(&handle) || + does_storage_number_exist(n_prev))) { + value = NAN; + if (does_storage_number_exist(n_prev)) { + // use the previously read database value + n_curr = n_prev; + } else { + // read the value from the database + n_curr = rd->state->query_ops.next_metric(&handle, &db_now); + } + n_prev = SN_EMPTY_SLOT; + // db_now has a different value than above + if (likely(now >= db_now)) { + if (likely(does_storage_number_exist(n_curr))) { + value = unpack_storage_number(n_curr); + if (likely(value != 0.0)) + values_in_group_non_zero++; + + if (unlikely(did_storage_number_reset(n_curr))) + group_value_flags |= RRDR_VALUE_RESET; + } + } else { + // We must postpone processing the value and fill the result with gaps instead + if (likely(does_storage_number_exist(n_curr))) { + n_prev = n_curr; + } + } + // add this value to grouping + r->internal.grouping_add(r, value); + values_in_group++; + db_points_read++; + } + + if (0 == values_in_group) { + // add NAN to grouping + r->internal.grouping_add(r, NAN); + } + + rrdr_line = rrdr_line_init(r, now, rrdr_line); + + if(unlikely(!min_date)) min_date = now; + max_date = now; + + // find the place to store our values + RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_line * r->d + dim_id_in_rrdr]; + + // update the dimension options + if(likely(values_in_group_non_zero)) + r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO; + + // store the specific point options + *rrdr_value_options_ptr = group_value_flags; + + // store the value + value = r->internal.grouping_flush(r, rrdr_value_options_ptr); + r->v[rrdr_line * r->d + dim_id_in_rrdr] = value; + + if(likely(points_added || dim_id_in_rrdr)) { + // find the min/max across all dimensions + + if(unlikely(value < min)) min = value; + if(unlikely(value > max)) max = value; + + } + else { + // runs only when dim_id_in_rrdr == 0 && points_added == 0 + // so, on the first point added for the query. + min = max = value; + } + + points_added++; + values_in_group = 0; + group_value_flags = RRDR_VALUE_NOTHING; + values_in_group_non_zero = 0; + } + rd->state->query_ops.finalize(&handle); + + r->internal.db_points_read += db_points_read; + r->internal.result_points_generated += points_added; + + r->min = min; + r->max = max; + r->before = max_date; + r->after = min_date - (r->group - 1) * dt; + rrdr_done(r, rrdr_line); + + #ifdef NETDATA_INTERNAL_CHECKS + if(unlikely(r->rows != points_added)) + error("INTERNAL ERROR: %s.%s added %zu rows, but RRDR says I added %zu.", r->st->name, rd->name, (size_t)points_added, (size_t)r->rows); + #endif +} + +static inline void do_dimension_fixedstep( + RRDR *r + , long points_wanted + , RRDDIM *rd + , long dim_id_in_rrdr + , time_t after_wanted + , time_t before_wanted +){ + RRDSET *st = r->st; + + time_t + now = after_wanted, + dt = r->update_every / r->group, /* usually is st->update_every */ + max_date = 0, + min_date = 0; + + long + group_size = r->group, + points_added = 0, + values_in_group = 0, + values_in_group_non_zero = 0, + rrdr_line = -1; + + RRDR_VALUE_FLAGS + group_value_flags = RRDR_VALUE_NOTHING; + + struct rrddim_query_handle handle; + + calculated_number min = r->min, max = r->max; + size_t db_points_read = 0; + time_t db_now = now; + + for(rd->state->query_ops.init(rd, &handle, now, before_wanted) ; points_added < points_wanted ; now += dt) { + // make sure we return data in the proper time range + if(unlikely(now > before_wanted)) { +#ifdef NETDATA_INTERNAL_CHECKS + r->internal.log = "stopped, because attempted to access the db after 'wanted before'"; +#endif + break; + } + if(unlikely(now < after_wanted)) { +#ifdef NETDATA_INTERNAL_CHECKS + r->internal.log = "skipped, because attempted to access the db before 'wanted after'"; +#endif + continue; + } + // read the value from the database + //storage_number n = rd->values[slot]; +#ifdef NETDATA_INTERNAL_CHECKS + if ((rd->rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) && + (rrdset_time2slot(st, now) != (long unsigned)handle.slotted.slot)) { + error("INTERNAL CHECK: Unaligned query for %s, database slot: %lu, expected slot: %lu", rd->id, (long unsigned)handle.slotted.slot, rrdset_time2slot(st, now)); + } +#endif + db_now = now; // this is needed to set db_now in case the next_metric implementation does not set it + storage_number n = rd->state->query_ops.next_metric(&handle, &db_now); + if(unlikely(db_now > before_wanted)) { +#ifdef NETDATA_INTERNAL_CHECKS + r->internal.log = "stopped, because attempted to access the db after 'wanted before'"; +#endif + break; + } + for ( ; now <= db_now ; now += dt) { + calculated_number value = NAN; + if(likely(now >= db_now && does_storage_number_exist(n))) { +#if defined(NETDATA_INTERNAL_CHECKS) && defined(ENABLE_DBENGINE) + if ((rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) && (now != handle.rrdeng.now)) { + error("INTERNAL CHECK: Unaligned query for %s, database time: %ld, expected time: %ld", rd->id, (long)handle.rrdeng.now, (long)now); + } +#endif + value = unpack_storage_number(n); + if(likely(value != 0.0)) + values_in_group_non_zero++; + + if(unlikely(did_storage_number_reset(n))) + group_value_flags |= RRDR_VALUE_RESET; + + } + + // add this value for grouping + r->internal.grouping_add(r, value); + values_in_group++; + db_points_read++; + + if(unlikely(values_in_group == group_size)) { + rrdr_line = rrdr_line_init(r, now, rrdr_line); + + if(unlikely(!min_date)) min_date = now; + max_date = now; + + // find the place to store our values + RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_line * r->d + dim_id_in_rrdr]; + + // update the dimension options + if(likely(values_in_group_non_zero)) + r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO; + + // store the specific point options + *rrdr_value_options_ptr = group_value_flags; + + // store the value + calculated_number value = r->internal.grouping_flush(r, rrdr_value_options_ptr); + r->v[rrdr_line * r->d + dim_id_in_rrdr] = value; + + if(likely(points_added || dim_id_in_rrdr)) { + // find the min/max across all dimensions + + if(unlikely(value < min)) min = value; + if(unlikely(value > max)) max = value; + + } + else { + // runs only when dim_id_in_rrdr == 0 && points_added == 0 + // so, on the first point added for the query. + min = max = value; + } + + points_added++; + values_in_group = 0; + group_value_flags = RRDR_VALUE_NOTHING; + values_in_group_non_zero = 0; + } + } + now = db_now; + } + rd->state->query_ops.finalize(&handle); + + r->internal.db_points_read += db_points_read; + r->internal.result_points_generated += points_added; + + r->min = min; + r->max = max; + r->before = max_date; + r->after = min_date - (r->group - 1) * dt; + rrdr_done(r, rrdr_line); + +#ifdef NETDATA_INTERNAL_CHECKS + if(unlikely(r->rows != points_added)) + error("INTERNAL ERROR: %s.%s added %zu rows, but RRDR says I added %zu.", r->st->name, rd->name, (size_t)points_added, (size_t)r->rows); +#endif +} + +// ---------------------------------------------------------------------------- +// fill RRDR for the whole chart + +#ifdef NETDATA_INTERNAL_CHECKS +static void rrd2rrdr_log_request_response_metdata(RRDR *r + , RRDR_GROUPING group_method + , int aligned + , long group + , long resampling_time + , long resampling_group + , time_t after_wanted + , time_t after_requested + , time_t before_wanted + , time_t before_requested + , long points_requested + , long points_wanted + //, size_t after_slot + //, size_t before_slot + , const char *msg + ) { + netdata_rwlock_rdlock(&r->st->rrdset_rwlock); + info("INTERNAL ERROR: rrd2rrdr() on %s update every %d with %s grouping %s (group: %ld, resampling_time: %ld, resampling_group: %ld), " + "after (got: %zu, want: %zu, req: %zu, db: %zu), " + "before (got: %zu, want: %zu, req: %zu, db: %zu), " + "duration (got: %zu, want: %zu, req: %zu, db: %zu), " + //"slot (after: %zu, before: %zu, delta: %zu), " + "points (got: %ld, want: %ld, req: %ld, db: %ld), " + "%s" + , r->st->name + , r->st->update_every + + // grouping + , (aligned) ? "aligned" : "unaligned" + , group_method2string(group_method) + , group + , resampling_time + , resampling_group + + // after + , (size_t)r->after + , (size_t)after_wanted + , (size_t)after_requested + , (size_t)rrdset_first_entry_t_nolock(r->st) + + // before + , (size_t)r->before + , (size_t)before_wanted + , (size_t)before_requested + , (size_t)rrdset_last_entry_t_nolock(r->st) + + // duration + , (size_t)(r->before - r->after + r->st->update_every) + , (size_t)(before_wanted - after_wanted + r->st->update_every) + , (size_t)(before_requested - after_requested) + , (size_t)((rrdset_last_entry_t_nolock(r->st) - rrdset_first_entry_t_nolock(r->st)) + r->st->update_every) + + // slot + /* + , after_slot + , before_slot + , (after_slot > before_slot) ? (r->st->entries - after_slot + before_slot) : (before_slot - after_slot) + */ + + // points + , r->rows + , points_wanted + , points_requested + , r->st->entries + + // message + , msg + ); + netdata_rwlock_unlock(&r->st->rrdset_rwlock); +} +#endif // NETDATA_INTERNAL_CHECKS + +// Returns 1 if an absolute period was requested or 0 if it was a relative period +static int rrdr_convert_before_after_to_absolute( + long long *after_requestedp + , long long *before_requestedp + , int update_every + , time_t first_entry_t + , time_t last_entry_t + , RRDR_OPTIONS options +) { + int absolute_period_requested = -1; + long long after_requested, before_requested; + + before_requested = *before_requestedp; + after_requested = *after_requestedp; + + if(before_requested == 0 && after_requested == 0) { + // dump the all the data + before_requested = last_entry_t; + after_requested = first_entry_t; + absolute_period_requested = 0; + } + + // allow relative for before (smaller than API_RELATIVE_TIME_MAX) + if(abs(before_requested) <= API_RELATIVE_TIME_MAX) { + if(abs(before_requested) % update_every) { + // make sure it is multiple of st->update_every + if(before_requested < 0) before_requested = before_requested - update_every - + before_requested % update_every; + else before_requested = before_requested + update_every - before_requested % update_every; + } + if(before_requested > 0) before_requested = first_entry_t + before_requested; + else before_requested = last_entry_t + before_requested; //last_entry_t is not really now_t + //TODO: fix before_requested to be relative to now_t + absolute_period_requested = 0; + } + + // allow relative for after (smaller than API_RELATIVE_TIME_MAX) + if(abs(after_requested) <= API_RELATIVE_TIME_MAX) { + if(after_requested == 0) after_requested = -update_every; + if(abs(after_requested) % update_every) { + // make sure it is multiple of st->update_every + if(after_requested < 0) after_requested = after_requested - update_every - after_requested % update_every; + else after_requested = after_requested + update_every - after_requested % update_every; + } + after_requested = before_requested + after_requested; + absolute_period_requested = 0; + } + + if(absolute_period_requested == -1) + absolute_period_requested = 1; + + // make sure they are within our timeframe + if(before_requested > last_entry_t) before_requested = last_entry_t; + if(before_requested < first_entry_t && !(options & RRDR_OPTION_ALLOW_PAST)) + before_requested = first_entry_t; + + if(after_requested > last_entry_t) after_requested = last_entry_t; + if(after_requested < first_entry_t && !(options & RRDR_OPTION_ALLOW_PAST)) + after_requested = first_entry_t; + + // check if they are reversed + if(after_requested > before_requested) { + time_t tmp = before_requested; + before_requested = after_requested; + after_requested = tmp; + } + + *before_requestedp = before_requested; + *after_requestedp = after_requested; + + return absolute_period_requested; +} + +static RRDR *rrd2rrdr_fixedstep( + RRDSET *st + , long points_requested + , long long after_requested + , long long before_requested + , RRDR_GROUPING group_method + , long resampling_time_requested + , RRDR_OPTIONS options + , const char *dimensions + , int update_every + , time_t first_entry_t + , time_t last_entry_t + , int absolute_period_requested + , struct context_param *context_param_list +) { + int aligned = !(options & RRDR_OPTION_NOT_ALIGNED); + + // the duration of the chart + time_t duration = before_requested - after_requested; + long available_points = duration / update_every; + + RRDDIM *temp_rd = context_param_list ? context_param_list->rd : NULL; + + if(duration <= 0 || available_points <= 0) + return rrdr_create(st, 1, context_param_list); + + // check the number of wanted points in the result + if(unlikely(points_requested < 0)) points_requested = -points_requested; + if(unlikely(points_requested > available_points)) points_requested = available_points; + if(unlikely(points_requested == 0)) points_requested = available_points; + + // calculate the desired grouping of source data points + long group = available_points / points_requested; + if(unlikely(group <= 0)) group = 1; + if(unlikely(available_points % points_requested > points_requested / 2)) group++; // rounding to the closest integer + + // resampling_time_requested enforces a certain grouping multiple + calculated_number resampling_divisor = 1.0; + long resampling_group = 1; + if(unlikely(resampling_time_requested > update_every)) { + if (unlikely(resampling_time_requested > duration)) { + // group_time is above the available duration + + #ifdef NETDATA_INTERNAL_CHECKS + info("INTERNAL CHECK: %s: requested gtime %ld secs, is greater than the desired duration %ld secs", st->id, resampling_time_requested, duration); + #endif + + after_requested = before_requested - resampling_time_requested; + duration = before_requested - after_requested; + available_points = duration / update_every; + group = available_points / points_requested; + } + + // if the duration is not aligned to resampling time + // extend the duration to the past, to avoid a gap at the chart + // only when the missing duration is above 1/10th of a point + if(duration % resampling_time_requested) { + time_t delta = duration % resampling_time_requested; + if(delta > resampling_time_requested / 10) { + after_requested -= resampling_time_requested - delta; + duration = before_requested - after_requested; + available_points = duration / update_every; + group = available_points / points_requested; + } + } + + // the points we should group to satisfy gtime + resampling_group = resampling_time_requested / update_every; + if(unlikely(resampling_time_requested % update_every)) { + #ifdef NETDATA_INTERNAL_CHECKS + info("INTERNAL CHECK: %s: requested gtime %ld secs, is not a multiple of the chart's data collection frequency %d secs", st->id, resampling_time_requested, update_every); + #endif + + resampling_group++; + } + + // adapt group according to resampling_group + if(unlikely(group < resampling_group)) group = resampling_group; // do not allow grouping below the desired one + if(unlikely(group % resampling_group)) group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group + + //resampling_divisor = group / resampling_group; + resampling_divisor = (calculated_number)(group * update_every) / (calculated_number)resampling_time_requested; + } + + // now that we have group, + // align the requested timeframe to fit it. + + if(aligned) { + // alignement has been requested, so align the values + before_requested -= before_requested % (group * update_every); + after_requested -= after_requested % (group * update_every); + } + + // we align the request on requested_before + time_t before_wanted = before_requested; + if(likely(before_wanted > last_entry_t)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, before_wanted is after db max", st->name); + #endif + + before_wanted = last_entry_t - (last_entry_t % ( ((aligned)?group:1) * update_every )); + } + //size_t before_slot = rrdset_time2slot(st, before_wanted); + + // we need to estimate the number of points, for having + // an integer number of values per point + long points_wanted = (before_wanted - after_requested) / (update_every * group); + + time_t after_wanted = before_wanted - (points_wanted * group * update_every) + update_every; + if(unlikely(after_wanted < first_entry_t)) { + // hm... we go to the past, calculate again points_wanted using all the db from before_wanted to the beginning + points_wanted = (before_wanted - first_entry_t) / group; + + // recalculate after wanted with the new number of points + after_wanted = before_wanted - (points_wanted * group * update_every) + update_every; + + if(unlikely(after_wanted < first_entry_t)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, after_wanted is before db min", st->name); + #endif + + after_wanted = first_entry_t - (first_entry_t % ( ((aligned)?group:1) * update_every )) + ( ((aligned)?group:1) * update_every ); + } + } + //size_t after_slot = rrdset_time2slot(st, after_wanted); + + // check if they are reversed + if(unlikely(after_wanted > before_wanted)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, reversed wanted after/before", st->name); + #endif + time_t tmp = before_wanted; + before_wanted = after_wanted; + after_wanted = tmp; + } + + // recalculate points_wanted using the final time-frame + points_wanted = (before_wanted - after_wanted) / update_every / group + 1; + if(unlikely(points_wanted < 0)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, points_wanted is %ld", st->name, points_wanted); + #endif + points_wanted = 0; + } + +#ifdef NETDATA_INTERNAL_CHECKS + duration = before_wanted - after_wanted; + + if(after_wanted < first_entry_t) + error("INTERNAL CHECK: after_wanted %u is too small, minimum %u", (uint32_t)after_wanted, (uint32_t)first_entry_t); + + if(after_wanted > last_entry_t) + error("INTERNAL CHECK: after_wanted %u is too big, maximum %u", (uint32_t)after_wanted, (uint32_t)last_entry_t); + + if(before_wanted < first_entry_t) + error("INTERNAL CHECK: before_wanted %u is too small, minimum %u", (uint32_t)before_wanted, (uint32_t)first_entry_t); + + if(before_wanted > last_entry_t) + error("INTERNAL CHECK: before_wanted %u is too big, maximum %u", (uint32_t)before_wanted, (uint32_t)last_entry_t); + +/* + if(before_slot >= (size_t)st->entries) + error("INTERNAL CHECK: before_slot is invalid %zu, expected 0 to %ld", before_slot, st->entries - 1); + + if(after_slot >= (size_t)st->entries) + error("INTERNAL CHECK: after_slot is invalid %zu, expected 0 to %ld", after_slot, st->entries - 1); +*/ + + if(points_wanted > (before_wanted - after_wanted) / group / update_every + 1) + error("INTERNAL CHECK: points_wanted %ld is more than points %ld", points_wanted, (before_wanted - after_wanted) / group / update_every + 1); + + if(group < resampling_group) + error("INTERNAL CHECK: group %ld is less than the desired group points %ld", group, resampling_group); + + if(group > resampling_group && group % resampling_group) + error("INTERNAL CHECK: group %ld is not a multiple of the desired group points %ld", group, resampling_group); +#endif + + // ------------------------------------------------------------------------- + // initialize our result set + // this also locks the chart for us + + RRDR *r = rrdr_create(st, points_wanted, context_param_list); + if(unlikely(!r)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL CHECK: Cannot create RRDR for %s, after=%u, before=%u, duration=%u, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (uint32_t)duration, points_wanted); + #endif + return NULL; + } + + if(unlikely(!r->d || !points_wanted)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL CHECK: Returning empty RRDR (no dimensions in RRDSET) for %s, after=%u, before=%u, duration=%zu, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (size_t)duration, points_wanted); + #endif + return r; + } + + if(unlikely(absolute_period_requested == 1)) + r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE; + else + r->result_options |= RRDR_RESULT_OPTION_RELATIVE; + + // find how many dimensions we have + long dimensions_count = r->d; + + // ------------------------------------------------------------------------- + // initialize RRDR + + r->group = group; + r->update_every = (int)group * update_every; + r->before = before_wanted; + r->after = after_wanted; + r->internal.points_wanted = points_wanted; + r->internal.resampling_group = resampling_group; + r->internal.resampling_divisor = resampling_divisor; + + + // ------------------------------------------------------------------------- + // assign the processor functions + + { + int i, found = 0; + for(i = 0; !found && api_v1_data_groups[i].name ;i++) { + if(api_v1_data_groups[i].value == group_method) { + r->internal.grouping_create= api_v1_data_groups[i].create; + r->internal.grouping_reset = api_v1_data_groups[i].reset; + r->internal.grouping_free = api_v1_data_groups[i].free; + r->internal.grouping_add = api_v1_data_groups[i].add; + r->internal.grouping_flush = api_v1_data_groups[i].flush; + found = 1; + } + } + if(!found) { + errno = 0; + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: grouping method %u not found for chart '%s'. Using 'average'", (unsigned int)group_method, r->st->name); + #endif + r->internal.grouping_create= grouping_create_average; + r->internal.grouping_reset = grouping_reset_average; + r->internal.grouping_free = grouping_free_average; + r->internal.grouping_add = grouping_add_average; + r->internal.grouping_flush = grouping_flush_average; + } + } + + // allocate any memory required by the grouping method + r->internal.grouping_data = r->internal.grouping_create(r); + + + // ------------------------------------------------------------------------- + // disable the not-wanted dimensions + + rrdset_check_rdlock(st); + + if(dimensions) + rrdr_disable_not_selected_dimensions(r, options, dimensions, temp_rd); + + + // ------------------------------------------------------------------------- + // do the work for each dimension + + time_t max_after = 0, min_before = 0; + long max_rows = 0; + + RRDDIM *rd; + long c, dimensions_used = 0, dimensions_nonzero = 0; + for(rd = temp_rd?temp_rd:st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) { + + // if we need a percentage, we need to calculate all dimensions + if(unlikely(!(options & RRDR_OPTION_PERCENTAGE) && (r->od[c] & RRDR_DIMENSION_HIDDEN))) { + if(unlikely(r->od[c] & RRDR_DIMENSION_SELECTED)) r->od[c] &= ~RRDR_DIMENSION_SELECTED; + continue; + } + r->od[c] |= RRDR_DIMENSION_SELECTED; + + // reset the grouping for the new dimension + r->internal.grouping_reset(r); + + do_dimension_fixedstep( + r + , points_wanted + , rd + , c + , after_wanted + , before_wanted + ); + + if(r->od[c] & RRDR_DIMENSION_NONZERO) + dimensions_nonzero++; + + // verify all dimensions are aligned + if(unlikely(!dimensions_used)) { + min_before = r->before; + max_after = r->after; + max_rows = r->rows; + } + else { + if(r->after != max_after) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'after' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)max_after, rd->name, (size_t)r->after); + #endif + r->after = (r->after > max_after) ? r->after : max_after; + } + + if(r->before != min_before) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'before' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)min_before, rd->name, (size_t)r->before); + #endif + r->before = (r->before < min_before) ? r->before : min_before; + } + + if(r->rows != max_rows) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'rows' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)max_rows, rd->name, (size_t)r->rows); + #endif + r->rows = (r->rows > max_rows) ? r->rows : max_rows; + } + } + + dimensions_used++; + } + + #ifdef NETDATA_INTERNAL_CHECKS + if (dimensions_used) { + if(r->internal.log) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ r->internal.log); + + if(r->rows != points_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'"); + + if(aligned && (r->before % group) != 0) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required"); + + // 'after' should not be aligned, since we start inside the first group + //if(aligned && (r->after % group) != 0) + // rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required"); + + if(r->before != before_requested) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'"); + + if(r->before != before_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'"); + + // reported 'after' varies, depending on group + if(r->after != after_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'"); + } + #endif + + // free all resources used by the grouping method + r->internal.grouping_free(r); + + // when all the dimensions are zero, we should return all of them + if(unlikely(options & RRDR_OPTION_NONZERO && !dimensions_nonzero)) { + // all the dimensions are zero + // mark them as NONZERO to send them all + for(rd = temp_rd?temp_rd:st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + r->od[c] |= RRDR_DIMENSION_NONZERO; + } + } + + rrdr_query_completed(r->internal.db_points_read, r->internal.result_points_generated); + return r; +} + +#ifdef ENABLE_DBENGINE +static RRDR *rrd2rrdr_variablestep( + RRDSET *st + , long points_requested + , long long after_requested + , long long before_requested + , RRDR_GROUPING group_method + , long resampling_time_requested + , RRDR_OPTIONS options + , const char *dimensions + , int update_every + , time_t first_entry_t + , time_t last_entry_t + , int absolute_period_requested + , struct rrdeng_region_info *region_info_array + , struct context_param *context_param_list +) { + int aligned = !(options & RRDR_OPTION_NOT_ALIGNED); + + // the duration of the chart + time_t duration = before_requested - after_requested; + long available_points = duration / update_every; + + RRDDIM *temp_rd = context_param_list ? context_param_list->rd : NULL; + + if(duration <= 0 || available_points <= 0) { + freez(region_info_array); + return rrdr_create(st, 1, context_param_list); + } + + // check the number of wanted points in the result + if(unlikely(points_requested < 0)) points_requested = -points_requested; + if(unlikely(points_requested > available_points)) points_requested = available_points; + if(unlikely(points_requested == 0)) points_requested = available_points; + + // calculate the desired grouping of source data points + long group = available_points / points_requested; + if(unlikely(group <= 0)) group = 1; + if(unlikely(available_points % points_requested > points_requested / 2)) group++; // rounding to the closest integer + + // resampling_time_requested enforces a certain grouping multiple + calculated_number resampling_divisor = 1.0; + long resampling_group = 1; + if(unlikely(resampling_time_requested > update_every)) { + if (unlikely(resampling_time_requested > duration)) { + // group_time is above the available duration + + #ifdef NETDATA_INTERNAL_CHECKS + info("INTERNAL CHECK: %s: requested gtime %ld secs, is greater than the desired duration %ld secs", st->id, resampling_time_requested, duration); + #endif + + after_requested = before_requested - resampling_time_requested; + duration = before_requested - after_requested; + available_points = duration / update_every; + group = available_points / points_requested; + } + + // if the duration is not aligned to resampling time + // extend the duration to the past, to avoid a gap at the chart + // only when the missing duration is above 1/10th of a point + if(duration % resampling_time_requested) { + time_t delta = duration % resampling_time_requested; + if(delta > resampling_time_requested / 10) { + after_requested -= resampling_time_requested - delta; + duration = before_requested - after_requested; + available_points = duration / update_every; + group = available_points / points_requested; + } + } + + // the points we should group to satisfy gtime + resampling_group = resampling_time_requested / update_every; + if(unlikely(resampling_time_requested % update_every)) { + #ifdef NETDATA_INTERNAL_CHECKS + info("INTERNAL CHECK: %s: requested gtime %ld secs, is not a multiple of the chart's data collection frequency %d secs", st->id, resampling_time_requested, update_every); + #endif + + resampling_group++; + } + + // adapt group according to resampling_group + if(unlikely(group < resampling_group)) group = resampling_group; // do not allow grouping below the desired one + if(unlikely(group % resampling_group)) group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group + + //resampling_divisor = group / resampling_group; + resampling_divisor = (calculated_number)(group * update_every) / (calculated_number)resampling_time_requested; + } + + // now that we have group, + // align the requested timeframe to fit it. + + if(aligned) { + // alignement has been requested, so align the values + before_requested -= before_requested % (group * update_every); + after_requested -= after_requested % (group * update_every); + } + + // we align the request on requested_before + time_t before_wanted = before_requested; + if(likely(before_wanted > last_entry_t)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, before_wanted is after db max", st->name); + #endif + + before_wanted = last_entry_t - (last_entry_t % ( ((aligned)?group:1) * update_every )); + } + //size_t before_slot = rrdset_time2slot(st, before_wanted); + + // we need to estimate the number of points, for having + // an integer number of values per point + long points_wanted = (before_wanted - after_requested) / (update_every * group); + + time_t after_wanted = before_wanted - (points_wanted * group * update_every) + update_every; + if(unlikely(after_wanted < first_entry_t)) { + // hm... we go to the past, calculate again points_wanted using all the db from before_wanted to the beginning + points_wanted = (before_wanted - first_entry_t) / group; + + // recalculate after wanted with the new number of points + after_wanted = before_wanted - (points_wanted * group * update_every) + update_every; + + if(unlikely(after_wanted < first_entry_t)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, after_wanted is before db min", st->name); + #endif + + after_wanted = first_entry_t - (first_entry_t % ( ((aligned)?group:1) * update_every )) + ( ((aligned)?group:1) * update_every ); + } + } + //size_t after_slot = rrdset_time2slot(st, after_wanted); + + // check if they are reversed + if(unlikely(after_wanted > before_wanted)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, reversed wanted after/before", st->name); + #endif + time_t tmp = before_wanted; + before_wanted = after_wanted; + after_wanted = tmp; + } + + // recalculate points_wanted using the final time-frame + points_wanted = (before_wanted - after_wanted) / update_every / group + 1; + if(unlikely(points_wanted < 0)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: rrd2rrdr() on %s, points_wanted is %ld", st->name, points_wanted); + #endif + points_wanted = 0; + } + +#ifdef NETDATA_INTERNAL_CHECKS + duration = before_wanted - after_wanted; + + if(after_wanted < first_entry_t) + error("INTERNAL CHECK: after_wanted %u is too small, minimum %u", (uint32_t)after_wanted, (uint32_t)first_entry_t); + + if(after_wanted > last_entry_t) + error("INTERNAL CHECK: after_wanted %u is too big, maximum %u", (uint32_t)after_wanted, (uint32_t)last_entry_t); + + if(before_wanted < first_entry_t) + error("INTERNAL CHECK: before_wanted %u is too small, minimum %u", (uint32_t)before_wanted, (uint32_t)first_entry_t); + + if(before_wanted > last_entry_t) + error("INTERNAL CHECK: before_wanted %u is too big, maximum %u", (uint32_t)before_wanted, (uint32_t)last_entry_t); + +/* + if(before_slot >= (size_t)st->entries) + error("INTERNAL CHECK: before_slot is invalid %zu, expected 0 to %ld", before_slot, st->entries - 1); + + if(after_slot >= (size_t)st->entries) + error("INTERNAL CHECK: after_slot is invalid %zu, expected 0 to %ld", after_slot, st->entries - 1); +*/ + + if(points_wanted > (before_wanted - after_wanted) / group / update_every + 1) + error("INTERNAL CHECK: points_wanted %ld is more than points %ld", points_wanted, (before_wanted - after_wanted) / group / update_every + 1); + + if(group < resampling_group) + error("INTERNAL CHECK: group %ld is less than the desired group points %ld", group, resampling_group); + + if(group > resampling_group && group % resampling_group) + error("INTERNAL CHECK: group %ld is not a multiple of the desired group points %ld", group, resampling_group); +#endif + + // ------------------------------------------------------------------------- + // initialize our result set + // this also locks the chart for us + + RRDR *r = rrdr_create(st, points_wanted, context_param_list); + if(unlikely(!r)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL CHECK: Cannot create RRDR for %s, after=%u, before=%u, duration=%u, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (uint32_t)duration, points_wanted); + #endif + freez(region_info_array); + return NULL; + } + + if(unlikely(!r->d || !points_wanted)) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL CHECK: Returning empty RRDR (no dimensions in RRDSET) for %s, after=%u, before=%u, duration=%zu, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (size_t)duration, points_wanted); + #endif + freez(region_info_array); + return r; + } + + r->result_options |= RRDR_RESULT_OPTION_VARIABLE_STEP; + if(unlikely(absolute_period_requested == 1)) + r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE; + else + r->result_options |= RRDR_RESULT_OPTION_RELATIVE; + + // find how many dimensions we have + long dimensions_count = r->d; + + // ------------------------------------------------------------------------- + // initialize RRDR + + r->group = group; + r->update_every = (int)group * update_every; + r->before = before_wanted; + r->after = after_wanted; + r->internal.points_wanted = points_wanted; + r->internal.resampling_group = resampling_group; + r->internal.resampling_divisor = resampling_divisor; + + + // ------------------------------------------------------------------------- + // assign the processor functions + + { + int i, found = 0; + for(i = 0; !found && api_v1_data_groups[i].name ;i++) { + if(api_v1_data_groups[i].value == group_method) { + r->internal.grouping_create= api_v1_data_groups[i].create; + r->internal.grouping_reset = api_v1_data_groups[i].reset; + r->internal.grouping_free = api_v1_data_groups[i].free; + r->internal.grouping_add = api_v1_data_groups[i].add; + r->internal.grouping_flush = api_v1_data_groups[i].flush; + found = 1; + } + } + if(!found) { + errno = 0; + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: grouping method %u not found for chart '%s'. Using 'average'", (unsigned int)group_method, r->st->name); + #endif + r->internal.grouping_create= grouping_create_average; + r->internal.grouping_reset = grouping_reset_average; + r->internal.grouping_free = grouping_free_average; + r->internal.grouping_add = grouping_add_average; + r->internal.grouping_flush = grouping_flush_average; + } + } + + // allocate any memory required by the grouping method + r->internal.grouping_data = r->internal.grouping_create(r); + + + // ------------------------------------------------------------------------- + // disable the not-wanted dimensions + + rrdset_check_rdlock(st); + + if(dimensions) + rrdr_disable_not_selected_dimensions(r, options, dimensions, temp_rd); + + + // ------------------------------------------------------------------------- + // do the work for each dimension + + time_t max_after = 0, min_before = 0; + long max_rows = 0; + + RRDDIM *rd; + long c, dimensions_used = 0, dimensions_nonzero = 0; + for(rd = temp_rd?temp_rd:st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) { + + // if we need a percentage, we need to calculate all dimensions + if(unlikely(!(options & RRDR_OPTION_PERCENTAGE) && (r->od[c] & RRDR_DIMENSION_HIDDEN))) { + if(unlikely(r->od[c] & RRDR_DIMENSION_SELECTED)) r->od[c] &= ~RRDR_DIMENSION_SELECTED; + continue; + } + r->od[c] |= RRDR_DIMENSION_SELECTED; + + // reset the grouping for the new dimension + r->internal.grouping_reset(r); + + do_dimension_variablestep( + r + , points_wanted + , rd + , c + , after_wanted + , before_wanted + ); + + if(r->od[c] & RRDR_DIMENSION_NONZERO) + dimensions_nonzero++; + + // verify all dimensions are aligned + if(unlikely(!dimensions_used)) { + min_before = r->before; + max_after = r->after; + max_rows = r->rows; + } + else { + if(r->after != max_after) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'after' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)max_after, rd->name, (size_t)r->after); + #endif + r->after = (r->after > max_after) ? r->after : max_after; + } + + if(r->before != min_before) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'before' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)min_before, rd->name, (size_t)r->before); + #endif + r->before = (r->before < min_before) ? r->before : min_before; + } + + if(r->rows != max_rows) { + #ifdef NETDATA_INTERNAL_CHECKS + error("INTERNAL ERROR: 'rows' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + st->name, (size_t)max_rows, rd->name, (size_t)r->rows); + #endif + r->rows = (r->rows > max_rows) ? r->rows : max_rows; + } + } + + dimensions_used++; + } + + #ifdef NETDATA_INTERNAL_CHECKS + + if (dimensions_used) { + if(r->internal.log) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ r->internal.log); + + if(r->rows != points_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'"); + + if(aligned && (r->before % group) != 0) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required"); + + // 'after' should not be aligned, since we start inside the first group + //if(aligned && (r->after % group) != 0) + // rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required"); + + if(r->before != before_requested) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'"); + + if(r->before != before_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'"); + + // reported 'after' varies, depending on group + if(r->after != after_wanted) + rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'"); + } + #endif + + // free all resources used by the grouping method + r->internal.grouping_free(r); + + // when all the dimensions are zero, we should return all of them + if(unlikely(options & RRDR_OPTION_NONZERO && !dimensions_nonzero)) { + // all the dimensions are zero + // mark them as NONZERO to send them all + for(rd = temp_rd?temp_rd:st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + r->od[c] |= RRDR_DIMENSION_NONZERO; + } + } + + rrdr_query_completed(r->internal.db_points_read, r->internal.result_points_generated); + freez(region_info_array); + return r; +} +#endif //#ifdef ENABLE_DBENGINE + +RRDR *rrd2rrdr( + RRDSET *st + , long points_requested + , long long after_requested + , long long before_requested + , RRDR_GROUPING group_method + , long resampling_time_requested + , RRDR_OPTIONS options + , const char *dimensions + , struct context_param *context_param_list +) +{ + int rrd_update_every; + int absolute_period_requested; + + time_t first_entry_t; + time_t last_entry_t; + if (context_param_list) { + first_entry_t = context_param_list->first_entry_t; + last_entry_t = context_param_list->last_entry_t; + } else { + rrdset_rdlock(st); + first_entry_t = rrdset_first_entry_t_nolock(st); + last_entry_t = rrdset_last_entry_t_nolock(st); + rrdset_unlock(st); + } + + rrd_update_every = st->update_every; + absolute_period_requested = rrdr_convert_before_after_to_absolute(&after_requested, &before_requested, + rrd_update_every, first_entry_t, + last_entry_t, options); + if (options & RRDR_OPTION_ALLOW_PAST) + if (first_entry_t > after_requested) + first_entry_t = after_requested; + + if (context_param_list) + rebuild_context_param_list(context_param_list, after_requested); + +#ifdef ENABLE_DBENGINE + if (st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + struct rrdeng_region_info *region_info_array; + unsigned regions, max_interval; + + /* This call takes the chart read-lock */ + regions = rrdeng_variable_step_boundaries(st, after_requested, before_requested, + ®ion_info_array, &max_interval, context_param_list); + if (1 == regions) { + if (region_info_array) { + if (rrd_update_every != region_info_array[0].update_every) { + rrd_update_every = region_info_array[0].update_every; + /* recalculate query alignment */ + absolute_period_requested = + rrdr_convert_before_after_to_absolute(&after_requested, &before_requested, rrd_update_every, + first_entry_t, last_entry_t, options); + } + freez(region_info_array); + } + return rrd2rrdr_fixedstep(st, points_requested, after_requested, before_requested, group_method, + resampling_time_requested, options, dimensions, rrd_update_every, + first_entry_t, last_entry_t, absolute_period_requested, context_param_list); + } else { + if (rrd_update_every != (uint16_t)max_interval) { + rrd_update_every = (uint16_t) max_interval; + /* recalculate query alignment */ + absolute_period_requested = rrdr_convert_before_after_to_absolute(&after_requested, &before_requested, + rrd_update_every, first_entry_t, + last_entry_t, options); + } + return rrd2rrdr_variablestep(st, points_requested, after_requested, before_requested, group_method, + resampling_time_requested, options, dimensions, rrd_update_every, + first_entry_t, last_entry_t, absolute_period_requested, region_info_array, context_param_list); + } + } +#endif + return rrd2rrdr_fixedstep(st, points_requested, after_requested, before_requested, group_method, + resampling_time_requested, options, dimensions, + rrd_update_every, first_entry_t, last_entry_t, absolute_period_requested, context_param_list); +} \ No newline at end of file diff --git a/web/api/queries/query.h b/web/api/queries/query.h new file mode 100644 index 0000000..6b8a51c --- /dev/null +++ b/web/api/queries/query.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_DATA_QUERY_H +#define NETDATA_API_DATA_QUERY_H + +typedef enum rrdr_grouping { + RRDR_GROUPING_UNDEFINED = 0, + RRDR_GROUPING_AVERAGE, + RRDR_GROUPING_MIN, + RRDR_GROUPING_MAX, + RRDR_GROUPING_SUM, + RRDR_GROUPING_INCREMENTAL_SUM, + RRDR_GROUPING_MEDIAN, + RRDR_GROUPING_STDDEV, + RRDR_GROUPING_CV, + RRDR_GROUPING_SES, + RRDR_GROUPING_DES, +} RRDR_GROUPING; + +extern const char *group_method2string(RRDR_GROUPING group); +extern void web_client_api_v1_init_grouping(void); +extern RRDR_GROUPING web_client_api_request_v1_data_group(const char *name, RRDR_GROUPING def); + +#endif //NETDATA_API_DATA_QUERY_H diff --git a/web/api/queries/rrdr.c b/web/api/queries/rrdr.c new file mode 100644 index 0000000..ef237fa --- /dev/null +++ b/web/api/queries/rrdr.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rrdr.h" + +/* +static void rrdr_dump(RRDR *r) +{ + long c, i; + RRDDIM *d; + + fprintf(stderr, "\nCHART %s (%s)\n", r->st->id, r->st->name); + + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { + fprintf(stderr, "DIMENSION %s (%s), %s%s%s%s\n" + , d->id + , d->name + , (r->od[c] & RRDR_EMPTY)?"EMPTY ":"" + , (r->od[c] & RRDR_RESET)?"RESET ":"" + , (r->od[c] & RRDR_DIMENSION_HIDDEN)?"HIDDEN ":"" + , (r->od[c] & RRDR_DIMENSION_NONZERO)?"NONZERO ":"" + ); + } + + if(r->rows <= 0) { + fprintf(stderr, "RRDR does not have any values in it.\n"); + return; + } + + fprintf(stderr, "RRDR includes %d values in it:\n", r->rows); + + // for each line in the array + for(i = 0; i < r->rows ;i++) { + calculated_number *cn = &r->v[ i * r->d ]; + RRDR_DIMENSION_FLAGS *co = &r->o[ i * r->d ]; + + // print the id and the timestamp of the line + fprintf(stderr, "%ld %ld ", i + 1, r->t[i]); + + // for each dimension + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; + if(unlikely(!(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + + if(co[c] & RRDR_EMPTY) + fprintf(stderr, "null "); + else + fprintf(stderr, CALCULATED_NUMBER_FORMAT " %s%s%s%s " + , cn[c] + , (co[c] & RRDR_EMPTY)?"E":" " + , (co[c] & RRDR_RESET)?"R":" " + , (co[c] & RRDR_DIMENSION_HIDDEN)?"H":" " + , (co[c] & RRDR_DIMENSION_NONZERO)?"N":" " + ); + } + + fprintf(stderr, "\n"); + } +} +*/ + + + + +inline static void rrdr_lock_rrdset(RRDR *r) { + if(unlikely(!r)) { + error("NULL value given!"); + return; + } + + rrdset_rdlock(r->st); + r->has_st_lock = 1; +} + +inline static void rrdr_unlock_rrdset(RRDR *r) { + if(unlikely(!r)) { + error("NULL value given!"); + return; + } + + if(likely(r->has_st_lock)) { + rrdset_unlock(r->st); + r->has_st_lock = 0; + } +} + +inline void rrdr_free(RRDR *r) +{ + if(unlikely(!r)) { + error("NULL value given!"); + return; + } + + rrdr_unlock_rrdset(r); + freez(r->t); + freez(r->v); + freez(r->o); + freez(r->od); + freez(r); +} + +RRDR *rrdr_create(struct rrdset *st, long n, struct context_param *context_param_list) +{ + if(unlikely(!st)) { + error("NULL value given!"); + return NULL; + } + + RRDR *r = callocz(1, sizeof(RRDR)); + r->st = st; + + rrdr_lock_rrdset(r); + + RRDDIM *temp_rd = context_param_list ? context_param_list->rd : NULL; + RRDDIM *rd; + if (temp_rd) { + RRDDIM *t = temp_rd; + while (t) { + r->d++; + t = t->next; + } + } else + rrddim_foreach_read(rd, st) r->d++; + + r->n = n; + + r->t = callocz((size_t)n, sizeof(time_t)); + r->v = mallocz(n * r->d * sizeof(calculated_number)); + r->o = mallocz(n * r->d * sizeof(RRDR_VALUE_FLAGS)); + r->od = mallocz(r->d * sizeof(RRDR_DIMENSION_FLAGS)); + + // set the hidden flag on hidden dimensions + int c; + for (c = 0, rd = temp_rd ? temp_rd : st->dimensions; rd; c++, rd = rd->next) { + if (unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN))) + r->od[c] = RRDR_DIMENSION_HIDDEN; + else + r->od[c] = RRDR_DIMENSION_DEFAULT; + } + + r->group = 1; + r->update_every = 1; + + return r; +} diff --git a/web/api/queries/rrdr.h b/web/api/queries/rrdr.h new file mode 100644 index 0000000..4d349c3 --- /dev/null +++ b/web/api/queries/rrdr.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_QUERIES_RRDR_H +#define NETDATA_QUERIES_RRDR_H + +#include "libnetdata/libnetdata.h" + +typedef enum rrdr_options { + RRDR_OPTION_NONZERO = 0x00000001, // don't output dimensions with just zero values + RRDR_OPTION_REVERSED = 0x00000002, // output the rows in reverse order (oldest to newest) + RRDR_OPTION_ABSOLUTE = 0x00000004, // values positive, for DATASOURCE_SSV before summing + RRDR_OPTION_MIN2MAX = 0x00000008, // when adding dimensions, use max - min, instead of sum + RRDR_OPTION_SECONDS = 0x00000010, // output seconds, instead of dates + RRDR_OPTION_MILLISECONDS = 0x00000020, // output milliseconds, instead of dates + RRDR_OPTION_NULL2ZERO = 0x00000040, // do not show nulls, convert them to zeros + RRDR_OPTION_OBJECTSROWS = 0x00000080, // each row of values should be an object, not an array + RRDR_OPTION_GOOGLE_JSON = 0x00000100, // comply with google JSON/JSONP specs + RRDR_OPTION_JSON_WRAP = 0x00000200, // wrap the response in a JSON header with info about the result + RRDR_OPTION_LABEL_QUOTES = 0x00000400, // in CSV output, wrap header labels in double quotes + RRDR_OPTION_PERCENTAGE = 0x00000800, // give values as percentage of total + RRDR_OPTION_NOT_ALIGNED = 0x00001000, // do not align charts for persistent timeframes + RRDR_OPTION_DISPLAY_ABS = 0x00002000, // for badges, display the absolute value, but calculate colors with sign + RRDR_OPTION_MATCH_IDS = 0x00004000, // when filtering dimensions, match only IDs + RRDR_OPTION_MATCH_NAMES = 0x00008000, // when filtering dimensions, match only names + RRDR_OPTION_CUSTOM_VARS = 0x00010000, // when wraping response in a JSON, return custom variables in response + RRDR_OPTION_ALLOW_PAST = 0x00020000, // The after parameter can extend in the past before the first entry +} RRDR_OPTIONS; + +typedef enum rrdr_value_flag { + RRDR_VALUE_NOTHING = 0x00, // no flag set (a good default) + RRDR_VALUE_EMPTY = 0x01, // the database value is empty + RRDR_VALUE_RESET = 0x02, // the database value is marked as reset (overflown) +} RRDR_VALUE_FLAGS; + +typedef enum rrdr_dimension_flag { + RRDR_DIMENSION_DEFAULT = 0x00, + RRDR_DIMENSION_HIDDEN = 0x04, // the dimension is hidden (not to be presented to callers) + RRDR_DIMENSION_NONZERO = 0x08, // the dimension is non zero (contains non-zero values) + RRDR_DIMENSION_SELECTED = 0x10, // the dimension is selected for evaluation in this RRDR +} RRDR_DIMENSION_FLAGS; + +// RRDR result options +typedef enum rrdr_result_flags { + RRDR_RESULT_OPTION_ABSOLUTE = 0x00000001, // the query uses absolute time-frames + // (can be cached by browsers and proxies) + RRDR_RESULT_OPTION_RELATIVE = 0x00000002, // the query uses relative time-frames + // (should not to be cached by browsers and proxies) + RRDR_RESULT_OPTION_VARIABLE_STEP = 0x00000004, // the query uses variable-step time-frames +} RRDR_RESULT_FLAGS; + +typedef struct rrdresult { + struct rrdset *st; // the chart this result refers to + + RRDR_RESULT_FLAGS result_options; // RRDR_RESULT_OPTION_* + + int d; // the number of dimensions + long n; // the number of values in the arrays + long rows; // the number of rows used + + RRDR_DIMENSION_FLAGS *od; // the options for the dimensions + + time_t *t; // array of n timestamps + calculated_number *v; // array n x d values + RRDR_VALUE_FLAGS *o; // array n x d options for each value returned + + long group; // how many collected values were grouped for each row + int update_every; // what is the suggested update frequency in seconds + + calculated_number min; + calculated_number max; + + time_t before; + time_t after; + + int has_st_lock; // if st is read locked by us + + // internal rrd2rrdr() members below this point + struct { + long points_wanted; + long resampling_group; + calculated_number resampling_divisor; + + void *(*grouping_create)(struct rrdresult *r); + void (*grouping_reset)(struct rrdresult *r); + void (*grouping_free)(struct rrdresult *r); + void (*grouping_add)(struct rrdresult *r, calculated_number value); + calculated_number (*grouping_flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + void *grouping_data; + + #ifdef NETDATA_INTERNAL_CHECKS + const char *log; + #endif + + size_t db_points_read; + size_t result_points_generated; + } internal; +} RRDR; + +#define rrdr_rows(r) ((r)->rows) + +#include "../../../database/rrd.h" +extern void rrdr_free(RRDR *r); +extern RRDR *rrdr_create(struct rrdset *st, long n, struct context_param *context_param_list); + +#include "../web_api_v1.h" +#include "web/api/queries/query.h" + +extern RRDR *rrd2rrdr( + RRDSET *st, long points_requested, long long after_requested, long long before_requested, + RRDR_GROUPING group_method, long resampling_time_requested, RRDR_OPTIONS options, const char *dimensions, + struct context_param *context_param_list); + +#include "query.h" + +#endif //NETDATA_QUERIES_RRDR_H diff --git a/web/api/queries/ses/Makefile.am b/web/api/queries/ses/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/ses/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/ses/README.md b/web/api/queries/ses/README.md new file mode 100644 index 0000000..c279701 --- /dev/null +++ b/web/api/queries/ses/README.md @@ -0,0 +1,61 @@ + + +# Single (or Simple) Exponential Smoothing (`ses`) + +> This query is also available as `ema` and `ewma`. + +An exponential moving average (`ema`), also known as an exponentially weighted moving average (`ewma`) +is a first-order infinite impulse response filter that applies weighting factors which decrease +exponentially. The weighting for each older datum decreases exponentially, never reaching zero. + +In simple terms, this is like an average value, but more recent values are given more weight. + +Netdata automatically adjusts the weight (`alpha`) based on the number of values processed, +using the formula: + +``` +window = max(number of values, 15) +alpha = 2 / (window + 1) +``` + +You can change the fixed value `15` by setting in `netdata.conf`: + +``` +[web] + ses max window = 15 +``` + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: ses -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`ses` does not change the units. For example, if the chart units is `requests/sec`, the exponential +moving average will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=ses` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average&value_color=yellow) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=ses&after=-60&label=single+exponential+smoothing&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) + +## References + +- +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fses%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/ses/ses.c b/web/api/queries/ses/ses.c new file mode 100644 index 0000000..772505f --- /dev/null +++ b/web/api/queries/ses/ses.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ses.h" + + +// ---------------------------------------------------------------------------- +// single exponential smoothing + +struct grouping_ses { + calculated_number alpha; + calculated_number alpha_other; + calculated_number level; + size_t count; +}; + +static size_t max_window_size = 15; + +void grouping_init_ses(void) { + long long ret = config_get_number(CONFIG_SECTION_WEB, "ses max window", (long long)max_window_size); + if(ret <= 1) { + config_set_number(CONFIG_SECTION_WEB, "ses max window", (long long)max_window_size); + } + else { + max_window_size = (size_t) ret; + } +} + +static inline calculated_number window(RRDR *r, struct grouping_ses *g) { + (void)g; + + calculated_number points; + if(r->group == 1) { + // provide a running DES + points = r->internal.points_wanted; + } + else { + // provide a SES with flush points + points = r->group; + } + + return (points > max_window_size) ? max_window_size : points; +} + +static inline void set_alpha(RRDR *r, struct grouping_ses *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + g->alpha = 2.0 / (window(r, g) + 1.0); + g->alpha_other = 1.0 - g->alpha; +} + +void *grouping_create_ses(RRDR *r) { + struct grouping_ses *g = (struct grouping_ses *)callocz(1, sizeof(struct grouping_ses)); + set_alpha(r, g); + g->level = 0.0; + return g; +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_ses(RRDR *r) { + struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; + g->level = 0.0; + g->count = 0; +} + +void grouping_free_ses(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_ses(RRDR *r, calculated_number value) { + struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; + + if(calculated_number_isnumber(value)) { + if(unlikely(!g->count)) + g->level = value; + + g->level = g->alpha * value + g->alpha_other * g->level; + g->count++; + } +} + +calculated_number grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; + + if(unlikely(!g->count || !calculated_number_isnumber(g->level))) { + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + return 0.0; + } + + return g->level; +} diff --git a/web/api/queries/ses/ses.h b/web/api/queries/ses/ses.h new file mode 100644 index 0000000..603fdb5 --- /dev/null +++ b/web/api/queries/ses/ses.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERIES_SES_H +#define NETDATA_API_QUERIES_SES_H + +#include "../query.h" +#include "../rrdr.h" + +extern void grouping_init_ses(void); + +extern void *grouping_create_ses(RRDR *r); +extern void grouping_reset_ses(RRDR *r); +extern void grouping_free_ses(RRDR *r); +extern void grouping_add_ses(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERIES_SES_H diff --git a/web/api/queries/stddev/Makefile.am b/web/api/queries/stddev/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/stddev/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/stddev/README.md b/web/api/queries/stddev/README.md new file mode 100644 index 0000000..7cd7d62 --- /dev/null +++ b/web/api/queries/stddev/README.md @@ -0,0 +1,93 @@ + + +# standard deviation (`stddev`) + +The standard deviation is a measure that is used to quantify the amount of variation or dispersion +of a set of data values. + +A low standard deviation indicates that the data points tend to be close to the mean (also called the +expected value) of the set, while a high standard deviation indicates that the data points are spread +out over a wider range of values. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: stddev -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`stdev` does not change the units. For example, if the chart units is `requests/sec`, the standard +deviation will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=stddev` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=average&after=-60&label=average&value_color=yellow) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=stddev&after=-60&label=standard+deviation&value_color=orange) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=max&after=-60&label=max) + +## References + +Check . + +--- + +# Coefficient of variation (`cv`) + +> This query is also available as `rsd`. + +The coefficient of variation (`cv`), also known as relative standard deviation (`rsd`), +is a standardized measure of dispersion of a probability distribution or frequency distribution. + +It is defined as the ratio of the **standard deviation** to the **mean**. + +In simple terms, it gives the percentage of change. So, if the average value of a metric is 1000 +and its standard deviation is 100 (meaning that it variates from 900 to 1100), then `cv` is 10%. + +This is an easy way to check the % variation, without using absolute values. + +For example, you may trigger an alarm if your web server requests/sec `cv` is above 20 (`%`) +over the last minute. So if your web server was serving 1000 reqs/sec over the last minute, +it will trigger the alarm if had spikes below 800/sec or above 1200/sec. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: cv -1m unaligned of my_dimension + units: % + warn: $this > 20 +``` + +The units reported by `cv` is always `%`. + +It can also be used in APIs and badges as `&group=cv` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=average&after=-60&label=average&value_color=yellow) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=cv&after=-60&label=coefficient+of+variation&value_color=orange&units=pcent) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=max&after=-60&label=max) + +## References + +Check . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fstddev%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/stddev/stddev.c b/web/api/queries/stddev/stddev.c new file mode 100644 index 0000000..1625844 --- /dev/null +++ b/web/api/queries/stddev/stddev.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stddev.h" + + +// ---------------------------------------------------------------------------- +// stddev + +// this implementation comes from: +// https://www.johndcook.com/blog/standard_deviation/ + +struct grouping_stddev { + long count; + calculated_number m_oldM, m_newM, m_oldS, m_newS; +}; + +void *grouping_create_stddev(RRDR *r) { + UNUSED (r); + return callocz(1, sizeof(struct grouping_stddev)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_stddev(RRDR *r) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + g->count = 0; +} + +void grouping_free_stddev(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_stddev(RRDR *r, calculated_number value) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + + if(calculated_number_isnumber(value)) { + g->count++; + + // See Knuth TAOCP vol 2, 3rd edition, page 232 + if (g->count == 1) { + g->m_oldM = g->m_newM = value; + g->m_oldS = 0.0; + } + else { + g->m_newM = g->m_oldM + (value - g->m_oldM) / g->count; + g->m_newS = g->m_oldS + (value - g->m_oldM) * (value - g->m_newM); + + // set up for next iteration + g->m_oldM = g->m_newM; + g->m_oldS = g->m_newS; + } + } +} + +static inline calculated_number mean(struct grouping_stddev *g) { + return (g->count > 0) ? g->m_newM : 0.0; +} + +static inline calculated_number variance(struct grouping_stddev *g) { + return ( (g->count > 1) ? g->m_newS/(g->count - 1) : 0.0 ); +} +static inline calculated_number stddev(struct grouping_stddev *g) { + return sqrtl(variance(g)); +} + +calculated_number grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + + calculated_number value; + + if(likely(g->count > 1)) { + value = stddev(g); + + if(!calculated_number_isnumber(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + else if(g->count == 1) { + value = 0.0; + } + else { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + grouping_reset_stddev(r); + + return value; +} + +// https://en.wikipedia.org/wiki/Coefficient_of_variation +calculated_number grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + + calculated_number value; + + if(likely(g->count > 1)) { + calculated_number m = mean(g); + value = 100.0 * stddev(g) / ((m < 0)? -m : m); + + if(unlikely(!calculated_number_isnumber(value))) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + else if(g->count == 1) { + // one value collected + value = 0.0; + } + else { + // no values collected + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + grouping_reset_stddev(r); + + return value; +} + + +/* + * Mean = average + * +calculated_number grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = mean(g); + + if(!isnormal(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + + grouping_reset_stddev(r); + + return value; +} + */ + +/* + * It is not advised to use this version of variance directly + * +calculated_number grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = variance(g); + + if(!isnormal(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + + grouping_reset_stddev(r); + + return value; +} +*/ \ No newline at end of file diff --git a/web/api/queries/stddev/stddev.h b/web/api/queries/stddev/stddev.h new file mode 100644 index 0000000..7a46975 --- /dev/null +++ b/web/api/queries/stddev/stddev.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERIES_STDDEV_H +#define NETDATA_API_QUERIES_STDDEV_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_stddev(RRDR *r); +extern void grouping_reset_stddev(RRDR *r); +extern void grouping_free_stddev(RRDR *r); +extern void grouping_add_stddev(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +extern calculated_number grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// extern calculated_number grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// extern calculated_number grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERIES_STDDEV_H diff --git a/web/api/queries/sum/Makefile.am b/web/api/queries/sum/Makefile.am new file mode 100644 index 0000000..161784b --- /dev/null +++ b/web/api/queries/sum/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/sum/README.md b/web/api/queries/sum/README.md new file mode 100644 index 0000000..aeace0a --- /dev/null +++ b/web/api/queries/sum/README.md @@ -0,0 +1,41 @@ + + +# Sum + +This module sums all the values in the time-frame requested. + +You can use `sum` to find the volume of something over a period. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: sum -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`sum` does not change the units. For example, if the chart units is `requests/sec`, the result +will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=sum` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max) +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=sum&after=-60&label=1m+sum&value_color=orange&units=requests) + +## References + +- . + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fapi%2Fqueries%2Fsum%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/api/queries/sum/sum.c b/web/api/queries/sum/sum.c new file mode 100644 index 0000000..0da9937 --- /dev/null +++ b/web/api/queries/sum/sum.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "sum.h" + +// ---------------------------------------------------------------------------- +// sum + +struct grouping_sum { + calculated_number sum; + size_t count; +}; + +void *grouping_create_sum(RRDR *r) { + (void)r; + return callocz(1, sizeof(struct grouping_sum)); +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_sum(RRDR *r) { + struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; + g->sum = 0; + g->count = 0; +} + +void grouping_free_sum(RRDR *r) { + freez(r->internal.grouping_data); + r->internal.grouping_data = NULL; +} + +void grouping_add_sum(RRDR *r, calculated_number value) { + if(!isnan(value)) { + struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; + g->sum += value; + g->count++; + } +} + +calculated_number grouping_flush_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->sum; + } + + g->sum = 0.0; + g->count = 0; + + return value; +} + + diff --git a/web/api/queries/sum/sum.h b/web/api/queries/sum/sum.h new file mode 100644 index 0000000..9dc8d20 --- /dev/null +++ b/web/api/queries/sum/sum.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERY_SUM_H +#define NETDATA_API_QUERY_SUM_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_create_sum(RRDR *r); +extern void grouping_reset_sum(RRDR *r); +extern void grouping_free_sum(RRDR *r); +extern void grouping_add_sum(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERY_SUM_H diff --git a/web/api/tests/valid_urls.c b/web/api/tests/valid_urls.c new file mode 100644 index 0000000..d8c261c --- /dev/null +++ b/web/api/tests/valid_urls.c @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../../../libnetdata/libnetdata.h" +#include "../../../libnetdata/required_dummies.h" +#include "../../../database/rrd.h" +#include "../../../web/server/web_client.h" +#include +#include +#include + +RRDHOST *sql_create_host_by_uuid(char *hostname) +{ + (void) hostname; + return NULL; +} + +RRDHOST *__wrap_sql_create_host_by_uuid(char *hostname) +{ + (void) hostname; + return NULL; +} + +void repr(char *result, int result_size, char const *buf, int size) +{ + int n; + char *end = result + result_size - 1; + unsigned char const *ubuf = (unsigned char const *)buf; + while (size && result_size > 0) { + if (*ubuf <= 0x20 || *ubuf >= 0x80) { + n = snprintf(result, result_size, "\\%02X", *ubuf); + } else { + *result = *ubuf; + n = 1; + } + result += n; + result_size -= n; + ubuf++; + size--; + } + if (result_size > 0) + *(result++) = 0; + else + *end = 0; +} + +// ---------------------------------- Mocking accesses from web_client ------------------------------------------------ + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + info("Mocking send: %zu bytes\n", len); + (void)sockfd; + (void)buf; + (void)flags; + return len; +} + +RRDHOST *__wrap_rrdhost_find_by_hostname(const char *hostname, uint32_t hash) +{ + (void)hostname; + (void)hash; + return NULL; +} + +/* Note: we've got some intricate code inside the global statistics module, might be useful to pull it inside the + test set instead of mocking it. */ +void __wrap_finished_web_request_statistics( + uint64_t dt, uint64_t bytes_received, uint64_t bytes_sent, uint64_t content_size, uint64_t compressed_content_size) +{ + (void)dt; + (void)bytes_received; + (void)bytes_sent; + (void)content_size; + (void)compressed_content_size; +} + +char *__wrap_config_get(struct config *root, const char *section, const char *name, const char *default_value) +{ + if (!strcmp(section, CONFIG_SECTION_WEB) && !strcmp(name, "web files owner")) + return "netdata"; + (void)root; + (void)default_value; + return "UNKNOWN FIX ME"; +} + +int __wrap_web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) +{ + char url_repr[160]; + repr(url_repr, sizeof(url_repr), url, strlen(url)); + printf("web_client_api_request_v1(url=\"%s\")\n", url_repr); + check_expected_ptr(host); + check_expected_ptr(w); + check_expected_ptr(url_repr); + return HTTP_RESP_OK; +} + +int __wrap_mysendfile(struct web_client *w, char *filename) +{ + (void)w; + printf("mysendfile(filename=\"%s\"\n", filename); + check_expected_ptr(filename); + return HTTP_RESP_OK; +} + +int __wrap_rrdpush_receiver_thread_spawn(RRDHOST *host, struct web_client *w, char *url) +{ + (void)host; + (void)w; + (void)url; + return 0; +} + +RRDHOST *__wrap_rrdhost_find_by_guid(const char *guid, uint32_t hash) +{ + (void)guid; + (void)hash; + printf("FIXME: rrdset_find_guid\n"); + return NULL; +} + +RRDSET *__wrap_rrdset_find_byname(RRDHOST *host, const char *name) +{ + (void)host; + (void)name; + printf("FIXME: rrdset_find_byname\n"); + return NULL; +} + +RRDSET *__wrap_rrdset_find(RRDHOST *host, const char *id) +{ + (void)host; + (void)id; + printf("FIXME: rrdset_find\n"); + return NULL; +} + +// -------------------------------- Mocking the log - dump straight through -------------------------------------------- + +void __wrap_debug_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + printf(" DEBUG: "); + printf(fmt, args); + printf("\n"); + va_end(args); +} + +void __wrap_info_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + printf(" INFO: "); + printf(fmt, args); + printf("\n"); + va_end(args); +} + +void __wrap_error_int( + const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)prefix; + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + printf(" ERROR: "); + printf(fmt, args); + printf("\n"); + va_end(args); +} + +void __wrap_fatal_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + printf("FATAL: "); + printf(fmt, args); + printf("\n"); + va_end(args); + fail(); +} + +WEB_SERVER_MODE web_server_mode = WEB_SERVER_MODE_STATIC_THREADED; +char *netdata_configured_web_dir = "UNKNOWN FIXME"; +RRDHOST *localhost = NULL; + +struct config netdata_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +/* Note: this is not a CMocka group_test_setup/teardown pair. This is performed per-test. +*/ +static struct web_client *setup_fresh_web_client() +{ + struct web_client *w = (struct web_client *)malloc(sizeof(struct web_client)); + memset(w, 0, sizeof(struct web_client)); + w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE); + w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE); + w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE); + strcpy(w->origin, "*"); // Simulate web_client_create_on_fd() + w->cookie1[0] = 0; // Simulate web_client_create_on_fd() + w->cookie2[0] = 0; // Simulate web_client_create_on_fd() + w->acl = 0x1f; // Everything on + return w; +} + +static void destroy_web_client(struct web_client *w) +{ + buffer_free(w->response.data); + buffer_free(w->response.header); + buffer_free(w->response.header_output); + free(w); +} + +//////////////////////////// Test cases /////////////////////////////////////////////////////////////////////////////// + +static void only_root(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET / HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_string(__wrap_mysendfile, filename, "/"); + + web_client_process_request(w); + + //assert_string_equal(w->decoded_query_string, def->query_out); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +static void two_slashes(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET // HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_string(__wrap_mysendfile, filename, "//"); + + web_client_process_request(w); + + //assert_string_equal(w->decoded_query_string, def->query_out); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +static void absolute_url(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET http://localhost:19999/api/v1/info HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, w); + expect_string(__wrap_web_client_api_request_v1, url_repr, "info"); + + web_client_process_request(w); + + assert_string_equal(w->decoded_query_string, "?blah"); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +static void valid_url(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /api/v1/info?blah HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, w); + expect_string(__wrap_web_client_api_request_v1, url_repr, "info"); + + web_client_process_request(w); + + assert_string_equal(w->decoded_query_string, "?blah"); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +/* RFC2616, section 4.1: + + In the interest of robustness, servers SHOULD ignore any empty + line(s) received where a Request-Line is expected. In other words, if + the server is reading the protocol stream at the beginning of a + message and receives a CRLF first, it should ignore the CRLF. +*/ +static void leading_blanks(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "\r\n\r\nGET /api/v1/info?blah HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, w); + expect_string(__wrap_web_client_api_request_v1, url_repr, "info"); + + web_client_process_request(w); + + assert_string_equal(w->decoded_query_string, "?blah"); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +static void empty_url(void **state) +{ + (void)state; + + if (localhost != NULL) + free(localhost); + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET HTTP/1.1\r\n\r\n"); + + char debug[4096]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("-> \"%s\"\n", debug); + + //char expected_url_repr[4096]; + //repr(expected_url_repr, sizeof(expected_url_repr), def->url_out_repr, strlen(def->url_out_repr)); + + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, w); + expect_string(__wrap_web_client_api_request_v1, url_repr, "info"); + + web_client_process_request(w); + + assert_string_equal(w->decoded_query_string, "?blah"); + destroy_web_client(w); + free(localhost); + localhost = NULL; +} + +/* If the %-escape is being performed at the correct time then the url should not be treated as a query, but instead + as a path "/api/v1/info?blah?" which should despatch into the API with the given values. +*/ +static void not_a_query(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /api/v1/info%3fblah%3f HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "info?blah?", 10); + + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, w); + expect_string(__wrap_web_client_api_request_v1, url_repr, expected_url_repr); + + web_client_process_request(w); + + assert_string_equal(w->decoded_query_string, ""); + destroy_web_client(w); + free(localhost); +} + +static void cr_in_url(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /api/v1/inf\ro\t?blah HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} +static void newline_in_url(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /api/v1/inf\no\t?blah HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void bad_version(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /api/v1/info?blah HTTP/1.2\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void pathless_query(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET ?blah HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void pathless_fragment(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET #blah HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void short_percent(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET % HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void short_percent2(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET %0 HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void short_percent3(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET %"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void percent_nulls(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET %00%00%00%00%00%00 HTTP/1.1\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void percent_invalid(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET /%x%x%x%x%x%x HTTP/1.1\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void space_in_url(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET / / HTTP/1.1\r\n\r\n"); + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void random_sploit1(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + // FIXME: Encoding probably needs to go through printf + buffer_need_bytes(w->response.data, 55); + memcpy( + w->response.data->buffer, + "GET \x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr HTTP/1.1\r\n\r\n", 54); + w->response.data->len = 54; + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +static void null_in_url(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET / / HTTP/1.1\r\n\r\n"); + w->response.data->buffer[5] = 0; + + char debug[160]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} +static void many_ands(void **state) +{ + (void)state; + localhost = malloc(sizeof(RRDHOST)); + + struct web_client *w = setup_fresh_web_client(); + buffer_strcat(w->response.data, "GET foo?"); + for (size_t i = 0; i < 600; i++) + buffer_strcat(w->response.data, "&"); + buffer_strcat(w->response.data, " HTTP/1.1\r\n\r\n"); + + char debug[2048]; + repr(debug, sizeof(debug), w->response.data->buffer, w->response.data->len); + printf("->%s\n", debug); + + char expected_url_repr[160]; + repr(expected_url_repr, sizeof(expected_url_repr), "inf\no\t", 6); + + web_client_process_request(w); + + assert_int_equal(w->response.code, HTTP_RESP_BAD_REQUEST); + + destroy_web_client(w); + free(localhost); +} + +int main(void) +{ + debug_flags = 0xffffffffffff; + int fails = 0; + + struct CMUnitTest static_tests[] = { + cmocka_unit_test(only_root), cmocka_unit_test(two_slashes), cmocka_unit_test(valid_url), + cmocka_unit_test(leading_blanks), cmocka_unit_test(empty_url), cmocka_unit_test(newline_in_url), + cmocka_unit_test(not_a_query), cmocka_unit_test(cr_in_url), cmocka_unit_test(pathless_query), + cmocka_unit_test(pathless_fragment), cmocka_unit_test(short_percent), cmocka_unit_test(short_percent2), + cmocka_unit_test(short_percent3), cmocka_unit_test(percent_nulls), cmocka_unit_test(percent_invalid), + cmocka_unit_test(space_in_url), cmocka_unit_test(random_sploit1), cmocka_unit_test(null_in_url), + cmocka_unit_test(absolute_url), + // cmocka_unit_test(many_ands), CMocka cannot recover after this crash + cmocka_unit_test(bad_version) + }; + (void)many_ands; + + fails += cmocka_run_group_tests_name("static_tests", static_tests, NULL, NULL); + return fails; +} diff --git a/web/api/tests/web_api.c b/web/api/tests/web_api.c new file mode 100644 index 0000000..3cc0a79 --- /dev/null +++ b/web/api/tests/web_api.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../../../libnetdata/libnetdata.h" +#include "../../../libnetdata/required_dummies.h" +#include "../../../database/rrd.h" +#include "../../../web/server/web_client.h" +#include +#include +#include + +RRDHOST *sql_create_host_by_uuid(char *hostname) +{ + (void) hostname; + return NULL; +} + +RRDHOST *__wrap_sql_create_host_by_uuid(char *hostname) +{ + (void) hostname; + return NULL; +} + +void repr(char *result, int result_size, char const *buf, int size) +{ + int n; + char *end = result + result_size - 1; + unsigned char const *ubuf = (unsigned char const *)buf; + while (size && result_size > 0) { + if (*ubuf <= 0x20 || *ubuf >= 0x80) { + n = snprintf(result, result_size, "\\%02X", *ubuf); + } else { + *result = *ubuf; + n = 1; + } + result += n; + result_size -= n; + ubuf++; + size--; + } + if (result_size > 0) + *(result++) = 0; + else + *end = 0; +} + +// ---------------------------------- Mocking accesses from web_client ------------------------------------------------ + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + info("Mocking send: %zu bytes\n", len); + (void)sockfd; + (void)buf; + (void)flags; + return len; +} + +RRDHOST *__wrap_rrdhost_find_by_hostname(const char *hostname, uint32_t hash) +{ + (void)hostname; + (void)hash; + return NULL; +} + +/* Note: we've got some intricate code inside the global statistics module, might be useful to pull it inside the + test set instead of mocking it. */ +void __wrap_finished_web_request_statistics( + uint64_t dt, uint64_t bytes_received, uint64_t bytes_sent, uint64_t content_size, uint64_t compressed_content_size) +{ + (void)dt; + (void)bytes_received; + (void)bytes_sent; + (void)content_size; + (void)compressed_content_size; +} + +char *__wrap_config_get(struct config *root, const char *section, const char *name, const char *default_value) +{ + if (!strcmp(section, CONFIG_SECTION_WEB) && !strcmp(name, "web files owner")) + return "netdata"; + (void)root; + (void)default_value; + return "UNKNOWN FIX ME"; +} + +int __wrap_web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) +{ + char url_repr[160]; + repr(url_repr, sizeof(url_repr), url, strlen(url)); + info("web_client_api_request_v1(url=\"%s\")\n", url_repr); + check_expected_ptr(host); + check_expected_ptr(w); + check_expected_ptr(url_repr); + return HTTP_RESP_OK; +} + +int __wrap_rrdpush_receiver_thread_spawn(RRDHOST *host, struct web_client *w, char *url) +{ + (void)host; + (void)w; + (void)url; + return 0; +} + +RRDHOST *__wrap_rrdhost_find_by_guid(const char *guid, uint32_t hash) +{ + (void)guid; + (void)hash; + printf("FIXME: rrdset_find_guid\n"); + return NULL; +} + +RRDSET *__wrap_rrdset_find_byname(RRDHOST *host, const char *name) +{ + (void)host; + (void)name; + printf("FIXME: rrdset_find_byname\n"); + return NULL; +} + +RRDSET *__wrap_rrdset_find(RRDHOST *host, const char *id) +{ + (void)host; + (void)id; + printf("FIXME: rrdset_find\n"); + return NULL; +} + +// -------------------------------- Mocking the log - capture per-test ------------------------------------------------ + +char log_buffer[10240] = { 0 }; +void __wrap_debug_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + size_t cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, " DEBUG: "); + cur = strlen(log_buffer); + vsnprintf(log_buffer + cur, sizeof(log_buffer) - cur, fmt, args); + cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, "\n"); + va_end(args); +} + +void __wrap_info_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + size_t cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, " INFO: "); + cur = strlen(log_buffer); + vsnprintf(log_buffer + cur, sizeof(log_buffer) - cur, fmt, args); + cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, "\n"); + va_end(args); +} + +void __wrap_error_int( + const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)prefix; + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + size_t cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, " ERROR: "); + cur = strlen(log_buffer); + vsnprintf(log_buffer + cur, sizeof(log_buffer) - cur, fmt, args); + cur = strlen(log_buffer); + snprintf(log_buffer + cur, sizeof(log_buffer) - cur, "\n"); + va_end(args); +} + +void __wrap_fatal_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + va_list args; + va_start(args, fmt); + printf("FATAL: "); + vprintf(fmt, args); + printf("\n"); + va_end(args); + fail(); +} + +WEB_SERVER_MODE web_server_mode = WEB_SERVER_MODE_STATIC_THREADED; +char *netdata_configured_web_dir = "UNKNOWN FIXME"; +RRDHOST *localhost = NULL; + +struct config netdata_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +const char *http_headers[] = { "Host: 254.254.0.1", + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_" // No , + "0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Connection: keep-alive", + "X-Forwarded-For: 1.254.1.251", + "Cookie: _ga=GA1.1.1227576758.1571113676; _gid=GA1.2.1222321739.1573628979", + "X-Requested-With: XMLHttpRequest", + "Accept-Encoding: gzip, deflate", + "Cache-Control: no-cache, no-store" }; +#define MAX_HEADERS (sizeof(http_headers) / (sizeof(const char *))) + +static void build_request(struct web_buffer *wb, const char *url, bool use_cr, size_t num_headers) +{ + buffer_reset(wb); + buffer_strcat(wb, "GET "); + buffer_strcat(wb, url); + buffer_strcat(wb, " HTTP/1.1"); + if (use_cr) + buffer_strcat(wb, "\r"); + buffer_strcat(wb, "\n"); + for (size_t i = 0; i < num_headers && i < MAX_HEADERS; i++) { + buffer_strcat(wb, http_headers[i]); + if (use_cr) + buffer_strcat(wb, "\r"); + buffer_strcat(wb, "\n"); + } + if (use_cr) + buffer_strcat(wb, "\r"); + buffer_strcat(wb, "\n"); +} + +/* Note: this is not a CMocka group_test_setup/teardown pair. This is performed per-test. +*/ +static struct web_client *setup_fresh_web_client() +{ + struct web_client *w = (struct web_client *)malloc(sizeof(struct web_client)); + memset(w, 0, sizeof(struct web_client)); + w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE); + w->response.data->date = 0; // Valgrind uninitialised value + w->response.data->expires = 0; // Valgrind uninitialised value + w->response.data->options = 0; // Valgrind uninitialised value + w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE); + w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE); + strcpy(w->origin, "*"); // Simulate web_client_create_on_fd() + w->cookie1[0] = 0; // Simulate web_client_create_on_fd() + w->cookie2[0] = 0; // Simulate web_client_create_on_fd() + w->acl = 0x1f; // Everything on + return w; +} + +static void destroy_web_client(struct web_client *w) +{ + buffer_free(w->response.data); + buffer_free(w->response.header); + buffer_free(w->response.header_output); + free(w); +} + +// ---------------------------------- Parameterized test-families ----------------------------------------------------- +// There is no way to pass a parameter block into the setup fixture, we would have to patch CMocka and maintain it +// locally. (The void **current_state in _run_group_tests would be set from a parameter). This is unfortunate as a +// parameteric unit-tester needs to be to pass parameters to the fixtures. We are faking this by calculating the +// space of tests in the launcher, passing an array of identical unit-tests to CMocka and then counting through the +// parameters in the shared state passed between tests. To initialise this counter structure we use this global to +// pass from the launcher (test-builder) to the setup-fixture. + +void *shared_test_state = NULL; + +// -------------------------------- Test family for /api/v1/info ------------------------------------------------------ + +struct test_def { + size_t num_headers; // Index coordinate + size_t prefix_len; // Index coordinate + char name[80]; + size_t full_len; + struct web_client *instance; // Used within this single test + bool completed, use_cr; + struct test_def *next, *prev; +}; + +static void api_info(void **state) +{ + (void)state; + struct test_def *def = (struct test_def *)shared_test_state; + shared_test_state = def->next; + + if (def->prev != NULL && !def->prev->completed && strlen(log_buffer) > 0) { + printf("Log of failing case %s:\n", def->prev->name); + puts(log_buffer); + } + log_buffer[0] = 0; + if (localhost != NULL) + free(localhost); + localhost = calloc(1,sizeof(RRDHOST)); + + def->instance = setup_fresh_web_client(); + build_request(def->instance->response.data, "/api/v1/info", def->use_cr, def->num_headers); + def->instance->response.data->len = def->prefix_len; + + char buffer_repr[1024]; + repr(buffer_repr, sizeof(buffer_repr), def->instance->response.data->buffer,def->prefix_len); + info("Buffer contains: %s [first %zu]", buffer_repr,def->prefix_len); + if (def->prefix_len == def->full_len) { + expect_value(__wrap_web_client_api_request_v1, host, localhost); + expect_value(__wrap_web_client_api_request_v1, w, def->instance); + expect_string(__wrap_web_client_api_request_v1, url_repr, "info"); + } + + web_client_process_request(def->instance); + + if (def->prefix_len == def->full_len) + assert_int_equal(def->instance->flags & WEB_CLIENT_FLAG_WAIT_RECEIVE, 0); + else + assert_int_equal(def->instance->flags & WEB_CLIENT_FLAG_WAIT_RECEIVE, WEB_CLIENT_FLAG_WAIT_RECEIVE); + assert_int_equal(def->instance->mode, WEB_CLIENT_MODE_NORMAL); + def->completed = true; + log_buffer[0] = 0; +} + +static int api_info_launcher() +{ + size_t num_tests = 0; + struct web_client *template = setup_fresh_web_client(); + struct test_def *current, *head = NULL; + struct test_def *prev = NULL; + + for (size_t i = 0; i < MAX_HEADERS; i++) { + build_request(template->response.data, "/api/v1/info", true, i); + for (size_t j = 0; j <= template->response.data->len; j++) { + if (j == 0 && i > 0) + continue; // All zero-length prefixes are identical, skip after first time + current = malloc(sizeof(struct test_def)); + if (prev != NULL) + prev->next = current; + else + head = current; + current->prev = prev; + prev = current; + + current->num_headers = i; + current->prefix_len = j; + current->full_len = template->response.data->len; + current->instance = NULL; + current->next = NULL; + current->use_cr = true; + current->completed = false; + sprintf( + current->name, "/api/v1/info@%zu,%zu/%zu+%d", current->num_headers, current->prefix_len, + current->full_len,true); + num_tests++; + } + } + for (size_t i = 0; i < MAX_HEADERS; i++) { + build_request(template->response.data, "/api/v1/info", false, i); + for (size_t j = 0; j <= template->response.data->len; j++) { + if (j == 0 && i > 0) + continue; // All zero-length prefixes are identical, skip after first time + current = malloc(sizeof(struct test_def)); + if (prev != NULL) + prev->next = current; + else + head = current; + current->prev = prev; + prev = current; + + current->num_headers = i; + current->prefix_len = j; + current->full_len = template->response.data->len; + current->instance = NULL; + current->next = NULL; + current->use_cr = false; + current->completed = false; + sprintf( + current->name, "/api/v1/info@%zu,%zu/%zu+%d", current->num_headers, current->prefix_len, + current->full_len,false); + num_tests++; + } + } + + struct CMUnitTest *tests = calloc(num_tests, sizeof(struct CMUnitTest)); + current = head; + for (size_t i = 0; i < num_tests; i++) { + tests[i].name = current->name; + tests[i].test_func = api_info; + tests[i].setup_func = NULL; + tests[i].teardown_func = NULL; + tests[i].initial_state = NULL; + current = current->next; + } + + printf("Setup %zu tests in %p\n", num_tests, head); + shared_test_state = head; + int fails = _cmocka_run_group_tests("web_api", tests, num_tests, NULL, NULL); + free(tests); + destroy_web_client(template); + current = head; + while (current != NULL) { + struct test_def *c = current; + current = current->next; + if (c->instance != NULL) // Clean up resources from tests that failed + destroy_web_client(c->instance); + free(c); + } + if (localhost!=NULL) + free(localhost); + return fails; +} + +/* Raw notes for the cases that we did not use in the unit testing suite. + Leaving them here instead of deleting them in-case we expand the suite during the + work on the URL parser. + + Any ' ' in the URI -> invalid response (Description in 5.1 of RFC2616) + Characters that can't be in paths #;? + "GET /apb/../api/v1/info" HTTP/1.1\r\n" + + https://github.com/uriparser/uriparser/blob/uriparser-0.9.3/test/FourSuite.cpp + Not clear why some of these are illegal -> reserved chars? + + ASSERT_TRUE(testBadUri("beepbeep\x07\x07", 8)); + ASSERT_TRUE(testBadUri("\n", 0)); + ASSERT_TRUE(testBadUri("::", 0)); // not OK, per Roy Fielding on the W3C uri list on 2004-04-01 + + // the following test cases are from a Perl script by David A. Wheeler + // at http://www.dwheeler.com/secure-programs/url.pl + ASSERT_TRUE(testBadUri("http://www yahoo.com", 10)); + ASSERT_TRUE(testBadUri("http://www.yahoo.com/hello world/", 26)); + ASSERT_TRUE(testBadUri("http://www.yahoo.com/yelp.html#\"", 31)); + + // the following test cases are from a Haskell program by Graham Klyne + // at http://www.ninebynine.org/Software/HaskellUtils/Network/URITest.hs + ASSERT_TRUE(testBadUri("[2010:836B:4179::836B:4179]", 0)); + ASSERT_TRUE(testBadUri(" ", 0)); + ASSERT_TRUE(testBadUri("%", 1)); + ASSERT_TRUE(testBadUri("A%Z", 2)); + ASSERT_TRUE(testBadUri("%ZZ", 1)); + ASSERT_TRUE(testBadUri("%AZ", 2)); + ASSERT_TRUE(testBadUri("A C", 1)); + ASSERT_TRUE(testBadUri("A\\'C", 1)); // r"A\'C" + ASSERT_TRUE(testBadUri("A`C", 1)); + ASSERT_TRUE(testBadUri("AC", 1)); + ASSERT_TRUE(testBadUri("A^C", 1)); + ASSERT_TRUE(testBadUri("A\\\\C", 1)); // r'A\\C' + ASSERT_TRUE(testBadUri("A{C", 1)); + ASSERT_TRUE(testBadUri("A|C", 1)); + ASSERT_TRUE(testBadUri("A}C", 1)); + ASSERT_TRUE(testBadUri("A[C", 1)); + ASSERT_TRUE(testBadUri("A]C", 1)); + ASSERT_TRUE(testBadUri("A[**]C", 1)); + ASSERT_TRUE(testBadUri("http://[xyz]/", 8)); + ASSERT_TRUE(testBadUri("http://]/", 7)); + ASSERT_TRUE(testBadUri("http://example.org/[2010:836B:4179::836B:4179]", 19)); + ASSERT_TRUE(testBadUri("http://example.org/abc#[2010:836B:4179::836B:4179]", 23)); + ASSERT_TRUE(testBadUri("http://example.org/xxx/[qwerty]#a[b]", 23)); + + // from a post to the W3C uri list on 2004-02-17 + // breaks at 22 instead of 17 because everything up to that point is a valid userinfo + ASSERT_TRUE(testBadUri("http://w3c.org:80path1/path2", 22)); + +*/ + +int main(void) +{ + debug_flags = 0xffffffffffff; + int fails = 0; + fails += api_info_launcher(); + + return fails; +} diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c new file mode 100644 index 0000000..73ac15d --- /dev/null +++ b/web/api/web_api_v1.c @@ -0,0 +1,1063 @@ +// 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; +} api_v1_data_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_MIN2MAX} + , {"ms" , 0 , RRDR_OPTION_MILLISECONDS} + , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS} + , {"abs" , 0 , RRDR_OPTION_ABSOLUTE} + , {"absolute" , 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} + , {"showcustomvars" , 0 , RRDR_OPTION_CUSTOM_VARS} + , {"allow_past" , 0 , RRDR_OPTION_ALLOW_PAST} + , { NULL, 0, 0} +}; + +static struct { + const char *name; + uint32_t hash; + uint32_t 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_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} + , { NULL, 0, 0} +}; + +static struct { + const char *name; + uint32_t hash; + uint32_t value; +} api_v1_data_google_formats[] = { + // this is not 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} + , { NULL, 0, 0} +}; + +void web_client_api_v1_init(void) { + int i; + + for(i = 0; api_v1_data_options[i].name ; i++) + api_v1_data_options[i].hash = simple_hash(api_v1_data_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); + + web_client_api_v1_init_grouping(); + + 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); + if(fd != -1) { + char buf[GUID_LEN + 1]; + if(read(fd, buf, GUID_LEN) != GUID_LEN) + error("Failed to read management API key from '%s'", api_key_filename); + else { + buf[GUID_LEN] = '\0'; + if(regenerate_guid(buf, guid) == -1) { + 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]) { + 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, 444); + if(fd == -1) + fatal("Cannot create unique management API key file '%s'. Please fix this.", api_key_filename); + + if(write(fd, guid, GUID_LEN) != GUID_LEN) + fatal("Cannot write the unique management API key file '%s'. Please fix this.", api_key_filename); + + close(fd); + } + + return guid; +} + +void web_client_api_v1_management_init(void) { + api_secret = get_mgmt_api_key(); +} + +inline uint32_t web_client_api_request_v1_data_options(char *o) { + uint32_t ret = 0x00000000; + char *tok; + + while(o && *o && (tok = mystrsep(&o, ", |"))) { + if(!*tok) continue; + + uint32_t hash = simple_hash(tok); + int i; + for(i = 0; api_v1_data_options[i].name ; i++) { + if (unlikely(hash == api_v1_data_options[i].hash && !strcmp(tok, api_v1_data_options[i].name))) { + ret |= api_v1_data_options[i].value; + break; + } + } + } + + return ret; +} + +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 = mystrsep(&url, "&"); + if (!value || !*value) continue; + + if(!strcmp(value, "all")) all = 1; + else if(!strcmp(value, "active")) 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->contenttype = 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->contenttype = 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 = mystrsep(&url, "&"); + if(!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + 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); + 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->contenttype = 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) { + uint32_t after = 0; + + while(url) { + char *value = mystrsep(&url, "&"); + if (!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0); + } + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + health_alarm_log2json(host, w->response.data, after); + 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 = mystrsep(&url, "&"); + if(!value || !*value) continue; + + char *name = mystrsep(&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->contenttype = CT_APPLICATION_JSON; + st->last_accessed_time = now_realtime_sec(); + callback(st, 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); +} + +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->contenttype = CT_APPLICATION_JSON; + charts2json(host, w->response.data, 0, 0); + return HTTP_RESP_OK; +} + +inline int web_client_api_request_v1_archivedcharts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + (void)url; + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; +#ifdef ENABLE_DBENGINE + if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) + sql_rrdset2json(host, w->response.data); +#endif + 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); +} + +void fix_google_param(char *s) { + if(unlikely(!s)) return; + + for( ; *s ;s++) { + if(!isalnum(*s) && *s != '.' && *s != '_' && *s != '-') + *s = '_'; + } +} + +// returns the HTTP code +inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) { + 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 + , *before_str = NULL + , *after_str = NULL + , *group_time_str = NULL + , *points_str = NULL + , *context = NULL + , *chart_label_key = NULL; + + int group = RRDR_GROUPING_AVERAGE; + uint32_t format = DATASOURCE_JSON; + uint32_t options = 0x00000000; + + while(url) { + char *value = mystrsep(&url, "&"); + if(!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + 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")) chart = value; + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!dimensions) dimensions = buffer_create(100); + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, 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, "gtime")) group_time_str = value; + else if(!strcmp(name, "group")) { + group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + } + else if(!strcmp(name, "format")) { + format = web_client_api_request_v1_data_format(value); + } + else if(!strcmp(name, "options")) { + options |= web_client_api_request_v1_data_options(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 = mystrsep(&value, ";"); + if(!tqx_value || !*tqx_value) continue; + + tqx_name = mystrsep(&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; + } + } + } + + // 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; + + if((!chart || !*chart) && (!context)) { + buffer_sprintf(w->response.data, "No chart id is given at the request."); + goto cleanup; + } + + struct context_param *context_param_list = NULL; + if (context && !chart) { + RRDSET *st1; + uint32_t context_hash = simple_hash(context); + + rrdhost_rdlock(host); + rrdset_foreach_read(st1, host) { + if (st1->hash_context == context_hash && !strcmp(st1->context, context) && + (!chart_label_key || rrdset_contains_label_keylist(st1, chart_label_key))) + build_context_param_list(&context_param_list, st1); + } + rrdhost_unlock(host); + if (likely(context_param_list && context_param_list->rd)) // Just set the first one + st = context_param_list->rd->rrdset; + } + else { + st = rrdset_find(host, chart); + if (!st) + st = rrdset_find_byname(host, chart); + if (likely(st)) + st->last_accessed_time = now_realtime_sec(); + } + + if (!st && !context_param_list) { + if (context && !chart) { + if (!chart_label_key) { + buffer_strcat(w->response.data, "Context is not found: "); + buffer_strcat_htmlescape(w->response.data, context); + } else { + buffer_strcat(w->response.data, "Context: "); + buffer_strcat_htmlescape(w->response.data, context); + buffer_strcat(w->response.data, " or chart label key: "); + buffer_strcat_htmlescape(w->response.data, chart_label_key); + buffer_strcat(w->response.data, " not found"); + } + } + else { + buffer_strcat(w->response.data, "Chart is not found: "); + buffer_strcat_htmlescape(w->response.data, chart); + } + ret = HTTP_RESP_NOT_FOUND; + goto cleanup; + } + + 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; + long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0; + + debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'" + , w->id + , chart + , (dimensions)?buffer_tostring(dimensions):"" + , after + , before + , points + , group + , format + , options + ); + + if(outFileName && *outFileName) { + buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); + 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"; + + 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:'%ld',table:", + responseHandler, google_version, google_reqId, st->last_updated.tv_sec); + } + else if(format == DATASOURCE_JSONP) { + if(responseHandler == NULL) + responseHandler = "callback"; + + buffer_strcat(w->response.data, responseHandler); + buffer_strcat(w->response.data, "("); + } + + ret = rrdset2anything_api_v1(st, w->response.data, dimensions, format, points, after, before, group, group_time + , options, &last_timestamp_in_data, context_param_list, chart_label_key); + + free_context_param_list(&context_param_list); + + 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, ");"); + + cleanup: + 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&machine=${machine_guid}&name=${hostname}&url=${url}&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"); +*/ + } + + char person_guid[GUID_LEN + 1] = ""; + + 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 *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "="); + if(cookie) + strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36); + + 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 = mystrsep(&url, "&"); + if (!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if (!name || !*name) continue; + if (!value || !*value) continue; + + 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 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 error("unused registry URL parameter '%s' with value '%s'", name, value); +#endif /* NETDATA_INTERNAL_CHECKS */ + } + + if(unlikely(respect_web_browser_do_not_track_policy && web_client_has_donottrack(w))) { + 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; + } + + if(unlikely(action == 'H')) { + // HELLO request, dashboard ACL + if(unlikely(!web_client_can_access_dashboard(w))) + return web_client_permission_denied(w); + } + else { + // everything else, registry ACL + if(unlikely(!web_client_can_access_registry(w))) + return web_client_permission_denied(w); + } + + switch(action) { + case 'A': + if(unlikely(!machine_guid || !machine_url || !url_name)) { + 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)) { + 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(!machine_guid || !machine_url || !search_machine_guid)) { + error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", 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, machine_guid, machine_url, search_machine_guid, now_realtime_sec()); + + case 'W': + if(unlikely(!machine_guid || !machine_url || !to_person_guid)) { + 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); + + 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; + } +} + +static inline void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb) { + int alarm_normal = 0, alarm_warn = 0, alarm_crit = 0; + RRDCALC *rc; + rrdhost_rdlock(host); + for(rc = host->alarms; rc ; rc = rc->next) { + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) + continue; + + switch(rc->status) { + case RRDCALC_STATUS_WARNING: + alarm_warn++; + break; + case RRDCALC_STATUS_CRITICAL: + alarm_crit++; + break; + default: + alarm_normal++; + } + } + rrdhost_unlock(host); + buffer_sprintf(wb, "\t\t\"normal\": %d,\n", alarm_normal); + buffer_sprintf(wb, "\t\t\"warning\": %d,\n", alarm_warn); + buffer_sprintf(wb, "\t\t\"critical\": %d\n", alarm_crit); +} + +static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) { + RRDHOST *host; + int count = 0; + + buffer_strcat(wb, "\t\"mirrored_hosts\": [\n"); + rrd_rdlock(); + rrdhost_foreach_read(host) { + if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) + continue; + if (count > 0) + buffer_strcat(wb, ",\n"); + + buffer_sprintf(wb, "\t\t\"%s\"", host->hostname); + count++; + } + + buffer_strcat(wb, "\n\t],\n\t\"mirrored_hosts_status\": [\n"); + count = 0; + rrdhost_foreach_read(host) + { + if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) + continue; + if (count > 0) + buffer_strcat(wb, ",\n"); + + netdata_mutex_lock(&host->receiver_lock); + buffer_sprintf( + wb, "\t\t{ \"guid\": \"%s\", \"reachable\": %s, \"claim_id\": ", host->machine_guid, + (host->receiver || host == localhost) ? "true" : "false"); + netdata_mutex_unlock(&host->receiver_lock); + + rrdhost_aclk_state_lock(host); + if (host->aclk_state.claimed_id) + buffer_sprintf(wb, "\"%s\" }", host->aclk_state.claimed_id); + else + buffer_strcat(wb, "null }"); + rrdhost_aclk_state_unlock(host); + + count++; + } + rrd_unlock(); + + buffer_strcat(wb, "\n\t],\n"); +} + +inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) { + char tabs[11]; + + if (indentation > 10) + indentation = 10; + + tabs[0] = '\0'; + while (indentation) { + strcat(tabs, "\t"); + indentation--; + } + + int count = 0; + rrdhost_rdlock(host); + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { + if(count > 0) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, tabs); + + char value[CONFIG_MAX_VALUE * 2 + 1]; + sanitize_json_string(value, label->value, CONFIG_MAX_VALUE * 2); + buffer_sprintf(wb, "\"%s\": \"%s\"", label->key, value); + + count++; + } + buffer_strcat(wb, "\n"); + netdata_rwlock_unlock(&host->labels.labels_rwlock); + rrdhost_unlock(host); +} + +extern int aclk_connected; +inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) +{ + buffer_strcat(wb, "{\n"); + buffer_sprintf(wb, "\t\"version\": \"%s\",\n", host->program_version); + buffer_sprintf(wb, "\t\"uid\": \"%s\",\n", host->machine_guid); + + web_client_api_request_v1_info_mirrored_hosts(wb); + + buffer_strcat(wb, "\t\"alarms\": {\n"); + web_client_api_request_v1_info_summary_alarm_statuses(host, wb); + buffer_strcat(wb, "\t},\n"); + + buffer_sprintf(wb, "\t\"os_name\": \"%s\",\n", (host->system_info->host_os_name) ? host->system_info->host_os_name : ""); + buffer_sprintf(wb, "\t\"os_id\": \"%s\",\n", (host->system_info->host_os_id) ? host->system_info->host_os_id : ""); + buffer_sprintf(wb, "\t\"os_id_like\": \"%s\",\n", (host->system_info->host_os_id_like) ? host->system_info->host_os_id_like : ""); + buffer_sprintf(wb, "\t\"os_version\": \"%s\",\n", (host->system_info->host_os_version) ? host->system_info->host_os_version : ""); + buffer_sprintf(wb, "\t\"os_version_id\": \"%s\",\n", (host->system_info->host_os_version_id) ? host->system_info->host_os_version_id : ""); + buffer_sprintf(wb, "\t\"os_detection\": \"%s\",\n", (host->system_info->host_os_detection) ? host->system_info->host_os_detection : ""); + buffer_sprintf(wb, "\t\"cores_total\": \"%s\",\n", (host->system_info->host_cores) ? host->system_info->host_cores : ""); + buffer_sprintf(wb, "\t\"total_disk_space\": \"%s\",\n", (host->system_info->host_disk_space) ? host->system_info->host_disk_space : ""); + buffer_sprintf(wb, "\t\"cpu_freq\": \"%s\",\n", (host->system_info->host_cpu_freq) ? host->system_info->host_cpu_freq : ""); + buffer_sprintf(wb, "\t\"ram_total\": \"%s\",\n", (host->system_info->host_ram_total) ? host->system_info->host_ram_total : ""); + + if (host->system_info->container_os_name) + buffer_sprintf(wb, "\t\"container_os_name\": \"%s\",\n", host->system_info->container_os_name); + if (host->system_info->container_os_id) + buffer_sprintf(wb, "\t\"container_os_id\": \"%s\",\n", host->system_info->container_os_id); + if (host->system_info->container_os_id_like) + buffer_sprintf(wb, "\t\"container_os_id_like\": \"%s\",\n", host->system_info->container_os_id_like); + if (host->system_info->container_os_version) + buffer_sprintf(wb, "\t\"container_os_version\": \"%s\",\n", host->system_info->container_os_version); + if (host->system_info->container_os_version_id) + buffer_sprintf(wb, "\t\"container_os_version_id\": \"%s\",\n", host->system_info->container_os_version_id); + if (host->system_info->container_os_detection) + buffer_sprintf(wb, "\t\"container_os_detection\": \"%s\",\n", host->system_info->container_os_detection); + if (host->system_info->is_k8s_node) + buffer_sprintf(wb, "\t\"is_k8s_node\": \"%s\",\n", host->system_info->is_k8s_node); + + buffer_sprintf(wb, "\t\"kernel_name\": \"%s\",\n", (host->system_info->kernel_name) ? host->system_info->kernel_name : ""); + buffer_sprintf(wb, "\t\"kernel_version\": \"%s\",\n", (host->system_info->kernel_version) ? host->system_info->kernel_version : ""); + buffer_sprintf(wb, "\t\"architecture\": \"%s\",\n", (host->system_info->architecture) ? host->system_info->architecture : ""); + buffer_sprintf(wb, "\t\"virtualization\": \"%s\",\n", (host->system_info->virtualization) ? host->system_info->virtualization : ""); + buffer_sprintf(wb, "\t\"virt_detection\": \"%s\",\n", (host->system_info->virt_detection) ? host->system_info->virt_detection : ""); + buffer_sprintf(wb, "\t\"container\": \"%s\",\n", (host->system_info->container) ? host->system_info->container : ""); + buffer_sprintf(wb, "\t\"container_detection\": \"%s\",\n", (host->system_info->container_detection) ? host->system_info->container_detection : ""); + + buffer_strcat(wb, "\t\"host_labels\": {\n"); + host_labels2json(host, wb, 2); + buffer_strcat(wb, "\t},\n"); + + buffer_strcat(wb, "\t\"collectors\": ["); + chartcollectors2json(host, wb); + buffer_strcat(wb, "\n\t],\n"); + +#ifdef DISABLE_CLOUD + buffer_strcat(wb, "\t\"cloud-enabled\": false,\n"); +#else + buffer_sprintf(wb, "\t\"cloud-enabled\": %s,\n", + appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", 1) ? "true" : "false"); +#endif + +#ifdef ENABLE_ACLK + buffer_strcat(wb, "\t\"cloud-available\": true,\n"); +#else + buffer_strcat(wb, "\t\"cloud-available\": false,\n"); +#endif + char *agent_id = is_agent_claimed(); + if (agent_id == NULL) + buffer_strcat(wb, "\t\"agent-claimed\": false,\n"); + else { + buffer_strcat(wb, "\t\"agent-claimed\": true,\n"); + freez(agent_id); + } +#ifdef ENABLE_ACLK + if (aclk_connected) + buffer_strcat(wb, "\t\"aclk-available\": true\n"); + else +#endif + buffer_strcat(wb, "\t\"aclk-available\": false\n"); // Intentionally valid with/without #ifdef above + + buffer_strcat(wb, "}"); + return 0; +} + +inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) { + (void)url; + if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED; + BUFFER *wb = w->response.data; + buffer_flush(wb); + wb->contenttype = CT_APPLICATION_JSON; + + web_client_api_request_v1_info_fill_buffer(host, wb); + + buffer_no_cacheable(wb); + return HTTP_RESP_OK; +} + +static struct api_command { + const char *command; + uint32_t hash; + WEB_CLIENT_ACL acl; + int (*callback)(RRDHOST *host, struct web_client *w, char *url); +} api_commands[] = { + { "info", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_info }, + { "data", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_data }, + { "chart", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_chart }, + { "charts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_charts }, + { "archivedcharts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_archivedcharts }, + + // registry checks the ACL by itself, so we allow everything + { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry }, + + // badges can be fetched with both dashboard and badge permissions + { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD|WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge }, + + { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms }, + { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms_values }, + { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_log }, + { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_variables }, + { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_count }, + { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_allmetrics }, + { "manage/health", 0, WEB_CLIENT_ACL_MGMT, web_client_api_request_v1_mgmt_health }, + // terminator + { NULL, 0, WEB_CLIENT_ACL_NONE, NULL }, +}; + +inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) { + static int initialized = 0; + int i; + + if(unlikely(initialized == 0)) { + initialized = 1; + + for(i = 0; api_commands[i].command ; i++) + api_commands[i].hash = simple_hash(api_commands[i].command); + } + + // get the command + 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(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, (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, url); + return HTTP_RESP_NOT_FOUND; + } + else { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Which API v1 command?"); + return HTTP_RESP_BAD_REQUEST; + } +} diff --git a/web/api/web_api_v1.h b/web/api/web_api_v1.h new file mode 100644 index 0000000..445b0e4 --- /dev/null +++ b/web/api/web_api_v1.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WEB_API_V1_H +#define NETDATA_WEB_API_V1_H 1 + +#include "daemon/common.h" +#include "web/api/badges/web_buffer_svg.h" +#include "web/api/formatters/rrd2json.h" +#include "web/api/health/health_cmdapi.h" + +extern uint32_t web_client_api_request_v1_data_options(char *o); +extern uint32_t web_client_api_request_v1_data_format(char *name); +extern uint32_t web_client_api_request_v1_data_google_format(char *name); + +extern int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)); +extern int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_archivedcharts(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb); +extern void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation); + +extern void web_client_api_v1_init(void); +extern void web_client_api_v1_management_init(void); + +extern char *api_secret; + +#endif //NETDATA_WEB_API_V1_H diff --git a/web/gui/.well-known/dnt/cookies b/web/gui/.well-known/dnt/cookies new file mode 100644 index 0000000..b7c70e5 --- /dev/null +++ b/web/gui/.well-known/dnt/cookies @@ -0,0 +1,14 @@ +{ + "tracking": "T", + "compliance": ["https://github.com/netdata/netdata/wiki/cookies#compliance"], + "qualifiers": "afc", + "controller": ["https://github.com/netdata/netdata/wiki/cookies#controller"], + "same-party": [ + "my-netdata.io", + "mynetdata.io", + "netdata.online", + "netdata.rocks", + "registry.my-netdata.io" + ], + "policy": "https://github.com/netdata/netdata/wiki/cookies#policy", +} diff --git a/web/gui/Makefile.am b/web/gui/Makefile.am new file mode 100644 index 0000000..c69380f --- /dev/null +++ b/web/gui/Makefile.am @@ -0,0 +1,189 @@ +# +# Copyright (C) 2015 Alon Bar-Lev +# SPDX-License-Identifier: GPL-3.0-or-later +# +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in +CLEANFILES = \ + version.txt \ + $(NULL) + +DASHBOARD_JS_FILES = \ + src/dashboard.js/prologue.js.inc \ + src/dashboard.js/utils.js \ + src/dashboard.js/server-detection.js \ + src/dashboard.js/dependencies.js \ + src/dashboard.js/error-handling.js \ + src/dashboard.js/compatibility.js \ + src/dashboard.js/xss.js \ + src/dashboard.js/colors.js \ + src/dashboard.js/units-conversion.js \ + src/dashboard.js/options.js \ + src/dashboard.js/localstorage.js \ + src/dashboard.js/timeout.js \ + src/dashboard.js/themes.js \ + src/dashboard.js/charting/dygraph.js \ + src/dashboard.js/charting/sparkline.js \ + src/dashboard.js/charting/google-charts.js \ + src/dashboard.js/charting/gauge.js \ + src/dashboard.js/charting/easy-pie-chart.js \ + src/dashboard.js/charting/d3pie.js \ + src/dashboard.js/charting/d3.js \ + src/dashboard.js/charting/peity.js \ + src/dashboard.js/charting/textonly.js \ + src/dashboard.js/charting.js \ + src/dashboard.js/chart-registry.js \ + src/dashboard.js/common.js \ + src/dashboard.js/main.js \ + src/dashboard.js/alarms.js \ + src/dashboard.js/registry.js \ + src/dashboard.js/boot.js \ + src/dashboard.js/epilogue.js.inc \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(DASHBOARD_JS_FILES) \ + $(NULL) + +dist_web_DATA = \ + demo.html \ + demo2.html \ + demosites.html \ + demosites2.html \ + dashboard.html \ + dashboard.js \ + dashboard_info.js \ + dashboard_info_custom_example.js \ + dashboard.css \ + dashboard.slate.css \ + favicon.ico \ + goto-host-from-alarm.html \ + index.html \ + main.css \ + main.js \ + console.html \ + infographic.html \ + robots.txt \ + refresh-badges.js \ + sitemap.xml \ + tv.html \ + dash-example.html \ + version.txt \ + $(NULL) + +webolddir=$(webdir)/old +dist_webold_DATA = \ + old/index.html \ + $(NULL) + +webstaticdir=$(webdir)/static/img +dist_webstatic_DATA = \ + static/img/netdata-logomark.svg \ + $(NULL) + +weblibdir=$(webdir)/lib +dist_weblib_DATA = \ + lib/bootstrap-3.3.7.min.js \ + lib/bootstrap-slider-10.0.0.min.js \ + lib/bootstrap-table-1.11.0.min.js \ + lib/bootstrap-table-export-1.11.0.min.js \ + lib/bootstrap-toggle-2.2.2.min.js \ + lib/clipboard-polyfill-be05dad.js \ + lib/d3-4.12.2.min.js \ + lib/d3pie-0.2.1-netdata-3.js \ + lib/dygraph-c91c859.min.js \ + lib/dygraph-smooth-plotter-c91c859.js \ + lib/fontawesome-all-5.0.1.min.js \ + lib/gauge-1.3.2.min.js \ + lib/jquery-2.2.4.min.js \ + lib/jquery.easypiechart-97b5824.min.js \ + lib/jquery.peity-3.2.0.min.js \ + lib/jquery.sparkline-2.1.2.min.js \ + lib/lz-string-1.4.4.min.js \ + lib/pako-1.0.6.min.js \ + lib/perfect-scrollbar-0.6.15.min.js \ + lib/tableExport-1.6.0.min.js \ + $(NULL) + +webcssdir=$(webdir)/css +dist_webcss_DATA = \ + css/morris-0.5.1.css \ + css/bootstrap-3.3.7.css \ + css/bootstrap-theme-3.3.7.min.css \ + css/bootstrap-slate-flat-3.3.7.css \ + css/bootstrap-slider-10.0.0.min.css \ + css/bootstrap-toggle-2.2.2.min.css \ + css/c3-0.4.18.min.css \ + $(NULL) + +webfontsdir=$(webdir)/fonts +dist_webfonts_DATA = \ + fonts/glyphicons-halflings-regular.eot \ + fonts/glyphicons-halflings-regular.svg \ + fonts/glyphicons-halflings-regular.ttf \ + fonts/glyphicons-halflings-regular.woff \ + fonts/glyphicons-halflings-regular.woff2 \ + $(NULL) + +webimagesdir=$(webdir)/images +dist_webimages_DATA = \ + images/netdata-logomark.svg \ + images/alert-128-orange.png \ + images/alert-128-red.png \ + images/alert-multi-size-orange.ico \ + images/alert-multi-size-red.ico \ + images/animated.gif \ + images/check-mark-2-128-green.png \ + images/check-mark-2-multi-size-green.ico \ + images/netdata.svg \ + images/post.png \ + images/android-icon-36x36.png \ + images/android-icon-48x48.png \ + images/android-icon-72x72.png \ + images/android-icon-96x96.png \ + images/android-icon-144x144.png \ + images/android-icon-192x192.png \ + images/apple-icon-57x57.png \ + images/apple-icon-60x60.png \ + images/apple-icon-72x72.png \ + images/apple-icon-76x76.png \ + images/apple-icon-114x114.png \ + images/apple-icon-120x120.png \ + images/apple-icon-144x144.png \ + images/apple-icon-152x152.png \ + images/apple-icon-180x180.png \ + images/apple-icon-precomposed.png \ + images/apple-icon.png \ + images/favicon-16x16.png \ + images/favicon-32x32.png \ + images/favicon-96x96.png \ + images/favicon.ico \ + images/ms-icon-70x70.png \ + images/ms-icon-144x144.png \ + images/ms-icon-150x150.png \ + images/ms-icon-310x310.png \ + images/banner-icon-144x144.png \ + $(NULL) + +dashboard.js: $(DASHBOARD_JS_FILES) + if test -f $@; then rm -f $@; fi + cat $(DASHBOARD_JS_FILES) > $@.tmp && mv $@.tmp $@ + +webwellknowndir=$(webdir)/.well-known +dist_webwellknown_DATA = \ + $(NULL) + +webdntdir=$(webdir)/.well-known/dnt +dist_webdnt_DATA = \ + .well-known/dnt/cookies \ + $(NULL) + +version.txt: + if test -d "$(top_srcdir)/.git"; then \ + git --git-dir="$(top_srcdir)/.git" log -n 1 --format=%H; \ + fi > $@.tmp + test -s $@.tmp || echo 0 > $@.tmp + mv $@.tmp $@ + +# regenerate these files, even if they are up to date +.PHONY: version.txt dashboard.js diff --git a/web/gui/README.md b/web/gui/README.md new file mode 100644 index 0000000..166cea7 --- /dev/null +++ b/web/gui/README.md @@ -0,0 +1,169 @@ + + +# Local Agent dashboard + +The local Netdata Agent dashboard is the heart of Netdata's performance troubleshooting toolkit. You've probably seen it +before: + +![The Netdata dashboard in +action](https://user-images.githubusercontent.com/1153921/101513938-fae28380-3939-11eb-9434-8ad86a39be62.gif) + +Learn more about how dashboards work and how they're populated using the `dashboards.js` file in our [web dashboards +overview](/web/README.md). + +By default, Netdata starts a web server for its dashboard at port `19999`. Open up your web browser of choice and +navigate to `http://NODE:19999`, replacing `NODE` with the IP address or hostname of your Agent. If you're unsure, try +`http://localhost:19999` first. + +Netdata uses an [internal, static-threaded web server](/web/server/README.md) to host the HTML, CSS, and JavaScript +files that make up the local Agent dashboard. You don't have to configure anything to access it, although you can adjust +[your settings](/web/server/README.md#other-netdataconf-web-section-options) in the `netdata.conf` file, or run Netdata +behind an [Nginx proxy](https://learn.netdata.cloud/docs/agent/running-behind-nginx), and so on. + +## Navigating the local dashboard + +Beyond charts, the local dashboard can be broken down into three key areas: + +1. [**Sections**](#sections) +2. [**Time & date picker**](#time--date-picker) +3. [**Metrics menus/submenus**](#metrics-menus) +4. [**Netdata Cloud menus: Spaces, War Rooms, and Visited nodes)**](#cloud-menus-spaces-war-rooms-and-visited-nodes) + +![Annotated screenshot of the local Agent +dashboard](https://user-images.githubusercontent.com/1153921/101509403-f7e59400-3935-11eb-9abd-cbecfa3ee49a.png) + +### Sections + +Netdata is broken up into multiple **sections**, such as **System Overview**, +**CPU**, **Disk**, and more. Inside each section you'll find a number of charts, +broken down into [contexts](/web/README.md#contexts) and +[families](/web/README.md#families). + +An example of the **Memory** section on a Linux desktop system. + +![Screenshot of the Memory section of the Netdata +dashboard](https://user-images.githubusercontent.com/1153921/101508423-e354cc00-3934-11eb-9b33-3ad57a5988b4.png) + +All sections and their associated charts appear on a single page, so all you need to do to view different sections is +scroll up and down. But it's usually quicker to use the [menus](#metrics-menus). + +### Time & date picker + +The local dashboard features a time & date picker to help you visualize specific timeframes of historical metrics. The +picker chooses an appropriate default to always show per-second granularity based on the width of your browser's +viewport. + +![The time & date picker in the local Agent +dashboard](https://user-images.githubusercontent.com/1153921/101507784-2c585080-3934-11eb-9d6e-eff30b8553e4.png) + +Use the Quick Selector to show metrics from the last 5 minutes, 15 minutes, 30 minutes, 2 hours, 6 hours, or 12 hours. + +Beneath the Quick Selector is an input field and dropdown you use in combination to select a specific timeframe of +minutes, hours, days, or months. Enter a number and choose the appropriate unit of time. + +Use the calendar to select multiple days. Click on a date to begin the timeframe selection, then an ending date. + +Click **Apply** to re-render all visualizations with new metrics data, or **Clear** to restore the default timeframe. + +[Increase the metrics retention policy](/docs/store/change-metrics-storage.md) for your node to see more historical +timeframes. + +### Metrics menus + +**Metrics menus** appears on the right-hand side of the local Agent dashboard. Netdata generates a menu for each +section, and menus link to the section they're associated with. + +![A screenshot of metrics menus](https://user-images.githubusercontent.com/1153921/80834638-f08f2880-8ba5-11ea-99ae-f610b2885fd6.png) + +Most metrics menu items will contain several **submenu** entries, which represent any +[families](/web/README.md#families) from that section. Netdata automatically +generates these submenu entries. + +Here's a **Disks** menu with several submenu entries for each disk drive and +partition Netdata recognizes. + +![Screenshot of some metrics +submenus](https://user-images.githubusercontent.com/1153921/80834697-11577e00-8ba6-11ea-979c-92fd19cdb480.png) + +### Cloud menus (Spaces, War Rooms, and Visited nodes) + +The dashboard also features a menu related to Netdata Cloud functionality. You can view your existing Spaces or create +new ones via the left vertical column of boxes. This menu also displays the name of your current Space, shows a list of +any War Rooms you've added you your Space, and lists any notes you recently visited via their Agent dashboards. Click on +a War Room's name to jump to the Netdata Cloud web interface. + +![A screenshot of the Cloud +menus](https://user-images.githubusercontent.com/1153921/80837210-3f8b8c80-8bab-11ea-9c75-128c2d823ef8.png) + +If you want to know more about how Cloud populates this menu, and the Agent-Cloud integration at a high level, see our +document on [using the Agent with Netdata Cloud](/docs/agent-cloud.md). + +## Customizing the local dashboard + +Netdata stores information about individual charts in the `dashboard_info.js` +file. This file includes section and subsection headings, descriptions, colors, +titles, tooltips, and other information for Netdata to render on the dashboard. + +For example, here is how `dashboard_info.js` defines the **System Overview** +section. + +```javascript +netdataDashboard.menu = { + 'system': { + title: 'System Overview', + icon: '', + info: 'Overview of the key system metrics.' + }, +``` + +If you want to customize this information, you should avoid editing +`dashboard_info.js` directly. These changes are not persistent; Netdata will +overwrite the file when it's updated. Instead, you should create a new file with +your customizations. + +We created an example file at `dashboard_info_custom_example.js`. You can +copy this to a new file with a name of your choice in the `web/` directory. This +directory changes based on your operating system and installation method. If +you're on a Linux system, it should be at `/usr/share/netdata/web/`. + +```shell +cd /usr/share/netdata/web/ +sudo cp dashboard_info_custom_example.js your_dashboard_info_file.js +``` + +Edit the file with your customizations. For example: + +```javascript +customDashboard.menu = { + system: { + title: "Testing, testing, 1 2 3", + icon: '', + info: "This is overwritten info for the system overview section!" + } +}; +``` + +Finally, tell Netdata where you placed your customization file by replacing +`your_dashboard_info_file.js` below. + +```conf +[web] + custom dashboard_info.js = your_dashboard_info_file.js +``` + +Once you restart Netdata, refresh the dashboard to find your custom +configuration: + +![Screenshot of overwritten text from dashboard_info.js +file](https://user-images.githubusercontent.com/1153921/62798924-570e6c80-ba94-11e9-9578-869753bec39c.png) + +## Custom dashboards + +For information on creating custom dashboards from scratch, see the [custom dashboards](/web/gui/custom/README.md) or +[Atlassian Confluence dashboards](/web/gui/confluence/README.md) guides. + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fgui%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)]() diff --git a/web/gui/browserconfig.xml b/web/gui/browserconfig.xml new file mode 100644 index 0000000..32f4759 --- /dev/null +++ b/web/gui/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff diff --git a/web/gui/confluence/README.md b/web/gui/confluence/README.md new file mode 100644 index 0000000..2e929e7 --- /dev/null +++ b/web/gui/confluence/README.md @@ -0,0 +1,1019 @@ + + +# Atlassian Confluence dashboards + +With Netdata you can build **live, interactive, monitoring dashboards** directly on Atlassian's **Confluence** pages. + +I see you already asking "why should I do this?" + +Well... think a bit of it.... confluence is the perfect place for something like that: + +1. All the employees of your company already have access to it. + +2. Most probably you have already several spaces on confluence, one for each project or service. Adding live monitoring information there is ideal: everything in one place. Your users will just click on the page and instantly the monitoring page they need will appear with only the information they need to know. + +3. You can create monitoring pages for very specific purposes, hiding all the information that is too detailed for most users, or explaining in detail things that are difficult for them to understand. + +So, what can we expect? What can Netdata do on confluence? + +You will be surprised! **Everything a Netdata dashboard does!**. Example: + +![final-confluence4](https://user-images.githubusercontent.com/2662304/34366214-767fa4b8-eaa1-11e7-83af-0b9b9b72aa73.gif) + +Let me show you how. + +> Let's assume we have 2 web servers we want to monitor. We will create a simple dashboard with key information about them, directly on confluence. + +### Before you begin + +Most likely your confluence is accessible via HTTPS. So, you need to proxy your Netdata servers via an apache or nginx to make them HTTPS too. If your Confluence is HTTPS but your Netdata are not, you will not be able to fetch the Netdata content from the confluence page. The Netdata wiki has many examples for proxying Netdata through another web server. + +> So, make sure Netdata and Confluence can be accessed with the same protocol (**http**, or **https**). + +For our example, I will use these 2 servers: + +| server | url | +|------|---| +| Server 1 | | +| Server 2 | | + +I will use the first server for the static dashboard javascript files. + +--- + +Then, you need to enable the `html` plugin of confluence. We will add some plain html content on that page, and this plugin is required. + +### Create a new page + +Create a new confluence page and paste this into an `html` box: + +```html + + + +``` + +like this (type `{html` for the html box to appear - you need the confluence html plugin enabled): + +![screenshot from 2017-12-25 00-46-20](https://user-images.githubusercontent.com/2662304/34329541-1dd9077c-e90d-11e7-988d-6820be31ff3f.png) + +### Add a few badges + +Then, go to your Netdata and copy an alarm badge (the `` version of it): + +![copy-embed-badge](https://user-images.githubusercontent.com/2662304/34329562-dddea37e-e90d-11e7-9830-041a9f6a5984.gif) + +Then add another HTML box on the page, and paste it, like this: + +![screenshot from 2017-12-25 00-55-18](https://user-images.githubusercontent.com/2662304/34329569-4fc3d07c-e90e-11e7-8127-3127a21e1657.png) + +Hit **update** and you will get this: + +![screenshot from 2017-12-25 00-56-58](https://user-images.githubusercontent.com/2662304/34329573-8d4237cc-e90e-11e7-80bf-6c260456c690.png) + +This badge is now auto-refreshing. It will update itself based on the update frequency of the alarm. + +> Keep in mind you can add badges with custom Netdata queries too. Netdata automatically creates badges for all the +> alarms, but every chart, every dimension on every chart, can be used for a badge. And Netdata badges are quite +> powerful! Check [Creating Badges](/web/api/badges/README.md) for more information on badges. + +So, let's create a table and add this badge for both our web servers: + +![screenshot from 2017-12-25 01-06-10](https://user-images.githubusercontent.com/2662304/34329609-d3e9ab00-e90f-11e7-99df-884196347538.png) + +Now we get this: + +![screenshot from 2017-12-25 01-07-10](https://user-images.githubusercontent.com/2662304/34329615-f7dea286-e90f-11e7-9b6f-600215494f96.png) + +### Add a Netdata chart + +The simplest form of a chart is this (it adds the chart `web_log_nginx_netdata.response_statuses`, using 100% of the width, 150px height, and the last 10 minutes of data): + +```html +
+``` + +Add this to `html` block on confluence: + +![screenshot from 2017-12-25 01-13-15](https://user-images.githubusercontent.com/2662304/34329635-cf83ab0a-e910-11e7-85a3-b72ccc2d54e4.png) + +And you will get this: + +![screenshot from 2017-12-25 01-14-09](https://user-images.githubusercontent.com/2662304/34329640-efd15574-e910-11e7-9004-94487dcde154.png) + +> This chart is **alive**, fully interactive. You can drag it, pan it, zoom it, etc like you do on Netdata dashboards! + +Of course this too big. We need something smaller to add inside the table. Let's try this: + +```html +
+``` + +The chart name is shown on all Netdata charts, so just copy it from a Netdata dashboard. + +We will fetch the same chart from both servers. To define the server we also added `data-host=` with the URL of each server, like this (we also added `
` for a newline between the badge and the chart): + +![screenshot from 2017-12-25 01-25-05](https://user-images.githubusercontent.com/2662304/34329695-76fd2680-e912-11e7-9969-87f8d5b36145.png) + +Which gives us this: + +![screenshot from 2017-12-25 01-26-04](https://user-images.githubusercontent.com/2662304/34329700-989f0f2e-e912-11e7-8ac9-c78f82cfbdb0.png) + +Note the color difference. This is because Netdata automatically hides dimensions that are just zero (the frankfurt server has only successful requests). To instruct Netdata to disable this feature, we need to add another html fragment at the bottom of the page (make sure this is added after loading `dashboard.js`). So we edit the first block we added, and append a new ` + + + + +``` + +Now they match: + +![screenshot from 2017-12-25 01-30-14](https://user-images.githubusercontent.com/2662304/34329716-2ea83680-e913-11e7-847e-52b3f402aeb0.png) + +#### more options + +If you want to change the colors append `data-colors="#001122 #334455 #667788"`. The colors will be used for the dimensions top to bottom, as shown on a Netdata dashboard. Keep in mind the default Netdata dashboards hide by default all dimensions that are just zero, so enable them at the dashboard settings to see them all. + +You can get a percentage chart, by adding these on these charts: + +```html + data-append-options="percentage" + data-decimal-digits="0" + data-dygraph-valuerange="[0, 100]" + data-dygraph-includezero="true" + data-units="%" +``` + +The first line instructs Netdata to calculate the percentage of each dimension, the second strips any fractional digits, the third instructs the charting library to size the chart from 0 to 100, the next one instructs it to include 0 in the chart and the last changes the units of the chart to `%`. This is how it will look: + +![screenshot from 2017-12-25 01-45-39](https://user-images.githubusercontent.com/2662304/34329774-570ef990-e915-11e7-899f-eee939564aaf.png) + +You can make any number of charts have common min and max on the y-range by adding `common-min="NAME"` and `common-max="NAME"`, where `NAME` is anything you like. Keep in mind for best results all the charts with the same `NAME` should be visible at once, otherwise a not-visible chart will influence the range and until it is updated the range will not adapt. + +### Add gauges + +Let's now add a few gauges. The chart we added has several dimensions: `success`, `error`, `redirect`, `bad` and `other`. + +Let's say we want to add 2 gauges: + +1. `success` and `redirect` together, in blue +2. `error`, `bad` and `other` together, in orange + +We will add the following for each server. We have enclosed them in another a `
` because Confluence will wrap them if the page width is not enough to fit them. With that additional `
` they will always be next to each other. + +```html +
+
+
+``` + +Adding the above will give you this: + +![final-confluence](https://user-images.githubusercontent.com/2662304/34329813-636bb8de-e917-11e7-8cc7-19e197859008.gif) + +### Final source - for the confluence source editor + +If you enable the source editor of Confluence, you can paste the whole example (implementing the first image on this post and demonstrating everything discussed on this page): + +```html +

Monitoring the health of the web servers, by analyzing the response codes they send.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
LondonFrankfurtSan FranciscoToronto
+ last hour +
+ requests +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+ last
1 hour
+
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+ last 10
minutes
+
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+
+

+
+

+ +
]]> + +

+
+

+ +
+ last 1
minute
+
+
+

+
+

+ + +
+
]]>
+
+

+
+

+
+
+
+

+
+

+ + +
+
]]>
+
+

+
+

+
+
+
+

+
+

+ + +
+
]]>
+
+

+
+

+
+
+
+

+
+

+ + +
+
]]>
+
+

+
+

+
+
+ now + +
+

+
+

+ + +
+
+
+
]]> + +

+
+

+ +
+
+

+
+

+ + +
+
+
+
]]> + +

+
+

+ +
+
+

+
+

+ + +
+
+
+
]]> + +

+
+

+ +
+
+

+
+

+ + +
+
+
+
]]> + +

+
+

+ +
+

+
+

+

+
+

+ + +// don't load bootstrap - confluence does not need this +var netdataNoBootstrap = true; + +// select the web notifications to show on this dashboard +// var netdataShowAlarms = true; +// var netdataAlarmsRecipients = [ 'sysadmin', 'webmaster' ]; + + + + + +]]> + +

+
+

+
+ +
+
+
+
+ +
+
+
+``` + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fgui%2Fconfluence%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/web/gui/console.html b/web/gui/console.html new file mode 100644 index 0000000..b85b4ff --- /dev/null +++ b/web/gui/console.html @@ -0,0 +1,72 @@ + + + + + Netdata Console + + + + + + + + + + + + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/web/gui/css/bootstrap-3.3.7.css b/web/gui/css/bootstrap-3.3.7.css new file mode 100644 index 0000000..8c4db1f --- /dev/null +++ b/web/gui/css/bootstrap-3.3.7.css @@ -0,0 +1,6758 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * SPDX-License-Identifier: MIT + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/web/gui/css/bootstrap-slate-flat-3.3.7.css b/web/gui/css/bootstrap-slate-flat-3.3.7.css new file mode 100644 index 0000000..7ce384f --- /dev/null +++ b/web/gui/css/bootstrap-slate-flat-3.3.7.css @@ -0,0 +1,7101 @@ +/*! + * bootswatch v3.3.7 + * Homepage: http://bootswatch.com + * Copyright 2012-2016 Thomas Park + * Licensed under MIT + * SPDX-License-Identifier: MIT + * Based on Bootstrap +*/ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + text-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #c8c8c8; + background-color: #272b30; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #ffffff; + text-decoration: none; +} +a:hover, +a:focus { + color: #ffffff; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #1c1e22; + border: 1px solid #0c0d0e; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #1c1e22; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #7a8288; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + background-color: #f89406; + padding: .2em; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #7a8288; +} +.text-primary { + color: #7a8288; +} +a.text-primary:hover, +a.text-primary:focus { + color: #62686d; +} +.text-success { + color: #ffffff; +} +a.text-success:hover, +a.text-success:focus { + color: #e6e6e6; +} +.text-info { + color: #ffffff; +} +a.text-info:hover, +a.text-info:focus { + color: #e6e6e6; +} +.text-warning { + color: #ffffff; +} +a.text-warning:hover, +a.text-warning:focus { + color: #e6e6e6; +} +.text-danger { + color: #ffffff; +} +a.text-danger:hover, +a.text-danger:focus { + color: #e6e6e6; +} +.bg-primary { + color: #fff; + background-color: #7a8288; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #62686d; +} +.bg-success { + background-color: #62c462; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #42b142; +} +.bg-info { + background-color: #5bc0de; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #31b0d5; +} +.bg-warning { + background-color: #f89406; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #c67605; +} +.bg-danger { + background-color: #ee5f5b; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e9322d; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #1c1e22; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #7a8288; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #7a8288; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #7a8288; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #7a8288; + border-left: 0; + text-align: right; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #ffffff; + background-color: #333333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #3a3f44; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: #2e3338; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #7a8288; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #1c1e22; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #1c1e22; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #1c1e22; +} +.table .table { + background-color: #272b30; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #1c1e22; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #1c1e22; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #353a41; +} +.table-hover > tbody > tr:hover { + background-color: #49515a; +} +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #49515a; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #3e444c; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #62c462; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #4fbd4f; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #5bc0de; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #46b8da; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #f89406; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #df8505; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #ee5f5b; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ec4844; +} +.table-responsive { + overflow-x: auto; + min-height: 0.01%; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #1c1e22; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + padding: 0; + margin: 0; + border: 0; + min-width: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #c8c8c8; + border: 0; + border-bottom: 1px solid #1c1e22; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 9px; + font-size: 14px; + line-height: 1.42857143; + color: #272b30; +} +.form-control { + display: block; + width: 100%; + height: 38px; + padding: 8px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #272b30; + background-color: #ffffff; + background-image: none; + border: 1px solid #000000; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #7a8288; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #7a8288; +} +.form-control::-webkit-input-placeholder { + color: #7a8288; +} +.form-control::-ms-expand { + border: 0; + background-color: transparent; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #999999; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 38px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 54px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 9px; + padding-bottom: 9px; + margin-bottom: 0; + min-height: 34px; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-left: 0; + padding-right: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 54px; + padding: 14px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 54px; + line-height: 54px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 54px; + padding: 14px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 54px; + line-height: 54px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 54px; + min-height: 38px; + padding: 15px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 47.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 38px; + height: 38px; + line-height: 38px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 54px; + height: 54px; + line-height: 54px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #ffffff; +} +.has-success .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-success .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #62c462; +} +.has-success .form-control-feedback { + color: #ffffff; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #ffffff; +} +.has-warning .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-warning .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #f89406; +} +.has-warning .form-control-feedback { + color: #ffffff; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #ffffff; +} +.has-error .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-error .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #ee5f5b; +} +.has-error .form-control-feedback { + color: #ffffff; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #ffffff; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 9px; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 29px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 9px; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 15px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 0px solid transparent; + white-space: nowrap; + padding: 8px 12px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #ffffff; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #ffffff; + background-color: #3a3f44; + border-color: #3a3f44; +} +.btn-default:focus, +.btn-default.focus { + color: #ffffff; + background-color: #232628; + border-color: #000000; +} +.btn-default:hover { + color: #ffffff; + background-color: #232628; + border-color: #1e2023; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #ffffff; + background-color: #232628; + border-color: #1e2023; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #ffffff; + background-color: #121415; + border-color: #000000; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #3a3f44; + border-color: #3a3f44; +} +.btn-default .badge { + color: #3a3f44; + background-color: #ffffff; +} +.btn-primary { + color: #ffffff; + background-color: #7a8288; + border-color: #7a8288; +} +.btn-primary:focus, +.btn-primary.focus { + color: #ffffff; + background-color: #62686d; + border-color: #3e4245; +} +.btn-primary:hover { + color: #ffffff; + background-color: #62686d; + border-color: #5d6368; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #62686d; + border-color: #5d6368; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #ffffff; + background-color: #51565a; + border-color: #3e4245; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #7a8288; + border-color: #7a8288; +} +.btn-primary .badge { + color: #7a8288; + background-color: #ffffff; +} +.btn-success { + color: #ffffff; + background-color: #62c462; + border-color: #62c462; +} +.btn-success:focus, +.btn-success.focus { + color: #ffffff; + background-color: #42b142; + border-color: #2d792d; +} +.btn-success:hover { + color: #ffffff; + background-color: #42b142; + border-color: #40a940; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #42b142; + border-color: #40a940; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #ffffff; + background-color: #399739; + border-color: #2d792d; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #62c462; + border-color: #62c462; +} +.btn-success .badge { + color: #62c462; + background-color: #ffffff; +} +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #5bc0de; +} +.btn-info:focus, +.btn-info.focus { + color: #ffffff; + background-color: #31b0d5; + border-color: #1f7e9a; +} +.btn-info:hover { + color: #ffffff; + background-color: #31b0d5; + border-color: #2aabd2; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #31b0d5; + border-color: #2aabd2; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #ffffff; + background-color: #269abc; + border-color: #1f7e9a; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #5bc0de; +} +.btn-info .badge { + color: #5bc0de; + background-color: #ffffff; +} +.btn-warning { + color: #ffffff; + background-color: #f89406; + border-color: #f89406; +} +.btn-warning:focus, +.btn-warning.focus { + color: #ffffff; + background-color: #c67605; + border-color: #7c4a03; +} +.btn-warning:hover { + color: #ffffff; + background-color: #c67605; + border-color: #bc7005; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #c67605; + border-color: #bc7005; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #ffffff; + background-color: #a36104; + border-color: #7c4a03; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f89406; + border-color: #f89406; +} +.btn-warning .badge { + color: #f89406; + background-color: #ffffff; +} +.btn-danger { + color: #ffffff; + background-color: #ee5f5b; + border-color: #ee5f5b; +} +.btn-danger:focus, +.btn-danger.focus { + color: #ffffff; + background-color: #e9322d; + border-color: #b71713; +} +.btn-danger:hover { + color: #ffffff; + background-color: #e9322d; + border-color: #e82924; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #e9322d; + border-color: #e82924; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #ffffff; + background-color: #dc1c17; + border-color: #b71713; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #ee5f5b; + border-color: #ee5f5b; +} +.btn-danger .badge { + color: #ee5f5b; + background-color: #ffffff; +} +.btn-link { + color: #ffffff; + font-weight: normal; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #ffffff; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #7a8288; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 14px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + -o-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #3a3f44; + border: 1px solid #272b30; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + -webkit-background-clip: padding-box; + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #272b30; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #c8c8c8; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + color: #ffffff; + background-color: #272b30; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #272b30; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #7a8288; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #7a8288; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-right-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + float: none; + display: table-cell; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 54px; + padding: 14px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 54px; + line-height: 54px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 8px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #272b30; + text-align: center; + background-color: #3a3f44; + border: 1px solid rgba(0, 0, 0, 0.6); + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 14px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #3e444c; +} +.nav > li.disabled > a { + color: #7a8288; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #7a8288; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #3e444c; + border-color: #ffffff; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #1c1e22; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #1c1e22 #1c1e22 #1c1e22; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #ffffff; + background-color: #3e444c; + border: 1px solid #1c1e22; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #1c1e22; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #1c1e22; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #272b30; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: transparent; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #1c1e22; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #1c1e22; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #272b30; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; + height: 50px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 6px; + margin-bottom: 6px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 6px; + margin-bottom: 6px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #3a3f44; + border-color: #2b2e32; +} +.navbar-default .navbar-brand { + color: #c8c8c8; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #ffffff; + background-color: none; +} +.navbar-default .navbar-text { + color: #c8c8c8; +} +.navbar-default .navbar-nav > li > a { + color: #c8c8c8; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #ffffff; + background-color: #272b2e; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #272b2e; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #272b2e; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #272b2e; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #c8c8c8; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #2b2e32; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: #272b2e; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #c8c8c8; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #272b2e; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #272b2e; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #c8c8c8; +} +.navbar-default .navbar-link:hover { + color: #ffffff; +} +.navbar-default .btn-link { + color: #c8c8c8; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #ffffff; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #cccccc; +} +.navbar-inverse { + background-color: #7a8288; + border-color: #62686d; +} +.navbar-inverse .navbar-brand { + color: #cccccc; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: none; +} +.navbar-inverse .navbar-text { + color: #cccccc; +} +.navbar-inverse .navbar-nav > li > a { + color: #cccccc; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: #5d6368; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #5d6368; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #5d6368; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #5d6368; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #697075; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #5d6368; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #62686d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #62686d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #cccccc; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #5d6368; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #5d6368; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #cccccc; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} +.navbar-inverse .btn-link { + color: #cccccc; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #ffffff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #cccccc; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: transparent; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #cccccc; +} +.breadcrumb > .active { + color: #7a8288; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 8px 12px; + line-height: 1.42857143; + text-decoration: none; + color: #ffffff; + background-color: #3a3f44; + border: 1px solid rgba(0, 0, 0, 0.6); + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #ffffff; + background-color: transparent; + border-color: rgba(0, 0, 0, 0.6); +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #ffffff; + background-color: #232628; + border-color: rgba(0, 0, 0, 0.6); + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #7a8288; + background-color: #ffffff; + border-color: rgba(0, 0, 0, 0.6); + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 14px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #3a3f44; + border: 1px solid rgba(0, 0, 0, 0.6); + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: transparent; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #7a8288; + background-color: #3a3f44; + cursor: not-allowed; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #3a3f44; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #232628; +} +.label-primary { + background-color: #7a8288; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #62686d; +} +.label-success { + background-color: #62c462; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #42b142; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f89406; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #c67605; +} +.label-danger { + background-color: #ee5f5b; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #e9322d; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #ffffff; + line-height: 1; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: #7a8288; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #ffffff; + background-color: #7a8288; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #1c1e22; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #050506; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; + padding-left: 15px; + padding-right: 15px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-left: 60px; + padding-right: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #1c1e22; + border: 1px solid #0c0d0e; + border-radius: 4px; + -webkit-transition: border 0.2s ease-in-out; + -o-transition: border 0.2s ease-in-out; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-left: auto; + margin-right: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #ffffff; +} +.thumbnail .caption { + padding: 9px; + color: #c8c8c8; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #62c462; + border-color: #62bd4f; + color: #ffffff; +} +.alert-success hr { + border-top-color: #55b142; +} +.alert-success .alert-link { + color: #e6e6e6; +} +.alert-info { + background-color: #5bc0de; + border-color: #3dced8; + color: #ffffff; +} +.alert-info hr { + border-top-color: #2ac7d2; +} +.alert-info .alert-link { + color: #e6e6e6; +} +.alert-warning { + background-color: #f89406; + border-color: #e96506; + color: #ffffff; +} +.alert-warning hr { + border-top-color: #d05a05; +} +.alert-warning .alert-link { + color: #e6e6e6; +} +.alert-danger { + background-color: #ee5f5b; + border-color: #ed4d63; + color: #ffffff; +} +.alert-danger hr { + border-top-color: #ea364f; +} +.alert-danger .alert-link { + color: #e6e6e6; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #1c1e22; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #ffffff; + text-align: center; + background-color: #7a8288; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #62c462; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f89406; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #ee5f5b; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + zoom: 1; + overflow: hidden; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #32383e; + border: 1px solid rgba(0, 0, 0, 0.6); +} +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #c8c8c8; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #ffffff; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + text-decoration: none; + color: #c8c8c8; + background-color: #3e444c; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + background-color: #999999; + color: #7a8288; + cursor: not-allowed; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #7a8288; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #3e444c; + border-color: rgba(0, 0, 0, 0.6); +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #a2aab4; +} +.list-group-item-success { + color: #ffffff; + background-color: #62c462; +} +a.list-group-item-success, +button.list-group-item-success { + color: #ffffff; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #ffffff; + background-color: #4fbd4f; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-info { + color: #ffffff; + background-color: #5bc0de; +} +a.list-group-item-info, +button.list-group-item-info { + color: #ffffff; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #ffffff; + background-color: #46b8da; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-warning { + color: #ffffff; + background-color: #f89406; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #ffffff; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #ffffff; + background-color: #df8505; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-danger { + color: #ffffff; + background-color: #ee5f5b; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #ffffff; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #ffffff; + background-color: #ec4844; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #2e3338; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #3e444c; + border-top: 1px solid rgba(0, 0, 0, 0.6); + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-left: 15px; + padding-right: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #1c1e22; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + border: 0; + margin-bottom: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid rgba(0, 0, 0, 0.6); +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid rgba(0, 0, 0, 0.6); +} +.panel-default { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-default > .panel-heading { + color: #c8c8c8; + background-color: #3e444c; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-default > .panel-heading .badge { + color: #3e444c; + background-color: #c8c8c8; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.panel-primary { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #7a8288; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-primary > .panel-heading .badge { + color: #7a8288; + background-color: #ffffff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.panel-success { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-success > .panel-heading { + color: #ffffff; + background-color: #62c462; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-success > .panel-heading .badge { + color: #62c462; + background-color: #ffffff; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.panel-info { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-info > .panel-heading { + color: #ffffff; + background-color: #5bc0de; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-info > .panel-heading .badge { + color: #5bc0de; + background-color: #ffffff; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.panel-warning { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-warning > .panel-heading { + color: #ffffff; + background-color: #f89406; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-warning > .panel-heading .badge { + color: #f89406; + background-color: #ffffff; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.panel-danger { + border-color: rgba(0, 0, 0, 0.6); +} +.panel-danger > .panel-heading { + color: #ffffff; + background-color: #ee5f5b; + border-color: rgba(0, 0, 0, 0.6); +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: rgba(0, 0, 0, 0.6); +} +.panel-danger > .panel-heading .badge { + color: #ee5f5b; + background-color: #ffffff; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: rgba(0, 0, 0, 0.6); +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #1c1e22; + border: 1px solid #0c0d0e; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #2e3338; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + -webkit-background-clip: padding-box; + background-clip: padding-box; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #1c1e22; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 20px; +} +.modal-footer { + padding: 20px; + text-align: right; + border-top: 1px solid #1c1e22; +} +.modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 12px; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} +.tooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.tooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.tooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.tooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + background-color: #000000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + right: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 14px; + background-color: #2e3338; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + background-color: #2e3338; + border-bottom: 1px solid #22262a; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #666666; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; +} +.popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #2e3338; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #666666; + border-right-color: rgba(0, 0, 0, 0.25); +} +.popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #2e3338; +} +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #666666; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #2e3338; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #666666; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #2e3338; + bottom: -10px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; +} +.carousel-inner > .item { + display: none; + position: relative; + -webkit-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform 0.6s ease-in-out; + -o-transition: -o-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + background-color: rgba(0, 0, 0, 0); +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control:hover, +.carousel-control:focus { + outline: 0; + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #ffffff; + border-radius: 10px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); +} +.carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #ffffff; +} +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + content: " "; + display: table; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +.navbar-default, +.navbar-inverse { + border: 1px solid rgba(0, 0, 0, 0.6); + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} +@media (min-width: 768px) { + .navbar-default .navbar-nav > li > a, + .navbar-inverse .navbar-nav > li > a { + border-right: 1px solid rgba(0, 0, 0, 0.2); + border-left: 1px solid rgba(255, 255, 255, 0.1); + } + .navbar-default .navbar-nav > li > a:hover, + .navbar-inverse .navbar-nav > li > a:hover { + border-left-color: transparent; + } + .navbar-default .nav .open > a, + .navbar-inverse .nav .open > a { + border-color: transparent; + } + .navbar-default .navbar-nav > li.active > a, + .navbar-inverse .navbar-nav > li.active > a { + border-left-color: transparent; + } + .navbar-default .navbar-form, + .navbar-inverse .navbar-form { + margin-left: 5px; + margin-right: 5px; + } +} +.navbar-default { + -webkit-filter: none; + filter: none; +} +.navbar-default .navbar-nav > li > a:hover { + -webkit-filter: none; + filter: none; +} +.navbar-inverse { + -webkit-filter: none; + filter: none; +} +.navbar-inverse .badge { + background-color: #5d6368; +} +.navbar-inverse .navbar-nav > li > a:hover { + -webkit-filter: none; + filter: none; +} +.btn, +.btn:hover { + border-color: rgba(0, 0, 0, 0.6); + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} +.btn-default { + -webkit-filter: none; + filter: none; +} +.btn-default:hover { + -webkit-filter: none; + filter: none; +} +.btn-primary { + -webkit-filter: none; + filter: none; +} +.btn-primary:hover { + -webkit-filter: none; + filter: none; +} +.btn-success { + -webkit-filter: none; + filter: none; +} +.btn-success:hover { + -webkit-filter: none; + filter: none; +} +.btn-info { + -webkit-filter: none; + filter: none; +} +.btn-info:hover { + -webkit-filter: none; + filter: none; +} +.btn-warning { + -webkit-filter: none; + filter: none; +} +.btn-warning:hover { + -webkit-filter: none; + filter: none; +} +.btn-danger { + -webkit-filter: none; + filter: none; +} +.btn-danger:hover { + -webkit-filter: none; + filter: none; +} +.btn-link, +.btn-link:hover { + border-color: transparent; +} +h1, +h2, +h3, +h4, +h5, +h6 { + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); +} +.text-primary, +.text-primary:hover { + color: #7a8288; +} +.text-success, +.text-success:hover { + color: #62c462; +} +.text-danger, +.text-danger:hover { + color: #ee5f5b; +} +.text-warning, +.text-warning:hover { + color: #f89406; +} +.text-info, +.text-info:hover { + color: #5bc0de; +} +.table .success, +.table .warning, +.table .danger, +.table .info { + color: #fff; +} +.table-bordered tbody tr.success td, +.table-bordered tbody tr.warning td, +.table-bordered tbody tr.danger td, +.table-bordered tbody tr.success:hover td, +.table-bordered tbody tr.warning:hover td, +.table-bordered tbody tr.danger:hover td { + border-color: #1c1e22; +} +.table-responsive > .table { + background-color: #2e3338; +} +input, +textarea { + color: #272b30; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label, +.has-warning .form-control-feedback { + color: #f89406; +} +.has-warning .form-control, +.has-warning .form-control:focus { + border-color: #f89406; +} +.has-warning .input-group-addon { + border-color: rgba(0, 0, 0, 0.6); +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label, +.has-error .form-control-feedback { + color: #ee5f5b; +} +.has-error .form-control, +.has-error .form-control:focus { + border-color: #ee5f5b; +} +.has-error .input-group-addon { + border-color: rgba(0, 0, 0, 0.6); +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label, +.has-success .form-control-feedback { + color: #62c462; +} +.has-success .form-control, +.has-success .form-control:focus { + border-color: #62c462; +} +.has-success .input-group-addon { + border-color: rgba(0, 0, 0, 0.6); +} +legend { + color: #fff; +} +.input-group-addon { + -webkit-filter: none; + filter: none; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); + color: #ffffff; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + border-color: rgba(0, 0, 0, 0.6); +} +.nav-pills > li > a { + -webkit-filter: none; + filter: none; + border: 1px solid rgba(0, 0, 0, 0.6); + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} +.nav-pills > li > a:hover { + -webkit-filter: none; + filter: none; + border: 1px solid rgba(0, 0, 0, 0.6); +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover { + background-color: none; + -webkit-filter: none; + filter: none; + border: 1px solid rgba(0, 0, 0, 0.6); +} +.nav-pills > li.disabled > a, +.nav-pills > li.disabled > a:hover { + -webkit-filter: none; + filter: none; +} +.pagination > li > a, +.pagination > li > span { + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-filter: none; + filter: none; +} +.pagination > li > a:hover, +.pagination > li > span:hover { + -webkit-filter: none; + filter: none; +} +.pagination > li.active > a, +.pagination > li.active > span { + -webkit-filter: none; + filter: none; +} +.pagination > li.disabled > a, +.pagination > li.disabled > a:hover, +.pagination > li.disabled > span, +.pagination > li.disabled > span:hover { + -webkit-filter: none; + filter: none; +} +.pager > li > a { + -webkit-filter: none; + filter: none; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); +} +.pager > li > a:hover { + -webkit-filter: none; + filter: none; +} +.pager > li.disabled > a, +.pager > li.disabled > a:hover { + -webkit-filter: none; + filter: none; +} +.breadcrumb { + border: 1px solid rgba(0, 0, 0, 0.6); + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-filter: none; + filter: none; +} +.alert .alert-link, +.alert a { + color: #fff; + text-decoration: underline; +} +.alert .close { + color: #000000; + text-decoration: none; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #0c0d0e; +} +a.list-group-item.active, +a.list-group-item.active:hover, +a.list-group-item.active:focus { + border-color: rgba(0, 0, 0, 0.6); +} +a.list-group-item-success.active { + background-color: #62c462; +} +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + background-color: #4fbd4f; +} +a.list-group-item-warning.active { + background-color: #f89406; +} +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + background-color: #df8505; +} +a.list-group-item-danger.active { + background-color: #ee5f5b; +} +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + background-color: #ec4844; +} +.jumbotron { + border: 1px solid rgba(0, 0, 0, 0.6); +} +.panel-primary .panel-heading, +.panel-success .panel-heading, +.panel-danger .panel-heading, +.panel-warning .panel-heading, +.panel-info .panel-heading { + border-color: #000; +} diff --git a/web/gui/css/bootstrap-slider-10.0.0.min.css b/web/gui/css/bootstrap-slider-10.0.0.min.css new file mode 100644 index 0000000..095be95 --- /dev/null +++ b/web/gui/css/bootstrap-slider-10.0.0.min.css @@ -0,0 +1,22 @@ +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * SPDX-License-Identifier: MIT + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal .tooltip{-ms-transform:translateX(-50%);transform:translateX(-50%)}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-horizontal.slider-rtl .tooltip{-ms-transform:translateX(50%);transform:translateX(50%)}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical .tooltip{-ms-transform:translateY(-50%);transform:translateY(-50%)}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} diff --git a/web/gui/css/bootstrap-theme-3.3.7.min.css b/web/gui/css/bootstrap-theme-3.3.7.min.css new file mode 100644 index 0000000..ba77cff --- /dev/null +++ b/web/gui/css/bootstrap-theme-3.3.7.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * SPDX-License-Identifier: MIT + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} +/*# sourceMappingURL=bootstrap-theme.min.css.map */ diff --git a/web/gui/css/bootstrap-toggle-2.2.2.min.css b/web/gui/css/bootstrap-toggle-2.2.2.min.css new file mode 100644 index 0000000..a3daa37 --- /dev/null +++ b/web/gui/css/bootstrap-toggle-2.2.2.min.css @@ -0,0 +1,29 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * SPDX-License-Identifier: MIT + * ======================================================================== */ +.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} +.toggle{position:relative;overflow:hidden} +.toggle input[type=checkbox]{display:none} +.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} +.toggle.off .toggle-group{left:-100%} +.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} +.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} +.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} +.toggle.btn{min-width:59px;min-height:34px} +.toggle-on.btn{padding-right:24px} +.toggle-off.btn{padding-left:24px} +.toggle.btn-lg{min-width:79px;min-height:45px} +.toggle-on.btn-lg{padding-right:31px} +.toggle-off.btn-lg{padding-left:31px} +.toggle-handle.btn-lg{width:40px} +.toggle.btn-sm{min-width:50px;min-height:30px} +.toggle-on.btn-sm{padding-right:20px} +.toggle-off.btn-sm{padding-left:20px} +.toggle.btn-xs{min-width:35px;min-height:22px} +.toggle-on.btn-xs{padding-right:12px} +.toggle-off.btn-xs{padding-left:12px} diff --git a/web/gui/css/c3-0.4.18.min.css b/web/gui/css/c3-0.4.18.min.css new file mode 100644 index 0000000..a033d72 --- /dev/null +++ b/web/gui/css/c3-0.4.18.min.css @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1} diff --git a/web/gui/css/morris-0.5.1.css b/web/gui/css/morris-0.5.1.css new file mode 100644 index 0000000..39203d3 --- /dev/null +++ b/web/gui/css/morris-0.5.1.css @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +.morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} +.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} diff --git a/web/gui/custom/README.md b/web/gui/custom/README.md new file mode 100644 index 0000000..733ef52 --- /dev/null +++ b/web/gui/custom/README.md @@ -0,0 +1,663 @@ + + +# Custom dashboards + +You can: + +- create your own dashboards using simple HTML (no javascript is required for + basic dashboards) +- utilize any or all of the available chart libraries, on the same dashboard +- use data from one or more Netdata servers, on the same dashboard +- host your dashboard HTML page on any web server, anywhere + +You can also add Netdata charts to existing web pages. + +Check this **[very simple working example of a custom dashboard](http://netdata.firehol.org/demo.html)**, and its +**[html source](https://raw.githubusercontent.com/netdata/netdata/master/web/gui/demo.html)**. + +You should also look at the [custom dashboard +template](https://my-netdata.io/dashboard.html), which contains samples of all +supported charts. The code is [here](https://raw.githubusercontent.com/netdata/netdata/master/web/gui/dashboard.html). + +If you plan to put the dashboard on TV, check out +[tv.html](https://raw.githubusercontent.com/netdata/netdata/master/web/gui/tv.html). Here's is a screenshot of it, +monitoring two servers on the same page: + +![image](https://cloud.githubusercontent.com/assets/2662304/14252187/d8d5f78e-fa8e-11e5-990d-99821d38c874.png) + +-- + +## Web directory + +All of the mentioned examples are available on your local Netdata installation +(e.g. `http://myhost:19999/dashboard.html`). The default web root directory with +the HTML and JS code is `/usr/share/netdata/web`. The main dashboard is also in +that directory and called `index.html`.\ +Note: index.html has a different syntax. Don't use it as a template for simple +custom dashboards. + +## Example empty dashboard + +If you need to create a new dashboard on an empty page, we suggest the following +header: + +```html + + + +  Your dashboard + +   +   +   +   +   +   + +   + + + + + + + + +``` + +## Dash (Multi-Host Dashboard) + +`dash-example.html` is an all-in-one page that automatically fetches graphs from all your hosts. Just add your graphs and charts (or use the defaults) one time using the `dash-*` syntax, and your selections will be automatically replicated for all of your hosts; showing alarms and graphs for all your hosts on **one page!** + +__**Dash will only work if you have implemented netdata streaming using `stream.conf`**__ + +`dash-example.html` was created as an experiment to demonstrate the capabilities of netdata in a multi-host environment. If you desire more features, submit a pull request or check out Netdata Cloud! + +### Configure Dash + +First, rename the file so it doesn't get overwritten. For instance, with a webroot at `/usr/share/netdata/web`: +``` +cp /usr/share/netdata/web/dash-example.html /usr/share/netdata/web/dash.html +``` + +Find and change the following line in `dash.html` to reflect your Netdata URLs. The second URL is only used if you access your Netdata dashboard through a reverse proxy. The reverse proxy URL is optional; if it is not set then both will use the Netdata host URL. + +```js +/* +* TUTORIAL: Change this to the URL of your netdata host +* If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so: +* new Dash('http://localhost:19999', 'https://my-domain.com/stats'); +*/ +var dash = new Dash('http://localhost:19999'); +``` + +### The `dash-*` Syntax + +If you want to change the graphs or styling to fit your needs, just add an element to the page as shown. Child divs will be generated to create your graph/chart: +``` +
<---- OPTIONAL: This overrides the default config. Any other data-* attributes will +
be added to the generated div, so you can set any desired options here + +
<---- Use this to override or append default options +
+``` + +To change the sizes of graphs and charts, find the `Dash.options` object in `dash.html` and set your preferences: +```js +/* +* TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust. +* Charts are square! Their width is the same as their height. +*/ +this.options = { + graph_width: '40em', + graph_height: '20em', + chart_width: '10em' // Charts are square +}; +``` + +To change the display order of your hosts, which is saved in localStorage, click the settings gear in the lower right corner + +We hope you like it! + +--- + + +## dashboard.js + +To add Netdata charts to any web page (dedicated to Netdata or not), you need to +include the `/dashboard.js` file of a Netdata server. + +For example, if your Netdata server listens at `http://box:19999/`, you will +need to add the following to the `head` section of your web page: + +```html + +``` + +### What does dashboard.js do? + +`dashboard.js` will automatically load the following: + +1. `dashboard.css`, required for the Netdata charts + +2. `jquery.min.js`, (only if jQuery is not already loaded for this web page) + +3. `bootstrap.min.js` (only if Bootstrap is not already loaded) and + `bootstrap.min.css`. + + You can disable this by adding the following before loading `dashboard.js`: + +```html + +``` + +4. `jquery.nanoscroller.min.js`, required for the scrollbar of the chart + legends. + +5. `bootstrap-toggle.min.js` and `bootstrap-toggle.min.css`, required for the + settings toggle buttons. + +6. `font-awesome.min.css`, for icons. + +When `dashboard.js` loads will scan the page for elements that define charts +(see below) and immediately start refreshing them. Keep in mind more javascript +modules may be loaded (every chart library is a different javascript file, that +is loaded on first use). + +### Prevent dashboard.js from starting chart refreshes + +If your web page is not static and you plan to add charts using JavaScript, you +can tell `dashboard.js` not to start processing charts immediately after loaded, +by adding this fragment before loading it: + +```html + +``` + +The above, will inform the `dashboard.js` to load everything, but not process the web page until you tell it to. +You can tell it to start processing the page, by running this javascript code: + +```js +NETDATA.start(); +``` + +Be careful not to call the `NETDATA.start()` multiple times. Each call to this +function will spawn a new thread that will start refreshing the charts. + +If, after calling `NETDATA.start()` you need to update the page (or even get +your javascript code synchronized with `dashboard.js`), you can call (after you +loaded `dashboard.js`): + +```js +NETDATA.pause(function() { + // ok, it is paused + + // update the DOM as you wish + + // and then call this to let the charts refresh: + NETDATA.unpause(); +}); +``` + +### The default Netdata server + +`dashboard.js` will attempt to auto-detect the URL of the Netdata server it is +loaded from, and set this server as the default Netdata server for all charts. + +If you need to set any other URL as the default Netdata server for all charts +that do not specify a Netdata server, add this before loading `dashboard.js`: + +```html + +``` + +--- + +## Adding charts + +To add charts, you need to add a `div` for each of them. Each of these `div` +elements accept a few `data-` attributes: + +### The chart unique ID + +The unique ID of a chart is shown at the title of the chart of the default +Netdata dashboard. You can also find all the charts available at your Netdata +server with this URL: `http://your.netdata.server:19999/api/v1/charts` +([example](http://netdata.firehol.org/api/v1/charts)). + +To specify the unique id, use this: + +```html +
+``` + +The above is enough for adding a chart. It most probably have the wrong visual +settings though. Keep reading... + +### The duration of the chart + +You can specify the duration of the chart (how much time of data it will show) +using: + +```html +
+``` + +`AFTER_SECONDS` and `BEFORE_SECONDS` are numbers representing a time-frame in +seconds. + +The can be either: + +- **absolute** unix timestamps (in javascript terms, they are `new + Date().getTime() / 1000`. Using absolute timestamps you can have a chart + showing always the same time-frame. + +- **relative** number of seconds to now. To show the last 10 minutes of data, + `AFTER_SECONDS` must be `-600` (relative to now) and `BEFORE_SECONDS` must + be `0` (meaning: now). If you want the chart to auto-refresh the current + values, you need to specify **relative** values. + +### Chart sizes + +You can set the size of the chart using this: + +```html +
+``` + +`WIDTH` and `HEIGHT` can be anything CSS accepts for width and height (e.g. +percentages, pixels, etc). Keep in mind that for certain chart libraries, +`dashboard.js` may apply an aspect ratio to these. + +If you want `dashboard.js` to permanently remember (browser local storage) the +dimensions of the chart (the user may resize it), you can add: `data-id=" +SETTINGS_ID"`, where `SETTINGS_ID` is anything that will be common for this +chart across user sessions. + +### Netdata server + +Each chart can get data from a different Netdata server. You can specify the Netdata server to use for each chart using: + +```html +
+``` + +If you have ephemeral monitoring setup ([More info here](/streaming/README.md#monitoring-ephemeral-nodes)) and have no +direct access to the nodes dashboards, you can use the following: + +```html +
+``` + +### Chart library + +Netdata supports a number of chart libraries. The default chart library is +`dygraph`, but you can set a different chart library per chart using +`data-chart-library`: + +```html +
+``` + +Each chart library has a number of specific settings. To learn more about them, +you should investigate the documentation of the given chart library, or visit +the appropriate JavaScript file that defines the library's options. These files +are concatenated into the monolithic `dashboard.js` for deployment. + +- [Dygraph](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L2034) +- [d3](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L4095) +- [d3pie](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L3753) +- [Gauge.js](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L3065) +- [Google Charts](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L2936) +- [EasyPieChart](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L3531) +- [Peity](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L4137) +- [Sparkline](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L2779) +- [Text-only](https://github.com/netdata/netdata/blob/5b57fc441c40959514c4e2d0863be2e6a417e352/web/gui/dashboard.js#L4200) + +### Data points + +For the time-frame requested, `dashboard.js` will use the chart dimensions and +the settings of the chart library to find out how many data points it can show. + +For example, most line chart libraries are using 3 pixels per data point. If the +chart shows 10 minutes of data (600 seconds), its update frequency is 1 second, +and the chart width is 1800 pixels, then `dashboard.js` will request from the +Netdata server: 10 minutes of data, represented in 600 points, and the chart +will be refreshed per second. If the user resizes the window so that the chart +becomes 600 pixels wide, then `dashboard.js` will request the same 10 minutes of +data, represented in 200 points and the chart will be refreshed once every 3 +seconds. + +If you need the chart to show a fixed number of points, you can set the `data-points` option. Replace `DATA_POINTS` with the number of points you need: + +```html +
+``` + +You can also overwrite the pixels-per-point per chart using this: + +```html +
+``` + +Where `PIXELS_PER_POINT` is the number of pixels each data point should occupy. + +### Data grouping method + +Netdata supports **average** (the default), **sum** and **max** grouping +methods. The grouping method is used when the Netdata server is requested to +return fewer points for a time-frame, compared to the number of points +available. + +You can give it per chart, using: + +```html +
+``` + +### Changing rates + +Netdata can change the rate of charts on the fly. So a charts that shows values +**per second** can be turned to **per minute** (or any other, e.g. **per 10 +seconds**), with this: + +```html +
+``` + +The above will provide the average rate per minute (60 seconds). Use 60 for +`/minute`, 3600 for `/hour`, 86400 for `/day` (provided you have that many +data). + +- The `data-gtime` setting does not change the units of the chart. You have to + change them yourself with `data-units`. +- This works only for `data-method="average"`. +- Netdata may aggregate multiple points to satisfy the `data-points` setting. + For example, you request `per minute` but the requested number of points to + be returned are not enough to report every single minute. In this case + Netdata will sum the `per second` raw data of the database to find the `per + minute` for every single minute and then **average** them to find the + **average per minute rate of every X minutes**. So, it works as if the data + collection frequency was per minute. + +### Selecting dimensions + +By default, `dashboard.js` will show all the dimensions of the chart. You can +select specific dimensions using this: + +```html +
+``` + +Netdata supports coma (`,`) or pipe (`|`) separated [simple +patterns](/libnetdata/simple_pattern/README.md) for dimensions. By default it +searches for both dimension IDs and dimension NAMEs. You can control the target +of the match with: `data-append-options="match-ids"` or +`data-append-options="match-names"`. Spaces in `data-dimensions=""` are matched +in the dimension names and IDs. + +### Chart title + +You can overwrite the title of the chart using this: + +```html +
+``` + +### Chart units + +You can overwrite the units of measurement of the dimensions of the chart, using +this: + +```html +
+``` + +### Chart colors + +`dashboard.js` has an internal palette of colors for the dimensions of the +charts. You can prepend colors to it (so that your will be used first) using +this: + +```html +
+``` + +### Extracting dimension values + +`dashboard.js` can update the selected values of the chart at elements you +specify. For example, let's assume we have a chart that measures the bandwidth +of eth0, with 2 dimensions `in` and `out`. You can use this: + +```html +
+ +My eth0 interface, is receiving +and transmitting . +``` + +### Hiding the legend of a chart + +On charts that by default have a legend managed by `dashboard.js` you can remove +it, using this: + +```html +
+``` + +### API options + +You can append Netdata **[REST API v1](/web/api/README.md)** data options, using this: + +```html +
+``` + +A few useful options are: + +- `absolute` to show all values are absolute (i.e. turn negative dimensions to + positive) +- `percentage` to express the values as a percentage of the chart total (so, + the values of the dimensions are added, and the sum of them if expressed as + a percentage of the sum of all dimensions) +- `unaligned` to prevent Netdata from aligning the charts (e.g. when + requesting 60 seconds aggregation per point, Netdata returns chart data + aligned to XX:XX:00 to XX:XX:59 - similarly for hours, days, etc - the + `unaligned` option disables this feature) +- `match-ids` or `match-names` is used to control what `data-dimensions=` will + match. + +### Chart library performance + +`dashboard.js` measures the performance of the chart library when it renders the +charts. You can specify an element ID you want this information to be +visualized, using this: + +```html +
+ +refreshed in milliseconds! +``` + +### Syncing charts y-range + +If you give the same `data-common-max="NAME"` to 2+ charts, then all of them +will share the same max value of their y-range. If one spikes, all of them will +be aligned to have the same scale. This is done for the cpu interrupts and and +cpu softnet charts at the dashboard and also for the `gauge` and `easypiecharts` +of the Netdata home page. + +```html +
+ +
+``` + +The same functionality exists for `data-common-min`. + +### Syncing chart units + +Netdata dashboards support auto-scaling of units. So, `MB` can become `KB`, +`GB`, etc dynamically, based on the value to be shown. + +Giving the same `NAME` with `data-common-units= "NAME"`, 2+ charts can be forced +to always have the same units. + +```html +
+ +
+``` + +### Setting desired units + +Charts can be scaled to specific units with `data-desired-units=" UNITS"`. If +the dashboard can convert the units to the desired one, it will do. + +```html +
+``` + +## Chart library settings + +### Dygraph + +You can set the min and max values of the y-axis using +`data-dygraph-valuerange=" [MIN, MAX] "`. + +### EasyPieChart + +#### Value range + +You can set the max value of the chart using the following snippet: + +```html +
+``` + +Be aware that values that exceed the max value will get expanded (e.g. "41" is +still 100%). Similar for the minimum: + +```html +
+``` + +If you specify both minimum and maximum, the rendering behavior changes. Instead +of displaying the `value` based from zero, it is now based on the range that is +provided by the snippet: + +```html +
+``` + +In the first example, a value of `30`, without specifying the minimum, fills the chart bar to '75 %` (100% / 40 * 30). However, in this example the range is now `20` (40 - 20 = 20). The value `30` will fill the chart to ** '50 %`**, since it's in the middle between 20 and 40. + +This scenario is useful if you have metrics that change only within a specific range, e.g. temperatures that are very unlikely to fall out of range. In these cases it is more useful to have the chart render the values between the given min and max, to better highlight the changes within them. + +#### Negative values + +EasyPieCharts can render negative values with the following flag: +```html +
+``` +Negative values are rendered counter-clockwise. + +#### Full example with EasyPieChart + +This is a chart that displays the hotwater temperature in the given range of 40 +to 50. +```html +
+``` + +![hot water +chart](https://user-images.githubusercontent.com/12159026/28666665-a7d68ad2-72c8-11e7-9a96-f6bf9691b471.png) + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fweb%2Fgui%2Fcustom%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)]() diff --git a/web/gui/dash-example.html b/web/gui/dash-example.html new file mode 100644 index 0000000..b1ff1af --- /dev/null +++ b/web/gui/dash-example.html @@ -0,0 +1,1028 @@ + + + + + + + + + + + + +
+
+
host
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/web/gui/dashboard.css b/web/gui/dashboard.css new file mode 100644 index 0000000..674131a --- /dev/null +++ b/web/gui/dashboard.css @@ -0,0 +1,757 @@ +/* SPDX-License-Identfier: GPL-3.0-or-later */ +html, +body { + /*font-family: Calibri,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;*/ + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-variant: normal; +} + +.morelink { + color: #765d9c; + text-decoration: none; +} + +.morelink:hover { + color: #563d7c; + text-decoration: none; +} + +.morelink:focus { + color: #765d9c; + text-decoration: none; +} + +.netdata-chart-alignment { + margin-left: 55px; +} + +.netdata-chart-row { + width: 100%; + text-align: center; + display: flex; + display: -webkit-flex; + display: -moz-flex; + align-items: baseline; + -moz-align-items: baseline; + -webkit-align-items: baseline; + justify-content: center; + -webkit-justify-content: center; + -moz-justify-content: center; + padding-top: 10px; +} + +.netdata-container { + display: inline-block; + overflow: hidden; + + transform: translate3d(0,0,0); + + /* required for child elements to have absolute position */ + position: relative; + + /* width and height is given per chart with data-width and data-height */ +} + +.netdata-container-gauge { + display: inline-block; + overflow: hidden; + + transform: translate3d(0,0,0); + + /* required for child elements to have absolute position */ + position: relative; + + /* width and height is given per chart with data-width and data-height */ +} + +.netdata-container-gauge:after { + padding-top: 60%; + display: block; + content: ''; +} + +.netdata-container-easypiechart { + display: inline-block; + overflow: hidden; + + transform: translate3d(0,0,0); + + /* required for child elements to have absolute position */ + position: relative; + + /* width and height is given per chart with data-width and data-height */ +} + +.netdata-container-easypiechart:after { + padding-top: 100%; + display: block; + content: ''; +} + +.netdata-aspect { + position: relative; + width: 100%; + padding: 0px; + margin: 0px; +} + +.netdata-container-with-legend { + display: inline-block; + overflow: hidden; + + transform: translate3d(0,0,0); + + /* fix minimum scrollbar issue in firefox */ + min-height: 99px; + + /* required for child elements to have absolute position */ + position: relative; + + /* width and height is given per chart with data-width and data-height */ +} + +.netdata-legend-resize-handler { + display: block; + position: absolute; + bottom: 0px; + right: 0px; + height: 15px; + width: 20px; + background-color: White; + font-size: 15px; + vertical-align: middle; + line-height: 15px; + cursor: ns-resize; + color: #DDDDDD; + text-align: center; + overflow: hidden; + z-index: 20; + padding: 0px; + margin: 0px; +} + +.netdata-legend-toolbox { + display: block; + position: absolute; + bottom: 0px; + right: 30px; + height: 15px; + width: 110px; + background-color: White; + font-size: 12px; + vertical-align: middle; + line-height: 15px; + color: #DDDDDD; + text-align: center; + overflow: hidden; + z-index: 20; + padding: 0px; + margin: 0px; + + /* prevent text selection after double click */ + -webkit-user-select: none; /* webkit (safari, chrome) browsers */ + -moz-user-select: none; /* mozilla browsers */ + -khtml-user-select: none; /* webkit (konqueror) browsers */ + -ms-user-select: none; /* IE10+ */ +} + +.netdata-legend-toolbox-button { + display: inline-block; + position: relative; + height: 15px; + width: 18px; + background-color: White; + font-size: 12px; + vertical-align: middle; + line-height: 15px; + color: #CDCDCD; + text-align: center; + overflow: hidden; + z-index: 21; + padding: 0px; + margin: 0px; + cursor: pointer; + + /* prevent text selection after double click */ + -webkit-user-select: none; /* webkit (safari, chrome) browsers */ + -moz-user-select: none; /* mozilla browsers */ + -khtml-user-select: none; /* webkit (konqueror) browsers */ + -ms-user-select: none; /* IE10+ */ +} + +.netdata-message { + display: inline-block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + text-align: left; + vertical-align: top; + font-weight: bold; + font-size: x-small; + overflow: hidden; + background: inherit; + z-index: 0; +} + +.netdata-message.hidden { + display: none; +} + +.netdata-message.icon { + color: #F8F8F8; + text-align: center; + vertical-align: middle; +} + +.netdata-chart-legend { + position: absolute; /* within .netdata-container */ + top: 0; + right: 0; + overflow: hidden; + text-overflow: ellipsis; + line-height: 14px; + display: block; + width: 140px; /* --legend-width */ + height: calc(100% - 15px); /* 10px for the resize handler and 5px for the top margin */ + font-size: 10px; + margin-top: 5px; + text-align: left; + /* width and height is calculated (depends on the appearance of the legend) */ +} + +.netdata-legend-title-date { + font-size: 10px; + font-weight: normal; + margin-top: 0px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.netdata-legend-title-time { + font-size: 11px; + font-weight: bold; + margin-top: 0px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.netdata-legend-title-units { + position: absolute; + right: 10px; + float: right; + font-size: 11px; + vertical-align: top; + font-weight: normal; + margin-top: 0px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.netdata-legend-series { + position: absolute; + width: 140px; /* legend-width */ + height: calc(100% - 50px); + overflow: hidden; + text-overflow: ellipsis; + line-height: 14.5px; /* line spacing at the legend */ + display: block; + font-size: 10px; + margin-top: 0px; +} + +.netdata-legend-name-table-line { + display: inline-block; + width: 13px; + height: 4px; + border-width: 0px; + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: white; +} + +.netdata-legend-name-table-area { + display: inline-block; + width: 13px; + height: 5px; + border-width: 1px; + border-top-width: 1px; + border-top-style: solid; + border-top-color: inherit; +} + +.netdata-legend-name-table-stacked { + display: inline-block; + width: 13px; + height: 5px; + border-width: 1px; + border-top-width: 1px; + border-top-style: solid; + border-top-color: inherit; +} + +.netdata-legend-name-tr { +} + +.netdata-legend-name-td { +} + +.netdata-legend-name { + text-align: left; + font-size: 11px; /* legend: dimension name size */ + font-weight: bold; + vertical-align: bottom; + margin-top: 0px; + z-index: 9; + padding: 0px; + width: 80px !important; + max-width: 80px !important; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + display: inline-block; + cursor: pointer; + -webkit-print-color-adjust: exact; +} + +.netdata-legend-value { + /*margin-left: 14px;*/ + position: absolute; + right: 10px; + float: right; + text-align: right; + font-size: 11px; /* legend: dimension value size */ + font-weight: bold; + vertical-align: bottom; + background-color: White; + margin-top: 0px; + z-index: 10; + padding: 0px; + padding-left: 15px; + cursor: pointer; + /* -webkit-font-smoothing: none; */ +} + +.netdata-legend-name.not-selected { + font-weight: normal; + opacity: 0.3; +} + +.netdata-chart { + position: absolute; /* within .netdata-container */ + top: 0; /* within .netdata-container */ + left: 0; /* within .netdata-container */ + display: inline-block; + overflow: hidden; + width: 100%; + height: 100%; + z-index: 5; + + /* width and height is calculated (depends on the appearance of the legend) */ +} + +.netdata-chart-with-legend-right { + position: absolute; /* within .netdata-container */ + top: 0; /* within .netdata-container */ + left: 0; /* within .netdata-container */ + display: block; + overflow: hidden; + margin-right: 140px; /* --legend-width */ + width: calc(100% - 140px); /* --legend-width */ + height: 100%; + z-index: 5; + flex-grow: 1; + + /* width and height is calculated (depends on the appearance of the legend) */ +} + +.netdata-peity-chart { + +} + +.netdata-sparkline-chart { + +} + +.netdata-dygraph-chart { + +} + +.netdata-morris-chart { + +} + +.netdata-google-chart { + +} + +.dygraph-ylabel { +} + +.dygraph-axis-label-x { + overflow-x: hidden; +} + +.dygraph-label-rotate-left { + text-align: center; + /* See http://caniuse.com/#feat=transforms2d */ + transform: rotate(90deg); + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); +} + +/* For y2-axis label */ +.dygraph-label-rotate-right { + text-align: center; + /* See http://caniuse.com/#feat=transforms2d */ + transform: rotate(-90deg); + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); +} + +.dygraph-title { + text-indent: 56px; + text-align: left; + position: absolute; + left: 0px; + top: 4px; + font-size: 11px; + font-weight: bold; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +/* fix for sparkline tooltip under bootstrap */ +.jqstooltip { + width: auto !important; + height: auto !important; +} + +.easyPieChart { + position: relative; + text-align: center; +} + +.easyPieChart canvas { + position: absolute; + top: 0; + left: 0; +} + +.easyPieChartLabel { + display: inline-block; + position: absolute; + float: left; + left: 0; + width: 100%; + text-align: center; + color: #666; + font-weight: normal; + text-shadow: #BBB 0px 0px 1px; + /* -webkit-font-smoothing: none; */ +} + +.easyPieChartTitle { + display: inline-block; + position: absolute; + float: left; + left: 0; + width: 64%; + margin-left: 18% !important; + text-align: center; + color: #999999; + font-weight: bold; +} + +.easyPieChartUnits { + display: inline-block; + position: absolute; + float: left; + left: 0; + width: 60%; + margin-left: 20% !important; + text-align: center; + color: #999999; + font-weight: normal; +} + +.gaugeChart { + position: relative; + text-align: center; +} + +.gaugeChart canvas { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 0; +} + +.gaugeChartLabel { + display: inline-block; + position: absolute; + float: left; + left: 0; + width: 100%; + text-align: center; + color: #FFFFFF; + font-weight: bold; + z-index: 1; + text-shadow: #777 0px 0px 1px; + /* text-shadow: #CCC 1px 1px 0px, #CCC -1px -1px 0px, #CCC 1px -1px 0px, #CCC -1px 1px 0px; */ + /* -webkit-text-stroke: 1px #777; */ + /* -webkit-font-smoothing: none; */ +} + +.gaugeChartTitle { + display: inline-block; + position: absolute; + float: left; + left: 0; + width: 100%; + text-align: center; + color: #999999; + font-weight: bold; +} + +.gaugeChartUnits { + display: inline-block; + position: absolute; + float: left; + left: 0; + bottom: 0; + width: 100%; + text-align: left; + margin-left: 5%; + color: #999999; + font-weight: normal; +} + +.gaugeChartMin { + display: inline-block; + position: absolute; + float: left; + left: 0; + bottom: 8%; + width: 92%; + margin-left: 8%; + text-align: left; + color: #999999; + font-weight: normal; +} + +.gaugeChartMax { + display: inline-block; + position: absolute; + float: left; + left: 0; + bottom: 8%; + width: 95%; + margin-right: 5%; + text-align: right; + color: #999999; + font-weight: normal; +} + +.popover-title { + font-weight: bold; + font-size: 12px; +} + +.popover-content { + font-size: 11px; +} + +/* ---------------------------------------------------------------------------- + perfect-scrollbar settings + */ + +.ps-container { + -ms-touch-action: auto; + touch-action: auto; + overflow: hidden !important; + -ms-overflow-style: none; +} + +@supports (-ms-overflow-style: none) { + .ps-container { + overflow: auto !important; + } +} + +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .ps-container { + overflow: auto !important; + } +} + +.ps-container.ps-active-x > .ps-scrollbar-x-rail, +.ps-container.ps-active-y > .ps-scrollbar-y-rail { + display: block; + background-color: transparent; +} + +.ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail { + background-color: transparent; /* background color when dragged away */ + opacity: 0.9; +} + +.ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x { + background-color: #aaa; /* scrollbar color when dragged away */ + height: 5px; +} + +.ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail { + background-color: transparent; /* background color when dragged away */ + opacity: 0.9; +} + +.ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y { + background-color: #aaa; /* scrollbar color when dragged away */ + width: 5px; +} + +.ps-container > .ps-scrollbar-x-rail { + display: none; + position: absolute; + /* please don't change 'position' */ + opacity: 0.2; /* the opacity when not on hover of the content */ + -webkit-transition: background-color .2s linear, opacity .2s linear; + -o-transition: background-color .2s linear, opacity .2s linear; + -moz-transition: background-color .2s linear, opacity .2s linear; + transition: background-color .2s linear, opacity .2s linear; + bottom: 0px; + /* there must be 'bottom' for ps-scrollbar-x-rail */ + height: 15px; +} + +.ps-container > .ps-scrollbar-x-rail > .ps-scrollbar-x { + position: absolute; + /* please don't change 'position' */ + background-color: #666; /* #aaa; the color on content hover */ + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out; + -o-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out; + -moz-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out; + bottom: 2px; + /* there must be 'bottom' for ps-scrollbar-x */ + height: 5px; /* the width of the scrollbar */ +} + +.ps-container > .ps-scrollbar-x-rail:hover > .ps-scrollbar-x, .ps-container > .ps-scrollbar-x-rail:active > .ps-scrollbar-x { + height: 4px; +} + +.ps-container > .ps-scrollbar-y-rail { + display: none; + position: absolute; + /* please don't change 'position' */ + opacity: 0.2; /* the opacity when not on hover of the content */ + -webkit-transition: background-color .2s linear, opacity .2s linear; + -o-transition: background-color .2s linear, opacity .2s linear; + -moz-transition: background-color .2s linear, opacity .2s linear; + transition: background-color .2s linear, opacity .2s linear; + right: 0; + /* there must be 'right' for ps-scrollbar-y-rail */ + width: 15px; +} + +.ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y { + position: absolute; + /* please don't change 'position' */ + background-color: #666; /* #aaa; the color on content hover */ + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out; + -o-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out; + -moz-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out; + transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out; + right: 2px; + /* there must be 'right' for ps-scrollbar-y */ + width: 5px; /* the width of the scrollbar */ +} + +.ps-container > .ps-scrollbar-y-rail:hover > .ps-scrollbar-y, .ps-container > .ps-scrollbar-y-rail:active > .ps-scrollbar-y { + width: 5px; +} + +.ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail { + background-color: transparent; /* background color when dragged */ + opacity: 0.9; +} + +.ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x { + background-color: #bbb; /* scrollbar color when dragged */ + height: 5px; +} + +.ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail { + background-color: transparent; /* background color when dragged */ + opacity: 0.9; +} + +.ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y { + background-color: #bbb; /* scrollbar color when dragged */ + width: 5px; +} + +.ps-container:hover > .ps-scrollbar-x-rail, +.ps-container:hover > .ps-scrollbar-y-rail { + opacity: 0.6; +} + +.ps-container:hover > .ps-scrollbar-x-rail:hover { + background-color: transparent; /* the background color on hover of the scrollbar */ + opacity: 0.9; +} + +.ps-container:hover > .ps-scrollbar-x-rail:hover > .ps-scrollbar-x { + background-color: #999; /* scrollbar color on hover */ +} + +.ps-container:hover > .ps-scrollbar-y-rail:hover { + background-color: transparent; /* the background color on hover of the scrollbar */ + opacity: 0.9; +} + +.ps-container:hover > .ps-scrollbar-y-rail:hover > .ps-scrollbar-y { + background-color: #999; /* scrollbar color on hover */ +} + +.dygraph__history-tip { + position: absolute; + top: 50%; + transform: translateY(-50%); + display: none; /* overriden in js */ + margin-right: 25px; + direction: rtl; + overflow: hidden; + pointer-events: none; +} + +.dygraph__history-tip-content { + display: inline-block; + white-space: nowrap; + direction: ltr; + pointer-events: auto; +} diff --git a/web/gui/dashboard.html b/web/gui/dashboard.html new file mode 100644 index 0000000..d843fc5 --- /dev/null +++ b/web/gui/dashboard.html @@ -0,0 +1,699 @@ + + + + + NetData Dashboard + + + + + + + + + + + + + + + + + + + + +
+ +

NetData Custom Dashboard

+ +This is a template for building custom dashboards. To build a dashboard you just do this: + +
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+    <div data-netdata="system.processes"
+        data-chart-library="dygraph"
+        data-width="600"
+        data-height="200"
+        data-after="-600"
+        ></div>
+</body>
+<script type="text/javascript" src="http://netdata.server:19999/dashboard.js"></script>
+</html>
+
+ +
    +
  • You can host your dashboard anywhere.
  • +
  • You can add as many charts as you like.
  • +
  • You can have charts from many different netdata servers (add
    data-host="http://another.netdata.server:19999/"
    to each chart).
  • +
  • You can use different chart libraries on the same page: peity, sparkline, dygraph, google
  • +
  • You can customize each chart to your preferences. For each chart library most of their attributes can be given in data- attributes.
  • +
  • Each chart can have each own duration - it is controlled with the data-after attribute to give that many seconds of data.
  • +
  • Depending on the width of the chart and data-after attribute, netdata will automatically refresh the chart when it needs to be updated. For example giving 600 pixels for width for -600 seconds of data, using a chart library that needs 3 pixels per point, will yeld in a chart updated once every 3 seconds.
  • +
+ + +
+

Sparkline Charts

+Sparkline charts support 'NULL' values, so the charts can indicate that values are missing. +Sparkline charts stretch the values to show the variations between values in more detail. +They also have mouse-hover support. +
+Sparklines are fantastic. You can inline charts in text. For example this +
is my current cpu usage (last 30 seconds), + while this +
is the bandwidth my netdata server is currently transmitting (last minute) + and this +
is the requests/sec it serves (last 3 minutes). + +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+ + + +
+

Peity Charts

+Peity charts do not support 'NULL' values, so the charts cannot indicate that values are missing. +Peity charts cannot have multiple dimensions on the charts - so netdata will use 'min2max' to show +the total of all dimensions. +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+ + + + +
+

Dygraph Charts

+The fastest charting engine that can chart complete charts (not just sparklines). +The charts are zoomable (drag their contents to pan, shift with mouse wheel to zoom-in or zoom-out, double click to reset it). +Netdata magic! Realtime charts on your web page! +
+Sparklines using dygraphs +
+ are also possible! This +
+ is an area chart, while this +
is a stacked area chart! + + +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+ + + +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+ + + +
+

EasyPieChart

+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+ + +
+

Gauge.js

+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+ + +
+

Google Charts

+NetData was originaly developed with Google Charts. +NetData is a complete Google Visualization API provider. +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+
+
+
+ rendered in X ms +
+ + + +
+

d3pie Charts

+
+
+
+ rendered in X ms +
+ +
+
+
+ rendered in X ms +
+ +
+
+
+ rendered in X ms +
+
+ + + + + + + + + + diff --git a/web/gui/dashboard.js b/web/gui/dashboard.js new file mode 100644 index 0000000..53e9090 --- /dev/null +++ b/web/gui/dashboard.js @@ -0,0 +1,10377 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// DO NOT EDIT: This file is automatically generated from the source files in src/ + +// ---------------------------------------------------------------------------- +// You can set the following variables before loading this script: + +// 'use strict'; + +/*global netdataNoDygraphs *//* boolean, disable dygraph charts + * (default: false) */ +/*global netdataNoSparklines *//* boolean, disable sparkline charts + * (default: false) */ +/*global netdataNoPeitys *//* boolean, disable peity charts + * (default: false) */ +/*global netdataNoGoogleCharts *//* boolean, disable google charts + * (default: false) */ +/*global netdataNoMorris *//* boolean, disable morris charts + * (default: false) */ +/*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts + * (default: false) */ +/*global netdataNoGauge *//* boolean, disable gauge.js charts + * (default: false) */ +/*global netdataNoD3 *//* boolean, disable d3 charts + * (default: false) */ +/*global netdataNoC3 *//* boolean, disable c3 charts + * (default: false) */ +/*global netdataNoD3pie *//* boolean, disable d3pie charts + * (default: false) */ +/*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too + * (default: false) */ +/*global netdataNoFontAwesome *//* boolean, disable fontawesome (do not load it) + * (default: false) */ +/*global netdataIcons *//* object, overwrite netdata fontawesome icons + * (default: null) */ +/*global netdataDontStart *//* boolean, do not start the thread to process the charts + * (default: false) */ +/*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error + * (default: null) */ +/*global netdataRegistry:true *//* boolean, use the netdata registry + * (default: false) */ +/*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard + * (obsolete - do not use this any more) */ +/*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry + * (default: null) */ +/*global netdataShowHelp:true *//* boolean, disable charts help + * (default: true) */ +/*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications + * (default: false) */ +/*global netdataRegistryAfterMs:true *//* ms, delay registry use at started + * (default: 1500) */ +/*global netdataCallback *//* function, callback to be called when netdata is ready to start + * (default: null) + * netdata will be running while this is called + * (call NETDATA.pause to stop it) */ +/*global netdataPrepCallback *//* function, callback to be called before netdata does anything else + * (default: null) */ +/*global netdataServer *//* string, the URL of the netdata server to use + * (default: the URL the page is hosted at) */ +/*global netdataServerStatic *//* string, the URL of the netdata server to use for static files + * (default: netdataServer) */ +/*global netdataSnapshotData *//* object, a netdata snapshot loaded + * (default: null) */ +/*global netdataAlarmsRecipients *//* array, an array of alarm recipients to show notifications for + * (default: null) */ +/*global netdataAlarmsRemember *//* boolen, keep our position in the alarm log at browser local storage + * (default: true) */ +/*global netdataAlarmsActiveCallback *//* function, a hook for the alarm logs + * (default: undefined) */ +/*global netdataAlarmsNotifCallback *//* function, a hook for alarm notifications + * (default: undefined) */ +/*global netdataIntersectionObserver *//* boolean, enable or disable the use of intersection observer + * (default: true) */ +/*global netdataCheckXSS *//* boolean, enable or disable checking for XSS issues + * (default: false) */ + +// ---------------------------------------------------------------------------- +// global namespace + +// Should stay var! +var NETDATA = window.NETDATA || {}; + +(function(window, document, $, undefined) { + +// *** src/dashboard.js/utils.js + +NETDATA.name2id = function (s) { + return s + .replace(/ /g, '_') + .replace(/:/g, '_') + .replace(/\(/g, '_') + .replace(/\)/g, '_') + .replace(/\./g, '_') + .replace(/\//g, '_'); +}; + +NETDATA.encodeURIComponent = function (s) { + if (typeof(s) === 'string') { + return encodeURIComponent(s); + } + + return s; +}; + +/// A heuristic for detecting slow devices. +let isSlowDeviceResult = undefined; +const isSlowDevice = function () { + if (!isSlowDeviceResult) { + return isSlowDeviceResult; + } + + try { + let ua = navigator.userAgent.toLowerCase(); + + let iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream; + let android = /android/.test(ua) && !window.MSStream; + isSlowDeviceResult = (iOS || android); + } catch (e) { + isSlowDeviceResult = false; + } + + return isSlowDeviceResult; +}; + +NETDATA.guid = function () { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); +}; + +NETDATA.zeropad = function (x) { + if (x > -10 && x < 10) { + return '0' + x.toString(); + } else { + return x.toString(); + } +}; + +NETDATA.seconds4human = function (seconds, options) { + let defaultOptions = { + now: 'now', + space: ' ', + negative_suffix: 'ago', + day: 'day', + days: 'days', + hour: 'hour', + hours: 'hours', + minute: 'min', + minutes: 'mins', + second: 'sec', + seconds: 'secs', + and: 'and' + }; + + if (typeof options !== 'object') { + options = defaultOptions; + } else { + for (var x in defaultOptions) { + if (typeof options[x] !== 'string') { + options[x] = defaultOptions[x]; + } + } + } + + if (typeof seconds === 'string') { + seconds = parseInt(seconds, 10); + } + + if (seconds === 0) { + return options.now; + } + + let suffix = ''; + if (seconds < 0) { + seconds = -seconds; + if (options.negative_suffix !== '') { + suffix = options.space + options.negative_suffix; + } + } + + let days = Math.floor(seconds / 86400); + seconds -= (days * 86400); + + let hours = Math.floor(seconds / 3600); + seconds -= (hours * 3600); + + let minutes = Math.floor(seconds / 60); + seconds -= (minutes * 60); + + let strings = []; + + if (days > 1) { + strings.push(days.toString() + options.space + options.days); + } else if (days === 1) { + strings.push(days.toString() + options.space + options.day); + } + + if (hours > 1) { + strings.push(hours.toString() + options.space + options.hours); + } else if (hours === 1) { + strings.push(hours.toString() + options.space + options.hour); + } + + if (minutes > 1) { + strings.push(minutes.toString() + options.space + options.minutes); + } else if (minutes === 1) { + strings.push(minutes.toString() + options.space + options.minute); + } + + if (seconds > 1) { + strings.push(Math.floor(seconds).toString() + options.space + options.seconds); + } else if (seconds === 1) { + strings.push(Math.floor(seconds).toString() + options.space + options.second); + } + + if (strings.length === 1) { + return strings.pop() + suffix; + } + + let last = strings.pop(); + return strings.join(", ") + " " + options.and + " " + last + suffix; +}; + +// ---------------------------------------------------------------------------------------------------------------- +// element data attributes + +NETDATA.dataAttribute = function (element, attribute, def) { + let key = 'data-' + attribute.toString(); + if (element.hasAttribute(key)) { + let data = element.getAttribute(key); + + if (data === 'true') { + return true; + } + if (data === 'false') { + return false; + } + if (data === 'null') { + return null; + } + + // Only convert to a number if it doesn't change the string + if (data === +data + '') { + return +data; + } + + if (/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test(data)) { + return JSON.parse(data); + } + + return data; + } else { + return def; + } +}; + +NETDATA.dataAttributeBoolean = function (element, attribute, def) { + let value = NETDATA.dataAttribute(element, attribute, def); + + if (value === true || value === false) // gmosx: Love this :) + { + return value; + } + + if (typeof(value) === 'string') { + if (value === 'yes' || value === 'on') { + return true; + } + + if (value === '' || value === 'no' || value === 'off' || value === 'null') { + return false; + } + + return def; + } + + if (typeof(value) === 'number') { + return value !== 0; + } + + return def; +}; + +// ---------------------------------------------------------------------------------------------------------------- +// fast numbers formatting + +NETDATA.fastNumberFormat = { + formattersFixed: [], + formattersZeroBased: [], + + // this is the fastest and the preferred + getIntlNumberFormat: function (min, max) { + let key = max; + if (min === max) { + if (typeof this.formattersFixed[key] === 'undefined') { + this.formattersFixed[key] = new Intl.NumberFormat(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + + return this.formattersFixed[key]; + } else if (min === 0) { + if (typeof this.formattersZeroBased[key] === 'undefined') { + this.formattersZeroBased[key] = new Intl.NumberFormat(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + + return this.formattersZeroBased[key]; + } else { + // this is never used + // it is added just for completeness + return new Intl.NumberFormat(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + }, + + // this respects locale + getLocaleString: function (min, max) { + let key = max; + if (min === max) { + if (typeof this.formattersFixed[key] === 'undefined') { + this.formattersFixed[key] = { + format: function (value) { + return value.toLocaleString(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + }; + } + + return this.formattersFixed[key]; + } else if (min === 0) { + if (typeof this.formattersZeroBased[key] === 'undefined') { + this.formattersZeroBased[key] = { + format: function (value) { + return value.toLocaleString(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + }; + } + + return this.formattersZeroBased[key]; + } else { + return { + format: function (value) { + return value.toLocaleString(undefined, { + // style: 'decimal', + // minimumIntegerDigits: 1, + // minimumSignificantDigits: 1, + // maximumSignificantDigits: 1, + useGrouping: true, + minimumFractionDigits: min, + maximumFractionDigits: max + }); + } + }; + } + }, + + // the fallback + getFixed: function (min, max) { + let key = max; + if (min === max) { + if (typeof this.formattersFixed[key] === 'undefined') { + this.formattersFixed[key] = { + format: function (value) { + if (value === 0) { + return "0"; + } + return value.toFixed(max); + } + }; + } + + return this.formattersFixed[key]; + } else if (min === 0) { + if (typeof this.formattersZeroBased[key] === 'undefined') { + this.formattersZeroBased[key] = { + format: function (value) { + if (value === 0) { + return "0"; + } + return value.toFixed(max); + } + }; + } + + return this.formattersZeroBased[key]; + } else { + return { + format: function (value) { + if (value === 0) { + return "0"; + } + return value.toFixed(max); + } + }; + } + }, + + testIntlNumberFormat: function () { + let value = 1.12345; + let e1 = "1.12", e2 = "1,12"; + let s = ""; + + try { + let x = new Intl.NumberFormat(undefined, { + useGrouping: true, + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + + s = x.format(value); + } catch (e) { + s = ""; + } + + // console.log('NumberFormat: ', s); + return (s === e1 || s === e2); + }, + + testLocaleString: function () { + let value = 1.12345; + let e1 = "1.12", e2 = "1,12"; + let s = ""; + + try { + s = value.toLocaleString(undefined, { + useGrouping: true, + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + } catch (e) { + s = ""; + } + + // console.log('localeString: ', s); + return (s === e1 || s === e2); + }, + + // on first run we decide which formatter to use + get: function (min, max) { + if (this.testIntlNumberFormat()) { + // console.log('numberformat'); + this.get = this.getIntlNumberFormat; + } else if (this.testLocaleString()) { + // console.log('localestring'); + this.get = this.getLocaleString; + } else { + // console.log('fixed'); + this.get = this.getFixed; + } + return this.get(min, max); + } +}; + +// ---------------------------------------------------------------------------------------------------------------- +// Detect the netdata server + +// http://stackoverflow.com/questions/984510/what-is-my-script-src-url +// http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url +NETDATA._scriptSource = function () { + let script = null; + + if (typeof document.currentScript !== 'undefined') { + script = document.currentScript; + } else { + const all_scripts = document.getElementsByTagName('script'); + script = all_scripts[all_scripts.length - 1]; + } + + if (typeof script.getAttribute.length !== 'undefined') { + script = script.src; + } else { + script = script.getAttribute('src', -1); + } + + return script; +}; + +// *** src/dashboard.js/server-detection.js + +if (typeof netdataServer !== 'undefined') { + NETDATA.serverDefault = netdataServer; +} else { + let s = NETDATA._scriptSource(); + if (s) { + NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)?$/g, ""); + } else { + console.log('WARNING: Cannot detect the URL of the netdata server.'); + NETDATA.serverDefault = null; + } +} + +if (NETDATA.serverDefault === null) { + NETDATA.serverDefault = ''; +} else if (NETDATA.serverDefault.slice(-1) !== '/') { + NETDATA.serverDefault += '/'; +} + +if (typeof netdataServerStatic !== 'undefined' && netdataServerStatic !== null && netdataServerStatic !== '') { + NETDATA.serverStatic = netdataServerStatic; + if (NETDATA.serverStatic.slice(-1) !== '/') { + NETDATA.serverStatic += '/'; + } +} else { + NETDATA.serverStatic = NETDATA.serverDefault; +} + +// *** src/dashboard.js/dependencies.js + +// default URLs for all the external files we need +// make them RELATIVE so that the whole thing can also be +// installed under a web server +NETDATA.jQuery = NETDATA.serverStatic + 'lib/jquery-2.2.4.min.js'; +NETDATA.peity_js = NETDATA.serverStatic + 'lib/jquery.peity-3.2.0.min.js'; +NETDATA.sparkline_js = NETDATA.serverStatic + 'lib/jquery.sparkline-2.1.2.min.js'; +NETDATA.easypiechart_js = NETDATA.serverStatic + 'lib/jquery.easypiechart-97b5824.min.js'; +NETDATA.gauge_js = NETDATA.serverStatic + 'lib/gauge-1.3.2.min.js'; +NETDATA.dygraph_js = NETDATA.serverStatic + 'lib/dygraph-c91c859.min.js'; +NETDATA.dygraph_smooth_js = NETDATA.serverStatic + 'lib/dygraph-smooth-plotter-c91c859.js'; +// NETDATA.raphael_js = NETDATA.serverStatic + 'lib/raphael-2.2.4-min.js'; +// NETDATA.c3_js = NETDATA.serverStatic + 'lib/c3-0.4.18.min.js'; +// NETDATA.c3_css = NETDATA.serverStatic + 'css/c3-0.4.18.min.css'; +NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-3.js'; +NETDATA.d3_js = NETDATA.serverStatic + 'lib/d3-4.12.2.min.js'; +// NETDATA.morris_js = NETDATA.serverStatic + 'lib/morris-0.5.1.min.js'; +// NETDATA.morris_css = NETDATA.serverStatic + 'css/morris-0.5.1.css'; +NETDATA.google_js = 'https://www.google.com/jsapi'; +// Error Handling + +NETDATA.errorCodes = { + 100: {message: "Cannot load chart library", alert: true}, + 101: {message: "Cannot load jQuery", alert: true}, + 402: {message: "Chart library not found", alert: false}, + 403: {message: "Chart library not enabled/is failed", alert: false}, + 404: {message: "Chart not found", alert: false}, + 405: {message: "Cannot download charts index from server", alert: true}, + 406: {message: "Invalid charts index downloaded from server", alert: true}, + 407: {message: "Cannot HELLO netdata server", alert: false}, + 408: {message: "Netdata servers sent invalid response to HELLO", alert: false}, + 409: {message: "Cannot ACCESS netdata registry", alert: false}, + 410: {message: "Netdata registry ACCESS failed", alert: false}, + 411: {message: "Netdata registry server send invalid response to DELETE ", alert: false}, + 412: {message: "Netdata registry DELETE failed", alert: false}, + 413: {message: "Netdata registry server send invalid response to SWITCH ", alert: false}, + 414: {message: "Netdata registry SWITCH failed", alert: false}, + 415: {message: "Netdata alarms download failed", alert: false}, + 416: {message: "Netdata alarms log download failed", alert: false}, + 417: {message: "Netdata registry server send invalid response to SEARCH ", alert: false}, + 418: {message: "Netdata registry SEARCH failed", alert: false} +}; + +NETDATA.errorLast = { + code: 0, + message: "", + datetime: 0 +}; + +NETDATA.error = function (code, msg) { + NETDATA.errorLast.code = code; + NETDATA.errorLast.message = msg; + NETDATA.errorLast.datetime = Date.now(); + + console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg); + + let ret = true; + if (typeof netdataErrorCallback === 'function') { + ret = netdataErrorCallback('system', code, msg); + } + + if (ret && NETDATA.errorCodes[code].alert) { + alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg); + } +}; + +NETDATA.errorReset = function () { + NETDATA.errorLast.code = 0; + NETDATA.errorLast.message = "You are doing fine!"; + NETDATA.errorLast.datetime = 0; +}; +// *** src/dashboard.js/compatibility.js + +// Compatibility fixes. + +// fix IE issue with console +if (!window.console) { + window.console = { + log: function () { + } + }; +} + +// if string.endsWith is not defined, define it +if (typeof String.prototype.endsWith !== 'function') { + String.prototype.endsWith = function (s) { + if (s.length > this.length) { + return false; + } + return this.slice(-s.length) === s; + }; +} + +// if string.startsWith is not defined, define it +if (typeof String.prototype.startsWith !== 'function') { + String.prototype.startsWith = function (s) { + if (s.length > this.length) { + return false; + } + return this.slice(s.length) === s; + }; +} +// ---------------------------------------------------------------------------------------------------------------- +// XSS checks + +NETDATA.xss = { + enabled: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS, + enabled_for_data: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS, + + string: function (s) { + return s.toString() + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, + + object: function (name, obj, ignore_regex) { + if (typeof ignore_regex !== 'undefined' && ignore_regex.test(name)) { + // console.log('XSS: ignoring "' + name + '"'); + return obj; + } + + switch (typeof(obj)) { + case 'string': + const ret = this.string(obj); + if (ret !== obj) { + console.log('XSS protection changed string ' + name + ' from "' + obj + '" to "' + ret + '"'); + } + return ret; + + case 'object': + if (obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + // console.log('checking array "' + name + '"'); + + let len = obj.length; + while (len--) { + obj[len] = this.object(name + '[' + len + ']', obj[len], ignore_regex); + } + } else { + // console.log('checking object "' + name + '"'); + + for (var i in obj) { + if (obj.hasOwnProperty(i) === false) { + continue; + } + if (this.string(i) !== i) { + console.log('XSS protection removed invalid object member "' + name + '.' + i + '"'); + delete obj[i]; + } else { + obj[i] = this.object(name + '.' + i, obj[i], ignore_regex); + } + } + } + return obj; + + default: + return obj; + } + }, + + checkOptional: function (name, obj, ignore_regex) { + if (this.enabled) { + //console.log('XSS: checking optional "' + name + '"...'); + return this.object(name, obj, ignore_regex); + } + return obj; + }, + + checkAlways: function (name, obj, ignore_regex) { + //console.log('XSS: checking always "' + name + '"...'); + return this.object(name, obj, ignore_regex); + }, + + checkData: function (name, obj, ignore_regex) { + if (this.enabled_for_data) { + //console.log('XSS: checking data "' + name + '"...'); + return this.object(name, obj, ignore_regex); + } + return obj; + } +}; +NETDATA.colorHex2Rgb = function (hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + + let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +}; + +NETDATA.colorLuminance = function (hex, lum) { + // validate hex string + hex = String(hex).replace(/[^0-9a-f]/gi, ''); + if (hex.length < 6) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + + lum = lum || 0; + + // convert to decimal and change luminosity + let rgb = "#"; + for (let i = 0; i < 3; i++) { + let c = parseInt(hex.substr(i * 2, 2), 16); + c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); + rgb += ("00" + c).substr(c.length); + } + + return rgb; +}; +NETDATA.unitsConversion = { + keys: {}, // keys for data-common-units + latest: {}, // latest selected units for data-common-units + + globalReset: function () { + this.keys = {}; + this.latest = {}; + }, + + scalableUnits: { + 'packets/s': { + 'pps': 1, + 'Kpps': 1000, + 'Mpps': 1000000 + }, + 'pps': { + 'pps': 1, + 'Kpps': 1000, + 'Mpps': 1000000 + }, + 'kilobits/s': { + 'bits/s': 1 / 1000, + 'kilobits/s': 1, + 'megabits/s': 1000, + 'gigabits/s': 1000000, + 'terabits/s': 1000000000 + }, + 'bytes/s': { + 'bytes/s': 1, + 'kilobytes/s': 1024, + 'megabytes/s': 1024 * 1024, + 'gigabytes/s': 1024 * 1024 * 1024, + 'terabytes/s': 1024 * 1024 * 1024 * 1024 + }, + 'kilobytes/s': { + 'bytes/s': 1 / 1024, + 'kilobytes/s': 1, + 'megabytes/s': 1024, + 'gigabytes/s': 1024 * 1024, + 'terabytes/s': 1024 * 1024 * 1024 + }, + 'B/s': { + 'B/s': 1, + 'KiB/s': 1024, + 'MiB/s': 1024 * 1024, + 'GiB/s': 1024 * 1024 * 1024, + 'TiB/s': 1024 * 1024 * 1024 * 1024 + }, + 'KB/s': { + 'B/s': 1 / 1024, + 'KB/s': 1, + 'MB/s': 1024, + 'GB/s': 1024 * 1024, + 'TB/s': 1024 * 1024 * 1024 + }, + 'KiB/s': { + 'B/s': 1 / 1024, + 'KiB/s': 1, + 'MiB/s': 1024, + 'GiB/s': 1024 * 1024, + 'TiB/s': 1024 * 1024 * 1024 + }, + 'B': { + 'B': 1, + 'KiB': 1024, + 'MiB': 1024 * 1024, + 'GiB': 1024 * 1024 * 1024, + 'TiB': 1024 * 1024 * 1024 * 1024, + 'PiB': 1024 * 1024 * 1024 * 1024 * 1024 + }, + 'KB': { + 'B': 1 / 1024, + 'KB': 1, + 'MB': 1024, + 'GB': 1024 * 1024, + 'TB': 1024 * 1024 * 1024 + }, + 'KiB': { + 'B': 1 / 1024, + 'KiB': 1, + 'MiB': 1024, + 'GiB': 1024 * 1024, + 'TiB': 1024 * 1024 * 1024 + }, + 'MB': { + 'B': 1 / (1024 * 1024), + 'KB': 1 / 1024, + 'MB': 1, + 'GB': 1024, + 'TB': 1024 * 1024, + 'PB': 1024 * 1024 * 1024 + }, + 'MiB': { + 'B': 1 / (1024 * 1024), + 'KiB': 1 / 1024, + 'MiB': 1, + 'GiB': 1024, + 'TiB': 1024 * 1024, + 'PiB': 1024 * 1024 * 1024 + }, + 'GB': { + 'B': 1 / (1024 * 1024 * 1024), + 'KB': 1 / (1024 * 1024), + 'MB': 1 / 1024, + 'GB': 1, + 'TB': 1024, + 'PB': 1024 * 1024, + 'EB': 1024 * 1024 * 1024 + }, + 'GiB': { + 'B': 1 / (1024 * 1024 * 1024), + 'KiB': 1 / (1024 * 1024), + 'MiB': 1 / 1024, + 'GiB': 1, + 'TiB': 1024, + 'PiB': 1024 * 1024, + 'EiB': 1024 * 1024 * 1024 + }, + 'num': { + 'num': 1, + 'num (K)': 1000, + 'num (M)': 1000000, + 'num (G)': 1000000000, + 'num (T)': 1000000000000 + } + /* + 'milliseconds': { + 'seconds': 1000 + }, + 'seconds': { + 'milliseconds': 0.001, + 'seconds': 1, + 'minutes': 60, + 'hours': 3600, + 'days': 86400 + } + */ + }, + + convertibleUnits: { + 'Celsius': { + 'Fahrenheit': { + check: function (max) { + void(max); + return NETDATA.options.current.temperature === 'fahrenheit'; + }, + convert: function (value) { + return value * 9 / 5 + 32; + } + } + }, + 'celsius': { + 'fahrenheit': { + check: function (max) { + void(max); + return NETDATA.options.current.temperature === 'fahrenheit'; + }, + convert: function (value) { + return value * 9 / 5 + 32; + } + } + }, + 'seconds': { + 'time': { + check: function (max) { + void(max); + return NETDATA.options.current.seconds_as_time; + }, + convert: function (seconds) { + return NETDATA.unitsConversion.seconds2time(seconds); + } + } + }, + 'milliseconds': { + 'milliseconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time && max < 1000; + }, + convert: function (milliseconds) { + let tms = Math.round(milliseconds * 10); + milliseconds = Math.floor(tms / 10); + + tms -= milliseconds * 10; + + return (milliseconds).toString() + '.' + tms.toString(); + } + }, + 'seconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time && max >= 1000 && max < 60000; + }, + convert: function (milliseconds) { + milliseconds = Math.round(milliseconds); + + let seconds = Math.floor(milliseconds / 1000); + milliseconds -= seconds * 1000; + + milliseconds = Math.round(milliseconds / 10); + + return seconds.toString() + '.' + + NETDATA.zeropad(milliseconds); + } + }, + 'M:SS.ms': { + check: function (max) { + return NETDATA.options.current.seconds_as_time && max >= 60000; + }, + convert: function (milliseconds) { + milliseconds = Math.round(milliseconds); + + let minutes = Math.floor(milliseconds / 60000); + milliseconds -= minutes * 60000; + + let seconds = Math.floor(milliseconds / 1000); + milliseconds -= seconds * 1000; + + milliseconds = Math.round(milliseconds / 10); + + return minutes.toString() + ':' + + NETDATA.zeropad(seconds) + '.' + + NETDATA.zeropad(milliseconds); + } + } + }, + 'nanoseconds': { + 'nanoseconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time && max < 1000; + }, + convert: function (nanoseconds) { + let tms = Math.round(nanoseconds * 10); + nanoseconds = Math.floor(tms / 10); + + tms -= nanoseconds * 10; + + return (nanoseconds).toString() + '.' + tms.toString(); + } + }, + 'microseconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time + && max >= 1000 && max < 1000 * 1000; + }, + convert: function (nanoseconds) { + nanoseconds = Math.round(nanoseconds); + + let microseconds = Math.floor(nanoseconds / 1000); + nanoseconds -= microseconds * 1000; + + nanoseconds = Math.round(nanoseconds / 10 ); + + return microseconds.toString() + '.' + + NETDATA.zeropad(nanoseconds); + } + }, + 'milliseconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time + && max >= 1000 * 1000 && max < 1000 * 1000 * 1000; + }, + convert: function (nanoseconds) { + nanoseconds = Math.round(nanoseconds); + + let milliseconds = Math.floor(nanoseconds / 1000 / 1000); + nanoseconds -= milliseconds * 1000 * 1000; + + nanoseconds = Math.round(nanoseconds / 1000 / 10); + + return milliseconds.toString() + '.' + + NETDATA.zeropad(nanoseconds); + } + }, + 'seconds': { + check: function (max) { + return NETDATA.options.current.seconds_as_time + && max >= 1000 * 1000 * 1000; + }, + convert: function (nanoseconds) { + nanoseconds = Math.round(nanoseconds); + + let seconds = Math.floor(nanoseconds / 1000 / 1000 / 1000); + nanoseconds -= seconds * 1000 * 1000 * 1000; + + nanoseconds = Math.round(nanoseconds / 1000 / 1000 / 10); + + return seconds.toString() + '.' + + NETDATA.zeropad(nanoseconds); + } + }, + } + }, + + seconds2time: function (seconds) { + seconds = Math.abs(seconds); + + let days = Math.floor(seconds / 86400); + seconds -= days * 86400; + + let hours = Math.floor(seconds / 3600); + seconds -= hours * 3600; + + let minutes = Math.floor(seconds / 60); + seconds -= minutes * 60; + + seconds = Math.round(seconds); + + let ms_txt = ''; + /* + let ms = seconds - Math.floor(seconds); + seconds -= ms; + ms = Math.round(ms * 1000); + + if (ms > 1) { + if (ms < 10) + ms_txt = '.00' + ms.toString(); + else if (ms < 100) + ms_txt = '.0' + ms.toString(); + else + ms_txt = '.' + ms.toString(); + } + */ + + return ((days > 0) ? days.toString() + 'd:' : '').toString() + + NETDATA.zeropad(hours) + ':' + + NETDATA.zeropad(minutes) + ':' + + NETDATA.zeropad(seconds) + + ms_txt; + }, + + // get a function that converts the units + // + every time units are switched call the callback + get: function (uuid, min, max, units, desired_units, common_units_name, switch_units_callback) { + // validate the parameters + if (typeof units === 'undefined') { + units = 'undefined'; + } + + // check if we support units conversion + if (typeof this.scalableUnits[units] === 'undefined' && typeof this.convertibleUnits[units] === 'undefined') { + // we can't convert these units + //console.log('DEBUG: ' + uuid.toString() + ' can\'t convert units: ' + units.toString()); + return function (value) { + return value; + }; + } + + // check if the caller wants the original units + if (typeof desired_units === 'undefined' || desired_units === null || desired_units === 'original' || desired_units === units) { + //console.log('DEBUG: ' + uuid.toString() + ' original units wanted'); + switch_units_callback(units); + return function (value) { + return value; + }; + } + + // now we know we can convert the units + // and the caller wants some kind of conversion + + let tunits = null; + let tdivider = 0; + + if (typeof this.scalableUnits[units] !== 'undefined') { + // units that can be scaled + // we decide a divider + + // console.log('NETDATA.unitsConversion.get(' + units.toString() + ', ' + desired_units.toString() + ', function()) decide divider with min = ' + min.toString() + ', max = ' + max.toString()); + + if (desired_units === 'auto') { + // the caller wants to auto-scale the units + + // find the absolute maximum value that is rendered on the chart + // based on this we decide the scale + min = Math.abs(min); + max = Math.abs(max); + if (min > max) { + max = min; + } + + // find the smallest scale that provides integers + // for (x in this.scalableUnits[units]) { + // if (this.scalableUnits[units].hasOwnProperty(x)) { + // let m = this.scalableUnits[units][x]; + // if (m <= max && m > tdivider) { + // tunits = x; + // tdivider = m; + // } + // } + // } + const sunit = this.scalableUnits[units]; + for (var x of Object.keys(sunit)) { + let m = sunit[x]; + if (m <= max && m > tdivider) { + tunits = x; + tdivider = m; + } + } + + if (tunits === null || tdivider <= 0) { + // we couldn't find one + //console.log('DEBUG: ' + uuid.toString() + ' cannot find an auto-scaling candidate for units: ' + units.toString() + ' (max: ' + max.toString() + ')'); + switch_units_callback(units); + return function (value) { + return value; + }; + } + + if (typeof common_units_name === 'string' && typeof uuid === 'string') { + // the caller wants several charts to have the same units + // data-common-units + + let common_units_key = common_units_name + '-' + units; + + // add our divider into the list of keys + let t = this.keys[common_units_key]; + if (typeof t === 'undefined') { + this.keys[common_units_key] = {}; + t = this.keys[common_units_key]; + } + t[uuid] = { + units: tunits, + divider: tdivider + }; + + // find the max divider of all charts + let common_units = t[uuid]; + for (var x in t) { + if (t.hasOwnProperty(x) && t[x].divider > common_units.divider) { + common_units = t[x]; + } + } + + // save our common_max to the latest keys + let latest = this.latest[common_units_key]; + if (typeof latest === 'undefined') { + this.latest[common_units_key] = {}; + latest = this.latest[common_units_key]; + } + latest.units = common_units.units; + latest.divider = common_units.divider; + + tunits = latest.units; + tdivider = latest.divider; + + //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', common-units=' + common_units_name.toString() + ((t[uuid].divider !== tdivider)?' USED COMMON, mine was ' + t[uuid].units:' set common').toString()); + + // apply it to this chart + switch_units_callback(tunits); + return function (value) { + if (tdivider !== latest.divider) { + // another chart switched our common units + // we should switch them too + //console.log('DEBUG: ' + uuid + ' switching units due to a common-units change, from ' + tunits.toString() + ' to ' + latest.units.toString()); + tunits = latest.units; + tdivider = latest.divider; + switch_units_callback(tunits); + } + + return value / tdivider; + }; + } else { + // the caller did not give data-common-units + // this chart auto-scales independently of all others + //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', autonomously'); + + switch_units_callback(tunits); + return function (value) { + return value / tdivider; + }; + } + } else { + // the caller wants specific units + + if (typeof this.scalableUnits[units][desired_units] !== 'undefined') { + // all good, set the new units + tdivider = this.scalableUnits[units][desired_units]; + // console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + desired_units.toString() + ' with divider ' + tdivider.toString() + ', by reference'); + switch_units_callback(desired_units); + return function (value) { + return value / tdivider; + }; + } else { + // oops! switch back to original units + console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.'); + switch_units_callback(units); + return function (value) { + return value; + }; + } + } + } else if (typeof this.convertibleUnits[units] !== 'undefined') { + // units that can be converted + if (desired_units === 'auto') { + for (var x in this.convertibleUnits[units]) { + if (this.convertibleUnits[units].hasOwnProperty(x)) { + if (this.convertibleUnits[units][x].check(max)) { + //console.log('DEBUG: ' + uuid.toString() + ' converting ' + units.toString() + ' to: ' + x.toString()); + switch_units_callback(x); + return this.convertibleUnits[units][x].convert; + } + } + } + + // none checked ok + //console.log('DEBUG: ' + uuid.toString() + ' no conversion available for ' + units.toString() + ' to: ' + desired_units.toString()); + switch_units_callback(units); + return function (value) { + return value; + }; + } else if (typeof this.convertibleUnits[units][desired_units] !== 'undefined') { + switch_units_callback(desired_units); + return this.convertibleUnits[units][desired_units].convert; + } else { + console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.'); + switch_units_callback(units); + return function (value) { + return value; + }; + } + } else { + // hm... did we forget to implement the new type? + console.log(`Unmatched unit conversion method for units ${units.toString()}`); + switch_units_callback(units); + return function (value) { + return value; + }; + } + } +}; + +NETDATA.icons = { + left: '', + reset: '', + right: '', + zoomIn: '', + zoomOut: '', + resize: '', + lineChart: '', + areaChart: '', + noChart: '', + loading: '', + noData: '' +}; + +if (typeof netdataIcons === 'object') { + // for (let icon in NETDATA.icons) { + // if (NETDATA.icons.hasOwnProperty(icon) && typeof(netdataIcons[icon]) === 'string') + // NETDATA.icons[icon] = netdataIcons[icon]; + // } + for (var icon of Object.keys(NETDATA.icons)) { + if (typeof(netdataIcons[icon]) === 'string') { + NETDATA.icons[icon] = netdataIcons[icon] + } + } +} + +if (typeof netdataSnapshotData === 'undefined') { + netdataSnapshotData = null; +} + +if (typeof netdataShowHelp === 'undefined') { + netdataShowHelp = true; +} + +if (typeof netdataShowAlarms === 'undefined') { + netdataShowAlarms = false; +} + +if (typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0) { + netdataRegistryAfterMs = 0; // 1500; +} + +if (typeof netdataRegistry === 'undefined') { + // backward compatibility + netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false); +} + +if (netdataRegistry === false && typeof netdataRegistryCallback === 'function') { + netdataRegistry = true; +} + +// ---------------------------------------------------------------------------------------------------------------- +// the defaults for all charts + +// if the user does not specify any of these, the following will be used + +NETDATA.chartDefaults = { + width: '100%', // the chart width - can be null + height: '100%', // the chart height - can be null + min_width: null, // the chart minimum width - can be null + library: 'dygraph', // the graphing library to use + method: 'average', // the grouping method + before: 0, // panning + after: -600, // panning + pixels_per_point: 1, // the detail of the chart + fill_luminance: 0.8 // luminance of colors in solid areas +}; + +// ---------------------------------------------------------------------------------------------------------------- +// global options + +NETDATA.options = { + pauseCallback: null, // a callback when we are really paused + + pause: false, // when enabled we don't auto-refresh the charts + + targets: [], // an array of all the state objects that are + // currently active (independently of their + // viewport visibility) + + updated_dom: true, // when true, the DOM has been updated with + // new elements we have to check. + + auto_refresher_fast_weight: 0, // this is the current time in ms, spent + // rendering charts continuously. + // used with .current.fast_render_timeframe + + page_is_visible: true, // when true, this page is visible + + auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the + // auto-refresher for some time (when a chart is + // performing pan or zoom, we need to stop refreshing + // all other charts, to have the maximum speed for + // rendering the chart that is panned or zoomed). + // Used with .current.global_pan_sync_time + + on_scroll_refresher_stop_until: 0, // timestamp in ms - used to stop evaluating + // charts for some time, after a page scroll + + last_page_resize: Date.now(), // the timestamp of the last resize request + + last_page_scroll: 0, // the timestamp the last time the page was scrolled + + browser_timezone: 'unknown', // timezone detected by javascript + server_timezone: 'unknown', // timezone reported by the server + + force_data_points: 0, // force the number of points to be returned for charts + fake_chart_rendering: false, // when set to true, the dashboard will download data but will not render the charts + + passive_events: null, // true if the browser supports passive events + + // the current profile + // we may have many... + current: { + units: 'auto', // can be 'auto' or 'original' + temperature: 'celsius', // can be 'celsius' or 'fahrenheit' + seconds_as_time: true, // show seconds as DDd:HH:MM:SS ? + timezone: 'default', // the timezone to use, or 'default' + user_set_server_timezone: 'default', // as set by the user on the dashboard + + legend_toolbox: true, // show the legend toolbox on charts + resize_charts: true, // show the resize handler on charts + + pixels_per_point: isSlowDevice() ? 5 : 1, // the minimum pixels per point for all charts + // increase this to speed javascript up + // each chart library has its own limit too + // the max of this and the chart library is used + // the final is calculated every time, so a change + // here will have immediate effect on the next chart + // update + + idle_between_charts: 100, // ms - how much time to wait between chart updates + + fast_render_timeframe: 200, // ms - render continuously until this time of continuous + // rendering has been reached + // this setting is used to make it render e.g. 10 + // charts at once, sleep idle_between_charts time + // and continue for another 10 charts. + + idle_between_loops: 500, // ms - if all charts have been updated, wait this + // time before starting again. + + idle_parallel_loops: 100, // ms - the time between parallel refresher updates + + idle_lost_focus: 500, // ms - when the window does not have focus, check + // if focus has been regained, every this time + + global_pan_sync_time: 300, // ms - when you pan or zoom a chart, the background + // auto-refreshing of charts is paused for this amount + // of time + + sync_selection_delay: 400, // ms - when you pan or zoom a chart, wait this amount + // of time before setting up synchronized selections + // on hover. + + sync_selection: true, // enable or disable selection sync + + pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart + + sync_pan_and_zoom: true, // enable or disable pan and zoom sync + + pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming + + update_only_visible: true, // enable or disable visibility management / used for printing + + parallel_refresher: !isSlowDevice(), // enable parallel refresh of charts + + concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts + + destroy_on_hide: isSlowDevice(), // destroy charts when they are not visible + + show_help: netdataShowHelp, // when enabled the charts will show some help + show_help_delay_show_ms: 500, + show_help_delay_hide_ms: 0, + + eliminate_zero_dimensions: true, // do not show dimensions with just zeros + + stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus + stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts + + double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap + + smooth_plot: !isSlowDevice(), // enable smooth plot, where possible + + color_fill_opacity_line: 1.0, + color_fill_opacity_area: 0.2, + color_fill_opacity_stacked: 0.8, + + pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox + pan_and_zoom_factor_multiplier_control: 2.0, + pan_and_zoom_factor_multiplier_shift: 3.0, + pan_and_zoom_factor_multiplier_alt: 4.0, + + abort_ajax_on_scroll: false, // kill pending ajax page scroll + async_on_scroll: false, // sync/async onscroll handler + onscroll_worker_duration_threshold: 30, // time in ms, for async scroll handler + + retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server + + setOptionCallback: function () { + } + }, + + debug: { + show_boxes: false, + main_loop: false, + focus: false, + visibility: false, + chart_data_url: false, + chart_errors: true, // remember to set it to false before merging + chart_timing: false, + chart_calls: false, + libraries: false, + dygraph: false, + globalSelectionSync: false, + globalPanAndZoom: false + } +}; + +NETDATA.statistics = { + refreshes_total: 0, + refreshes_active: 0, + refreshes_active_max: 0 +}; + +// local storage options + +NETDATA.localStorage = { + default: {}, + current: {}, + callback: {} // only used for resetting back to defaults +}; + +NETDATA.localStorageTested = -1; +NETDATA.localStorageTest = function () { + if (NETDATA.localStorageTested !== -1) { + return NETDATA.localStorageTested; + } + + if (typeof Storage !== "undefined" && typeof localStorage === 'object') { + let test = 'test'; + try { + localStorage.setItem(test, test); + localStorage.removeItem(test); + NETDATA.localStorageTested = true; + } catch (e) { + NETDATA.localStorageTested = false; + } + } else { + NETDATA.localStorageTested = false; + } + + return NETDATA.localStorageTested; +}; + +NETDATA.localStorageGet = function (key, def, callback) { + let ret = def; + + if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') { + NETDATA.localStorage.default[key.toString()] = def; + NETDATA.localStorage.callback[key.toString()] = callback; + } + + if (NETDATA.localStorageTest()) { + try { + // console.log('localStorage: loading "' + key.toString() + '"'); + ret = localStorage.getItem(key.toString()); + // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString()); + if (ret === null || ret === 'undefined') { + // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"'); + localStorage.setItem(key.toString(), JSON.stringify(def)); + ret = def; + } else { + // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"'); + ret = JSON.parse(ret); + // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret)); + } + } catch (error) { + console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"'); + ret = def; + } + } + + if (typeof ret === 'undefined' || ret === 'undefined') { + console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret)); + ret = def; + } + + NETDATA.localStorage.current[key.toString()] = ret; + return ret; +}; + +NETDATA.localStorageSet = function (key, value, callback) { + if (typeof value === 'undefined' || value === 'undefined') { + console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value)); + } + + if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') { + NETDATA.localStorage.default[key.toString()] = value; + NETDATA.localStorage.current[key.toString()] = value; + NETDATA.localStorage.callback[key.toString()] = callback; + } + + if (NETDATA.localStorageTest()) { + // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"'); + try { + localStorage.setItem(key.toString(), JSON.stringify(value)); + } catch (e) { + console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"'); + } + } + + NETDATA.localStorage.current[key.toString()] = value; + return value; +}; + +NETDATA.localStorageGetRecursive = function (obj, prefix, callback) { + let keys = Object.keys(obj); + let len = keys.length; + while (len--) { + let i = keys[len]; + + if (typeof obj[i] === 'object') { + //console.log('object ' + prefix + '.' + i.toString()); + NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback); + continue; + } + + obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback); + } +}; + +NETDATA.setOption = function (key, value) { + if (key.toString() === 'setOptionCallback') { + if (typeof NETDATA.options.current.setOptionCallback === 'function') { + NETDATA.options.current[key.toString()] = value; + NETDATA.options.current.setOptionCallback(); + } + } else if (NETDATA.options.current[key.toString()] !== value) { + let name = 'options.' + key.toString(); + + if (typeof NETDATA.localStorage.default[name.toString()] === 'undefined') { + console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value); + } + + //console.log(NETDATA.localStorage); + //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()])); + //console.log(NETDATA.options); + NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null); + + if (typeof NETDATA.options.current.setOptionCallback === 'function') { + NETDATA.options.current.setOptionCallback(); + } + } + + return true; +}; + +NETDATA.getOption = function (key) { + return NETDATA.options.current[key.toString()]; +}; + +// read settings from local storage +NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null); + +// always start with this option enabled. +NETDATA.setOption('stop_updates_when_focus_is_lost', true); + +NETDATA.resetOptions = function () { + let keys = Object.keys(NETDATA.localStorage.default); + let len = keys.length; + + while (len--) { + let i = keys[len]; + let a = i.split('.'); + + if (a[0] === 'options') { + if (a[1] === 'setOptionCallback') { + continue; + } + if (typeof NETDATA.localStorage.default[i] === 'undefined') { + continue; + } + if (NETDATA.options.current[i] === NETDATA.localStorage.default[i]) { + continue; + } + + NETDATA.setOption(a[1], NETDATA.localStorage.default[i]); + } else if (a[0] === 'chart_heights') { + if (typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') { + NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]); + } + } + } + + NETDATA.dateTime.init(NETDATA.options.current.timezone); +}; + +// *** src/dashboard.js/timeout.js + +// TODO: Better name needed + +NETDATA.timeout = { + // by default, these are just wrappers to setTimeout() / clearTimeout() + + step: function (callback) { + return window.setTimeout(callback, 1000 / 60); + }, + + set: function (callback, delay) { + return window.setTimeout(callback, delay); + }, + + clear: function (id) { + return window.clearTimeout(id); + }, + + init: function () { + let custom = true; + + if (window.requestAnimationFrame) { + this.step = function (callback) { + return window.requestAnimationFrame(callback); + }; + + this.clear = function (handle) { + return window.cancelAnimationFrame(handle.value); + }; + // } else if (window.webkitRequestAnimationFrame) { + // this.step = function (callback) { + // return window.webkitRequestAnimationFrame(callback); + // }; + + // if (window.webkitCancelAnimationFrame) { + // this.clear = function (handle) { + // return window.webkitCancelAnimationFrame(handle.value); + // }; + // } else if (window.webkitCancelRequestAnimationFrame) { + // this.clear = function (handle) { + // return window.webkitCancelRequestAnimationFrame(handle.value); + // }; + // } + // } else if (window.mozRequestAnimationFrame) { + // this.step = function (callback) { + // return window.mozRequestAnimationFrame(callback); + // }; + + // this.clear = function (handle) { + // return window.mozCancelRequestAnimationFrame(handle.value); + // }; + // } else if (window.oRequestAnimationFrame) { + // this.step = function (callback) { + // return window.oRequestAnimationFrame(callback); + // }; + + // this.clear = function (handle) { + // return window.oCancelRequestAnimationFrame(handle.value); + // }; + // } else if (window.msRequestAnimationFrame) { + // this.step = function (callback) { + // return window.msRequestAnimationFrame(callback); + // }; + + // this.clear = function (handle) { + // return window.msCancelRequestAnimationFrame(handle.value); + // }; + } else { + custom = false; + } + + if (custom) { + // we have installed custom .step() / .clear() functions + // overwrite the .set() too + + this.set = function (callback, delay) { + let start = Date.now(), + handle = new Object(); + + const loop = () => { + let current = Date.now(), + delta = current - start; + + if (delta >= delay) { + callback.call(); + } else { + handle.value = this.step(loop); + } + } + + handle.value = this.step(loop); + return handle; + }; + } + } +}; + +NETDATA.timeout.init(); +// Codacy declarations +/* global netdataTheme */ + +NETDATA.themes = { + white: { + bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-3.3.7.css', + dashboard_css: NETDATA.serverStatic + 'dashboard.css?v20190902-0', + background: '#FFFFFF', + foreground: '#000000', + grid: '#F0F0F0', + axis: '#F0F0F0', + highlight: '#F5F5F5', + colors: ['#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477', + '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6', + '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707', + '#329262', '#3B3EAC'], + easypiechart_track: '#f0f0f0', + easypiechart_scale: '#dfe0e0', + gauge_pointer: '#C0C0C0', + gauge_stroke: '#F0F0F0', + gauge_gradient: false, + d3pie: { + title: '#333333', + subtitle: '#666666', + footer: '#888888', + other: '#aaaaaa', + mainlabel: '#333333', + percentage: '#dddddd', + value: '#aaaa22', + tooltip_bg: '#000000', + tooltip_fg: '#efefef', + segment_stroke: "#ffffff", + gradient_color: '#000000' + } + }, + slate: { + bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1', + dashboard_css: NETDATA.serverStatic + 'dashboard.slate.css?v20190902-0', + background: '#272b30', + foreground: '#C8C8C8', + grid: '#283236', + axis: '#283236', + highlight: '#383838', + /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00', + '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0', + '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a', + '#a6a479', '#a66da8' ], + */ + colors: ['#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00', + '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700', + '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737', + '#329262', '#3B3EFF'], + easypiechart_track: '#373b40', + easypiechart_scale: '#373b40', + gauge_pointer: '#474b50', + gauge_stroke: '#373b40', + gauge_gradient: false, + d3pie: { + title: '#C8C8C8', + subtitle: '#283236', + footer: '#283236', + other: '#283236', + mainlabel: '#C8C8C8', + percentage: '#dddddd', + value: '#cccc44', + tooltip_bg: '#272b30', + tooltip_fg: '#C8C8C8', + segment_stroke: "#283236", + gradient_color: '#000000' + } + } +}; + +if (typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined') { + NETDATA.themes.current = NETDATA.themes[netdataTheme]; +} else { + NETDATA.themes.current = NETDATA.themes.white; +} + +NETDATA.colors = NETDATA.themes.current.colors; + +// these are the colors Google Charts are using +// we have them here to attempt emulate their look and feel on the other chart libraries +// http://there4.io/2012/05/02/google-chart-color-list/ +//NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6', +// '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11', +// '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ]; + +// an alternative set +// http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/ +// (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray) +//NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ]; +// dygraph + +// Codacy declarations +/* global smoothPlotter */ +/* global Dygraph */ + +NETDATA.dygraph = { + smooth: false +}; + +NETDATA.dygraphToolboxPanAndZoom = function (state, after, before) { + if (after < state.netdata_first) { + after = state.netdata_first; + } + + if (before > state.netdata_last) { + before = state.netdata_last; + } + + state.setMode('zoom'); + NETDATA.globalSelectionSync.stop(); + NETDATA.globalSelectionSync.delay(); + state.tmp.dygraph_user_action = true; + state.tmp.dygraph_force_zoom = true; + // state.log('toolboxPanAndZoom'); + state.updateChartPanOrZoom(after, before); + NETDATA.globalPanAndZoom.setMaster(state, after, before); +}; + +NETDATA.dygraphSetSelection = function (state, t) { + if (typeof state.tmp.dygraph_instance !== 'undefined') { + let r = state.calculateRowForTime(t); + if (r !== -1) { + state.tmp.dygraph_instance.setSelection(r); + return true; + } else { + state.tmp.dygraph_instance.clearSelection(); + state.legendShowUndefined(); + } + } + + return false; +}; + +NETDATA.dygraphClearSelection = function (state) { + if (typeof state.tmp.dygraph_instance !== 'undefined') { + state.tmp.dygraph_instance.clearSelection(); + } + return true; +}; + +NETDATA.dygraphSmoothInitialize = function (callback) { + $.ajax({ + url: NETDATA.dygraph_smooth_js, + cache: true, + dataType: "script", + xhrFields: {withCredentials: true} // required for the cookie + }) + .done(function () { + NETDATA.dygraph.smooth = true; + smoothPlotter.smoothing = 0.3; + }) + .fail(function () { + NETDATA.dygraph.smooth = false; + }) + .always(function () { + if (typeof callback === "function") { + return callback(); + } + }); +}; + +NETDATA.dygraphInitialize = function (callback) { + if (typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) { + $.ajax({ + url: NETDATA.dygraph_js, + cache: true, + dataType: "script", + xhrFields: {withCredentials: true} // required for the cookie + }) + .done(function () { + NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js); + }) + .fail(function () { + NETDATA.chartLibraries.dygraph.enabled = false; + NETDATA.error(100, NETDATA.dygraph_js); + }) + .always(function () { + if (NETDATA.chartLibraries.dygraph.enabled && NETDATA.options.current.smooth_plot) { + NETDATA.dygraphSmoothInitialize(callback); + } else if (typeof callback === "function") { + return callback(); + } + }); + } else { + NETDATA.chartLibraries.dygraph.enabled = false; + if (typeof callback === "function") { + return callback(); + } + } +}; + +NETDATA.dygraphChartUpdate = function (state, data) { + let dygraph = state.tmp.dygraph_instance; + + if (typeof dygraph === 'undefined') { + return NETDATA.dygraphChartCreate(state, data); + } + + // when the chart is not visible, and hidden + // if there is a window resize, dygraph detects + // its element size as 0x0. + // this will make it re-appear properly + + if (state.tm.last_unhidden > state.tmp.dygraph_last_rendered) { + dygraph.resize(); + } + + let options = { + file: data.result.data, + colors: state.chartColors(), + labels: data.result.labels, + //labelsDivWidth: state.chartWidth() - 70, + includeZero: state.tmp.dygraph_include_zero, + visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names) + }; + + if (state.tmp.dygraph_chart_type === 'stacked') { + if (options.includeZero && state.dimensions_visibility.countSelected() < options.visibility.length) { + options.includeZero = 0; + } + } + + if (!NETDATA.chartLibraries.dygraph.isSparkline(state)) { + options.ylabel = state.units_current; // (state.units_desired === 'auto')?"":state.units_current; + } + + if (state.tmp.dygraph_force_zoom) { + if (NETDATA.options.debug.dygraph || state.debug) { + state.log('dygraphChartUpdate() forced zoom update'); + } + + options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null; + //options.isZoomedIgnoreProgrammaticZoom = true; + state.tmp.dygraph_force_zoom = false; + } else if (state.current.name !== 'auto') { + if (NETDATA.options.debug.dygraph || state.debug) { + state.log('dygraphChartUpdate() loose update'); + } + } else { + if (NETDATA.options.debug.dygraph || state.debug) { + state.log('dygraphChartUpdate() strict update'); + } + + options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null; + //options.isZoomedIgnoreProgrammaticZoom = true; + } + + options.valueRange = state.tmp.dygraph_options.valueRange; + + let oldMax = null, oldMin = null; + if (state.tmp.__commonMin !== null) { + state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0]; + oldMin = options.valueRange[0] = NETDATA.commonMin.get(state); + } + if (state.tmp.__commonMax !== null) { + state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1]; + oldMax = options.valueRange[1] = NETDATA.commonMax.get(state); + } + + if (state.tmp.dygraph_smooth_eligible) { + if ((NETDATA.options.current.smooth_plot && state.tmp.dygraph_options.plotter !== smoothPlotter) + || (NETDATA.options.current.smooth_plot === false && state.tmp.dygraph_options.plotter === smoothPlotter)) { + NETDATA.dygraphChartCreate(state, data); + return; + } + } + + if (netdataSnapshotData !== null && NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(state) === false) { + // pan and zoom on snapshots + options.dateWindow = [NETDATA.globalPanAndZoom.force_after_ms, NETDATA.globalPanAndZoom.force_before_ms]; + //options.isZoomedIgnoreProgrammaticZoom = true; + } + + if (NETDATA.chartLibraries.dygraph.isLogScale(state)) { + if (Array.isArray(options.valueRange) && options.valueRange[0] <= 0) { + options.valueRange[0] = null; + } + } + + dygraph.updateOptions(options); + + let redraw = false; + if (oldMin !== null && oldMin > state.tmp.dygraph_instance.axes_[0].extremeRange[0]) { + state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0]; + options.valueRange[0] = NETDATA.commonMin.get(state); + redraw = true; + } + if (oldMax !== null && oldMax < state.tmp.dygraph_instance.axes_[0].extremeRange[1]) { + state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1]; + options.valueRange[1] = NETDATA.commonMax.get(state); + redraw = true; + } + + if (redraw) { + // state.log('forcing redraw to adapt to common- min/max'); + dygraph.updateOptions(options); + } + + state.tmp.dygraph_last_rendered = Date.now(); + return true; +}; + +NETDATA.dygraphChartCreate = function (state, data) { + if (NETDATA.options.debug.dygraph || state.debug) { + state.log('dygraphChartCreate()'); + } + + state.tmp.dygraph_chart_type = NETDATA.dataAttribute(state.element, 'dygraph-type', state.chart.chart_type); + if (state.tmp.dygraph_chart_type === 'stacked' && data.dimensions === 1) { + state.tmp.dygraph_chart_type = 'area'; + } + if (state.tmp.dygraph_chart_type === 'stacked' && NETDATA.chartLibraries.dygraph.isLogScale(state)) { + state.tmp.dygraph_chart_type = 'area'; + } + + let highlightCircleSize = NETDATA.chartLibraries.dygraph.isSparkline(state) ? 3 : 4; + + let smooth = NETDATA.dygraph.smooth + ? (NETDATA.dataAttributeBoolean(state.element, 'dygraph-smooth', (state.tmp.dygraph_chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))) + : false; + + state.tmp.dygraph_include_zero = NETDATA.dataAttribute(state.element, 'dygraph-includezero', (state.tmp.dygraph_chart_type === 'stacked')); + let drawAxis = NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawaxis', true); + + state.tmp.dygraph_options = { + colors: NETDATA.dataAttribute(state.element, 'dygraph-colors', state.chartColors()), + + // leave a few pixels empty on the right of the chart + rightGap: NETDATA.dataAttribute(state.element, 'dygraph-rightgap', 5), + showRangeSelector: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showrangeselector', false), + showRoller: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showroller', false), + title: NETDATA.dataAttribute(state.element, 'dygraph-title', state.title), + titleHeight: NETDATA.dataAttribute(state.element, 'dygraph-titleheight', 19), + legend: NETDATA.dataAttribute(state.element, 'dygraph-legend', 'always'), // we need this to get selection events + labels: data.result.labels, + labelsDiv: NETDATA.dataAttribute(state.element, 'dygraph-labelsdiv', state.element_legend_childs.hidden), + //labelsDivStyles: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivstyles', { 'fontSize':'1px' }), + //labelsDivWidth: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivwidth', state.chartWidth() - 70), + labelsSeparateLines: NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsseparatelines', true), + labelsShowZeroValues: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsshowzerovalues', true), + labelsKMB: false, + labelsKMG2: false, + showLabelsOnHighlight: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showlabelsonhighlight', true), + hideOverlayOnMouseOut: NETDATA.dataAttributeBoolean(state.element, 'dygraph-hideoverlayonmouseout', true), + includeZero: state.tmp.dygraph_include_zero, + xRangePad: NETDATA.dataAttribute(state.element, 'dygraph-xrangepad', 0), + yRangePad: NETDATA.dataAttribute(state.element, 'dygraph-yrangepad', 1), + valueRange: NETDATA.dataAttribute(state.element, 'dygraph-valuerange', [null, null]), + ylabel: state.units_current, // (state.units_desired === 'auto')?"":state.units_current, + yLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-ylabelwidth', 12), + + // the function to plot the chart + plotter: null, + + // The width of the lines connecting data points. + // This can be used to increase the contrast or some graphs. + strokeWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokewidth', ((state.tmp.dygraph_chart_type === 'stacked') ? 0.1 : ((smooth === true) ? 1.5 : 0.7))), + strokePattern: NETDATA.dataAttribute(state.element, 'dygraph-strokepattern', undefined), + + // The size of the dot to draw on each point in pixels (see drawPoints). + // A dot is always drawn when a point is "isolated", + // i.e. there is a missing point on either side of it. + // This also controls the size of those dots. + drawPoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawpoints', false), + + // Draw points at the edges of gaps in the data. + // This improves visibility of small data segments or other data irregularities. + drawGapEdgePoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgapedgepoints', true), + connectSeparatedPoints: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-connectseparatedpoints', false), + pointSize: NETDATA.dataAttribute(state.element, 'dygraph-pointsize', 1), + + // enabling this makes the chart with little square lines + stepPlot: NETDATA.dataAttributeBoolean(state.element, 'dygraph-stepplot', false), + + // Draw a border around graph lines to make crossing lines more easily + // distinguishable. Useful for graphs with many lines. + strokeBorderColor: NETDATA.dataAttribute(state.element, 'dygraph-strokebordercolor', NETDATA.themes.current.background), + strokeBorderWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokeborderwidth', (state.tmp.dygraph_chart_type === 'stacked') ? 0.0 : 0.0), + fillGraph: NETDATA.dataAttribute(state.element, 'dygraph-fillgraph', (state.tmp.dygraph_chart_type === 'area' || state.tmp.dygraph_chart_type === 'stacked')), + fillAlpha: NETDATA.dataAttribute(state.element, 'dygraph-fillalpha', + ((state.tmp.dygraph_chart_type === 'stacked') + ? NETDATA.options.current.color_fill_opacity_stacked + : NETDATA.options.current.color_fill_opacity_area) + ), + stackedGraph: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraph', (state.tmp.dygraph_chart_type === 'stacked')), + stackedGraphNaNFill: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraphnanfill', 'none'), + drawAxis: drawAxis, + axisLabelFontSize: NETDATA.dataAttribute(state.element, 'dygraph-axislabelfontsize', 10), + axisLineColor: NETDATA.dataAttribute(state.element, 'dygraph-axislinecolor', NETDATA.themes.current.axis), + axisLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-axislinewidth', 1.0), + drawGrid: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgrid', true), + gridLinePattern: NETDATA.dataAttribute(state.element, 'dygraph-gridlinepattern', null), + gridLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-gridlinewidth', 1.0), + gridLineColor: NETDATA.dataAttribute(state.element, 'dygraph-gridlinecolor', NETDATA.themes.current.grid), + maxNumberWidth: NETDATA.dataAttribute(state.element, 'dygraph-maxnumberwidth', 8), + sigFigs: NETDATA.dataAttribute(state.element, 'dygraph-sigfigs', null), + digitsAfterDecimal: NETDATA.dataAttribute(state.element, 'dygraph-digitsafterdecimal', 2), + valueFormatter: NETDATA.dataAttribute(state.element, 'dygraph-valueformatter', undefined), + highlightCircleSize: NETDATA.dataAttribute(state.element, 'dygraph-highlightcirclesize', highlightCircleSize), + highlightSeriesOpts: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesopts', null), // TOO SLOW: { strokeWidth: 1.5 }, + highlightSeriesBackgroundAlpha: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesbackgroundalpha', null), // TOO SLOW: (state.tmp.dygraph_chart_type === 'stacked')?0.7:0.5, + pointClickCallback: NETDATA.dataAttribute(state.element, 'dygraph-pointclickcallback', undefined), + visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names), + logscale: NETDATA.chartLibraries.dygraph.isLogScale(state) ? 'y' : undefined, + + // Expects a string in the format ": + + + + + + + + + + + + +
+ +

Monitor your systems and applications, the right way!

+

+ Unparalleled insights, in real-time, + of everything happening on your systems and applications, + with stunning, interactive web dashboards + and powerful performance and health alarms. +

+ +
+
+
+

Enter the world of Netdata!

+
+

1s granularity

+
+
+
+

+ Per second data collection and visualization, for all metrics! +

+

+ Netdata zooms into the problems by providing higher resolution information, compared to any other monitoring solution. +

+
+
+

+ The world goes real-time. +
 
+ High resolution metrics are required to effectively monitor and troubleshoot systems and applications, especially on virtual environments. +

+
+
+
+ +
+ +
+
+
+

Unlimited metrics

+
+
+
+

+ Use all the metrics, from all available sources! +

+

+ Netdata collects all the metrics native console tools do. It has been designed to kill the console for troubleshooting infrastructure slowdowns and outages. +

+
+
+

+ All metrics are important and all should be available when you need them. +
 
+ Filtering out most metrics is like reading a book by skipping most of its pages. +

+
+
+
+ +
+
+
+
+

Meaningful presentation

+
+
+
+

+ Explore all metrics in a meaningful, easy to understand way! +

+

+ Netdata engineers and experts on our community organize metrics in a meaningful way, so that you can learn and understand them right on the job, while troubleshooting issues of your infrastructure. +

+
+
+

+ Metrics are a lot more than name-value pairs over time. +

+ It is just not practical to require from all users to have a deep understanding of all metrics for monitoring their systems and applications. +

+
+
+
+ +
+ +
+
+
+

Immediate results

+
+
+
+

+ Install and use immediately! Get fully functional visualization and alarms, in just a couple of seconds after installation! +

+

+ Netdata decouples your skills from your monitoring infrastructure. + No matter how skillful or novice you are, Netdata will apply all the community knowledge and expertise to your monitoring infrastructure. +

+
+
+

+ Most of our infrastructure is based on standardized systems and applications. +
 
+ It is a tremendous waste of time and effort, in a global scale, to require from all users to configure their infrastructure dashboards and alarms metric by metric. +

+
+
+
+ +
+ +
+

How it works

+
+

+ Netdata is a monitoring agent you install on all your systems: +
+ physical servers, virtual servers, containers, IoT. +

+

+ Netdata is lightweight, designed to permanently run on all systems without disrupting their core function. + By default, it needs just 1% CPU of a single core, a few MB or RAM and no disk I/O at all. +

+

+ Each Netdata is (by default) autonomous, taking care of all the following. +
But all your Netdata are integrated into one large distributed application. +

+
+
+

Collect

+

+ Netdata automatically detects data collection sources on the host it runs. + It comes with hundreds of plugins for collecting system and application metrics, + including databases, web servers, and commonly used application servers. +

+ Netdata is also a high performance, distributed statsd server, allowing custom + application metrics to be collected and visualized. +

+
+

Check (alarms)

+

+ Each Netdata spawns a thread that examines the metrics as they get collected, + evaluates pre-configured alarm expressions and triggers alarm notifications. +

+ Netdata comes with hundreds of alarms to detect common system and application issues, + that are automatically attached to the collected metrics, + supporting dozens of alarm notification integrations. +

+
+

Stream

+

+ Each Netdata can stream its metrics, in real-time, to any other Netdata. + Streaming allows Netdata to be used in ephemeral nodes and containers in auto-scaled environments, + but it also allows building Netdata hierarchies for aggregating the metrics of multiple Netdata nodes. +

+
+

Store

+

+ Each Netdata has its own internal metrics database. This database is optimized + for minimal memory footprint, and due to its lockless design allows one writer + and multiple readers per metric, concurrently, contributing significantly to + the performance of Netdata. +

+
+

Archive

+

+ Netdata can archive its metrics to time-series databases (prometheus, graphite, opentsdb, json document dbs, etc) + so that Netdata can be integrated to existing monitoring tool-chains. +

+
+

Visualize

+

+ The best part of Netdata is its visualization. Low latency, speedy and snazzy. +

+ Netdata dashboards are optimized for visual anomaly detection, a powerful tool to troubleshoot + performance issues. +

+
+
+
+
+ +
+   +
+ + +

netdata live demo sites

+
+
+ +
+
+
+
+
+ Enter London! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+ Enter Atlanta! +
+ Donated by CDN77.com +
+
+
+
+
+
+
+
+ Enter California! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+ Enter Canada! +
+ Donated by DigitalOcean.com +
+
+
+
 
+
+
+
+
+
+ Enter Germany! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+ Enter New York! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+ Enter Singapore! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+ Enter India! +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+ Israel +
+
+
+ requests/s +
+
+
+
+
+
+
+ Enter Israel! +
+ Donated by octopuscs.com +
+
+
+ +
+
+
+ EU - Spain +
+
+
+ requests/s +
+
+
+
+
+
+
+ Enter Madrid! +
+ Donated by stackscale.com +
+
+
+
+
+
+
+
+
+ +
+ Charts are coming from all servers, in parallel. +
+ The servers are not aware of this multi-server dashboard. +
+ +
+
+
+ EU - London connected clients +
+
+
+
+
+
+ +
+ Each server is not aware of the other servers. +
+ But on this dashboard they are one! (hover on the chart above) +
+ + + +
+
+ +

Who uses netdata?

+
+
+

+ Netdata is used by hundreds of thousands of users all over the world. +
 
+ Check our GitHub watchers list. +
+ You will find people working for Amazon, Atos, Baidu, Cisco Systems, Citrix, + Deutsche Telekom, DigitalOcean, Elastic, EPAM Systems, Ericsson, Google, + Groupon, Hortonworks, HP, Huawei, IBM, Microsoft, NewRelic, + Nvidia, Red Hat, SAP, Selectel, TicketMaster, Vimeo, and many more! +

+ + The following figures come from users using the netdata public global registry.
Counting since May 16th 2016. Actual figures may be a lot higher.
+
+
+
+ netdata unique users +
+
+
+
+
+
+
+
+
+ netdata monitored servers +
+
+
+
+
+
+
+
+
+ netdata sessions served +
+
+
+
+
+
+

+ + +

+

+ + netdata can generate auto-refreshing badges, like these: + +
+ + + +
+ These badges auto-refresh every minute. +

+
+
+ netdata is featured at the GitHub's state of the Octoverse 2016 +
+ + + +
+
+ +
+
+
+
+ + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + diff --git a/web/gui/demosites2.html b/web/gui/demosites2.html new file mode 100644 index 0000000..fe35cfb --- /dev/null +++ b/web/gui/demosites2.html @@ -0,0 +1,1112 @@ + + + + + NetData - Real-time performance monitoring, done right! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ netdata +
+
+ real-time performance monitoring +
+
+ scaled out! +
+
+ pick a netdata demo server +
+
+ these demo servers show what you will get by installing netdata +
+ +
+
+ +
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by CDN77.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
 
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+
+
+
+
 
+ +
+ Donated by DigitalOcean.com +
+
+
+
+
+ +
+ this page is a custom netdata dashboard +
+
+ charts are coming from 8 servers, in parallel +
+ the servers are not aware of this multi-server dashboard, +
+ each server is not aware of the other servers, +
+ but on this dashboard they are one! +
+
+ + hover on a chart below, or drag it to show the past - the others will follow! +
+ double click on a chart to reset them all +
+ +
+ our nginx performance +
+
+ (we proxy netdata through nginx, on the demo sites) +
+ + + + + +
+
+
+
+ EU - London web requests/s +
+
+
+
+
+ +
+
+ US - Atlanta web requests/s +
+
+
+
+
+ +
+
+ US - California web requests/s +
+
+
+
+
+ +
+
+ Canada web requests/s +
+
+
+
+
+
+ +
+
+
+ EU - London active connections +
+
+
+
+
+ +
+
+ US - Atlanta active connections +
+
+
+
+
+ +
+
+ US - California active connections +
+
+
+
+
+ +
+
+ Canada active connections +
+
+
+
+
+
+
+ +
+ these charts are draggable and touchable, double click them to reset them +
+ + +
+ bandwidth consumption on the demo sites +
+
+ Linux QoS is configured by FireQOS +
+ + + + + +
+
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+
+
+
+
+
+ these legends are interactive and the charts are resizable here ^^^ +
+ +
+ DDoS protection performance on the demo sites +
+
+ iptables SYNPROXY configured by FireHOL +
+ +
+ +
+
+ EU - London, TCP SYN packets/s received +
+
+
+
+
+ +
+
+ US - Atlanta, TCP SYN packets/s received +
+
+
+
+
+ +
+
+ US - California, TCP SYN packets/s received +
+
+
+
+
+ +
+
+ Canada, TCP SYN packets/s received +
+
+
+
+
+
+
+ did you notice the decimal numbers? +
netdata interpolates collected values at second boundaries, with nanosecond detail!
+
+ + +
+ CPU Utilization of the demo sites +
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ what is using so much CPU? +
The site iplists.firehol.org is maintained by FireHOL - the CPU is used for comparing security IP Lists.
+
+ +
+ Netdata performance +
+
+ netdata monitors users, user groups, applications (process trees) +
+ containers (lxc, docker, etc.) and SNMP devices. +
+ + + + + +
+
+
+
+ EU - London, CPU % of a single core +
+
+
+
+
+ +
+
+ US - Atlanta, CPU % of a single core +
+
+
+
+
+ +
+
+ US - California, CPU % of a single core +
+
+
+
+
+ +
+
+ Toronto, CPU % of a single core +
+
+
+
+
+ +
+ this utilization is about the whole netdata process tree and the percentage is of a single core! +
including BASH plugins (it monitors mysql on the demo sites), node.js plugins (it monitors bind9 on the demo sites), etc. +
and including the chart refreshes for the dashboards of all viewers.
+
+
+ +
+
+
+ EU - London, API average response time in milliseconds +
+
+
+
+
+ +
+
+ US - Atlanta, API average response time in milliseconds +
+
+
+
+
+ +
+
+ US - California, API average response time in milliseconds +
+
+
+
+
+ +
+
+ Canada, API average response time in milliseconds +
+
+
+
+
+ +
+ netdata is really fast (the values are milliseconds!) +
+ These values include everything, from the reception of the first byte to the dispatch of the last, including gzip compression. +
+ Values above 2-3ms are usually chart refreshes of charts with several dimensions, charts with very long durations (zoomed out), or file transfers. +
+
+
+
+ +
+ want to know more? +
+ jump to the netdata page at github +
+ it needs just 3 mins to be installed on your servers! +
+   +
+
+ + + diff --git a/web/gui/favicon.ico b/web/gui/favicon.ico new file mode 100644 index 0000000..064032a Binary files /dev/null and b/web/gui/favicon.ico differ diff --git a/web/gui/fonts/glyphicons-halflings-regular.eot b/web/gui/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/web/gui/fonts/glyphicons-halflings-regular.eot differ diff --git a/web/gui/fonts/glyphicons-halflings-regular.svg b/web/gui/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..2a4aaba --- /dev/null +++ b/web/gui/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/gui/fonts/glyphicons-halflings-regular.ttf b/web/gui/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/web/gui/fonts/glyphicons-halflings-regular.ttf differ diff --git a/web/gui/fonts/glyphicons-halflings-regular.woff b/web/gui/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/web/gui/fonts/glyphicons-halflings-regular.woff differ diff --git a/web/gui/fonts/glyphicons-halflings-regular.woff2 b/web/gui/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/web/gui/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/web/gui/goto-host-from-alarm.html b/web/gui/goto-host-from-alarm.html new file mode 100644 index 0000000..7d12f84 --- /dev/null +++ b/web/gui/goto-host-from-alarm.html @@ -0,0 +1,251 @@ + + + + + + Goto a host you know... + + + + + + + + + + + + + +
+
+ Please wait... + +
+ +
+
+

+ This page can only find netdata URLs you have already visited and are linked to your account on this netdata registry. +

+
+
+ +
+ + diff --git a/web/gui/images/alert-128-orange.png b/web/gui/images/alert-128-orange.png new file mode 100644 index 0000000..c6182bf Binary files /dev/null and b/web/gui/images/alert-128-orange.png differ diff --git a/web/gui/images/alert-128-red.png b/web/gui/images/alert-128-red.png new file mode 100644 index 0000000..90b9c73 Binary files /dev/null and b/web/gui/images/alert-128-red.png differ diff --git a/web/gui/images/alert-multi-size-orange.ico b/web/gui/images/alert-multi-size-orange.ico new file mode 100644 index 0000000..edca438 Binary files /dev/null and b/web/gui/images/alert-multi-size-orange.ico differ diff --git a/web/gui/images/alert-multi-size-red.ico b/web/gui/images/alert-multi-size-red.ico new file mode 100644 index 0000000..8f7cbd0 Binary files /dev/null and b/web/gui/images/alert-multi-size-red.ico differ diff --git a/web/gui/images/android-icon-144x144.png b/web/gui/images/android-icon-144x144.png new file mode 100644 index 0000000..69efa5a Binary files /dev/null and b/web/gui/images/android-icon-144x144.png differ diff --git a/web/gui/images/android-icon-192x192.png b/web/gui/images/android-icon-192x192.png new file mode 100644 index 0000000..e574435 Binary files /dev/null and b/web/gui/images/android-icon-192x192.png differ diff --git a/web/gui/images/android-icon-36x36.png b/web/gui/images/android-icon-36x36.png new file mode 100644 index 0000000..4ba804d Binary files /dev/null and b/web/gui/images/android-icon-36x36.png differ diff --git a/web/gui/images/android-icon-48x48.png b/web/gui/images/android-icon-48x48.png new file mode 100644 index 0000000..04970d4 Binary files /dev/null and b/web/gui/images/android-icon-48x48.png differ diff --git a/web/gui/images/android-icon-72x72.png b/web/gui/images/android-icon-72x72.png new file mode 100644 index 0000000..5cbc701 Binary files /dev/null and b/web/gui/images/android-icon-72x72.png differ diff --git a/web/gui/images/android-icon-96x96.png b/web/gui/images/android-icon-96x96.png new file mode 100644 index 0000000..21f27ce Binary files /dev/null and b/web/gui/images/android-icon-96x96.png differ diff --git a/web/gui/images/animated.gif b/web/gui/images/animated.gif new file mode 100644 index 0000000..0e94a20 Binary files /dev/null and b/web/gui/images/animated.gif differ diff --git a/web/gui/images/apple-icon-114x114.png b/web/gui/images/apple-icon-114x114.png new file mode 100644 index 0000000..7993e05 Binary files /dev/null and b/web/gui/images/apple-icon-114x114.png differ diff --git a/web/gui/images/apple-icon-120x120.png b/web/gui/images/apple-icon-120x120.png new file mode 100644 index 0000000..3fbe8fd Binary files /dev/null and b/web/gui/images/apple-icon-120x120.png differ diff --git a/web/gui/images/apple-icon-144x144.png b/web/gui/images/apple-icon-144x144.png new file mode 100644 index 0000000..8d46569 Binary files /dev/null and b/web/gui/images/apple-icon-144x144.png differ diff --git a/web/gui/images/apple-icon-152x152.png b/web/gui/images/apple-icon-152x152.png new file mode 100644 index 0000000..11a1072 Binary files /dev/null and b/web/gui/images/apple-icon-152x152.png differ diff --git a/web/gui/images/apple-icon-180x180.png b/web/gui/images/apple-icon-180x180.png new file mode 100644 index 0000000..314efb1 Binary files /dev/null and b/web/gui/images/apple-icon-180x180.png differ diff --git a/web/gui/images/apple-icon-57x57.png b/web/gui/images/apple-icon-57x57.png new file mode 100644 index 0000000..8528361 Binary files /dev/null and b/web/gui/images/apple-icon-57x57.png differ diff --git a/web/gui/images/apple-icon-60x60.png b/web/gui/images/apple-icon-60x60.png new file mode 100644 index 0000000..2662e85 Binary files /dev/null and b/web/gui/images/apple-icon-60x60.png differ diff --git a/web/gui/images/apple-icon-72x72.png b/web/gui/images/apple-icon-72x72.png new file mode 100644 index 0000000..4a6b056 Binary files /dev/null and b/web/gui/images/apple-icon-72x72.png differ diff --git a/web/gui/images/apple-icon-76x76.png b/web/gui/images/apple-icon-76x76.png new file mode 100644 index 0000000..c2bf6c9 Binary files /dev/null and b/web/gui/images/apple-icon-76x76.png differ diff --git a/web/gui/images/apple-icon-precomposed.png b/web/gui/images/apple-icon-precomposed.png new file mode 100644 index 0000000..9c3e73e Binary files /dev/null and b/web/gui/images/apple-icon-precomposed.png differ diff --git a/web/gui/images/apple-icon.png b/web/gui/images/apple-icon.png new file mode 100644 index 0000000..9c3e73e Binary files /dev/null and b/web/gui/images/apple-icon.png differ diff --git a/web/gui/images/banner-icon-144x144.png b/web/gui/images/banner-icon-144x144.png new file mode 100644 index 0000000..fef3dca Binary files /dev/null and b/web/gui/images/banner-icon-144x144.png differ diff --git a/web/gui/images/check-mark-2-128-green.png b/web/gui/images/check-mark-2-128-green.png new file mode 100644 index 0000000..e04ddca Binary files /dev/null and b/web/gui/images/check-mark-2-128-green.png differ diff --git a/web/gui/images/check-mark-2-multi-size-green.ico b/web/gui/images/check-mark-2-multi-size-green.ico new file mode 100644 index 0000000..2fc4141 Binary files /dev/null and b/web/gui/images/check-mark-2-multi-size-green.ico differ diff --git a/web/gui/images/favicon-128.png b/web/gui/images/favicon-128.png new file mode 100644 index 0000000..5371f92 Binary files /dev/null and b/web/gui/images/favicon-128.png differ diff --git a/web/gui/images/favicon-16x16.png b/web/gui/images/favicon-16x16.png new file mode 100644 index 0000000..5729f5a Binary files /dev/null and b/web/gui/images/favicon-16x16.png differ diff --git a/web/gui/images/favicon-196x196.png b/web/gui/images/favicon-196x196.png new file mode 100644 index 0000000..a208c27 Binary files /dev/null and b/web/gui/images/favicon-196x196.png differ diff --git a/web/gui/images/favicon-32x32.png b/web/gui/images/favicon-32x32.png new file mode 100644 index 0000000..cdb0a48 Binary files /dev/null and b/web/gui/images/favicon-32x32.png differ diff --git a/web/gui/images/favicon-96x96.png b/web/gui/images/favicon-96x96.png new file mode 100644 index 0000000..dbe7dea Binary files /dev/null and b/web/gui/images/favicon-96x96.png differ diff --git a/web/gui/images/favicon.ico b/web/gui/images/favicon.ico new file mode 100644 index 0000000..064032a Binary files /dev/null and b/web/gui/images/favicon.ico differ diff --git a/web/gui/images/ms-icon-144x144.png b/web/gui/images/ms-icon-144x144.png new file mode 100644 index 0000000..8d46569 Binary files /dev/null and b/web/gui/images/ms-icon-144x144.png differ diff --git a/web/gui/images/ms-icon-150x150.png b/web/gui/images/ms-icon-150x150.png new file mode 100644 index 0000000..4683d56 Binary files /dev/null and b/web/gui/images/ms-icon-150x150.png differ diff --git a/web/gui/images/ms-icon-310x150.png b/web/gui/images/ms-icon-310x150.png new file mode 100644 index 0000000..5d4ac57 Binary files /dev/null and b/web/gui/images/ms-icon-310x150.png differ diff --git a/web/gui/images/ms-icon-310x310.png b/web/gui/images/ms-icon-310x310.png new file mode 100644 index 0000000..bdb591b Binary files /dev/null and b/web/gui/images/ms-icon-310x310.png differ diff --git a/web/gui/images/ms-icon-36x36.png b/web/gui/images/ms-icon-36x36.png new file mode 100644 index 0000000..e251302 Binary files /dev/null and b/web/gui/images/ms-icon-36x36.png differ diff --git a/web/gui/images/ms-icon-70x70.png b/web/gui/images/ms-icon-70x70.png new file mode 100644 index 0000000..5371f92 Binary files /dev/null and b/web/gui/images/ms-icon-70x70.png differ diff --git a/web/gui/images/netdata-logomark.svg b/web/gui/images/netdata-logomark.svg new file mode 100644 index 0000000..18152fb --- /dev/null +++ b/web/gui/images/netdata-logomark.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/web/gui/images/netdata.svg b/web/gui/images/netdata.svg new file mode 100644 index 0000000..f8ddbda --- /dev/null +++ b/web/gui/images/netdata.svg @@ -0,0 +1,18 @@ + + + + + + Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + + diff --git a/web/gui/images/packaging-beta-tag.svg b/web/gui/images/packaging-beta-tag.svg new file mode 100644 index 0000000..cebdc08 --- /dev/null +++ b/web/gui/images/packaging-beta-tag.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/gui/images/post.png b/web/gui/images/post.png new file mode 100644 index 0000000..6bad547 Binary files /dev/null and b/web/gui/images/post.png differ diff --git a/web/gui/images/seo-performance-128.png b/web/gui/images/seo-performance-128.png new file mode 100644 index 0000000..2a212a4 Binary files /dev/null and b/web/gui/images/seo-performance-128.png differ diff --git a/web/gui/index.html b/web/gui/index.html new file mode 100644 index 0000000..d309910 --- /dev/null +++ b/web/gui/index.html @@ -0,0 +1,1322 @@ + + + + + netdata dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ You must enable JavaScript in order to use Netdata!
+ You can do this in your browser settings. +
+
+ + + + + + + + +
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/gui/infographic.html b/web/gui/infographic.html new file mode 100644 index 0000000..24ff8f4 --- /dev/null +++ b/web/gui/infographic.html @@ -0,0 +1,171 @@ + + + + + + NetData: Get control of your Linux Servers. Simple. Effective. Awesome. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ Interactive infographic of netdata features and functions +

+

+ Hover and click on the infographic, to open the related wiki page. +
+ + The links and the docs are still a work in progress. + The interactive infographic is a feature of draw.io. + +

+
+ +
+

+ New to netdata? Have a look at a netdata demo. You will love it! +

+

+ + + +

+
+
+
+
+
+
+ + + + + + + + + +
+ + + + + + diff --git a/web/gui/lib/bootstrap-3.3.7.min.js b/web/gui/lib/bootstrap-3.3.7.min.js new file mode 100644 index 0000000..03a9716 --- /dev/null +++ b/web/gui/lib/bootstrap-3.3.7.min.js @@ -0,0 +1,8 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + * SPDX-License-Identifier: MIT + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); diff --git a/web/gui/lib/bootstrap-slider-10.0.0.min.js b/web/gui/lib/bootstrap-slider-10.0.0.min.js new file mode 100644 index 0000000..87e8349 --- /dev/null +++ b/web/gui/lib/bootstrap-slider-10.0.0.min.js @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f0)for(var t=0;t0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return Math.round(d)===this.options.max?this.options.max:(d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d)},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};if(d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;dd;d++)g[c][d]=!1;for(c=0;ce;e++)g[c+e][k]=!0;for(e=0;j>e;e++)g[c][k+e]=!0}},g=function(){if(null===b){var c,d,e=a("

").addClass("fixed-table-scroll-inner"),f=a("

").addClass("fixed-table-scroll-outer");f.append(e),a("body").append(f),c=e[0].offsetWidth,f.css("overflow","scroll"),d=e[0].offsetWidth,c===d&&(d=f[0].clientWidth),f.remove(),b=c-d}return b},h=function(b,d,e,f){var g=d;if("string"==typeof d){var h=d.split(".");h.length>1?(g=window,a.each(h,function(a,b){g=g[b]})):g=window[d]}return"object"==typeof g?g:"function"==typeof g?g.apply(b,e):!g&&"string"==typeof d&&c.apply(this,[d].concat(e))?c.apply(this,[d].concat(e)):f},i=function(b,c,d){var e=Object.getOwnPropertyNames(b),f=Object.getOwnPropertyNames(c),g="";if(d&&e.length!==f.length)return!1;for(var h=0;h-1&&b[g]!==c[g])return!1;return!0},j=function(a){return"string"==typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/`/g,"`"):a},k=function(b){var c=0;return b.children().each(function(){c0||navigator.userAgent.match(/Trident.*rv\:11\./))},o=function(){Object.keys||(Object.keys=function(){var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&("function"!=typeof e||null===e))throw new TypeError("Object.keys called on non-object");var f,g,h=[];for(f in e)a.call(e,f)&&h.push(f);if(b)for(g=0;d>g;g++)a.call(e,c[g])&&h.push(c[g]);return h}}())},p=function(b,c){this.options=c,this.$el=a(b),this.$el_=this.$el.clone(),this.timeoutId_=0,this.timeoutFooter_=0,this.init()};p.DEFAULTS={classes:"table table-hover",locale:void 0,height:void 0,undefinedText:"-",sortName:void 0,sortOrder:"asc",sortStable:!1,striped:!1,columns:[[]],data:[],dataField:"rows",method:"get",url:void 0,ajax:void 0,cache:!0,contentType:"application/json",dataType:"json",ajaxOptions:{},queryParams:function(a){return a},queryParamsType:"limit",responseHandler:function(a){return a},pagination:!1,onlyInfoPagination:!1,sidePagination:"client",totalRows:0,pageNumber:1,pageSize:10,pageList:[10,25,50,100],paginationHAlign:"right",paginationVAlign:"bottom",paginationDetailHAlign:"left",paginationPreText:"‹",paginationNextText:"›",search:!1,searchOnEnterKey:!1,strictSearch:!1,searchAlign:"right",selectItemName:"btSelectItem",showHeader:!0,showFooter:!1,showColumns:!1,showPaginationSwitch:!1,showRefresh:!1,showToggle:!1,buttonsAlign:"right",smartDisplay:!0,escape:!1,minimumCountColumns:1,idField:void 0,uniqueId:void 0,cardView:!1,detailView:!1,detailFormatter:function(){return""},trimOnSearch:!0,clickToSelect:!1,singleSelect:!1,toolbar:void 0,toolbarAlign:"left",checkboxHeader:!0,sortable:!0,silentSort:!0,maintainSelected:!1,searchTimeOut:500,searchText:"",iconSize:void 0,buttonsClass:"default",iconsPrefix:"glyphicon",icons:{paginationSwitchDown:"glyphicon-collapse-down icon-chevron-down",paginationSwitchUp:"glyphicon-collapse-up icon-chevron-up",refresh:"glyphicon-refresh icon-refresh",toggle:"glyphicon-list-alt icon-list-alt",columns:"glyphicon-th icon-th",detailOpen:"glyphicon-plus icon-plus",detailClose:"glyphicon-minus icon-minus"},customSearch:a.noop,customSort:a.noop,rowStyle:function(){return{}},rowAttributes:function(){return{}},footerStyle:function(){return{}},onAll:function(){return!1},onClickCell:function(){return!1},onDblClickCell:function(){return!1},onClickRow:function(){return!1},onDblClickRow:function(){return!1},onSort:function(){return!1},onCheck:function(){return!1},onUncheck:function(){return!1},onCheckAll:function(){return!1},onUncheckAll:function(){return!1},onCheckSome:function(){return!1},onUncheckSome:function(){return!1},onLoadSuccess:function(){return!1},onLoadError:function(){return!1},onColumnSwitch:function(){return!1},onPageChange:function(){return!1},onSearch:function(){return!1},onToggle:function(){return!1},onPreBody:function(){return!1},onPostBody:function(){return!1},onPostHeader:function(){return!1},onExpandRow:function(){return!1},onCollapseRow:function(){return!1},onRefreshOptions:function(){return!1},onRefresh:function(){return!1},onResetView:function(){return!1}},p.LOCALES={},p.LOCALES["en-US"]=p.LOCALES.en={formatLoadingMessage:function(){return"Loading, please wait..."},formatRecordsPerPage:function(a){return c("%s rows per page",a)},formatShowingRows:function(a,b,d){return c("Showing %s to %s of %s rows",a,b,d)},formatDetailPagination:function(a){return c("Showing %s rows",a)},formatSearch:function(){return"Search"},formatNoMatches:function(){return"No matching records found"},formatPaginationSwitch:function(){return"Hide/Show pagination"},formatRefresh:function(){return"Refresh"},formatToggle:function(){return"Toggle"},formatColumns:function(){return"Columns"},formatAllRows:function(){return"All"}},a.extend(p.DEFAULTS,p.LOCALES["en-US"]),p.COLUMN_DEFAULTS={radio:!1,checkbox:!1,checkboxEnabled:!0,field:void 0,title:void 0,titleTooltip:void 0,"class":void 0,align:void 0,halign:void 0,falign:void 0,valign:void 0,width:void 0,sortable:!1,order:"asc",visible:!0,switchable:!0,clickToSelect:!0,formatter:void 0,footerFormatter:void 0,events:void 0,sorter:void 0,sortName:void 0,cellStyle:void 0,searchable:!0,searchFormatter:!0,cardVisible:!0},p.EVENTS={"all.bs.table":"onAll","click-cell.bs.table":"onClickCell","dbl-click-cell.bs.table":"onDblClickCell","click-row.bs.table":"onClickRow","dbl-click-row.bs.table":"onDblClickRow","sort.bs.table":"onSort","check.bs.table":"onCheck","uncheck.bs.table":"onUncheck","check-all.bs.table":"onCheckAll","uncheck-all.bs.table":"onUncheckAll","check-some.bs.table":"onCheckSome","uncheck-some.bs.table":"onUncheckSome","load-success.bs.table":"onLoadSuccess","load-error.bs.table":"onLoadError","column-switch.bs.table":"onColumnSwitch","page-change.bs.table":"onPageChange","search.bs.table":"onSearch","toggle.bs.table":"onToggle","pre-body.bs.table":"onPreBody","post-body.bs.table":"onPostBody","post-header.bs.table":"onPostHeader","expand-row.bs.table":"onExpandRow","collapse-row.bs.table":"onCollapseRow","refresh-options.bs.table":"onRefreshOptions","reset-view.bs.table":"onResetView","refresh.bs.table":"onRefresh"},p.prototype.init=function(){this.initLocale(),this.initContainer(),this.initTable(),this.initHeader(),this.initData(),this.initFooter(),this.initToolbar(),this.initPagination(),this.initBody(),this.initSearchText(),this.initServer()},p.prototype.initLocale=function(){if(this.options.locale){var b=this.options.locale.split(/-|_/);b[0].toLowerCase(),b[1]&&b[1].toUpperCase(),a.fn.bootstrapTable.locales[this.options.locale]?a.extend(this.options,a.fn.bootstrapTable.locales[this.options.locale]):a.fn.bootstrapTable.locales[b.join("-")]?a.extend(this.options,a.fn.bootstrapTable.locales[b.join("-")]):a.fn.bootstrapTable.locales[b[0]]&&a.extend(this.options,a.fn.bootstrapTable.locales[b[0]])}},p.prototype.initContainer=function(){this.$container=a(['
','
',"top"===this.options.paginationVAlign||"both"===this.options.paginationVAlign?'
':"",'
','
','
','
',this.options.formatLoadingMessage(),"
","
",'',"bottom"===this.options.paginationVAlign||"both"===this.options.paginationVAlign?'
':"","
","
"].join("")),this.$container.insertAfter(this.$el),this.$tableContainer=this.$container.find(".fixed-table-container"),this.$tableHeader=this.$container.find(".fixed-table-header"),this.$tableBody=this.$container.find(".fixed-table-body"),this.$tableLoading=this.$container.find(".fixed-table-loading"),this.$tableFooter=this.$container.find(".fixed-table-footer"),this.$toolbar=this.$container.find(".fixed-table-toolbar"),this.$pagination=this.$container.find(".fixed-table-pagination"),this.$tableBody.append(this.$el),this.$container.after('
'),this.$el.addClass(this.options.classes),this.options.striped&&this.$el.addClass("table-striped"),-1!==a.inArray("table-no-bordered",this.options.classes.split(" "))&&this.$tableContainer.addClass("table-no-bordered")},p.prototype.initTable=function(){var b=this,c=[],d=[];if(this.$header=this.$el.find(">thead"),this.$header.length||(this.$header=a("").appendTo(this.$el)),this.$header.find("tr").each(function(){var b=[];a(this).find("th").each(function(){"undefined"!=typeof a(this).data("field")&&a(this).data("field",a(this).data("field")+""),b.push(a.extend({},{title:a(this).html(),"class":a(this).attr("class"),titleTooltip:a(this).attr("title"),rowspan:a(this).attr("rowspan")?+a(this).attr("rowspan"):void 0,colspan:a(this).attr("colspan")?+a(this).attr("colspan"):void 0},a(this).data()))}),c.push(b)}),a.isArray(this.options.columns[0])||(this.options.columns=[this.options.columns]),this.options.columns=a.extend(!0,[],c,this.options.columns),this.columns=[],f(this.options.columns),a.each(this.options.columns,function(c,d){a.each(d,function(d,e){e=a.extend({},p.COLUMN_DEFAULTS,e),"undefined"!=typeof e.fieldIndex&&(b.columns[e.fieldIndex]=e),b.options.columns[c][d]=e})}),!this.options.data.length){var e=[];this.$el.find(">tbody>tr").each(function(c){var f={};f._id=a(this).attr("id"),f._class=a(this).attr("class"),f._data=l(a(this).data()),a(this).find(">td").each(function(d){for(var g,h,i=a(this),j=+i.attr("colspan")||1,k=+i.attr("rowspan")||1;e[c]&&e[c][d];d++);for(g=d;d+j>g;g++)for(h=c;c+k>h;h++)e[h]||(e[h]=[]),e[h][g]=!0;var m=b.columns[d].field;f[m]=a(this).html(),f["_"+m+"_id"]=a(this).attr("id"),f["_"+m+"_class"]=a(this).attr("class"),f["_"+m+"_rowspan"]=a(this).attr("rowspan"),f["_"+m+"_colspan"]=a(this).attr("colspan"),f["_"+m+"_title"]=a(this).attr("title"),f["_"+m+"_data"]=l(a(this).data())}),d.push(f)}),this.options.data=d,d.length&&(this.fromHtml=!0)}},p.prototype.initHeader=function(){var b=this,d={},e=[];this.header={fields:[],styles:[],classes:[],formatters:[],events:[],sorters:[],sortNames:[],cellStyles:[],searchables:[]},a.each(this.options.columns,function(f,g){e.push(""),0===f&&!b.options.cardView&&b.options.detailView&&e.push(c('
',b.options.columns.length)),a.each(g,function(a,f){var g="",h="",i="",j="",k=c(' class="%s"',f["class"]),l=(b.options.sortOrder||f.order,"px"),m=f.width;if(void 0===f.width||b.options.cardView||"string"==typeof f.width&&-1!==f.width.indexOf("%")&&(l="%"),f.width&&"string"==typeof f.width&&(m=f.width.replace("%","").replace("px","")),h=c("text-align: %s; ",f.halign?f.halign:f.align),i=c("text-align: %s; ",f.align),j=c("vertical-align: %s; ",f.valign),j+=c("width: %s; ",!f.checkbox&&!f.radio||m?m?m+l:void 0:"36px"),"undefined"!=typeof f.fieldIndex){if(b.header.fields[f.fieldIndex]=f.field,b.header.styles[f.fieldIndex]=i+j,b.header.classes[f.fieldIndex]=k,b.header.formatters[f.fieldIndex]=f.formatter,b.header.events[f.fieldIndex]=f.events,b.header.sorters[f.fieldIndex]=f.sorter,b.header.sortNames[f.fieldIndex]=f.sortName,b.header.cellStyles[f.fieldIndex]=f.cellStyle,b.header.searchables[f.fieldIndex]=f.searchable,!f.visible)return;if(b.options.cardView&&!f.cardVisible)return;d[f.field]=f}e.push(""),e.push(c('
',b.options.sortable&&f.sortable?"sortable both":"")),g=f.title,f.checkbox&&(!b.options.singleSelect&&b.options.checkboxHeader&&(g=''),b.header.stateField=f.field),f.radio&&(g="",b.header.stateField=f.field,b.options.singleSelect=!0),e.push(g),e.push("
"),e.push('
'),e.push("
"),e.push("")}),e.push("")}),this.$header.html(e.join("")),this.$header.find("th[data-field]").each(function(){a(this).data(d[a(this).data("field")])}),this.$container.off("click",".th-inner").on("click",".th-inner",function(c){var d=a(this);return b.options.detailView&&d.closest(".bootstrap-table")[0]!==b.$container[0]?!1:void(b.options.sortable&&d.parent().data().sortable&&b.onSort(c))}),this.$header.children().children().off("keypress").on("keypress",function(c){if(b.options.sortable&&a(this).data().sortable){var d=c.keyCode||c.which;13==d&&b.onSort(c)}}),a(window).off("resize.bootstrap-table"),!this.options.showHeader||this.options.cardView?(this.$header.hide(),this.$tableHeader.hide(),this.$tableLoading.css("top",0)):(this.$header.show(),this.$tableHeader.show(),this.$tableLoading.css("top",this.$header.outerHeight()+1),this.getCaret(),a(window).on("resize.bootstrap-table",a.proxy(this.resetWidth,this))),this.$selectAll=this.$header.find('[name="btSelectAll"]'),this.$selectAll.off("click").on("click",function(){var c=a(this).prop("checked");b[c?"checkAll":"uncheckAll"](),b.updateSelected()})},p.prototype.initFooter=function(){!this.options.showFooter||this.options.cardView?this.$tableFooter.hide():this.$tableFooter.show()},p.prototype.initData=function(a,b){this.data="append"===b?this.data.concat(a):"prepend"===b?[].concat(a).concat(this.data):a||this.options.data,this.options.data="append"===b?this.options.data.concat(a):"prepend"===b?[].concat(a).concat(this.options.data):this.data,"server"!==this.options.sidePagination&&this.initSort()},p.prototype.initSort=function(){var b=this,c=this.options.sortName,d="desc"===this.options.sortOrder?-1:1,e=a.inArray(this.options.sortName,this.header.fields);return this.options.customSort!==a.noop?void this.options.customSort.apply(this,[this.options.sortName,this.options.sortOrder]):void(-1!==e&&(this.options.sortStable&&a.each(this.data,function(a,b){b.hasOwnProperty("_position")||(b._position=a)}),this.data.sort(function(f,g){b.header.sortNames[e]&&(c=b.header.sortNames[e]);var i=m(f,c,b.options.escape),j=m(g,c,b.options.escape),k=h(b.header,b.header.sorters[e],[i,j]);return void 0!==k?d*k:((void 0===i||null===i)&&(i=""),(void 0===j||null===j)&&(j=""),b.options.sortStable&&i===j&&(i=f._position,j=g._position),a.isNumeric(i)&&a.isNumeric(j)?(i=parseFloat(i),j=parseFloat(j),j>i?-1*d:d):i===j?0:("string"!=typeof i&&(i=i.toString()),-1===i.localeCompare(j)?-1*d:d))})))},p.prototype.onSort=function(b){var c="keypress"===b.type?a(b.currentTarget):a(b.currentTarget).parent(),d=this.$header.find("th").eq(c.index());return this.$header.add(this.$header_).find("span.order").remove(),this.options.sortName===c.data("field")?this.options.sortOrder="asc"===this.options.sortOrder?"desc":"asc":(this.options.sortName=c.data("field"),this.options.sortOrder="asc"===c.data("order")?"desc":"asc"),this.trigger("sort",this.options.sortName,this.options.sortOrder),c.add(d).data("order",this.options.sortOrder),this.getCaret(),"server"===this.options.sidePagination?void this.initServer(this.options.silentSort):(this.initSort(),void this.initBody())},p.prototype.initToolbar=function(){var b,d,e=this,f=[],g=0,i=0;this.$toolbar.find(".bs-bars").children().length&&a("body").append(a(this.options.toolbar)),this.$toolbar.html(""),("string"==typeof this.options.toolbar||"object"==typeof this.options.toolbar)&&a(c('
',this.options.toolbarAlign)).appendTo(this.$toolbar).append(a(this.options.toolbar)),f=[c('
',this.options.buttonsAlign,this.options.buttonsAlign)],"string"==typeof this.options.icons&&(this.options.icons=h(null,this.options.icons)),this.options.showPaginationSwitch&&f.push(c('"),this.options.showRefresh&&f.push(c('"),this.options.showToggle&&f.push(c('"),this.options.showColumns&&(f.push(c('
',this.options.formatColumns()),'",'","
")),f.push("
"),(this.showToolbar||f.length>2)&&this.$toolbar.append(f.join("")),this.options.showPaginationSwitch&&this.$toolbar.find('button[name="paginationSwitch"]').off("click").on("click",a.proxy(this.togglePagination,this)),this.options.showRefresh&&this.$toolbar.find('button[name="refresh"]').off("click").on("click",a.proxy(this.refresh,this)),this.options.showToggle&&this.$toolbar.find('button[name="toggle"]').off("click").on("click",function(){e.toggleView()}),this.options.showColumns&&(b=this.$toolbar.find(".keep-open"),i<=this.options.minimumCountColumns&&b.find("input").prop("disabled",!0),b.find("li").off("click").on("click",function(a){a.stopImmediatePropagation()}),b.find("input").off("click").on("click",function(){var b=a(this);e.toggleColumn(a(this).val(),b.prop("checked"),!1),e.trigger("column-switch",a(this).data("field"),b.prop("checked"))})),this.options.search&&(f=[],f.push('"),this.$toolbar.append(f.join("")),d=this.$toolbar.find(".search input"),d.off("keyup drop").on("keyup drop",function(b){e.options.searchOnEnterKey&&13!==b.keyCode||a.inArray(b.keyCode,[37,38,39,40])>-1||(clearTimeout(g),g=setTimeout(function(){e.onSearch(b)},e.options.searchTimeOut))}),n()&&d.off("mouseup").on("mouseup",function(a){clearTimeout(g),g=setTimeout(function(){e.onSearch(a)},e.options.searchTimeOut)}))},p.prototype.onSearch=function(b){var c=a.trim(a(b.currentTarget).val());this.options.trimOnSearch&&a(b.currentTarget).val()!==c&&a(b.currentTarget).val(c),c!==this.searchText&&(this.searchText=c,this.options.searchText=c,this.options.pageNumber=1,this.initSearch(),this.updatePagination(),this.trigger("search",c))},p.prototype.initSearch=function(){var b=this;if("server"!==this.options.sidePagination){if(this.options.customSearch!==a.noop)return void this.options.customSearch.apply(this,[this.searchText]);var c=this.searchText&&(this.options.escape?j(this.searchText):this.searchText).toLowerCase(),d=a.isEmptyObject(this.filterColumns)?null:this.filterColumns;this.data=d?a.grep(this.options.data,function(b){for(var c in d)if(a.isArray(d[c])&&-1===a.inArray(b[c],d[c])||b[c]!==d[c])return!1;return!0}):this.options.data,this.data=c?a.grep(this.data,function(d,f){for(var g=0;g-1&&(n=!0)}this.totalPages=~~((this.options.totalRows-1)/this.options.pageSize)+1,this.options.totalPages=this.totalPages}if(this.totalPages>0&&this.options.pageNumber>this.totalPages&&(this.options.pageNumber=this.totalPages),this.pageFrom=(this.options.pageNumber-1)*this.options.pageSize+1,this.pageTo=this.options.pageNumber*this.options.pageSize,this.pageTo>this.options.totalRows&&(this.pageTo=this.options.totalRows),m.push('
','',this.options.onlyInfoPagination?this.options.formatDetailPagination(this.options.totalRows):this.options.formatShowingRows(this.pageFrom,this.pageTo,this.options.totalRows),""),!this.options.onlyInfoPagination){m.push('');var r=[c('',"top"===this.options.paginationVAlign||"both"===this.options.paginationVAlign?"dropdown":"dropup"),'",'"),m.push(this.options.formatRecordsPerPage(r.join(""))),m.push(""),m.push("
",'")}this.$pagination.html(m.join("")),this.options.onlyInfoPagination||(f=this.$pagination.find(".page-list a"),g=this.$pagination.find(".page-first"),h=this.$pagination.find(".page-pre"),i=this.$pagination.find(".page-next"),j=this.$pagination.find(".page-last"),k=this.$pagination.find(".page-number"),this.options.smartDisplay&&(this.totalPages<=1&&this.$pagination.find("div.pagination").hide(),(p.length<2||this.options.totalRows<=p[0])&&this.$pagination.find("span.page-list").hide(),this.$pagination[this.getData().length?"show":"hide"]()),n&&(this.options.pageSize=this.options.formatAllRows()),f.off("click").on("click",a.proxy(this.onPageListChange,this)),g.off("click").on("click",a.proxy(this.onPageFirst,this)),h.off("click").on("click",a.proxy(this.onPagePre,this)),i.off("click").on("click",a.proxy(this.onPageNext,this)),j.off("click").on("click",a.proxy(this.onPageLast,this)),k.off("click").on("click",a.proxy(this.onPageNumber,this)))},p.prototype.updatePagination=function(b){b&&a(b.currentTarget).hasClass("disabled")||(this.options.maintainSelected||this.resetRows(),this.initPagination(),"server"===this.options.sidePagination?this.initServer():this.initBody(),this.trigger("page-change",this.options.pageNumber,this.options.pageSize))},p.prototype.onPageListChange=function(b){var c=a(b.currentTarget);c.parent().addClass("active").siblings().removeClass("active"),this.options.pageSize=c.text().toUpperCase()===this.options.formatAllRows().toUpperCase()?this.options.formatAllRows():+c.text(),this.$toolbar.find(".page-size").text(this.options.pageSize),this.updatePagination(b)},p.prototype.onPageFirst=function(a){this.options.pageNumber=1,this.updatePagination(a)},p.prototype.onPagePre=function(a){this.options.pageNumber-1===0?this.options.pageNumber=this.options.totalPages:this.options.pageNumber--,this.updatePagination(a)},p.prototype.onPageNext=function(a){this.options.pageNumber+1>this.options.totalPages?this.options.pageNumber=1:this.options.pageNumber++,this.updatePagination(a)},p.prototype.onPageLast=function(a){this.options.pageNumber=this.totalPages,this.updatePagination(a)},p.prototype.onPageNumber=function(b){this.options.pageNumber!==+a(b.currentTarget).text()&&(this.options.pageNumber=+a(b.currentTarget).text(),this.updatePagination(b))},p.prototype.initBody=function(b){var f=this,g=[],i=this.getData();this.trigger("pre-body",i),this.$body=this.$el.find(">tbody"),this.$body.length||(this.$body=a("").appendTo(this.$el)),this.options.pagination&&"server"!==this.options.sidePagination||(this.pageFrom=1,this.pageTo=i.length);for(var k=this.pageFrom-1;k"),this.options.cardView&&g.push(c('
',this.header.fields.length)),!this.options.cardView&&this.options.detailView&&g.push("",'',c('',this.options.iconsPrefix,this.options.icons.detailOpen),"",""),a.each(this.header.fields,function(b,e){var i="",j=m(n,e,f.options.escape),l="",q={},r="",s=f.header.classes[b],t="",u="",v="",w="",x=f.columns[b];if(!(f.fromHtml&&"undefined"==typeof j||!x.visible||f.options.cardView&&!x.cardVisible)){if(o=c('style="%s"',p.concat(f.header.styles[b]).join("; ")),n["_"+e+"_id"]&&(r=c(' id="%s"',n["_"+e+"_id"])),n["_"+e+"_class"]&&(s=c(' class="%s"',n["_"+e+"_class"])),n["_"+e+"_rowspan"]&&(u=c(' rowspan="%s"',n["_"+e+"_rowspan"])),n["_"+e+"_colspan"]&&(v=c(' colspan="%s"',n["_"+e+"_colspan"])),n["_"+e+"_title"]&&(w=c(' title="%s"',n["_"+e+"_title"])),q=h(f.header,f.header.cellStyles[b],[j,n,k,e],q),q.classes&&(s=c(' class="%s"',q.classes)),q.css){var y=[];for(var z in q.css)y.push(z+": "+q.css[z]);o=c('style="%s"',y.concat(f.header.styles[b]).join("; "))}j=h(x,f.header.formatters[b],[j,n,k],j),n["_"+e+"_data"]&&!a.isEmptyObject(n["_"+e+"_data"])&&a.each(n["_"+e+"_data"],function(a,b){"index"!==a&&(t+=c(' data-%s="%s"',a,b))}),x.checkbox||x.radio?(l=x.checkbox?"checkbox":l,l=x.radio?"radio":l,i=[c(f.options.cardView?'
':'',x["class"]||""),"",f.header.formatters[b]&&"string"==typeof j?j:"",f.options.cardView?"
":""].join(""),n[f.header.stateField]=j===!0||j&&j.checked):(j="undefined"==typeof j||null===j?f.options.undefinedText:j,i=f.options.cardView?['
',f.options.showHeader?c('%s',o,d(f.columns,"field","title",e)):"",c('%s',j),"
"].join(""):[c("",r,s,o,t,u,v,w),j,""].join(""),f.options.cardView&&f.options.smartDisplay&&""===j&&(i='
')),g.push(i)}}),this.options.cardView&&g.push("
"),g.push("")}g.length||g.push('',c('%s',this.$header.find("th").length,this.options.formatNoMatches()),""),this.$body.html(g.join("")),b||this.scrollTo(0),this.$body.find("> tr[data-index] > td").off("click dblclick").on("click dblclick",function(b){var d=a(this),g=d.parent(),h=f.data[g.data("index")],i=d[0].cellIndex,j=f.getVisibleFields(),k=j[f.options.detailView&&!f.options.cardView?i-1:i],l=f.columns[e(f.columns,k)],n=m(h,k,f.options.escape);if(!d.find(".detail-icon").length&&(f.trigger("click"===b.type?"click-cell":"dbl-click-cell",k,n,h,d),f.trigger("click"===b.type?"click-row":"dbl-click-row",h,g,k), +"click"===b.type&&f.options.clickToSelect&&l.clickToSelect)){var o=g.find(c('[name="%s"]',f.options.selectItemName));o.length&&o[0].click()}}),this.$body.find("> tr[data-index] > td > .detail-icon").off("click").on("click",function(){var b=a(this),d=b.parent().parent(),e=d.data("index"),g=i[e];if(d.next().is("tr.detail-view"))b.find("i").attr("class",c("%s %s",f.options.iconsPrefix,f.options.icons.detailOpen)),d.next().remove(),f.trigger("collapse-row",e,g);else{b.find("i").attr("class",c("%s %s",f.options.iconsPrefix,f.options.icons.detailClose)),d.after(c('',d.find("td").length));var j=d.next().find("td"),k=h(f.options,f.options.detailFormatter,[e,g,j],"");1===j.length&&j.append(k),f.trigger("expand-row",e,g,j)}f.resetView()}),this.$selectItem=this.$body.find(c('[name="%s"]',this.options.selectItemName)),this.$selectItem.off("click").on("click",function(b){b.stopImmediatePropagation();var c=a(this),d=c.prop("checked"),e=f.data[c.data("index")];f.options.maintainSelected&&a(this).is(":radio")&&a.each(f.options.data,function(a,b){b[f.header.stateField]=!1}),e[f.header.stateField]=d,f.options.singleSelect&&(f.$selectItem.not(this).each(function(){f.data[a(this).data("index")][f.header.stateField]=!1}),f.$selectItem.filter(":checked").not(this).prop("checked",!1)),f.updateSelected(),f.trigger(d?"check":"uncheck",e,c)}),a.each(this.header.events,function(b,c){if(c){"string"==typeof c&&(c=h(null,c));var d=f.header.fields[b],e=a.inArray(d,f.getVisibleFields());f.options.detailView&&!f.options.cardView&&(e+=1);for(var g in c)f.$body.find(">tr:not(.no-records-found)").each(function(){var b=a(this),h=b.find(f.options.cardView?".card-view":"td").eq(e),i=g.indexOf(" "),j=g.substring(0,i),k=g.substring(i+1),l=c[g];h.find(k).off(j).on(j,function(a){var c=b.data("index"),e=f.data[c],g=e[d];l.apply(this,[a,g,e,c])})})}}),this.updateSelected(),this.resetView(),this.trigger("post-body",i)},p.prototype.initServer=function(b,c,d){var e,f=this,g={},i={searchText:this.searchText,sortName:this.options.sortName,sortOrder:this.options.sortOrder};this.options.pagination&&(i.pageSize=this.options.pageSize===this.options.formatAllRows()?this.options.totalRows:this.options.pageSize,i.pageNumber=this.options.pageNumber),(d||this.options.url||this.options.ajax)&&("limit"===this.options.queryParamsType&&(i={search:i.searchText,sort:i.sortName,order:i.sortOrder},this.options.pagination&&(i.offset=this.options.pageSize===this.options.formatAllRows()?0:this.options.pageSize*(this.options.pageNumber-1),i.limit=this.options.pageSize===this.options.formatAllRows()?this.options.totalRows:this.options.pageSize)),a.isEmptyObject(this.filterColumnsPartial)||(i.filter=JSON.stringify(this.filterColumnsPartial,null)),g=h(this.options,this.options.queryParams,[i],g),a.extend(g,c||{}),g!==!1&&(b||this.$tableLoading.show(),e=a.extend({},h(null,this.options.ajaxOptions),{type:this.options.method,url:d||this.options.url,data:"application/json"===this.options.contentType&&"post"===this.options.method?JSON.stringify(g):g,cache:this.options.cache,contentType:this.options.contentType,dataType:this.options.dataType,success:function(a){a=h(f.options,f.options.responseHandler,[a],a),f.load(a),f.trigger("load-success",a),b||f.$tableLoading.hide()},error:function(a){f.trigger("load-error",a.status,a),b||f.$tableLoading.hide()}}),this.options.ajax?h(this,this.options.ajax,[e],null):(this._xhr&&4!==this._xhr.readyState&&this._xhr.abort(),this._xhr=a.ajax(e))))},p.prototype.initSearchText=function(){if(this.options.search&&""!==this.options.searchText){var a=this.$toolbar.find(".search input");a.val(this.options.searchText),this.onSearch({currentTarget:a})}},p.prototype.getCaret=function(){var b=this;a.each(this.$header.find("th"),function(c,d){a(d).find(".sortable").removeClass("desc asc").addClass(a(d).data("field")===b.options.sortName?b.options.sortOrder:"both")})},p.prototype.updateSelected=function(){var b=this.$selectItem.filter(":enabled").length&&this.$selectItem.filter(":enabled").length===this.$selectItem.filter(":enabled").filter(":checked").length;this.$selectAll.add(this.$selectAll_).prop("checked",b),this.$selectItem.each(function(){a(this).closest("tr")[a(this).prop("checked")?"addClass":"removeClass"]("selected")})},p.prototype.updateRows=function(){var b=this;this.$selectItem.each(function(){b.data[a(this).data("index")][b.header.stateField]=a(this).prop("checked")})},p.prototype.resetRows=function(){var b=this;a.each(this.data,function(a,c){b.$selectAll.prop("checked",!1),b.$selectItem.prop("checked",!1),b.header.stateField&&(c[b.header.stateField]=!1)})},p.prototype.trigger=function(b){var c=Array.prototype.slice.call(arguments,1);b+=".bs.table",this.options[p.EVENTS[b]].apply(this.options,c),this.$el.trigger(a.Event(b),c),this.options.onAll(b,c),this.$el.trigger(a.Event("all.bs.table"),[b,c])},p.prototype.resetHeader=function(){clearTimeout(this.timeoutId_),this.timeoutId_=setTimeout(a.proxy(this.fitHeader,this),this.$el.is(":hidden")?100:0)},p.prototype.fitHeader=function(){var b,d,e,f,h=this;if(h.$el.is(":hidden"))return void(h.timeoutId_=setTimeout(a.proxy(h.fitHeader,h),100));if(b=this.$tableBody.get(0),d=b.scrollWidth>b.clientWidth&&b.scrollHeight>b.clientHeight+this.$header.outerHeight()?g():0,this.$el.css("margin-top",-this.$header.outerHeight()),e=a(":focus"),e.length>0){var i=e.parents("th");if(i.length>0){var j=i.attr("data-field");if(void 0!==j){var k=this.$header.find("[data-field='"+j+"']");k.length>0&&k.find(":input").addClass("focus-temp")}}}this.$header_=this.$header.clone(!0,!0),this.$selectAll_=this.$header_.find('[name="btSelectAll"]'),this.$tableHeader.css({"margin-right":d}).find("table").css("width",this.$el.outerWidth()).html("").attr("class",this.$el.attr("class")).append(this.$header_),f=a(".focus-temp:visible:eq(0)"),f.length>0&&(f.focus(),this.$header.find(".focus-temp").removeClass("focus-temp")),this.$header.find("th[data-field]").each(function(){h.$header_.find(c('th[data-field="%s"]',a(this).data("field"))).data(a(this).data())});var l=this.getVisibleFields(),m=this.$header_.find("th");this.$body.find(">tr:first-child:not(.no-records-found) > *").each(function(b){var d=a(this),e=b;h.options.detailView&&!h.options.cardView&&(0===b&&h.$header_.find("th.detail").find(".fht-cell").width(d.innerWidth()),e=b-1);var f=h.$header_.find(c('th[data-field="%s"]',l[e]));f.length>1&&(f=a(m[d[0].cellIndex])),f.find(".fht-cell").width(d.innerWidth())}),this.$tableBody.off("scroll").on("scroll",function(){h.$tableHeader.scrollLeft(a(this).scrollLeft()),h.options.showFooter&&!h.options.cardView&&h.$tableFooter.scrollLeft(a(this).scrollLeft())}),h.trigger("post-header")},p.prototype.resetFooter=function(){var b=this,d=b.getData(),e=[];this.options.showFooter&&!this.options.cardView&&(!this.options.cardView&&this.options.detailView&&e.push('
 
'),a.each(this.columns,function(a,f){var g,i="",j="",k=[],l={},m=c(' class="%s"',f["class"]);if(f.visible&&(!b.options.cardView||f.cardVisible)){if(i=c("text-align: %s; ",f.falign?f.falign:f.align),j=c("vertical-align: %s; ",f.valign),l=h(null,b.options.footerStyle),l&&l.css)for(g in l.css)k.push(g+": "+l.css[g]);e.push(""),e.push('
'),e.push(h(f,f.footerFormatter,[d]," ")||" "),e.push("
"),e.push('
'),e.push("
"),e.push("")}}),this.$tableFooter.find("tr").html(e.join("")),this.$tableFooter.show(),clearTimeout(this.timeoutFooter_),this.timeoutFooter_=setTimeout(a.proxy(this.fitFooter,this),this.$el.is(":hidden")?100:0))},p.prototype.fitFooter=function(){var b,c,d;return clearTimeout(this.timeoutFooter_),this.$el.is(":hidden")?void(this.timeoutFooter_=setTimeout(a.proxy(this.fitFooter,this),100)):(c=this.$el.css("width"),d=c>this.$tableBody.width()?g():0,this.$tableFooter.css({"margin-right":d}).find("table").css("width",c).attr("class",this.$el.attr("class")),b=this.$tableFooter.find("td"),void this.$body.find(">tr:first-child:not(.no-records-found) > *").each(function(c){var d=a(this);b.eq(c).find(".fht-cell").width(d.innerWidth())}))},p.prototype.toggleColumn=function(a,b,d){if(-1!==a&&(this.columns[a].visible=b,this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns)){var e=this.$toolbar.find(".keep-open input").prop("disabled",!1);d&&e.filter(c('[value="%s"]',a)).prop("checked",b),e.filter(":checked").length<=this.options.minimumCountColumns&&e.filter(":checked").prop("disabled",!0)}},p.prototype.toggleRow=function(a,b,d){-1!==a&&this.$body.find("undefined"!=typeof a?c('tr[data-index="%s"]',a):c('tr[data-uniqueid="%s"]',b))[d?"show":"hide"]()},p.prototype.getVisibleFields=function(){var b=this,c=[];return a.each(this.header.fields,function(a,d){var f=b.columns[e(b.columns,d)];f.visible&&c.push(d)}),c},p.prototype.resetView=function(a){var b=0;if(a&&a.height&&(this.options.height=a.height),this.$selectAll.prop("checked",this.$selectItem.length>0&&this.$selectItem.length===this.$selectItem.filter(":checked").length),this.options.height){var c=k(this.$toolbar),d=k(this.$pagination),e=this.options.height-c-d;this.$tableContainer.css("height",e+"px")}return this.options.cardView?(this.$el.css("margin-top","0"),this.$tableContainer.css("padding-bottom","0"),void this.$tableFooter.hide()):(this.options.showHeader&&this.options.height?(this.$tableHeader.show(),this.resetHeader(),b+=this.$header.outerHeight()):(this.$tableHeader.hide(),this.trigger("post-header")),this.options.showFooter&&(this.resetFooter(),this.options.height&&(b+=this.$tableFooter.outerHeight()+1)),this.getCaret(),this.$tableContainer.css("padding-bottom",b+"px"),void this.trigger("reset-view"))},p.prototype.getData=function(b){return!this.searchText&&a.isEmptyObject(this.filterColumns)&&a.isEmptyObject(this.filterColumnsPartial)?b?this.options.data.slice(this.pageFrom-1,this.pageTo):this.options.data:b?this.data.slice(this.pageFrom-1,this.pageTo):this.data},p.prototype.load=function(b){var c=!1;"server"===this.options.sidePagination?(this.options.totalRows=b.total,c=b.fixedScroll,b=b[this.options.dataField]):a.isArray(b)||(c=b.fixedScroll,b=b.data),this.initData(b),this.initSearch(),this.initPagination(),this.initBody(c)},p.prototype.append=function(a){this.initData(a,"append"),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0)},p.prototype.prepend=function(a){this.initData(a,"prepend"),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0)},p.prototype.remove=function(b){var c,d,e=this.options.data.length;if(b.hasOwnProperty("field")&&b.hasOwnProperty("values")){for(c=e-1;c>=0;c--)d=this.options.data[c],d.hasOwnProperty(b.field)&&-1!==a.inArray(d[b.field],b.values)&&this.options.data.splice(c,1);e!==this.options.data.length&&(this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0))}},p.prototype.removeAll=function(){this.options.data.length>0&&(this.options.data.splice(0,this.options.data.length),this.initSearch(),this.initPagination(),this.initBody(!0))},p.prototype.getRowByUniqueId=function(a){var b,c,d,e=this.options.uniqueId,f=this.options.data.length,g=null;for(b=f-1;b>=0;b--){if(c=this.options.data[b],c.hasOwnProperty(e))d=c[e];else{if(!c._data.hasOwnProperty(e))continue;d=c._data[e]}if("string"==typeof d?a=a.toString():"number"==typeof d&&(Number(d)===d&&d%1===0?a=parseInt(a):d===Number(d)&&0!==d&&(a=parseFloat(a))),d===a){g=c;break}}return g},p.prototype.removeByUniqueId=function(a){var b=this.options.data.length,c=this.getRowByUniqueId(a);c&&this.options.data.splice(this.options.data.indexOf(c),1),b!==this.options.data.length&&(this.initSearch(),this.initPagination(),this.initBody(!0))},p.prototype.updateByUniqueId=function(b){var c=this,d=a.isArray(b)?b:[b];a.each(d,function(b,d){var e;d.hasOwnProperty("id")&&d.hasOwnProperty("row")&&(e=a.inArray(c.getRowByUniqueId(d.id),c.options.data),-1!==e&&a.extend(c.options.data[e],d.row))}),this.initSearch(),this.initSort(),this.initBody(!0)},p.prototype.insertRow=function(a){a.hasOwnProperty("index")&&a.hasOwnProperty("row")&&(this.data.splice(a.index,0,a.row),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0))},p.prototype.updateRow=function(b){var c=this,d=a.isArray(b)?b:[b];a.each(d,function(b,d){d.hasOwnProperty("index")&&d.hasOwnProperty("row")&&a.extend(c.options.data[d.index],d.row)}),this.initSearch(),this.initSort(),this.initBody(!0)},p.prototype.showRow=function(a){(a.hasOwnProperty("index")||a.hasOwnProperty("uniqueId"))&&this.toggleRow(a.index,a.uniqueId,!0)},p.prototype.hideRow=function(a){(a.hasOwnProperty("index")||a.hasOwnProperty("uniqueId"))&&this.toggleRow(a.index,a.uniqueId,!1)},p.prototype.getRowsHidden=function(b){var c=a(this.$body[0]).children().filter(":hidden"),d=0;if(b)for(;dtr");if(this.options.detailView&&!this.options.cardView&&(g+=1),e=j.eq(f).find(">td").eq(g),!(0>f||0>g||f>=this.data.length)){for(c=f;f+h>c;c++)for(d=g;g+i>d;d++)j.eq(c).find(">td").eq(d).hide();e.attr("rowspan",h).attr("colspan",i).show()}},p.prototype.updateCell=function(a){a.hasOwnProperty("index")&&a.hasOwnProperty("field")&&a.hasOwnProperty("value")&&(this.data[a.index][a.field]=a.value,a.reinit!==!1&&(this.initSort(),this.initBody(!0)))},p.prototype.getOptions=function(){return this.options},p.prototype.getSelections=function(){var b=this;return a.grep(this.options.data,function(a){return a[b.header.stateField]})},p.prototype.getAllSelections=function(){var b=this;return a.grep(this.options.data,function(a){return a[b.header.stateField]})},p.prototype.checkAll=function(){this.checkAll_(!0)},p.prototype.uncheckAll=function(){this.checkAll_(!1)},p.prototype.checkInvert=function(){var b=this,c=b.$selectItem.filter(":enabled"),d=c.filter(":checked");c.each(function(){a(this).prop("checked",!a(this).prop("checked"))}),b.updateRows(),b.updateSelected(),b.trigger("uncheck-some",d),d=b.getSelections(),b.trigger("check-some",d)},p.prototype.checkAll_=function(a){var b;a||(b=this.getSelections()),this.$selectAll.add(this.$selectAll_).prop("checked",a),this.$selectItem.filter(":enabled").prop("checked",a),this.updateRows(),a&&(b=this.getSelections()),this.trigger(a?"check-all":"uncheck-all",b)},p.prototype.check=function(a){this.check_(!0,a)},p.prototype.uncheck=function(a){this.check_(!1,a)},p.prototype.check_=function(a,b){var d=this.$selectItem.filter(c('[data-index="%s"]',b)).prop("checked",a);this.data[b][this.header.stateField]=a,this.updateSelected(),this.trigger(a?"check":"uncheck",this.data[b],d)},p.prototype.checkBy=function(a){this.checkBy_(!0,a)},p.prototype.uncheckBy=function(a){this.checkBy_(!1,a)},p.prototype.checkBy_=function(b,d){if(d.hasOwnProperty("field")&&d.hasOwnProperty("values")){var e=this,f=[];a.each(this.options.data,function(g,h){if(!h.hasOwnProperty(d.field))return!1;if(-1!==a.inArray(h[d.field],d.values)){var i=e.$selectItem.filter(":enabled").filter(c('[data-index="%s"]',g)).prop("checked",b);h[e.header.stateField]=b,f.push(h),e.trigger(b?"check":"uncheck",h,i)}}),this.updateSelected(),this.trigger(b?"check-some":"uncheck-some",f)}},p.prototype.destroy=function(){this.$el.insertBefore(this.$container),a(this.options.toolbar).insertBefore(this.$el),this.$container.next().remove(),this.$container.remove(),this.$el.html(this.$el_.html()).css("margin-top","0").attr("class",this.$el_.attr("class")||"")},p.prototype.showLoading=function(){this.$tableLoading.show()},p.prototype.hideLoading=function(){this.$tableLoading.hide()},p.prototype.togglePagination=function(){this.options.pagination=!this.options.pagination;var a=this.$toolbar.find('button[name="paginationSwitch"] i');this.options.pagination?a.attr("class",this.options.iconsPrefix+" "+this.options.icons.paginationSwitchDown):a.attr("class",this.options.iconsPrefix+" "+this.options.icons.paginationSwitchUp),this.updatePagination()},p.prototype.refresh=function(a){a&&a.url&&(this.options.pageNumber=1),this.initServer(a&&a.silent,a&&a.query,a&&a.url),this.trigger("refresh",a)},p.prototype.resetWidth=function(){this.options.showHeader&&this.options.height&&this.fitHeader(),this.options.showFooter&&this.fitFooter()},p.prototype.showColumn=function(a){this.toggleColumn(e(this.columns,a),!0,!0)},p.prototype.hideColumn=function(a){this.toggleColumn(e(this.columns,a),!1,!0)},p.prototype.getHiddenColumns=function(){return a.grep(this.columns,function(a){return!a.visible})},p.prototype.getVisibleColumns=function(){return a.grep(this.columns,function(a){return a.visible})},p.prototype.toggleAllColumns=function(b){if(a.each(this.columns,function(a){this.columns[a].visible=b}),this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns){var c=this.$toolbar.find(".keep-open input").prop("disabled",!1);c.filter(":checked").length<=this.options.minimumCountColumns&&c.filter(":checked").prop("disabled",!0)}},p.prototype.showAllColumns=function(){this.toggleAllColumns(!0)},p.prototype.hideAllColumns=function(){this.toggleAllColumns(!1)},p.prototype.filterBy=function(b){this.filterColumns=a.isEmptyObject(b)?{}:b,this.options.pageNumber=1,this.initSearch(),this.updatePagination()},p.prototype.scrollTo=function(a){return"string"==typeof a&&(a="bottom"===a?this.$tableBody[0].scrollHeight:0),"number"==typeof a&&this.$tableBody.scrollTop(a),"undefined"==typeof a?this.$tableBody.scrollTop():void 0},p.prototype.getScrollPosition=function(){return this.scrollTo()},p.prototype.selectPage=function(a){a>0&&a<=this.options.totalPages&&(this.options.pageNumber=a,this.updatePagination())},p.prototype.prevPage=function(){this.options.pageNumber>1&&(this.options.pageNumber--,this.updatePagination())},p.prototype.nextPage=function(){this.options.pageNumber tr[data-index="%s"]',b));d.next().is("tr.detail-view")===(a?!1:!0)&&d.find("> td > .detail-icon").click()},p.prototype.expandRow=function(a){this.expandRow_(!0,a)},p.prototype.collapseRow=function(a){this.expandRow_(!1,a)},p.prototype.expandAllRows=function(b){if(b){var d=this.$body.find(c('> tr[data-index="%s"]',0)),e=this,f=null,g=!1,h=-1;if(d.next().is("tr.detail-view")?d.next().next().is("tr.detail-view")||(d.next().find(".detail-icon").click(),g=!0):(d.find("> td > .detail-icon").click(),g=!0),g)try{h=setInterval(function(){f=e.$body.find("tr.detail-view").last().find(".detail-icon"),f.length>0?f.click():clearInterval(h)},1)}catch(i){clearInterval(h)}}else for(var j=this.$body.children(),k=0;k.btn-group"),g=f.find("div.export");if(!g.length){g=a(['
','",'","
"].join("")).appendTo(f);var h=g.find(".dropdown-menu"),i=this.options.exportTypes;if("string"==typeof this.options.exportTypes){var j=this.options.exportTypes.slice(1,-1).replace(/ /g,"").split(",");i=[],a.each(j,function(a,b){i.push(b.slice(1,-1))})}a.each(i,function(a,b){c.hasOwnProperty(b)&&h.append(['
  • ','',c[b],"","
  • "].join(""))}),h.find("li").click(function(){var b=a(this).data("type"),c=function(){d.$el.tableExport(a.extend({},d.options.exportOptions,{type:b,escape:!1}))};if("all"===d.options.exportDataType&&d.options.pagination)d.$el.one("server"===d.options.sidePagination?"post-body.bs.table":"page-change.bs.table",function(){c(),d.togglePagination()}),d.togglePagination();else if("selected"===d.options.exportDataType){var e=d.getData(),f=d.getAllSelections();d.load(f),c(),d.load(e)}else c()})}}}}(jQuery); diff --git a/web/gui/lib/bootstrap-toggle-2.2.2.min.js b/web/gui/lib/bootstrap-toggle-2.2.2.min.js new file mode 100644 index 0000000..a11e156 --- /dev/null +++ b/web/gui/lib/bootstrap-toggle-2.2.2.min.js @@ -0,0 +1,10 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * SPDX-License-Identifier: MIT + * ======================================================================== */ ++function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('