@cryptotaxi247 / netdata-1 / commits / b6ce81754

Cloud Sign-In (#5095)

* Manually merged changes from old hub-support branch, tracking #131 Call claim url #4771 Claim ui improvements #4771 Cleanup Implement Sign Out Introduced sign-in modal #3990 Added sign-in button More work on the iframe trick More work More work on the logic, removed old obsolete stuff Close modal Implement account menu Minor rename Renamed my-netdata to My Agents Show migrate button Collect known agents Work on migrateRegistryDidClick Minor Actually show agents from netdata cloud in the menu Some cleanup Keep all the alternate_urls for each agent Fix for tooltips over SignIn/AccountMenu * Actually use NETDATA.registry.cloudBaseURL Tricky! * Hide switch identity when signed-in #153 * Manually merged changes from old hub-support branch, tracking #131 Call claim url #4771 Claim ui improvements #4771 Cleanup Implement Sign Out Introduced sign-in modal #3990 Added sign-in button More work on the iframe trick More work More work on the logic, removed old obsolete stuff Close modal Implement account menu Minor rename Renamed my-netdata to My Agents Show migrate button Collect known agents Work on migrateRegistryDidClick Minor Actually show agents from netdata cloud in the menu Some cleanup Keep all the alternate_urls for each agent Fix for tooltips over SignIn/AccountMenu * Actually use NETDATA.registry.cloudBaseURL Tricky! * Hide switch identity when signed-in #153 * Cleanup * Refresh menu on sign-in * Disable cloud functionality if cloud base url is not set. This wll allow the merging of the branch into master, so we can avoid nasty rebases. * Updated to use the latest API endpoints * Fixed a couple of LGTM warnings * Improved migration algorithm, some cleanup. * Update My-Netdata menu on sign-out * Minor * Replaced modal with window * Update the My-Agents menu after migration, cleanup * Make the agent work after switching cloudBaseURL, cleanup * Introduced event tracing for analytics * Minor * Removed GA * Fixed error reported by LGTM * Only send the diff when syncing agents to ameliorate the load on the backend, cleanup * Reverted My-Netdata name, added some logging * Add Netdata Cloud menu item * Minor * Use the merge: false option and a fix * Added loading message in my-netdata menu * Show error if we cannot connect to netdata.cloud * Minor * Implemented deleteCloudKnownAgentURL api call, use it in my-netdata menu. * Removed menu entry * Disable my-netdata menu if user is not signed-in and using the global registry * Stop accessing the registry if it's not used. * Mask the agent url if the registry is in 'disabled' mode * Filter masked urls * Improved filtering of masked urls * Try to eagerly initialize the account ui to improve perceived performance * Minor * Don't search for other people's urls in cloud-enabled mode. * Added basic my-netdata filtering * Filter streamed host, aesthetic fixes * Minor * Some improvements of the filter ui * Removed What is this * Added placeholder to input, other fixes #240 * Show message if no databases match filter criteria * Fixed bug where agent lists where not merged * Minor * Hide modal if it redirects to self. * autocomplete off for filter input * Enable delete for custom registries, don't show error if delete fails * Filter agents without urls * Fix LGTM warning * Minor * Concatenate at client side, used the faster merge: false path * Added a clear button to the filter for extra usability * Minor * Minor * Improvements for small screens (more needed) * Combined my-netdata menu and hostname * Re-enabled registry masking * Show agent-filter only when signed-in * Improved syncAgents * Don't mask if using custom registry * Reject agents with empty urls * Filter valid agents * Fixed a couple of bugs * Applied Chris' fixes * Fix in registry.c * Cleanup * Only sync once * Implemented forceSync * Added what is this * sso, wip * Working SSO sign-in/sign-out, cleanup * Added Chris' patch * Added a modal that explains what synchronize is doing * Use sso-agent * Use origin as query param in sign-in * iframe -> origin * Pass machine_guid to sso * Make sure that the current netdata agent is synchronized hub#262 * Normalize originURL * Reenable tryFastInitCloud() * Updated to the latest endpoints * Support synchronizing to multiple cloud accounts * Set default cloud base url to netdata.cloud * Fix filter issues with Firefox * Fix for double tooltip on sign-in * Show known servers in console for debugging purposes * Don't block on errors to delete from registry when signed in * Disable tryFastInitCloud * Improved styling for filter input * Improved styling in my-netdata menu * Display the registry url in the sync-registry modal * agents -> nodes in texts * Support for sso-precheck * Do not implicitly synchronize custom registries. * Improvement to syncAgents (more coming) * More fixes * Don't sign in users with private registries if they don't consent on the sync * Set netdataRegistryAfterMs = 0 * Don't pass url to sso-agent * Added Chris' patch to alarm-notify * Refactored syncAgent/mergeAgents, make sure current Agent is synced on sign-in. * Fix for LGTM warning * Minor * Fix for a XSS warning * Extra check for dataLayer

George Moschovitis committed Jan 28, 2019 at 12:27 UTC b6ce8175451c70db785f36f31b1d26c23f9f6979
11 files changed +1004 -111
.gitignore
+3
@@ -156,3 +156,6 @@ python.d/python-modules-installer.sh
156 docs/generator/src
157 docs/generator/build
158 docs/generator/mkdocs.yml
159 +
160 +netdata-updater.sh
161 +.environment.sh
health/notifications/alarm-notify.sh.in
+21 -1
@@ -1809,7 +1809,27 @@ urlencode "${args_host}" >/dev/null; url_host="${REPLY}"
1809 urlencode "${chart}" >/dev/null; url_chart="${REPLY}"
1810 urlencode "${family}" >/dev/null; url_family="${REPLY}"
1811 urlencode "${name}" >/dev/null; url_name="${REPLY}"
1812 -goto_url="${NETDATA_REGISTRY_URL}/goto-host-from-alarm.html?host=${url_host}&chart=${url_chart}&family=${url_family}&alarm=${url_name}&alarm_unique_id=${unique_id}&alarm_id=${alarm_id}&alarm_event_id=${event_id}"
1812 +
1813 +redirect_params="host=${url_host}&chart=${url_chart}&family=${url_family}&alarm=${url_name}&alarm_unique_id=${unique_id}&alarm_id=${alarm_id}&alarm_event_id=${event_id}"
1814 +GOTOCLOUD=0
1815 +
1816 +if [ "${NETDATA_REGISTRY_URL}" == "https://registry.my-netdata.io" ] ; then
1817 + if [ -z "${NETDATA_REGISTRY_UNIQUE_ID}" ] ; then
1818 + if [ -f "@registrydir_POST@/netdata.public.unique.id" ]; then
1819 + NETDATA_REGISTRY_UNIQUE_ID="$(cat "@registrydir_POST@/netdata.public.unique.id")"
1820 + fi
1821 + fi
1822 + if [ ! -z "${NETDATA_REGISTRY_UNIQUE_ID}" ] ; then
1823 + GOTOCLOUD=1
1824 + fi
1825 +fi
1826 +
1827 +if [ ${GOTOCLOUD} -eq 0 ] ; then
1828 + goto_url="${NETDATA_REGISTRY_URL}/goto-host-from-alarm.html?${redirect_params}"
1829 +else
1830 + urlencode "${NETDATA_REGISTRY_URL}/goto-host-from-alarm.html" >/dev/null; url_registrypath="${REPLY}"
1831 + goto_url="https://netdata.cloud/alarms/redirect?agentID=${NETDATA_REGISTRY_UNIQUE_ID}&registrypath=${url_registrypath}&${redirect_params}"
1832 +fi
1833
1834 # the severity of the alarm
1835 severity="${status}"
registry/registry.c
+4
@@ -81,6 +81,8 @@ static int registry_json_person_url_callback(void *entry, void *data) {
81 struct registry_json_walk_person_urls_callback *c = (struct registry_json_walk_person_urls_callback *)data;
82 struct web_client *w = c->w;
83
84 + if (!strcmp(pu->url->url,"***")) return 0;
85 +
86 if(unlikely(c->count++))
87 buffer_strcat(w->response.data, ",");
88
@@ -97,6 +99,8 @@ static int registry_json_machine_url_callback(void *entry, void *data) {
99 struct web_client *w = c->w;
100 REGISTRY_MACHINE *m = c->m;
101
102 + if (!strcmp(mu->url->url,"***")) return 1;
103 +
104 if(unlikely(c->count++))
105 buffer_strcat(w->response.data, ",");
106
registry/registry_init.c
+3 -1
@@ -37,10 +37,12 @@ int registry_init(void) {
37 registry.persons_expiration = config_get_number(CONFIG_SECTION_REGISTRY, "registry expire idle persons days", 365) * 86400;
38 registry.registry_domain = config_get(CONFIG_SECTION_REGISTRY, "registry domain", "");
39 registry.registry_to_announce = config_get(CONFIG_SECTION_REGISTRY, "registry to announce", "https://registry.my-netdata.io");
40 - registry.cloud_base_url = config_get(CONFIG_SECTION_CLOUD, "cloud base url", "https://netdata.cloud");
40 registry.hostname = config_get(CONFIG_SECTION_REGISTRY, "registry hostname", netdata_configured_hostname);
41 registry.verify_cookies_redirects = config_get_boolean(CONFIG_SECTION_REGISTRY, "verify browser cookies support", 1);
42
43 + // netdata.cloud configuration, if cloud_base_url == "", cloud functionality is disabled.
44 + registry.cloud_base_url = config_get(CONFIG_SECTION_CLOUD, "cloud base url", "https://netdata.cloud");
45 +
46 setenv("NETDATA_REGISTRY_HOSTNAME", registry.hostname, 1);
47 setenv("NETDATA_REGISTRY_URL", registry.registry_to_announce, 1);
48
web/gui/dashboard.js
+39 -10
@@ -1246,7 +1246,7 @@ if (typeof netdataShowAlarms === 'undefined') {
1246 }
1247
1248 if (typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0) {
1249 - netdataRegistryAfterMs = 1500;
1249 + netdataRegistryAfterMs = 0; // 1500;
1250 }
1251
1252 if (typeof netdataRegistry === 'undefined') {
@@ -9677,6 +9677,7 @@ NETDATA.alarms = {
9677
9678 NETDATA.registry = {
9679 server: null, // the netdata registry server
9680 + isCloudEnabled: false,// is netdata.cloud functionality enabled?
9681 cloudBaseURL: null, // the netdata cloud base url
9682 person_guid: null, // the unique ID of this browser / user
9683 machine_guid: null, // the unique ID the netdata server that served dashboard.js
@@ -9685,8 +9686,17 @@ NETDATA.registry = {
9686 machines_array: null, // the user's other URLs in an array
9687 person_urls: null,
9688
9689 + MASKED_DATA: "***",
9690 +
9691 + isUsingGlobalRegistry: function() {
9692 + return NETDATA.registry.server == "https://registry.my-netdata.io";
9693 + },
9694 +
9695 + isRegistryEnabled: function() {
9696 + return !(NETDATA.registry.isUsingGlobalRegistry() || isSignedIn())
9697 + },
9698 +
9699 parsePersonUrls: function (person_urls) {
9689 - // console.log(person_urls);
9700 NETDATA.registry.person_urls = person_urls;
9701
9702 if (person_urls) {
@@ -9739,14 +9749,21 @@ NETDATA.registry = {
9749 NETDATA.registry.hello(NETDATA.serverDefault, function (data) {
9750 if (data) {
9751 NETDATA.registry.server = data.registry;
9742 - NETDATA.registry.cloudBaseURL = data.cloud_base_url;
9752 + if (data.cloud_base_url != "") {
9753 + NETDATA.registry.isCloudEnabled = true;
9754 + NETDATA.registry.cloudBaseURL = data.cloud_base_url;
9755 + } else {
9756 + NETDATA.registry.isCloudEnabled = false;
9757 + NETDATA.registry.cloudBaseURL = "";
9758 + }
9759 NETDATA.registry.machine_guid = data.machine_guid;
9760 NETDATA.registry.hostname = data.hostname;
9745 - if (data.anonymous_statistics) dataLayer.push({"anonymous_statistics" : "true", "machine_guid" : data.machine_guid});
9761 + if (dataLayer) {
9762 + if (data.anonymous_statistics) dataLayer.push({"anonymous_statistics" : "true", "machine_guid" : data.machine_guid});
9763 + }
9764 NETDATA.registry.access(2, function (person_urls) {
9765 NETDATA.registry.parsePersonUrls(person_urls);
9748 -
9749 - });
9766 + });
9767 }
9768 });
9769 },
@@ -9789,13 +9806,25 @@ NETDATA.registry = {
9806 },
9807
9808 access: function (max_redirects, callback) {
9809 + let name = NETDATA.registry.MASKED_DATA;
9810 + let url = NETDATA.registry.MASKED_DATA;
9811 +
9812 + if (!NETDATA.registry.isUsingGlobalRegistry()) {
9813 + // If the user is using a private registry keep sending identifiable
9814 + // data.
9815 + name = NETDATA.registry.hostname;
9816 + url = NETDATA.serverDefault;
9817 + }
9818 +
9819 + console.log("ACCESS", name, url);
9820 +
9821 // send ACCESS to a netdata registry:
9822 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
9823 // 2. it responds with a list of netdata servers we know
9824 // the registry identifies us using a cookie it sets the first time we access it
9825 // the registry may respond with a redirect URL to send us to another registry
9826 $.ajax({
9798 - url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
9827 + url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(name) + '&url=' + encodeURIComponent(url), // + '&visible_url=' + encodeURIComponent(document.location),
9828 async: true,
9829 cache: false,
9830 headers: {
@@ -9827,14 +9856,14 @@ NETDATA.registry = {
9856 return callback(null);
9857 }
9858 }
9830 - }
9831 - else {
9859 + } else {
9860 if (typeof data.person_guid === 'string') {
9861 NETDATA.registry.person_guid = data.person_guid;
9862 }
9863
9864 if (typeof callback === 'function') {
9837 - return callback(data.urls);
9865 + const urls = data.urls.filter((u) => u[1] !== NETDATA.registry.MASKED_DATA);
9866 + return callback(urls);
9867 }
9868 }
9869 })
web/gui/goto-host-from-alarm.html
+7 -2
@@ -2,16 +2,21 @@
2 <!-- SPDX-License-Identifier: GPL-3.0-or-later -->
3 <html lang="en">
4 <head>
5 + <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
6 + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
7 + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
8 + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
9 + })(window,document,'script','dataLayer','GTM-N6CBMJD');
10 + dataLayer.push({"anonymous_statistics" : "false"});
11 + </script>
12 <title>Goto a host you know...</title>
13 <meta name="application-name" content="netdata">
7 -
14 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
15 <meta charset="utf-8">
16 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
17 <meta name="viewport" content="width=device-width, initial-scale=1">
18 <meta name="apple-mobile-web-app-capable" content="yes">
19 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
14 -
20 </head>
21 <script>
22 var netdataRegistry = true;
web/gui/index.html
+99 -8
@@ -88,18 +88,23 @@
88 </script>
89 <nav class="navbar navbar-default navbar-fixed-top" role="banner">
90 <div class="container">
91 - <nav id="mynetdata_nav" class="collapse navbar-collapse navbar-left hidden-sm hidden-xs" role="navigation" style="padding-right: 20px;">
91 + <!-- <nav id="mynetdata_nav" class="collapse navbar-collapse navbar-left" role="navigation" style="padding-right: 20px;">
92 <ul class="nav navbar-nav">
93 <li data-placement="right" style="line-height: 50px; margin-right: 15px" title="Netdata Agent">
94 <img src="images/netdata-logomark.svg" height="32"/>
95 </li>
96 <li class="dropdown" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right">
97 - <a href="#" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a>
97 + <a href="#" id="hostname" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a>
98 <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu">
99 + <div class="agent-item" style="white-space: nowrap">
100 + <i class="fas fa-hourglass-half"></i>
101 + Loading, please wait...
102 + <div></div>
103 + </div>
104 </div>
105 </li>
106 </ul>
102 - </nav>
107 + </nav> -->
108 <div class="navbar-header">
109 <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-collapse">
110 <span class="sr-only">Toggle navigation</span>
@@ -107,26 +112,56 @@
112 <span class="icon-bar"></span>
113 <span class="icon-bar"></span>
114 </button>
110 - <a href="/" class="navbar-brand" id="hostname" title="server hostname<br/>click it to reload the dashboard" data-toggle="tooltip" data-placement="bottom">netdata</a>
115 + <!-- <a href="/" class="navbar-brand" id="1hostname" title="server hostname<br/>click it to reload the dashboard" data-toggle="tooltip" data-placement="bottom">netdata</a> -->
116 + <ul class="nav navbar-nav" style="display: inline-block; max-width: 300px">
117 + <li data-placement="right" class="hidden-xs hidden-sm" style="line-height: 50px; margin-right: 15px" title="Netdata Agent">
118 + <img src="images/netdata-logomark.svg" height="32"/>
119 + </li>
120 + <li class="dropdown" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right">
121 + <a href="#" id="hostname" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a>
122 + <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu">
123 + <div class="agent-item" style="white-space: nowrap">
124 + <i class="fas fa-hourglass-half"></i>
125 + Loading, please wait...
126 + <div></div>
127 + </div>
128 + </div>
129 + </li>
130 + </ul>
131 </div>
132 <nav class="collapse navbar-collapse navbar-right" role="navigation">
133 <ul class="nav navbar-nav">
134 <li id="alarmsButton" title="check the health monitoring alarms and their log" data-toggle="tooltip" data-placement="bottom"><a href="#" class="btn" data-toggle="modal" data-target="#alarmsModal"><i class="fas fa-bell"></i>&nbsp;<span class="hidden-sm hidden-md">Alarms&nbsp;</span><span id="alarms_count_badge" class="badge"></span></a></li>
135 <li title="change dashboard settings" data-toggle="tooltip" data-placement="bottom"><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fas fa-cog"></i>&nbsp;<span class="hidden-sm hidden-md">Settings</span></a></li>
136 <li title="check for netdata updates<br/>you should keep your netdata updated" data-toggle="tooltip" data-placement="bottom" class="hidden-sm" id="updateButton"><a href="#" class="btn" data-toggle="modal" data-target="#updateModal"><i class="fas fa-cloud-download-alt"></i> <span class="hidden-sm hidden-md">Update </span><span id="update_badge" class="badge"></span></a></li>
117 - <li title="the netdata wiki home at github<br/>remember to <b>give netdata a <i class=&quot;fas fa-star&quot;></i></b> !" data-toggle="tooltip" data-placement="bottom" class="hidden-sm hidden-md"><a href="https://github.com/netdata/netdata/wiki" class="btn" target="_blank"><i class="fab fa-github"></i></a></li>
118 - <li title="follow netdata on twitter" data-toggle="tooltip" data-placement="bottom" class="hidden-sm hidden-md"><a href="https://twitter.com/linuxnetdata" class="btn" target="_blank"><i class="fab fa-twitter"></i></a></li>
119 - <li title="like netdata on facebook" data-toggle="tooltip" data-placement="bottom" class="hidden-sm hidden-md"><a href="https://www.facebook.com/linuxnetdata/" class="btn" target="_blank"><i class="fab fa-facebook"></i></a></li>
137 + <li title="the netdata wiki home at github<br/>remember to <b>give netdata a <i class=&quot;fas fa-star&quot;></i></b> !" data-toggle="tooltip" data-placement="bottom" class="hidden-xs hidden-sm hidden-md"><a href="https://github.com/netdata/netdata/wiki" class="btn" target="_blank"><i class="fab fa-github"></i></a></li>
138 + <li title="follow netdata on twitter" data-toggle="tooltip" data-placement="bottom" class="hidden-xs hidden-sm hidden-md"><a href="https://twitter.com/linuxnetdata" class="btn" target="_blank"><i class="fab fa-twitter"></i></a></li>
139 + <li title="like netdata on facebook" data-toggle="tooltip" data-placement="bottom" class="hidden-xs hidden-sm hidden-md"><a href="https://www.facebook.com/linuxnetdata/" class="btn" target="_blank"><i class="fab fa-facebook"></i></a></li>
140 <li title="import / load a netdata snapshot" data-toggle="tooltip" data-placement="bottom" id="loadButton"><a href="#" class="btn" data-toggle="modal" data-target="#loadSnapshotModal"><i class="fas fa-download"></i>&nbsp;<span class="hidden-sm hidden-md hidden-lg">Import</span></a></li>
141 <li title="export / save a netdata snapshot" data-toggle="tooltip" data-placement="bottom" id="saveButton"><a href="#" class="btn" data-toggle="modal" data-target="#saveSnapshotModal"><i class="fas fa-upload"></i>&nbsp;<span class="hidden-sm hidden-md hidden-lg">Export</span></a></li>
142 <li title="print this dashboard to PDF" data-toggle="tooltip" data-placement="bottom" id="printButton"><a href="#" class="btn" data-toggle="modal" data-target="#printPreflightModal"><i class="fas fa-print"></i>&nbsp;<span class="hidden-sm hidden-md hidden-lg">Print</span></a></li>
143 <li title="get help on using the charts" data-toggle="tooltip" data-placement="bottom" class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fas fa-question-circle"></i>&nbsp;<span class="hidden-sm hidden-md">Help</span></a></li>
124 - <li class="dropdown hidden-sm hidden-md hidden-lg">
144 + <li id="account-menu-container" class="dropdown" data-toggle="tooltip"></li>
145 + <!--
146 + <li class="dropdown hidden-sm hidden-md hidden-lg" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right">
147 <a href="#" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a>
148 + <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu">
149 + <div class="agent-item" style="white-space: nowrap">
150 + <i class="fas fa-hourglass-half"></i>
151 + Loading, please wait...
152 + <div></div>
153 + </div>
154 + </div>
155 + </li>
156 + -->
157 + <!--
158 + <li class="dropdown hidden-sm hidden-md hidden-lg">
159 + <a href="#" class="dropdown-toggle" data-toggle="dropdown">My Nodes <strong class="caret"></strong></a>
160 <ul id="mynetdata_servers2" class="dropdown-menu scrollable-menu inpagemenu" role="menu">
161 <li><a href="#" onclick="return false;" style="color: #999;">loading...</a></li>
162 </ul>
163 </li>
164 + -->
165 </ul>
166 </nav>
167 </div>
@@ -1186,6 +1221,61 @@
1221 </div>
1222 </div>
1223
1224 + <div class="modal fade" id="signInModal" tabindex="-1" role="dialog" aria-labelledby="signInModalLabel">
1225 + <div class="modal-dialog" role="document">
1226 + <div class="modal-content">
1227 + <div class="modal-header">
1228 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
1229 + <h4 class="modal-title" id="signInModalLabel">Sign In</h4>
1230 + </div>
1231 + <div class="modal-body">
1232 + <p>
1233 + Signing-in to netdata.cloud will synchronize the list of
1234 + your netdata monitored nodes known at registry
1235 + <strong><span id="sim-registry"></span></strong>. This
1236 + may include server hostnames, urls and identification
1237 + GUIDs.
1238 + </p>
1239 + <p>
1240 + After you upgrade all your netdata servers, your private
1241 + registry will not be needed any more.
1242 + </p>
1243 + <p>
1244 + Are you sure you want to proceed?
1245 + </p>
1246 + </div>
1247 + <div class="modal-footer">
1248 + <button type="button" class="btn btn-success" data-dismiss="modal">Cancel</button>
1249 + <a href="#" onclick="explicitlySignIn(); return false;" type="button" class="btn btn-danger">Sign In</a>
1250 + </div>
1251 + </div>
1252 + </div>
1253 + </div>
1254 +
1255 + <div class="modal fade" id="syncRegistryModal" tabindex="-1" role="dialog" aria-labelledby="syncRegistryModalLabel">
1256 + <div class="modal-dialog" role="document">
1257 + <div class="modal-content">
1258 + <div class="modal-header">
1259 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
1260 + <h4 class="modal-title" id="syncRegistryModalLabel">Synchronize netdata.cloud with registry?</h4>
1261 + </div>
1262 + <div class="modal-body">
1263 + <p>
1264 + You are about to synchronize your netdata.cloud account with data from the registry at <strong><span id="sync-registry-modal-registry"></span></strong>.
1265 + This may include server hostnames, urls and identification GUIDs.
1266 + </p>
1267 + <p>
1268 + Are you sure you want to proceed?
1269 + </p>
1270 + </div>
1271 + <div class="modal-footer">
1272 + <button type="button" class="btn btn-success" data-dismiss="modal">Cancel</button>
1273 + <a href="#" onclick="explicitlySyncAgents(); return false;" type="button" class="btn btn-danger">Synchronize</a>
1274 + </div>
1275 + </div>
1276 + </div>
1277 + </div>
1278 +
1279 <div class="modal fade" id="deleteRegistryModal" tabindex="-1" role="dialog" aria-labelledby="deleteRegistryModalLabel">
1280 <div class="modal-dialog" role="document">
1281 <div class="modal-content">
@@ -1275,6 +1365,7 @@
1365 </div>
1366 </div>
1367 </div>
1368 + <iframe id="ssoifrm" width="0" height="0"></iframe>
1369 <div id="hiddenDownloadLinks" style="display: none;" hidden></div>
1370 <script type="text/javascript" src="dashboard.js?v20181211-1"></script>
1371 </body>
web/gui/main.css
+59 -2
@@ -532,7 +532,11 @@ body.modal-open {
532 transition: 0s
533 }
534
535 -/* --- */
535 +/* -------------------------------------------------------------------------- */
536 +
537 +#my-netdata-dropdown-content {
538 + width: 500px;
539 +}
540
541 #my-netdata-dropdown-content a:hover {
542 color: #fff;
@@ -609,9 +613,62 @@ body.modal-open {
613 }
614
615 #my-netdata-dropdown-content.theme-white a {
612 - color: #555;
616 + color: #888;
617 }
618
619 #my-netdata-dropdown-content.theme-white a:hover {
620 color: #000;
621 }
622 +
623 +#sign-in-iframe {
624 + background-color: #fff;
625 + border: none;
626 +}
627 +
628 +#cloud-menu {
629 +}
630 +
631 +#cloud-menu.dropdown-menu > li > a {
632 + text-align: left;
633 +}
634 +
635 +#my-netdata-menu-filter-input {
636 + color: #fff;
637 + /* background: none; */
638 + /* border: 1px solid #616161; */
639 + border: none;
640 + background-color: #4b4f55;
641 + margin: 5px 14px;
642 + width: 90%;
643 + padding: 2px 5px;
644 + outline: none;
645 +}
646 +
647 +#my-netdata-menu-filter-input::placeholder {
648 + opacity: 0.7;
649 +}
650 +
651 +#my-netdata-dropdown-content.theme-white #my-netdata-menu-filter-input {
652 + /* border: 1px solid #ddd; */
653 + background-color: #e7e7e7;
654 + color: #555;
655 +}
656 +
657 +.filter-control {
658 + position: relative;
659 +}
660 +
661 +.filter-control .filter-control__clear {
662 + cursor: pointer;
663 + position: relative;
664 + left: -36px;
665 +}
666 +
667 +/* .collapse a.btn {
668 + text-align: left;
669 +} */
670 +
671 +#hostname {
672 + font-size: 18px;
673 +}
674 +
web/gui/main.js
+730 -77
@@ -453,7 +453,7 @@ function saveObjectToClient(data, filename) {
453 saveTextToClient(JSON.stringify(data), filename);
454 }
455
456 -// --------------------------------------------------------------------
456 +// -----------------------------------------------------------------------------
457 // registry call back to render my-netdata menu
458
459 function toggleExpandIcon(svgEl) {
@@ -476,19 +476,6 @@ function toggleAgentItem(e, guid) {
476 }
477 }
478
479 -// TODO: consider renaming to `truncateString`
480 -
481 -/// Enforces a maximum string length while retaining the prefix and the postfix of
482 -/// the string.
483 -function clipString(str, maxLength) {
484 - if (str.length <= maxLength) {
485 - return str;
486 - }
487 -
488 - const spanLength = Math.floor((maxLength - 3) / 2);
489 - return `${str.substring(0, spanLength)}...${str.substring(str.length - spanLength)}`;
490 -}
491 -
479 // When you stream metrics from netdata to netdata, the recieving netdata now
480 // has multiple host databases. It's own, and multiple mirrored. Mirrored databases
481 // can be accessed with <http://localhost:19999/host/NAME/>
@@ -512,10 +499,20 @@ function renderStreamedHosts(options) {
499 return naturalSortCompare(a.hostname, b.hostname);
500 });
501
502 + let displayedDatabases = false;
503 +
504 for (var s of sorted) {
505 let url, icon;
506 const hostname = s.hostname;
507
508 + if (myNetdataMenuFilterValue !== "") {
509 + if (!hostname.includes(myNetdataMenuFilterValue)) {
510 + continue;
511 + }
512 + }
513 +
514 + displayedDatabases = true;
515 +
516 if (hostname === master) {
517 url = `${base}/`;
518 icon = 'home';
@@ -537,11 +534,24 @@ function renderStreamedHosts(options) {
534 )
535 }
536
537 + if (!displayedDatabases) {
538 + html += (
539 + `<div class="info-item">
540 + <i class="fas fa-filter"></i>
541 + <span style="margin-left: 8px">no databases match the filter criteria.<span>
542 + </div>`
543 + )
544 + }
545 +
546 return html;
547 }
548
549 function renderMachines(machinesArray) {
544 - let html = `<div class="info-item">My netdata agents</div>`;
550 + // let html = isSignedIn()
551 + // ? `<div class="info-item">My nodes</div>`
552 + // : `<div class="info-item">My nodes</div>`;
553 +
554 + let html = `<div class="info-item">My nodes</div>`;
555
556 if (machinesArray === null) {
557 let ret = loadLocalStorage("registryCallback");
@@ -552,6 +562,9 @@ function renderMachines(machinesArray) {
562 }
563
564 let found = false;
565 + let displayedAgents = false;
566 +
567 + const maskedURL = NETDATA.registry.MASKED_DATA;
568
569 if (machinesArray) {
570 saveLocalStorage("registryCallback", JSON.stringify(machinesArray));
@@ -563,18 +576,31 @@ function renderMachines(machinesArray) {
576 for (var machine of machines) {
577 found = true;
578
579 + if (myNetdataMenuFilterValue !== "") {
580 + if (!machine.name.includes(myNetdataMenuFilterValue)) {
581 + continue;
582 + }
583 + }
584 +
585 + displayedAgents = true;
586 +
587 const alternateUrlItems = (
588 `<div class="agent-alternate-urls agent-${machine.guid} collapsed">
568 - ${machine.alternate_urls.reduce(
569 - (str, url) => str + (
570 - `<div class="agent-item agent-item--alternate">
571 - <div></div>
572 - <a href="${url}" title="${url}">${clipString(url, 64)}</a>
573 - <a href="#" onclick="deleteRegistryModalHandler('${machine.guid}', '${machine.name}', '${url}'); return false;">
574 - <i class="fas fa-trash" style="color: #777;"></i>
575 - </a>
576 - </div>`
577 - ),
589 + ${machine.alternate_urls.reduce((str, url) => {
590 + if (url === maskedURL) {
591 + return str
592 + }
593 +
594 + return str + (
595 + `<div class="agent-item agent-item--alternate">
596 + <div></div>
597 + <a href="${url}" title="${url}">${truncateString(url, 64)}</a>
598 + <a href="#" onclick="deleteRegistryModalHandler('${machine.guid}', '${machine.name}', '${url}'); return false;">
599 + <i class="fas fa-trash" style="color: #777;"></i>
600 + </a>
601 + </div>`
602 + )
603 + },
604 ''
605 )}
606 </div>`
@@ -593,13 +619,22 @@ function renderMachines(machinesArray) {
619 ${alternateUrlItems}`
620 )
621 }
622 +
623 + if (found && (!displayedAgents)) {
624 + html += (
625 + `<div class="info-item">
626 + <i class="fas fa-filter"></i>
627 + <span style="margin-left: 8px">zero nodes are matching the filter value.<span>
628 + </div>`
629 + )
630 + }
631 }
632
633 if (!found) {
634 if (machines) {
635 html += (
636 `<div class="info-item">
602 - <a href="https://github.com/netdata/netdata/tree/master/registry#netdata-registry" target="_blank">Your netdata server list is empty</a>
637 + <a href="https://github.com/netdata/netdata/tree/master/registry#netdata-registry" target="_blank">Your nodes list is empty</a>
638 </div>`
639 )
640 } else {
@@ -611,7 +646,7 @@ function renderMachines(machinesArray) {
646 }
647
648 html += `<hr />`;
614 - html += `<div class="info-item">Demo netdata agents</div>`;
649 + html += `<div class="info-item">Demo netdata nodes</div>`;
650
651 const demoServers = [
652 {url: "//london.netdata.rocks/default.html", title: "UK - London (DigitalOcean.com)"},
@@ -628,7 +663,7 @@ function renderMachines(machinesArray) {
663 for (var server of demoServers) {
664 html += (
665 `<div class="agent-item">
631 - <i class="fas fa-chart-bar" color: #fff"></i>
666 + <i class="fas fa-chart-bar" style="color: #fff"></i>
667 <a href="${server.url}">${server.title}</a>
668 <div></div>
669 </div>
@@ -640,36 +675,110 @@ function renderMachines(machinesArray) {
675 return html;
676 }
677
643 -// Populates the my-netdata menu.
644 -function netdataRegistryCallback(machinesArray) {
678 +function setMyNetdataMenu(html) {
679 + const el = document.getElementById('my-netdata-dropdown-content')
680 + el.innerHTML = html;
681 +}
682 +
683 +function clearMyNetdataMenu() {
684 + setMyNetdataMenu(`<div class="agent-item" style="white-space: nowrap">
685 + <i class="fas fa-hourglass-half"></i>
686 + Loading, please wait...
687 + <div></div>
688 + </div>`);
689 +}
690 +
691 +function errorMyNetdataMenu() {
692 + setMyNetdataMenu(`<div class="agent-item" style="white-space: nowrap">
693 + <i class="fas fa-exclamation-triangle" style="color: red"></i>
694 + Cannot load known netdata agents from netdata.cloud!
695 + <div></div>
696 + </div>`);
697 +}
698 +
699 +function restrictMyNetdataMenu() {
700 + setMyNetdataMenu(`<div class="info-item" style="white-space: nowrap">
701 + <span>Please <a href="#" onclick="signInDidClick(event); return false">sign in to netdata.cloud</a> to view your nodes!</span>
702 + <div></div>
703 + </div>`);
704 +}
705 +
706 +function renderMyNetdataMenu(machinesArray) {
707 + const el = document.getElementById('my-netdata-dropdown-content');
708 + el.classList.add(`theme-${netdataTheme}`);
709 +
710 + if (!isSignedIn()) {
711 + if (!NETDATA.registry.isRegistryEnabled()) {
712 + restrictMyNetdataMenu();
713 + return;
714 + }
715 + }
716 +
717 + if (machinesArray == registryAgents) {
718 + console.log("Rendering my-netdata menu from registry");
719 + } else {
720 + console.log("Rendering my-netdata menu from netdata.cloud", machinesArray);
721 + }
722 +
723 let html = '';
724
725 + if (isSignedIn()) {
726 + html += (
727 + `<div class="filter-control">
728 + <input
729 + id="my-netdata-menu-filter-input"
730 + type="text"
731 + placeholder="filter nodes..."
732 + autocomplete="off"
733 + value="${myNetdataMenuFilterValue}"
734 + onkeydown="myNetdataFilterDidChange(event)"
735 + />
736 + <span class="filter-control__clear" onclick="myNetdataFilterClearDidClick(event)"><i class="fas fa-times"></i><span>
737 + </div>
738 + <hr />`
739 + );
740 + }
741 +
742 if (options.hosts.length > 1) {
648 - html += renderStreamedHosts(options) + `<hr />`;
743 + html += `<div id="my-netdata-menu-streamed">${renderStreamedHosts(options)}</div><hr />`;
744 }
745
651 - html += renderMachines(machinesArray);
652 -
653 - html += (
654 - `<hr />
655 - <div class="agent-item">
656 - <i class="fas fa-cog""></i>
657 - <a href="#" onclick="switchRegistryModalHandler(); return false;">Switch Identity</a>
658 - <div></div>
659 - </div>
660 - <div class="agent-item">
661 - <i class="fas fa-question-circle""></i>
662 - <a href="https://github.com/netdata/netdata/tree/master/registry#netdata-registry" target="_blank">What is this?</a>
663 - <div></div>
664 - </div>`
665 - )
746 + html += `<div id="my-netdata-menu-machines">${renderMachines(machinesArray)}</div>`;
747 +
748 + if (!isSignedIn()) {
749 + html += (
750 + `<hr />
751 + <div class="agent-item">
752 + <i class="fas fa-cog""></i>
753 + <a href="#" onclick="switchRegistryModalHandler(); return false;">Switch Identity</a>
754 + <div></div>
755 + </div>
756 + <div class="agent-item">
757 + <i class="fas fa-question-circle""></i>
758 + <a href="https://github.com/netdata/netdata/tree/master/registry#netdata-registry" target="_blank">What is this?</a>
759 + <div></div>
760 + </div>`
761 + )
762 + } else {
763 + html += (
764 + `<hr />
765 + <div class="agent-item">
766 + <i class="fas fa-sync"></i>
767 + <a href="#" onclick="showSyncModal(); return false">Synchronize with netdata.cloud</a>
768 + <div></div>
769 + </div>
770 + <div class="agent-item">
771 + <i class="fas fa-question-circle""></i>
772 + <a href="https://netdata.cloud/about" target="_blank">What is this?</a>
773 + <div></div>
774 + </div>`
775 + )
776 + }
777
667 - const el = document.getElementById('my-netdata-dropdown-content')
668 - el.classList.add(`theme-${netdataTheme}`);
778 el.innerHTML = html;
779
780 gotoServerInit();
672 -};
781 +}
782
783 function isdemo() {
784 if (this_is_demo !== null) {
@@ -764,6 +873,7 @@ function gotoServerValidateUrl(id, guid, url) {
873 } else {
874 document.getElementById('gotoServerResponse').innerHTML += 'found it! It is at:<br/><small>' + escapeUserInputHTML(url) + '</small>';
875 document.location = verifyURL(finalURL);
876 + $('#gotoServerModal').modal('hide');
877 }
878 }
879 } else {
@@ -802,24 +912,29 @@ function gotoServerModalHandler(guid) {
912 gotoServerValidateUrl(count++, guid, url);
913 }
914
805 - setTimeout(function () {
806 - if (gotoServerStop === false) {
807 - document.getElementById('gotoServerResponse').innerHTML = '<b>Added all the known URLs for this machine.</b>';
808 - NETDATA.registry.search(guid, function (data) {
809 - // console.log(data);
810 - len = data.urls.length;
811 - while (len--) {
812 - var url = data.urls[len][1];
813 - // console.log(url);
814 - if (typeof checked[url] === 'undefined') {
815 - gotoServerValidateRemaining++;
816 - checked[url] = true;
817 - gotoServerValidateUrl(count++, guid, url);
915 + if (!isSignedIn()) {
916 + // When the registry is enabled, if the user's known URLs are not working
917 + // we consult the registry to get additional URLs.
918 + setTimeout(function () {
919 + if (gotoServerStop === false) {
920 + document.getElementById('gotoServerResponse').innerHTML = '<b>Added all the known URLs for this machine.</b>';
921 + NETDATA.registry.search(guid, function (data) {
922 + // console.log(data);
923 + len = data.urls.length;
924 + while (len--) {
925 + var url = data.urls[len][1];
926 + // console.log(url);
927 + if (typeof checked[url] === 'undefined') {
928 + gotoServerValidateRemaining++;
929 + checked[url] = true;
930 + gotoServerValidateUrl(count++, guid, url);
931 + }
932 }
819 - }
820 - });
821 - }
822 - }, 2000);
933 + });
934 + }
935 + }, 2000);
936 + }
937 +
938 return false;
939 }
940
@@ -860,30 +975,55 @@ function notifyForSwitchRegistry() {
975 }
976 }
977
978 +var deleteRegistryGuid = null;
979 var deleteRegistryUrl = null;
980
981 function deleteRegistryModalHandler(guid, name, url) {
866 - void (guid);
982 + // void (guid);
983
984 + deleteRegistryGuid = guid;
985 deleteRegistryUrl = url;
986 +
987 document.getElementById('deleteRegistryServerName').innerHTML = name;
988 document.getElementById('deleteRegistryServerName2').innerHTML = name;
989 document.getElementById('deleteRegistryServerURL').innerHTML = url;
990 document.getElementById('deleteRegistryResponse').innerHTML = '';
991 +
992 $('#deleteRegistryModal').modal('show');
993 }
994
995 function notifyForDeleteRegistry() {
996 + const responseEl = document.getElementById('deleteRegistryResponse');
997 +
998 if (deleteRegistryUrl) {
878 - NETDATA.registry.delete(deleteRegistryUrl, function (result) {
879 - if (result !== null) {
880 - deleteRegistryUrl = null;
881 - $('#deleteRegistryModal').modal('hide');
882 - NETDATA.registry.init();
883 - } else {
884 - document.getElementById('deleteRegistryResponse').innerHTML = "<b>Sorry! this command was rejected by the registry server.</b>";
885 - }
886 - });
999 + if (isSignedIn()) {
1000 + deleteCloudAgentURL(deleteRegistryGuid, deleteRegistryUrl)
1001 + .then((count) => {
1002 + if (!count) {
1003 + responseEl.innerHTML = "<b>Sorry, this command was rejected by netdata.cloud!</b>";
1004 + return;
1005 + }
1006 + NETDATA.registry.delete(deleteRegistryUrl, function (result) {
1007 + if (result === null) {
1008 + console.log("Received error from registry", result);
1009 + }
1010 +
1011 + deleteRegistryUrl = null;
1012 + $('#deleteRegistryModal').modal('hide');
1013 + NETDATA.registry.init();
1014 + });
1015 + });
1016 + } else {
1017 + NETDATA.registry.delete(deleteRegistryUrl, function (result) {
1018 + if (result !== null) {
1019 + deleteRegistryUrl = null;
1020 + $('#deleteRegistryModal').modal('hide');
1021 + NETDATA.registry.init();
1022 + } else {
1023 + responseEl.innerHTML = "<b>Sorry, this command was rejected by the registry server!</b>";
1024 + }
1025 + });
1026 + }
1027 }
1028 }
1029
@@ -2563,7 +2703,7 @@ function initializeDynamicDashboardWithData(data) {
2703 }
2704
2705 // update the dashboard hostname
2566 - document.getElementById('hostname').innerHTML = options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString();
2706 + document.getElementById('hostname').innerHTML = options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString() + '&nbsp;&nbsp;<strong class="caret">';
2707 document.getElementById('hostname').href = NETDATA.serverDefault;
2708 document.getElementById('netdataVersion').innerHTML = options.version;
2709
@@ -4344,3 +4484,516 @@ var selected_server_timezone = function (timezone, status) {
4484 // var netdataStarted = performance.now();
4485
4486 var netdataCallback = initializeDynamicDashboard;
4487 +
4488 +// =================================================================================================
4489 +// netdata.cloud
4490 +
4491 +let registryAgents = [];
4492 +
4493 +let cloudAgents = [];
4494 +
4495 +let myNetdataMenuFilterValue = "";
4496 +
4497 +let cloudAccountID = null;
4498 +
4499 +let cloudAccountName = null;
4500 +
4501 +let cloudToken = null;
4502 +
4503 +/// Enforces a maximum string length while retaining the prefix and the postfix of
4504 +/// the string.
4505 +function truncateString(str, maxLength) {
4506 + if (str.length <= maxLength) {
4507 + return str;
4508 + }
4509 +
4510 + const spanLength = Math.floor((maxLength - 3) / 2);
4511 + return `${str.substring(0, spanLength)}...${str.substring(str.length - spanLength)}`;
4512 +}
4513 +
4514 +// -------------------------------------------------------------------------------------------------
4515 +// netdata.cloud API Client
4516 +// -------------------------------------------------------------------------------------------------
4517 +
4518 +function isValidAgent(a) {
4519 + return a.urls != null && a.urls.length > 0;
4520 +}
4521 +
4522 +// https://github.com/netdata/hub/issues/146
4523 +function getCloudAccountAgents() {
4524 + if (!isSignedIn()) {
4525 + return [];
4526 + }
4527 +
4528 + return fetch(
4529 + `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`,
4530 + {
4531 + method: "GET",
4532 + mode: "cors",
4533 + headers: {
4534 + "Authorization": `Bearer ${cloudToken}`
4535 + }
4536 + }
4537 + ).then((response) => {
4538 + if (!response.ok) {
4539 + throw Error("Cannot fetch known accounts");
4540 + }
4541 + return response.json();
4542 + }).then((payload) => {
4543 + const agents = payload.result ? payload.result.agents : null;
4544 +
4545 + if (!agents) {
4546 + return [];
4547 + }
4548 +
4549 + return agents.filter((a) => isValidAgent(a)).map((a) => {
4550 + return {
4551 + "guid": a.id,
4552 + "name": a.name,
4553 + "url": a.urls[0],
4554 + "alternate_urls": a.urls
4555 + }
4556 + })
4557 + }).catch(function (error) {
4558 + console.log(error);
4559 + return null;
4560 + });
4561 +}
4562 +
4563 +// https://github.com/netdata/hub/issues/128
4564 +function postCloudAccountAgents(agentsToSync) {
4565 + if (!isSignedIn()) {
4566 + return [];
4567 + }
4568 +
4569 + const maskedURL = NETDATA.registry.MASKED_DATA;
4570 +
4571 + const agents = agentsToSync.map((a) => {
4572 + const urls = a.alternate_urls.filter((url) => url != maskedURL);
4573 +
4574 + return {
4575 + "id": a.guid,
4576 + "name": a.name,
4577 + "urls": urls
4578 + }
4579 + }).filter((a) => isValidAgent(a))
4580 +
4581 + const payload = {
4582 + "accountID": cloudAccountID,
4583 + "agents": agents,
4584 + "merge": false,
4585 + };
4586 +
4587 + return fetch(
4588 + `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`,
4589 + {
4590 + method: "POST",
4591 + mode: "cors",
4592 + headers: {
4593 + "Content-Type": "application/json; charset=utf-8",
4594 + "Authorization": `Bearer ${cloudToken}`
4595 + },
4596 + body: JSON.stringify(payload)
4597 + }
4598 + ).then((response) => {
4599 + return response.json();
4600 + }).then((payload) => {
4601 + const agents = payload.result ? payload.result.agents : null;
4602 +
4603 + if (!agents) {
4604 + return [];
4605 + }
4606 +
4607 + return agents.filter((a) => isValidAgent(a)).map((a) => {
4608 + return {
4609 + "guid": a.id,
4610 + "name": a.name,
4611 + "url": a.urls[0],
4612 + "alternate_urls": a.urls
4613 + }
4614 + })
4615 + });
4616 +}
4617 +
4618 +function deleteCloudAgentURL(agentID, url) {
4619 + if (!isSignedIn()) {
4620 + return [];
4621 + }
4622 +
4623 + return fetch(
4624 + `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents/${agentID}/url?value=${encodeURIComponent(url)}`,
4625 + {
4626 + method: "DELETE",
4627 + mode: "cors",
4628 + headers: {
4629 + "Content-Type": "application/json; charset=utf-8",
4630 + "Authorization": `Bearer ${cloudToken}`
4631 + },
4632 + }
4633 + ).then((response) => {
4634 + return response.json();
4635 + }).then((payload) => {
4636 + const count = payload.result ? payload.result.count : 0;
4637 + return count;
4638 + });
4639 +}
4640 +
4641 +// -------------------------------------------------------------------------------------------------
4642 +
4643 +function signInDidClick(e) {
4644 + e.preventDefault();
4645 + e.stopPropagation();
4646 +
4647 + if (!NETDATA.registry.isUsingGlobalRegistry()) {
4648 + // If user is using a private registry, request his consent for
4649 + // synchronizing with cloud.
4650 + showSignInModal();
4651 + return;
4652 + }
4653 +
4654 + signIn();
4655 +}
4656 +
4657 +function signOutDidClick(e) {
4658 + e.preventDefault();
4659 + e.stopPropagation();
4660 + signOut();
4661 +}
4662 +
4663 +// -------------------------------------------------------------------------------------------------
4664 +
4665 +function updateMyNetdataAfterFilterChange() {
4666 + const machinesEl = document.getElementById("my-netdata-menu-machines")
4667 + machinesEl.innerHTML = renderMachines(cloudAgents);
4668 +
4669 + if (options.hosts.length > 1) {
4670 + const streamedEl = document.getElementById("my-netdata-menu-streamed")
4671 + streamedEl.innerHTML = renderStreamedHosts(options);
4672 + }
4673 +}
4674 +
4675 +function myNetdataFilterDidChange(e) {
4676 + const inputEl = e.target;
4677 + setTimeout(() => {
4678 + myNetdataMenuFilterValue = inputEl.value;
4679 + updateMyNetdataAfterFilterChange();
4680 + }, 1);
4681 +}
4682 +
4683 +function myNetdataFilterClearDidClick(e) {
4684 + e.preventDefault();
4685 + e.stopPropagation();
4686 +
4687 + const inputEl = document.getElementById("my-netdata-menu-filter-input");
4688 + inputEl.value = "";
4689 + myNetdataMenuFilterValue = "";
4690 +
4691 + updateMyNetdataAfterFilterChange();
4692 +
4693 + inputEl.focus();
4694 +}
4695 +
4696 +// -------------------------------------------------------------------------------------------------
4697 +
4698 +function clearCloudVariables() {
4699 + cloudAccountID = null;
4700 + cloudAccountName = null;
4701 + cloudToken = null;
4702 +}
4703 +
4704 +function clearCloudLocalStorageItems() {
4705 + localStorage.removeItem("cloud.baseURL");
4706 + localStorage.removeItem("cloud.agentID");
4707 + localStorage.removeItem("cloud.sync");
4708 +}
4709 +
4710 +function signIn() {
4711 + const url = `${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?origin=${encodeURIComponent(window.location.origin + "/")}`;
4712 + window.open(url);
4713 +}
4714 +
4715 +function signOut() {
4716 + cloudSSOSignOut();
4717 +}
4718 +
4719 +function renderAccountUI() {
4720 + if (!NETDATA.registry.isCloudEnabled) {
4721 + return
4722 + }
4723 +
4724 + const container = document.getElementById("account-menu-container");
4725 + if (isSignedIn()) {
4726 + container.removeAttribute("title");
4727 + container.removeAttribute("data-original-title");
4728 + container.removeAttribute("data-placement");
4729 + container.innerHTML = (
4730 + `<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span id="amc-account-name"></span> <strong class="caret"></strong></a>
4731 + <ul id="cloud-menu" class="dropdown-menu scrollable-menu inpagemenu" role="menu">
4732 + <li>
4733 + <a href="#" class="btn" onclick="signOutDidClick(event); return false">
4734 + <i class="fas fa-sign-out-alt"></i>&nbsp;&nbsp;<span class="hidden-sm hidden-md">Sign Out</span>
4735 + </a>
4736 + </li>
4737 + </ul>`
4738 + )
4739 + document.getElementById("amc-account-name").textContent = cloudAccountName; // Anti-XSS
4740 + } else {
4741 + container.setAttribute("data-original-title", "sign in");
4742 + container.setAttribute("data-placement", "bottom");
4743 + container.innerHTML = (
4744 + `<a href="#" class="btn" onclick="signInDidClick(event); return false">
4745 + <i class="fas fa-sign-in-alt"></i>&nbsp;<span class="hidden-sm hidden-md">Sign In</span>
4746 + </a>`
4747 + )
4748 + }
4749 +}
4750 +
4751 +function handleMessage(e) {
4752 + switch (e.data.type) {
4753 + case "sign-in":
4754 + handleSignInMessage(e);
4755 + break;
4756 +
4757 + case "sign-out":
4758 + handleSignOutMessage(e);
4759 + break;
4760 +
4761 + default:
4762 + console.log("Unknown message", e);
4763 + }
4764 +}
4765 +
4766 +function handleSignInMessage(e) {
4767 + localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL);
4768 +
4769 + cloudAccountID = e.data.accountID;
4770 + cloudAccountName = e.data.accountName;
4771 + cloudToken = e.data.token;
4772 +
4773 + netdataRegistryCallback(registryAgents);
4774 +}
4775 +
4776 +function handleSignOutMessage(e) {
4777 + clearCloudVariables();
4778 + renderAccountUI();
4779 + renderMyNetdataMenu(registryAgents);
4780 +}
4781 +
4782 +function isSignedIn() {
4783 + return cloudToken != null && cloudAccountID != null;
4784 +}
4785 +
4786 +function sortedArraysEqual(a, b) {
4787 + if (a.length != b.length) return false;
4788 +
4789 + for (var i = 0; i < a.length; ++i) {
4790 + if (a[i] !== b[i]) return false;
4791 + }
4792 +
4793 + return true;
4794 +}
4795 +
4796 +// If merging is needed returns the merged agents set, otherwise returns null.
4797 +function mergeAgents(cloud, local) {
4798 + let dirty = false;
4799 +
4800 + const union = new Map();
4801 +
4802 + for (const cagent of cloud) {
4803 + union.set(cagent.guid, cagent);
4804 + }
4805 +
4806 + for (const lagent of local) {
4807 + const cagent = union.get(lagent.guid);
4808 + if (cagent) {
4809 + for (const u of lagent.alternate_urls) {
4810 + if (u === NETDATA.registry.MASKED_DATA) { // TODO: temp until registry is updated.
4811 + continue;
4812 + }
4813 +
4814 + if (!cagent.alternate_urls.includes(u)) {
4815 + dirty = true;
4816 + cagent.alternate_urls.push(u);
4817 + }
4818 + }
4819 + } else {
4820 + dirty = true;
4821 + union.set(lagent.guid, lagent);
4822 + }
4823 + }
4824 +
4825 + if (dirty) {
4826 + return Array.from(union.values());
4827 + }
4828 +
4829 + return null;
4830 +}
4831 +
4832 +function showSignInModal() {
4833 + document.getElementById("sim-registry").innerHTML = NETDATA.registry.server;
4834 + $("#signInModal").modal("show");
4835 +}
4836 +
4837 +function explicitlySignIn() {
4838 + $("#signInModal").modal("hide");
4839 + signIn();
4840 +}
4841 +
4842 +function showSyncModal() {
4843 + document.getElementById("sync-registry-modal-registry").innerHTML = NETDATA.registry.server;
4844 + $("#syncRegistryModal").modal("show");
4845 +}
4846 +
4847 +function explicitlySyncAgents() {
4848 + $("#syncRegistryModal").modal("hide");
4849 +
4850 + const json = localStorage.getItem("cloud.sync");
4851 + const sync = json ? JSON.parse(json): {};
4852 + delete sync[cloudAccountID];
4853 + localStorage.setItem("cloud.sync", JSON.stringify(sync));
4854 +
4855 + NETDATA.registry.init();
4856 +}
4857 +
4858 +function syncAgents(callback) {
4859 + const json = localStorage.getItem("cloud.sync");
4860 + const sync = json ? JSON.parse(json): {};
4861 +
4862 + const currentAgent = {
4863 + guid: NETDATA.registry.machine_guid,
4864 + name: NETDATA.registry.hostname,
4865 + url: NETDATA.serverDefault,
4866 + alternate_urls: [NETDATA.serverDefault],
4867 + }
4868 +
4869 + const localAgents = sync[cloudAccountID]
4870 + ? [currentAgent]
4871 + : registryAgents.concat([currentAgent]);
4872 +
4873 + console.log("Checking if sync is needed.", localAgents);
4874 +
4875 + const agentsToSync = mergeAgents(cloudAgents, localAgents);
4876 +
4877 + if ((!sync[cloudAccountID]) || agentsToSync) {
4878 + sync[cloudAccountID] = new Date().getTime();
4879 + localStorage.setItem("cloud.sync", JSON.stringify(sync));
4880 + }
4881 +
4882 + if (agentsToSync) {
4883 + console.log("Synchronizing with netdata.cloud.");
4884 +
4885 + postCloudAccountAgents(agentsToSync).then((agents) => {
4886 + // TODO: clear syncTime on error!
4887 + cloudAgents = agents;
4888 + callback(cloudAgents);
4889 + });
4890 +
4891 + return
4892 + }
4893 +
4894 + callback(cloudAgents);
4895 +}
4896 +
4897 +let isCloudSSOInitialized = false;
4898 +
4899 +function cloudSSOInit() {
4900 + const iframeEl = document.getElementById("ssoifrm");
4901 + const url = `${NETDATA.registry.cloudBaseURL}/account/sso-agent?id=${NETDATA.registry.machine_guid}`;
4902 + iframeEl.src = url;
4903 + isCloudSSOInitialized = true;
4904 +}
4905 +
4906 +function cloudSSOSignOut() {
4907 + const iframe = document.getElementById("ssoifrm");
4908 + const url = `${NETDATA.registry.cloudBaseURL}/account/sign-out-agent`;
4909 + iframe.src = url;
4910 +}
4911 +
4912 +function initCloud() {
4913 + if (!NETDATA.registry.isCloudEnabled) {
4914 + clearCloudVariables();
4915 + clearCloudLocalStorageItems();
4916 + return;
4917 + }
4918 +
4919 + if (NETDATA.registry.cloudBaseURL != localStorage.getItem("cloud.baseURL")) {
4920 + clearCloudVariables();
4921 + clearCloudLocalStorageItems();
4922 + if (NETDATA.registry.cloudBaseURL) {
4923 + localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL);
4924 + }
4925 + }
4926 +
4927 + if (!isCloudSSOInitialized) {
4928 + cloudSSOInit();
4929 + }
4930 +
4931 + renderAccountUI();
4932 +}
4933 +
4934 +// This callback is called after NETDATA.registry is initialized.
4935 +function netdataRegistryCallback(machinesArray) {
4936 + localStorage.setItem("cloud.agentID", NETDATA.registry.machine_guid);
4937 +
4938 + initCloud();
4939 +
4940 + registryAgents = machinesArray;
4941 +
4942 + if (isSignedIn()) {
4943 + // We call getCloudAccountAgents() here because it requires that
4944 + // NETDATA.registry is initialized.
4945 + clearMyNetdataMenu();
4946 + getCloudAccountAgents().then((agents) => {
4947 + if (!agents) {
4948 + errorMyNetdataMenu();
4949 + return;
4950 + }
4951 + cloudAgents = agents;
4952 + syncAgents((agents) => {
4953 + const agentsMap = {}
4954 + for (const agent of agents) {
4955 + agentsMap[agent.guid] = agent;
4956 + }
4957 +
4958 + NETDATA.registry.machines = agentsMap;
4959 + NETDATA.registry.machines_array = agents;
4960 +
4961 + renderMyNetdataMenu(agents);
4962 + });
4963 + });
4964 + } else {
4965 + renderMyNetdataMenu(machinesArray)
4966 + }
4967 +};
4968 +
4969 +// If we know the cloudBaseURL and agentID from local storage render (eagerly)
4970 +// the account ui before receiving the definitive response from the web server.
4971 +// This improves the perceived performance.
4972 +function tryFastInitCloud() {
4973 + const baseURL = localStorage.getItem("cloud.baseURL");
4974 + const agentID = localStorage.getItem("cloud.agentID");
4975 +
4976 + if (baseURL && agentID) {
4977 + NETDATA.registry.cloudBaseURL = baseURL;
4978 + NETDATA.registry.machine_guid = agentID;
4979 + NETDATA.registry.isCloudEnabled = true;
4980 +
4981 + initCloud();
4982 + }
4983 +}
4984 +
4985 +function initializeApp() {
4986 + window.addEventListener("message", handleMessage, false);
4987 +
4988 +// tryFastInitCloud();
4989 +}
4990 +
4991 +if (document.readyState === "complete") {
4992 + initializeApp();
4993 +} else {
4994 + document.addEventListener("readystatechange", () => {
4995 + if (document.readyState === "complete") {
4996 + initializeApp();
4997 + }
4998 + })
4999 +}
web/gui/src/dashboard.js/options.js
+1 -1
@@ -38,7 +38,7 @@ if (typeof netdataShowAlarms === 'undefined') {
38 }
39
40 if (typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0) {
41 - netdataRegistryAfterMs = 1500;
41 + netdataRegistryAfterMs = 0; // 1500;
42 }
43
44 if (typeof netdataRegistry === 'undefined') {
web/gui/src/dashboard.js/registry.js
+38 -9
@@ -3,6 +3,7 @@
3
4 NETDATA.registry = {
5 server: null, // the netdata registry server
6 + isCloudEnabled: false,// is netdata.cloud functionality enabled?
7 cloudBaseURL: null, // the netdata cloud base url
8 person_guid: null, // the unique ID of this browser / user
9 machine_guid: null, // the unique ID the netdata server that served dashboard.js
@@ -11,8 +12,17 @@ NETDATA.registry = {
12 machines_array: null, // the user's other URLs in an array
13 person_urls: null,
14
15 + MASKED_DATA: "***",
16 +
17 + isUsingGlobalRegistry: function() {
18 + return NETDATA.registry.server == "https://registry.my-netdata.io";
19 + },
20 +
21 + isRegistryEnabled: function() {
22 + return !(NETDATA.registry.isUsingGlobalRegistry() || isSignedIn())
23 + },
24 +
25 parsePersonUrls: function (person_urls) {
15 - // console.log(person_urls);
26 NETDATA.registry.person_urls = person_urls;
27
28 if (person_urls) {
@@ -65,14 +75,21 @@ NETDATA.registry = {
75 NETDATA.registry.hello(NETDATA.serverDefault, function (data) {
76 if (data) {
77 NETDATA.registry.server = data.registry;
68 - NETDATA.registry.cloudBaseURL = data.cloud_base_url;
78 + if (data.cloud_base_url != "") {
79 + NETDATA.registry.isCloudEnabled = true;
80 + NETDATA.registry.cloudBaseURL = data.cloud_base_url;
81 + } else {
82 + NETDATA.registry.isCloudEnabled = false;
83 + NETDATA.registry.cloudBaseURL = "";
84 + }
85 NETDATA.registry.machine_guid = data.machine_guid;
86 NETDATA.registry.hostname = data.hostname;
71 - if (data.anonymous_statistics) dataLayer.push({"anonymous_statistics" : "true", "machine_guid" : data.machine_guid});
87 + if (dataLayer) {
88 + if (data.anonymous_statistics) dataLayer.push({"anonymous_statistics" : "true", "machine_guid" : data.machine_guid});
89 + }
90 NETDATA.registry.access(2, function (person_urls) {
91 NETDATA.registry.parsePersonUrls(person_urls);
74 -
75 - });
92 + });
93 }
94 });
95 },
@@ -115,13 +132,25 @@ NETDATA.registry = {
132 },
133
134 access: function (max_redirects, callback) {
135 + let name = NETDATA.registry.MASKED_DATA;
136 + let url = NETDATA.registry.MASKED_DATA;
137 +
138 + if (!NETDATA.registry.isUsingGlobalRegistry()) {
139 + // If the user is using a private registry keep sending identifiable
140 + // data.
141 + name = NETDATA.registry.hostname;
142 + url = NETDATA.serverDefault;
143 + }
144 +
145 + console.log("ACCESS", name, url);
146 +
147 // send ACCESS to a netdata registry:
148 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
149 // 2. it responds with a list of netdata servers we know
150 // the registry identifies us using a cookie it sets the first time we access it
151 // the registry may respond with a redirect URL to send us to another registry
152 $.ajax({
124 - url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
153 + url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(name) + '&url=' + encodeURIComponent(url), // + '&visible_url=' + encodeURIComponent(document.location),
154 async: true,
155 cache: false,
156 headers: {
@@ -153,14 +182,14 @@ NETDATA.registry = {
182 return callback(null);
183 }
184 }
156 - }
157 - else {
185 + } else {
186 if (typeof data.person_guid === 'string') {
187 NETDATA.registry.person_guid = data.person_guid;
188 }
189
190 if (typeof callback === 'function') {
163 - return callback(data.urls);
191 + const urls = data.urls.filter((u) => u[1] !== NETDATA.registry.MASKED_DATA);
192 + return callback(urls);
193 }
194 }
195 })