Merge pull request #3778 from jjm2473/patch-4
[project/luci.git] / applications / luci-app-statistics / htdocs / luci-static / resources / view / statistics / graphs.js
1 'use strict';
2 'require view';
3 'require dom';
4 'require poll';
5 'require ui';
6 'require uci';
7 'require statistics.rrdtool as rrdtool';
8
9 var pollFn = null,
10 activePlugin = null,
11 activeInstance = null;
12
13 return view.extend({
14 load: function() {
15 return rrdtool.load();
16 },
17
18 updatePluginTab: function(host, span, time, ev) {
19 var container = ev.target,
20 width = Math.max(200, container.offsetWidth - 100),
21 plugin = ev.detail.tab,
22 render_instances = [],
23 plugin_instances = rrdtool.pluginInstances(host.value, plugin),
24 cache = {};
25
26 activePlugin = plugin;
27
28 dom.content(container, [
29 E('p', {}, [
30 E('em', { 'class': 'spinning' }, [ _('Loading data…') ])
31 ])
32 ]);
33
34 for (var i = 0; i < plugin_instances.length; i++)
35 if (rrdtool.hasInstanceDetails(host.value, plugin, plugin_instances[i]))
36 render_instances.push(plugin_instances[i]);
37
38 if (render_instances.length == 0 || render_instances.length > 1)
39 render_instances.unshift('-');
40
41 Promise.all(render_instances.map(function(instance) {
42 if (instance == '-') {
43 var tasks = [];
44
45 for (var i = 0; i < plugin_instances.length; i++)
46 tasks.push(rrdtool.render(plugin, plugin_instances[i], true, host.value, span.value, width, null, cache));
47
48 return Promise.all(tasks).then(function(blobs) {
49 return Array.prototype.concat.apply([], blobs);
50 });
51 }
52 else {
53 return rrdtool.render(plugin, instance, false, host.value, span.value, width, null, cache);
54 }
55 })).then(function(blobs) {
56 var multiple = blobs.length > 1;
57
58 dom.content(container, E('div', {}, blobs.map(function(blobs, i) {
59 var plugin_instance = i ? plugin_instances[i-1] : plugin_instances.join('|'),
60 title = '%s: %s'.format(rrdtool.pluginTitle(plugin), i ? plugin_instance : _('Overview'));
61
62 return E('div', {
63 'class': 'center',
64 'data-tab': multiple ? i : null,
65 'data-tab-title': multiple ? title : null,
66 'data-plugin': plugin,
67 'data-plugin-instance': plugin_instance,
68 'data-is-index': i ? null : true,
69 'cbi-tab-active': function(ev) { activeInstance = ev.target.getAttribute('data-plugin-instance') }
70 }, blobs.map(function(blob) {
71 return E('img', {
72 'src': URL.createObjectURL(new Blob([blob], { type: 'image/png' }))
73 });
74 }));
75 })));
76
77 if (multiple)
78 ui.tabs.initTabGroup(container.lastElementChild.childNodes);
79 else
80 activeInstance = plugin_instances.join('|');
81 });
82 },
83
84 updateGraphs: function(host, span, time, container, ev) {
85 var plugin_names = rrdtool.pluginNames(host.value);
86
87 container.querySelectorAll('img').forEach(function(img) {
88 URL.revokeObjectURL(img.src);
89 });
90
91 dom.content(container, null);
92
93 if (container.hasAttribute('data-initialized')) {
94 container.removeAttribute('data-initialized');
95 container.parentNode.removeChild(container.previousElementSibling);
96 }
97
98 for (var i = 0; i < plugin_names.length; i++) {
99 if (!rrdtool.hasDefinition(plugin_names[i]))
100 continue;
101
102 container.appendChild(E('div', {
103 'data-tab': plugin_names[i],
104 'data-tab-title': rrdtool.pluginTitle(plugin_names[i]),
105 'cbi-tab-active': L.bind(this.updatePluginTab, this, host, span, time)
106 }, [
107 E('p', {}, [
108 E('em', { 'class': 'spinning' }, [ _('Loading data…') ])
109 ])
110 ]));
111 }
112
113 ui.tabs.initTabGroup(container.childNodes);
114 },
115
116 refreshGraphs: function(host, span, time, container) {
117 var div = document.querySelector('[data-plugin="%s"][data-plugin-instance="%s"]'.format(activePlugin, activeInstance || '')),
118 width = Math.max(200, container.offsetWidth - 100),
119 render_instances = activeInstance.split(/\|/);
120
121 return Promise.all(render_instances.map(function(render_instance) {
122 return rrdtool.render(activePlugin, render_instance || '', div.hasAttribute('data-is-index'), host.value, span.value, width);
123 })).then(function(blobs) {
124 return Array.prototype.concat.apply([], blobs);
125 }).then(function(blobs) {
126 return Promise.all(blobs.map(function(blob) {
127 return new Promise(function(resolveFn, rejectFn) {
128 var img = E('img', { 'src': URL.createObjectURL(new Blob([blob], { type: 'image/png' })) });
129 img.onload = function(ev) { resolveFn(img) };
130 img.onerror = function(ev) { resolveFn(img) };
131 });
132 })).then(function(imgs) {
133 while (div.childNodes.length > imgs.length)
134 div.removeChild(div.lastElementChild);
135
136 for (var i = 0; i < imgs.length; i++) {
137 if (i < div.childNodes.length) {
138 URL.revokeObjectURL(div.childNodes[i].src);
139 div.childNodes[i].src = imgs[i].src;
140 }
141 else {
142 div.appendChild(E('img', { 'src': imgs[i].src }));
143 }
144 }
145 });
146 });
147 },
148
149 togglePolling: function(host, span, time, container, ev) {
150 var btn = ev.currentTarget;
151
152 if (pollFn) {
153 poll.remove(pollFn);
154 pollFn = null;
155 }
156
157 if (time.value != '0') {
158 pollFn = L.bind(this.refreshGraphs, this, host, span, time, container);
159 poll.add(pollFn, +time.value);
160 }
161 },
162
163 render: function() {
164 var hosts = rrdtool.hostInstances();
165 return hosts.length ? this.renderGraphs() : this.renderNoData();
166 },
167
168 renderNoData: function() {
169 ui.showModal(_('No RRD data found'), [
170 E('p', {}, _('There is no RRD data available yet to render graphs.')),
171 E('p', {}, _('You need to configure <em>collectd</em> to gather data into <em>.rrd</em> files.')),
172 E('div', { 'class': 'right' }, [
173 E('button', {
174 'class': 'cbi-button',
175 'click': function(ev) { location.href = 'collectd' }
176 }, [ _('Setup collectd') ])
177 ])
178 ]);
179 },
180
181 renderGraphs: function() {
182 var hostSel = E('select', { 'style': 'max-width:170px', 'data-name': 'host' }, rrdtool.hostInstances().map(function(host) {
183 return E('option', {
184 'selected': (rrdtool.opts.host == host) ? 'selected' : null
185 }, [ host ])
186 }));
187
188 var spanSel = E('select', { 'style': 'max-width:170px', 'data-name': 'timespan' }, L.toArray(uci.get('luci_statistics', 'collectd_rrdtool', 'RRATimespans')).map(function(span) {
189 return E('option', {
190 'selected': (rrdtool.opts.timespan == span) ? 'selected' : null
191 }, [ span ])
192 }));
193
194 var timeSel = E('select', { 'style': 'max-width:170px', 'data-name': 'refresh' }, [
195 E('option', { 'value': 0 }, [ _('Do not refresh') ]),
196 E('option', { 'value': 5 }, [ _('Every 5 seconds') ]),
197 E('option', { 'value': 30 }, [ _('Every 30 seconds') ]),
198 E('option', { 'value': 60 }, [ _('Every minute') ])
199 ]);
200
201 var graphDiv = E('div', { 'data-name': 'graphs' });
202
203 var view = E([], [
204 E('h2', {}, [ _('Statistics') ]),
205 E('div', {}, [
206 E('p', { 'class': 'controls' }, [
207 E('span', { 'class': 'nowrap' }, [
208 hostSel,
209 E('button', {
210 'class': 'cbi-button cbi-button-apply',
211 'click': ui.createHandlerFn(this, 'updateGraphs', hostSel, spanSel, timeSel, graphDiv, )
212 }, [ _('Display Host »') ]),
213 ]),
214 ' ',
215 E('span', { 'class': 'nowrap' }, [
216 spanSel,
217 E('button', {
218 'class': 'cbi-button cbi-button-apply',
219 'click': ui.createHandlerFn(this, 'updateGraphs', hostSel, spanSel, timeSel, graphDiv)
220 }, [ _('Display timespan »') ]),
221 ]),
222 ' ',
223 E('span', { 'class': 'nowrap' }, [
224 timeSel,
225 E('button', {
226 'class': 'cbi-button cbi-button-apply',
227 'click': ui.createHandlerFn(this, 'togglePolling', hostSel, spanSel, timeSel, graphDiv)
228 }, [ _('Apply interval »') ])
229 ])
230 ]),
231 E('hr'),
232 graphDiv
233 ])
234 ]);
235
236 requestAnimationFrame(L.bind(this.updateGraphs, this, hostSel, spanSel, timeSel, graphDiv));
237
238 return view;
239 },
240
241 handleSave: null,
242 handleSaveApply: null,
243 handleReset: null
244 });