mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: fix graph reset (#1788)
* feat: add query history * fix: change detect keyUp for nav query history * feat: set default query history * feat: change graph legend * update dependencies * update codemirror version * fix: correct update period time after zoom/pan * fix: optimize data processing for the graph * fix: eliminate memory leaks related to mouse events * fix: correct display of straight line * Merge branch 'master' into vmui-fix-reset-graph * app/vmselect: `make vmui-update` Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
34b5414ba8
commit
6d1d558c4f
23 changed files with 336 additions and 242 deletions
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.13758bae.chunk.css",
|
||||
"main.js": "./static/js/main.8ee3b57f.chunk.js",
|
||||
"runtime-main.js": "./static/js/runtime-main.1f86c8c6.js",
|
||||
"main.css": "./static/css/main.23671ff2.chunk.css",
|
||||
"main.js": "./static/js/main.3039190d.chunk.js",
|
||||
"runtime-main.js": "./static/js/runtime-main.3e786f5f.js",
|
||||
"static/css/2.81b2a0ac.chunk.css": "./static/css/2.81b2a0ac.chunk.css",
|
||||
"static/js/2.a3c6b0f1.chunk.js": "./static/js/2.a3c6b0f1.chunk.js",
|
||||
"static/js/3.4a272e5c.chunk.js": "./static/js/3.4a272e5c.chunk.js",
|
||||
"static/js/2.a835ef7b.chunk.js": "./static/js/2.a835ef7b.chunk.js",
|
||||
"static/js/3.b7e923a6.chunk.js": "./static/js/3.b7e923a6.chunk.js",
|
||||
"index.html": "./index.html",
|
||||
"static/js/2.a3c6b0f1.chunk.js.LICENSE.txt": "./static/js/2.a3c6b0f1.chunk.js.LICENSE.txt"
|
||||
"static/js/2.a835ef7b.chunk.js.LICENSE.txt": "./static/js/2.a835ef7b.chunk.js.LICENSE.txt"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.1f86c8c6.js",
|
||||
"static/js/runtime-main.3e786f5f.js",
|
||||
"static/css/2.81b2a0ac.chunk.css",
|
||||
"static/js/2.a3c6b0f1.chunk.js",
|
||||
"static/css/main.13758bae.chunk.css",
|
||||
"static/js/main.8ee3b57f.chunk.js"
|
||||
"static/js/2.a835ef7b.chunk.js",
|
||||
"static/css/main.23671ff2.chunk.css",
|
||||
"static/js/main.3039190d.chunk.js"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="./static/css/2.81b2a0ac.chunk.css" rel="stylesheet"><link href="./static/css/main.13758bae.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"4a272e5c"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="./static/js/2.a3c6b0f1.chunk.js"></script><script src="./static/js/main.8ee3b57f.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="./static/css/2.81b2a0ac.chunk.css" rel="stylesheet"><link href="./static/css/main.23671ff2.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"b7e923a6"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="./static/js/2.a835ef7b.chunk.js"></script><script src="./static/js/main.3039190d.chunk.js"></script></body></html>
|
|
@ -1 +0,0 @@
|
|||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.MuiAccordionSummary-content{margin:10px 0!important}.cm-activeLine{background-color:inherit!important}.cm-editor{border-radius:4px;border:1px solid #b9b9b9;font-size:10px}.one-line-scroll .cm-editor{height:24px}.cm-gutters{border-radius:4px 0 0 4px;height:100%}.multi-line-scroll .cm-content,.multi-line-scroll .cm-gutters{min-height:51px!important}.one-line-scroll .cm-content,.one-line-scroll .cm-gutters{min-height:auto}.uplot .u-legend{display:grid;align-items:center;justify-content:start;text-align:left;margin-top:25px}.uplot .u-legend .u-series{font-size:12px}.u-tooltip{position:absolute;display:none;grid-gap:12px;max-width:300px;padding:8px;border-radius:4px;background:rgba(57,57,57,.9);color:#fff;font-size:10px;line-height:1.4em;font-weight:500;word-wrap:break-word;font-family:monospace;pointer-events:none;z-index:100}.u-tooltip-data{display:flex;flex-wrap:wrap;align-items:center;font-size:11px}.u-tooltip__info{display:grid;grid-gap:4px}.u-tooltip__marker{width:12px;height:12px;margin-right:4px}
|
1
app/vmselect/vmui/static/css/main.23671ff2.chunk.css
Normal file
1
app/vmselect/vmui/static/css/main.23671ff2.chunk.css
Normal file
|
@ -0,0 +1 @@
|
|||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.MuiAccordionSummary-content{margin:10px 0!important}.cm-activeLine{background-color:inherit!important}.cm-editor{border-radius:4px;border:1px solid #b9b9b9;font-size:10px}.one-line-scroll .cm-editor{height:24px}.cm-gutters{border-radius:4px 0 0 4px;height:100%}.multi-line-scroll .cm-content,.multi-line-scroll .cm-gutters{min-height:51px!important}.one-line-scroll .cm-content,.one-line-scroll .cm-gutters{min-height:auto}.u-tooltip{position:absolute;display:none;grid-gap:12px;max-width:300px;padding:8px;border-radius:4px;background:rgba(57,57,57,.9);color:#fff;font-size:10px;line-height:1.4em;font-weight:500;word-wrap:break-word;font-family:monospace;pointer-events:none;z-index:100}.u-tooltip-data{display:flex;flex-wrap:wrap;align-items:center;font-size:11px;line-height:150%}.u-tooltip-data__value{padding:4px;font-weight:700}.u-tooltip__info{display:grid;grid-gap:4px}.u-tooltip__marker{width:12px;height:12px;margin-right:4px}.legendWrapper{margin-top:20px}.legendItem{display:inline-grid;grid-template-columns:auto auto;grid-gap:4px;align-items:center;justify-content:start;padding:5px 10px;background-color:#fff;cursor:pointer;transition:.2s ease}.legendItemHide{text-decoration:line-through;opacity:.5}.legendItem:hover{background-color:rgba(0,0,0,.1)}.legendMarker{width:12px;height:12px;border-width:2px;border-style:solid;box-sizing:border-box;transition:.2s ease}.legendLabel{font-size:12px;font-weight:600}
|
File diff suppressed because one or more lines are too long
2
app/vmselect/vmui/static/js/2.a835ef7b.chunk.js
Normal file
2
app/vmselect/vmui/static/js/2.a835ef7b.chunk.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
(this.webpackJsonpvmui=this.webpackJsonpvmui||[]).push([[3],{289:function(e,t,n){"use strict";n.r(t),n.d(t,"getCLS",(function(){return y})),n.d(t,"getFCP",(function(){return g})),n.d(t,"getFID",(function(){return C})),n.d(t,"getLCP",(function(){return k})),n.d(t,"getTTFB",(function(){return D}));var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(n=m(e,r,t),o&&a(o),s((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),f((function(){p.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&f((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&s((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},P={},k=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e)),n()},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){P[r.id]||(o.takeRecords().map(a),o.disconnect(),P[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),f(v,!0),s((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,P[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("pageshow",t)}}}]);
|
||||
(this.webpackJsonpvmui=this.webpackJsonpvmui||[]).push([[3],{288:function(e,t,n){"use strict";n.r(t),n.d(t,"getCLS",(function(){return y})),n.d(t,"getFCP",(function(){return g})),n.d(t,"getFID",(function(){return C})),n.d(t,"getLCP",(function(){return k})),n.d(t,"getTTFB",(function(){return D}));var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(n=m(e,r,t),o&&a(o),s((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),f((function(){p.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&f((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&s((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},P={},k=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e)),n()},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){P[r.id]||(o.takeRecords().map(a),o.disconnect(),P[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),f(v,!0),s((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,P[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("pageshow",t)}}}]);
|
1
app/vmselect/vmui/static/js/main.3039190d.chunk.js
Normal file
1
app/vmselect/vmui/static/js/main.3039190d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"4a272e5c"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
||||
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"b7e923a6"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
35
app/vmui/packages/vmui/package-lock.json
generated
35
app/vmui/packages/vmui/package-lock.json
generated
|
@ -14,7 +14,7 @@
|
|||
"@codemirror/highlight": "^0.19.6",
|
||||
"@codemirror/state": "^0.19.2",
|
||||
"@codemirror/view": "^0.19.9",
|
||||
"@date-io/dayjs": "^2.11.0",
|
||||
"@date-io/dayjs": "^1.3.13",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
|
@ -2129,24 +2129,19 @@
|
|||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||
},
|
||||
"node_modules/@date-io/core": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.11.0.tgz",
|
||||
"integrity": "sha512-DvPBnNoeuLaoSJZaxgpu54qzRhRKjSYVyQjhznTFrllKuDpm0sDFjHo6lvNLCM/cfMx2gb2PM2zY2kc9C8nmuw=="
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz",
|
||||
"integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA=="
|
||||
},
|
||||
"node_modules/@date-io/dayjs": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.11.0.tgz",
|
||||
"integrity": "sha512-w67vRK56NZJIKhJM/CrNbfnIcuMvR3ApfxzNZiCZ5w29sxgBDeKuX4M+P7A9r5HXOMGcsOcpgaoTDINNGkdpGQ==",
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-1.3.13.tgz",
|
||||
"integrity": "sha512-nD39xWYwQjDMIdpUzHIcADHxY9m1hm1DpOaRn3bc2rBdgmwQC0PfW0WYaHyGGP/6LEzEguINRbHuotMhf+T9Sg==",
|
||||
"dependencies": {
|
||||
"@date-io/core": "^2.11.0"
|
||||
"@date-io/core": "^1.3.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"dayjs": "^1.8.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"dayjs": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
|
@ -23519,16 +23514,16 @@
|
|||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||
},
|
||||
"@date-io/core": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.11.0.tgz",
|
||||
"integrity": "sha512-DvPBnNoeuLaoSJZaxgpu54qzRhRKjSYVyQjhznTFrllKuDpm0sDFjHo6lvNLCM/cfMx2gb2PM2zY2kc9C8nmuw=="
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz",
|
||||
"integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA=="
|
||||
},
|
||||
"@date-io/dayjs": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.11.0.tgz",
|
||||
"integrity": "sha512-w67vRK56NZJIKhJM/CrNbfnIcuMvR3ApfxzNZiCZ5w29sxgBDeKuX4M+P7A9r5HXOMGcsOcpgaoTDINNGkdpGQ==",
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-1.3.13.tgz",
|
||||
"integrity": "sha512-nD39xWYwQjDMIdpUzHIcADHxY9m1hm1DpOaRn3bc2rBdgmwQC0PfW0WYaHyGGP/6LEzEguINRbHuotMhf+T9Sg==",
|
||||
"requires": {
|
||||
"@date-io/core": "^2.11.0"
|
||||
"@date-io/core": "^1.3.13"
|
||||
}
|
||||
},
|
||||
"@emotion/hash": {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"@codemirror/highlight": "^0.19.6",
|
||||
"@codemirror/state": "^0.19.2",
|
||||
"@codemirror/view": "^0.19.9",
|
||||
"@date-io/dayjs": "^2.11.0",
|
||||
"@date-io/dayjs": "^1.3.13",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
|
|
|
@ -47,8 +47,8 @@ const QueryConfigurator: FC = () => {
|
|||
|
||||
const onRunQuery = () => {
|
||||
const { values } = queryHistory;
|
||||
if (query === values[values.length - 1]) return;
|
||||
dispatch({type: "RUN_QUERY"});
|
||||
if (query === values[values.length - 1]) return;
|
||||
dispatch({type: "SET_QUERY_HISTORY_INDEX", payload: values.length});
|
||||
dispatch({type: "SET_QUERY_HISTORY_VALUES", payload: [...values, query]});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import {InstantMetricResult, MetricResult} from "../../../api/types";
|
|||
import {saveToStorage} from "../../../utils/storage";
|
||||
import {isValidHttpUrl} from "../../../utils/url";
|
||||
import {useAuthState} from "../../../state/auth/AuthStateContext";
|
||||
import {TimeParams} from "../../../types";
|
||||
|
||||
export const useFetchQuery = (): {
|
||||
fetchUrl?: string,
|
||||
|
@ -21,6 +22,7 @@ export const useFetchQuery = (): {
|
|||
const [graphData, setGraphData] = useState<MetricResult[]>();
|
||||
const [liveData, setLiveData] = useState<InstantMetricResult[]>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [prevPeriod, setPrevPeriod] = useState<TimeParams>();
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
|
@ -29,62 +31,78 @@ export const useFetchQuery = (): {
|
|||
}
|
||||
}, [error]);
|
||||
|
||||
const fetchUrl = useMemo(() => {
|
||||
if (period) {
|
||||
if (!serverUrl) {
|
||||
setError("Please enter Server URL");
|
||||
return;
|
||||
}
|
||||
if (!query.trim()) {
|
||||
setError("Please enter a valid Query and execute it");
|
||||
return;
|
||||
}
|
||||
if (isValidHttpUrl(serverUrl)) {
|
||||
const duration = (period.end - period.start)/2;
|
||||
const doublePeriod = {...period, start: period.start - duration, end: period.end + duration};
|
||||
return displayType === "chart"
|
||||
? getQueryRangeUrl(serverUrl, query, doublePeriod, nocache)
|
||||
: getQueryUrl(serverUrl, query, period);
|
||||
const needUpdateData = useMemo(() => {
|
||||
if (!prevPeriod) return true;
|
||||
const duration = (prevPeriod.end - prevPeriod.start) / 3;
|
||||
const factorLimit = duration / (period.end - period.start) >= 0.7;
|
||||
const maxLimit = period.end > (prevPeriod.end + duration);
|
||||
const minLimit = period.start < (prevPeriod.start - duration);
|
||||
return factorLimit || maxLimit || minLimit;
|
||||
}, [period]);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!fetchUrl) return;
|
||||
setIsLoading(true);
|
||||
setPrevPeriod(period);
|
||||
|
||||
const headers = new Headers();
|
||||
if (authMethod === "BASIC_AUTH") {
|
||||
headers.set("Authorization", "Basic " + btoa(`${basicData?.login || ""}:${basicData?.password || ""}`));
|
||||
}
|
||||
if (authMethod === "BEARER_AUTH") {
|
||||
headers.set("Authorization", bearerData?.token || "");
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(fetchUrl, { headers });
|
||||
if (response.ok) {
|
||||
saveToStorage("LAST_QUERY", query);
|
||||
const resp = await response.json();
|
||||
setError(undefined);
|
||||
displayType === "chart" ? setGraphData(resp.data.result) : setLiveData(resp.data.result);
|
||||
} else {
|
||||
setError("Please provide a valid URL");
|
||||
setError((await response.json())?.error);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchUrl = useMemo(() => {
|
||||
if (!period) return;
|
||||
if (!serverUrl) {
|
||||
setError("Please enter Server URL");
|
||||
} else if (!query.trim()) {
|
||||
setError("Please enter a valid Query and execute it");
|
||||
} else if (isValidHttpUrl(serverUrl)) {
|
||||
const duration = (period.end - period.start) / 2;
|
||||
const bufferPeriod = {...period, start: period.start - duration, end: period.end + duration};
|
||||
return displayType === "chart"
|
||||
? getQueryRangeUrl(serverUrl, query, bufferPeriod, nocache)
|
||||
: getQueryUrl(serverUrl, query, period);
|
||||
} else {
|
||||
setError("Please provide a valid URL");
|
||||
}
|
||||
},
|
||||
[serverUrl, period, displayType]);
|
||||
|
||||
useEffect(() => {
|
||||
setPrevPeriod(undefined);
|
||||
}, [query]);
|
||||
|
||||
// TODO: this should depend on query as well, but need to decide when to do the request.
|
||||
// Doing it on each query change - looks to be a bad idea. Probably can be done on blur
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (fetchUrl) {
|
||||
const headers = new Headers();
|
||||
if (authMethod === "BASIC_AUTH") {
|
||||
headers.set("Authorization", "Basic " + btoa(`${basicData?.login || ""}:${basicData?.password || ""}`));
|
||||
}
|
||||
if (authMethod === "BEARER_AUTH") {
|
||||
headers.set("Authorization", bearerData?.token || "");
|
||||
}
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(fetchUrl, {
|
||||
headers
|
||||
});
|
||||
if (response.ok) {
|
||||
saveToStorage("LAST_QUERY", query);
|
||||
const resp = await response.json();
|
||||
setError(undefined);
|
||||
displayType === "chart" ? setGraphData(resp.data.result) : setLiveData(resp.data.result);
|
||||
} else {
|
||||
setError((await response.json())?.error);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [fetchUrl, serverUrl, displayType]);
|
||||
fetchData();
|
||||
}, [serverUrl, displayType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (needUpdateData) {
|
||||
fetchData();
|
||||
}
|
||||
}, [period]);
|
||||
|
||||
return {
|
||||
fetchUrl,
|
||||
|
|
|
@ -1,15 +1,114 @@
|
|||
import React, {FC} from "react";
|
||||
import React, {FC, useEffect, useState} from "react";
|
||||
import {MetricResult} from "../../../api/types";
|
||||
import LineChart from "../../LineChart/LineChart";
|
||||
import {AlignedData as uPlotData, Series as uPlotSeries} from "uplot";
|
||||
import {Legend, LegendItem} from "../../Legend/Legend";
|
||||
import {useAppState} from "../../../state/common/StateContext";
|
||||
import {getNameForMetric} from "../../../utils/metric";
|
||||
import {getColorFromString} from "../../../utils/color";
|
||||
import {useGraphDispatch, useGraphState} from "../../../state/graph/GraphStateContext";
|
||||
import {getHideSeries} from "../../../utils/uPlot";
|
||||
|
||||
export interface GraphViewProps {
|
||||
data?: MetricResult[];
|
||||
}
|
||||
|
||||
const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
const {time: {period}} = useAppState();
|
||||
|
||||
const { yaxis } = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
|
||||
const [timeArray, setTimeArray] = useState<number[]>([]);
|
||||
const [dataChart, setDataChart] = useState<uPlotData>([[]]);
|
||||
const [series, setSeries] = useState<uPlotSeries[]>([]);
|
||||
const [legend, setLegend] = useState<LegendItem[]>([]);
|
||||
const [hideSeries, setHideSeries] = useState<string[]>([]);
|
||||
|
||||
const setTimes = (times: number[]) => {
|
||||
const allTimes = times.sort((a,b) => a-b);
|
||||
const startTime = allTimes[0] || 0;
|
||||
const endTime = allTimes[allTimes.length - 1] || 0;
|
||||
const step = period.step || 1;
|
||||
const length = Math.round((endTime - startTime) / step);
|
||||
setTimeArray(new Array(length).fill(0).map((d, i) => startTime + (step * i)));
|
||||
};
|
||||
|
||||
const setLimitsYaxis = (values: number[]) => {
|
||||
if (!yaxis.limits.enable || (yaxis.limits.range.every(item => !item))) {
|
||||
const allValues = values.flat().sort((a,b) => a-b);
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: [allValues[0], allValues[allValues.length - 1]]});
|
||||
}
|
||||
};
|
||||
|
||||
const getSeriesItem = (d: MetricResult) => {
|
||||
const label = getNameForMetric(d);
|
||||
return {
|
||||
label,
|
||||
width: 1.5,
|
||||
stroke: getColorFromString(label),
|
||||
show: !hideSeries.includes(label)
|
||||
};
|
||||
};
|
||||
|
||||
const getLegendItem = (s: uPlotSeries): LegendItem => ({
|
||||
label: s.label || "",
|
||||
color: s.stroke as string,
|
||||
checked: s.show || false
|
||||
});
|
||||
|
||||
const onChangeLegend = (label: string, metaKey: boolean) => {
|
||||
setHideSeries(getHideSeries({hideSeries, label, metaKey, series}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const tempTimes: number[] = [];
|
||||
const tempValues: number[] = [];
|
||||
const tempLegend: LegendItem[] = [];
|
||||
const tempSeries: uPlotSeries[] = [];
|
||||
|
||||
data?.forEach(d => {
|
||||
const seriesItem = getSeriesItem(d);
|
||||
tempSeries.push(seriesItem);
|
||||
tempLegend.push(getLegendItem(seriesItem));
|
||||
|
||||
d.values.forEach(v => {
|
||||
if (tempTimes.indexOf(v[0]) === -1) tempTimes.push(v[0]);
|
||||
tempValues.push(+v[1]);
|
||||
});
|
||||
});
|
||||
|
||||
setTimes(tempTimes);
|
||||
setLimitsYaxis(tempValues);
|
||||
setSeries([{}, ...tempSeries]);
|
||||
setLegend(tempLegend);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const tempLegend: LegendItem[] = [];
|
||||
const tempSeries: uPlotSeries[] = [];
|
||||
data?.forEach(d => {
|
||||
const seriesItem = getSeriesItem(d);
|
||||
tempSeries.push(seriesItem);
|
||||
tempLegend.push(getLegendItem(seriesItem));
|
||||
});
|
||||
setSeries([{}, ...tempSeries]);
|
||||
setLegend(tempLegend);
|
||||
}, [hideSeries]);
|
||||
|
||||
useEffect(() => {
|
||||
setDataChart([timeArray, ...data.map(d => timeArray.map(t => {
|
||||
const v = d.values.find(v => v[0] === t);
|
||||
return v ? +v[1] : null;
|
||||
}))]);
|
||||
}, [timeArray]);
|
||||
|
||||
return <>
|
||||
{(data.length > 0)
|
||||
? <LineChart data={data} />
|
||||
? <div>
|
||||
<LineChart data={dataChart} series={series} metrics={data}/>
|
||||
<Legend labels={legend} onChange={onChangeLegend}/>
|
||||
</div>
|
||||
: <div style={{textAlign: "center"}}>No data to show</div>}
|
||||
</>;
|
||||
};
|
||||
|
|
|
@ -1,67 +1,31 @@
|
|||
import React, {FC, useMemo} from "react";
|
||||
import {Checkbox, FormControlLabel, Typography} from "@material-ui/core";
|
||||
import {MetricCategory} from "../../hooks/useSortedCategories";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
import React, {FC} from "react";
|
||||
import {hexToRGB} from "../../utils/color";
|
||||
import "./legend.css";
|
||||
|
||||
export interface LegendItem {
|
||||
seriesName: string;
|
||||
labelData: {[key: string]: string};
|
||||
label: string;
|
||||
color: string;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export interface LegendProps {
|
||||
labels: LegendItem[];
|
||||
categories: MetricCategory[];
|
||||
onChange: (index: number) => void;
|
||||
onChange: (legend: string, metaKey: boolean) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
legendWrapper: {
|
||||
display: "grid",
|
||||
width: "100%",
|
||||
gridTemplateColumns: "repeat(auto-fit)", // experiments like repeat(auto-fit, minmax(200px , auto)) may reduce size but readability as well
|
||||
gridColumnGap: ".5em",
|
||||
paddingLeft: "8px"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const Legend: FC<LegendProps> = ({labels, onChange, categories}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const commonLabels = useMemo(() => labels.length > 0
|
||||
? categories
|
||||
.filter(c => c.variations === 1)
|
||||
.map(c => `${c.key}: ${labels[0].labelData[c.key]}`)
|
||||
: [], [categories, labels]);
|
||||
|
||||
const uncommonLabels = useMemo(() => categories.filter(c => c.variations !== 1).map(c => c.key), [categories]);
|
||||
|
||||
return <div>
|
||||
<div style={{textAlign: "center"}}>{`Legend for ${commonLabels.join(", ")}`}</div>
|
||||
<div className={classes.legendWrapper}>
|
||||
{labels.map((legendItem: LegendItem, index) =>
|
||||
<div key={legendItem.seriesName}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={legendItem.checked}
|
||||
onChange={() => {
|
||||
onChange(index);
|
||||
}}
|
||||
style={{
|
||||
color: legendItem.color,
|
||||
padding: "4px"
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={<Typography variant="body2">{uncommonLabels.map(l => `${l}: ${legendItem.labelData[l]}`).join(", ")}</Typography>}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
export const Legend: FC<LegendProps> = ({labels, onChange}) => {
|
||||
return <div className="legendWrapper">
|
||||
{labels.map((legendItem: LegendItem) =>
|
||||
<div className={legendItem.checked ? "legendItem" : "legendItem legendItemHide"}
|
||||
key={legendItem.label}
|
||||
onClick={(e) => onChange(legendItem.label, e.ctrlKey || e.metaKey)}>
|
||||
<div className="legendMarker"
|
||||
style={{
|
||||
borderColor: legendItem.color,
|
||||
backgroundColor: `rgba(${hexToRGB(legendItem.color)}, 0.1)`
|
||||
}}/>
|
||||
<div className="legendLabel">{legendItem.checked} {legendItem.label}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>;
|
||||
};
|
38
app/vmui/packages/vmui/src/components/Legend/legend.css
Normal file
38
app/vmui/packages/vmui/src/components/Legend/legend.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
.legendWrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.legendItem {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
padding: 5px 10px;
|
||||
background-color: #FFF;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
|
||||
.legendItemHide {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.legendItem:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.legendMarker {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
box-sizing: border-box;
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
|
||||
.legendLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
|
@ -1,44 +1,29 @@
|
|||
import React, {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import React, {FC, useRef, useState} from "react";
|
||||
import {useAppDispatch, useAppState} from "../../state/common/StateContext";
|
||||
import {GraphViewProps} from "../Home/Views/GraphView";
|
||||
import uPlot, {AlignedData as uPlotData, Options as uPlotOptions, Series as uPlotSeries} from "uplot";
|
||||
import UplotReact from "uplot-react";
|
||||
import "uplot/dist/uPlot.min.css";
|
||||
import numeral from "numeral";
|
||||
import "./legend.css";
|
||||
import "./tooltip.css";
|
||||
import {useGraphDispatch, useGraphState} from "../../state/graph/GraphStateContext";
|
||||
import {getDataChart, getLimitsTimes, getLimitsYaxis, getSeries, setTooltip} from "../../utils/uPlot";
|
||||
import {useGraphState} from "../../state/graph/GraphStateContext";
|
||||
import {setTooltip } from "../../utils/uPlot";
|
||||
import {MetricResult} from "../../api/types";
|
||||
|
||||
const LineChart: FC<GraphViewProps> = ({data = []}) => {
|
||||
export interface LineChartProps {
|
||||
metrics: MetricResult[]
|
||||
data: uPlotData;
|
||||
series: uPlotSeries[]
|
||||
}
|
||||
|
||||
const LineChart: FC<LineChartProps> = ({data, series, metrics = []}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {time: {period}} = useAppState();
|
||||
const [scale, setScale] = useState({min: period.start, max: period.end});
|
||||
const { yaxis } = useGraphState();
|
||||
const refContainer = useRef<HTMLDivElement>(null);
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
const [zoomPos, setZoomPos] = useState(0);
|
||||
const tooltipIdx = {seriesIdx: 1, dataIdx: 0};
|
||||
const tooltipOffset = {left: 0, top: 0};
|
||||
|
||||
const {yaxis} = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const setStateLimits = (range: [number, number]) => {
|
||||
if (!yaxis.limits.enable || (yaxis.limits.range.every(item => !item))) {
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: range});
|
||||
}
|
||||
};
|
||||
|
||||
const times = useMemo(() => {
|
||||
const [start, end] = getLimitsTimes(data);
|
||||
const output = [];
|
||||
for (let i = start; i < end; i += period.step || 1) { output.push(i); }
|
||||
return output;
|
||||
}, [data]);
|
||||
|
||||
const series = useMemo((): uPlotSeries[] => getSeries(data), [data]);
|
||||
|
||||
const dataChart = useMemo((): uPlotData => getDataChart(data, times), [data]);
|
||||
const tooltipIdx = { seriesIdx: 1, dataIdx: 0 };
|
||||
const tooltipOffset = { left: 0, top: 0 };
|
||||
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.className = "u-tooltip";
|
||||
|
@ -48,14 +33,12 @@ const LineChart: FC<GraphViewProps> = ({data = []}) => {
|
|||
tooltipOffset.left = parseFloat(u.over.style.left);
|
||||
tooltipOffset.top = parseFloat(u.over.style.top);
|
||||
u.root.querySelector(".u-wrap")?.appendChild(tooltip);
|
||||
|
||||
// wheel drag pan
|
||||
u.over.addEventListener("mousedown", e => {
|
||||
if (e.button !== 0) return;
|
||||
setIsPanning(true);
|
||||
e.preventDefault();
|
||||
const left0 = e.clientX;
|
||||
|
||||
const onmove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const dx = (u.posToVal(1, "x") - u.posToVal(0, "x")) * (e.clientX - left0);
|
||||
|
@ -64,17 +47,14 @@ const LineChart: FC<GraphViewProps> = ({data = []}) => {
|
|||
u.setScale("x", {min, max});
|
||||
setScale({min, max});
|
||||
};
|
||||
|
||||
const onup = () => {
|
||||
setIsPanning(false);
|
||||
document.removeEventListener("mousemove", onmove);
|
||||
document.removeEventListener("mouseup", onup);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onmove);
|
||||
document.addEventListener("mouseup", onup);
|
||||
});
|
||||
|
||||
// wheel scroll zoom
|
||||
u.over.addEventListener("wheel", e => {
|
||||
if (!e.ctrlKey && !e.metaKey) return;
|
||||
|
@ -97,7 +77,7 @@ const LineChart: FC<GraphViewProps> = ({data = []}) => {
|
|||
if (tooltipIdx.dataIdx === u.cursor.idx) return;
|
||||
tooltipIdx.dataIdx = u.cursor.idx || 0;
|
||||
if (tooltipIdx.seriesIdx && tooltipIdx.dataIdx) {
|
||||
setTooltip({u, tooltipIdx, data, series, tooltip, tooltipOffset});
|
||||
setTooltip({u, tooltipIdx, metrics, series, tooltip, tooltipOffset});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -105,44 +85,47 @@ const LineChart: FC<GraphViewProps> = ({data = []}) => {
|
|||
if (tooltipIdx.seriesIdx === sidx) return;
|
||||
tooltipIdx.seriesIdx = sidx || 0;
|
||||
sidx && tooltipIdx.dataIdx
|
||||
? setTooltip({u, tooltipIdx, data, series, tooltip, tooltipOffset})
|
||||
? setTooltip({u, tooltipIdx, metrics, series, tooltip, tooltipOffset})
|
||||
: tooltip.style.display = "none";
|
||||
};
|
||||
|
||||
useEffect(() => { setStateLimits(getLimitsYaxis(data)); }, [data]);
|
||||
|
||||
useEffect(() => { setScale({min: period.start, max: period.end}); }, [period]);
|
||||
|
||||
useEffect(() => {
|
||||
const duration = (period.end - period.start)/3;
|
||||
const factor = duration / (scale.max - scale.min);
|
||||
if (scale.max > period.end + duration || scale.min < period.start - duration || factor >= 0.7) {
|
||||
dispatch({type: "SET_PERIOD", payload: {from: new Date(scale.min * 1000), to: new Date(scale.max * 1000)}});
|
||||
}
|
||||
}, [scale]);
|
||||
const setScale = ({min, max}: {min: number, max: number}): void => {
|
||||
dispatch({type: "SET_PERIOD", payload: {from: new Date(min * 1000), to: new Date(max * 1000)}});
|
||||
};
|
||||
|
||||
const options: uPlotOptions = {
|
||||
width: refContainer.current ? refContainer.current.offsetWidth : 400,
|
||||
height: 500,
|
||||
series: series,
|
||||
plugins: [{hooks: {ready: onReadyChart, setCursor, setSeries: seriesFocus}}],
|
||||
cursor: {drag: {x: false, y: false}, focus: {prox: 30}},
|
||||
axes: [
|
||||
{space: 80},
|
||||
{
|
||||
show: true,
|
||||
font: "10px Arial",
|
||||
values: (self, ticks) => ticks.map(n => n > 1000 ? numeral(n).format("0.0a") : n)
|
||||
width: refContainer.current ? refContainer.current.offsetWidth : 400, height: 500, series: series,
|
||||
plugins: [{ hooks: { ready: onReadyChart, setCursor, setSeries: seriesFocus }}],
|
||||
cursor: {
|
||||
drag: { x: false, y: false },
|
||||
focus: { prox: 30 },
|
||||
bind: {
|
||||
mouseup: () => null,
|
||||
mousedown: () => null,
|
||||
click: () => null,
|
||||
dblclick: () => null,
|
||||
mouseenter: () => null,
|
||||
}
|
||||
},
|
||||
legend: { show: false },
|
||||
axes: [
|
||||
{ space: 80 },
|
||||
{ show: true, font: "10px Arial",
|
||||
values: (self, ticks) => ticks.map(n => n > 1000 ? numeral(n).format("0.0a") : n) }
|
||||
],
|
||||
scales: {
|
||||
x: {range: () => [scale.min, scale.max]},
|
||||
y: {range: (self, min, max) => yaxis.limits.enable ? yaxis.limits.range : [min, max]}
|
||||
x: { range: () => [period.start, period.end] },
|
||||
y: {
|
||||
range: (self, min, max) => {
|
||||
const offsetFactor = 0.05; // 5%
|
||||
return yaxis.limits.enable ? yaxis.limits.range : [min - (min * offsetFactor), max + (max * offsetFactor)];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <div ref={refContainer} style={{pointerEvents: isPanning ? "none" : "auto"}}>
|
||||
{dataChart && <UplotReact options={options} data={dataChart}/>}
|
||||
return <div ref={refContainer} style={{pointerEvents: isPanning ? "none" : "auto", height: "500px"}}>
|
||||
<UplotReact options={options} data={data}/>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
.uplot .u-legend {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.uplot .u-legend .u-series {
|
||||
font-size: 12px;
|
||||
}
|
|
@ -21,6 +21,12 @@
|
|||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
.u-tooltip-data__value {
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.u-tooltip__info {
|
||||
|
|
|
@ -9,4 +9,12 @@ export const getColorFromString = (str: string): string => {
|
|||
colour += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return colour;
|
||||
};
|
||||
|
||||
export const hexToRGB = (hex: string): string => {
|
||||
if (hex.length != 7) return "0, 0, 0";
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return `${r}, ${g}, ${b}`;
|
||||
};
|
|
@ -1,23 +1,29 @@
|
|||
import uPlot, {AlignedData, Series} from "uplot";
|
||||
import uPlot, {Series} from "uplot";
|
||||
import {getColorFromString} from "./color";
|
||||
import dayjs from "dayjs";
|
||||
import {MetricResult} from "../api/types";
|
||||
import {getNameForMetric} from "./metric";
|
||||
|
||||
interface SetupTooltip {
|
||||
u: uPlot,
|
||||
data: MetricResult[],
|
||||
metrics: MetricResult[],
|
||||
series: Series[],
|
||||
tooltip: HTMLDivElement,
|
||||
tooltipOffset: {left: number, top: number},
|
||||
tooltipIdx: {seriesIdx: number, dataIdx: number}
|
||||
}
|
||||
|
||||
export const setTooltip = ({ u, tooltipIdx, data, series, tooltip, tooltipOffset }: SetupTooltip) : void => {
|
||||
interface HideSeriesArgs {
|
||||
hideSeries: string[],
|
||||
label: string,
|
||||
metaKey: boolean,
|
||||
series: Series[]
|
||||
}
|
||||
|
||||
export const setTooltip = ({ u, tooltipIdx, metrics, series, tooltip, tooltipOffset }: SetupTooltip) : void => {
|
||||
const {seriesIdx, dataIdx} = tooltipIdx;
|
||||
const dataSeries = u.data[seriesIdx][dataIdx];
|
||||
const dataTime = u.data[0][dataIdx];
|
||||
const metric = data[seriesIdx - 1]?.metric || {};
|
||||
const metric = metrics[seriesIdx - 1]?.metric || {};
|
||||
const color = getColorFromString(series[seriesIdx].label || "");
|
||||
|
||||
const {width, height} = u.over.getBoundingClientRect();
|
||||
|
@ -35,30 +41,18 @@ export const setTooltip = ({ u, tooltipIdx, data, series, tooltip, tooltipOffset
|
|||
const marker = `<div class="u-tooltip__marker" style="background: ${color}"></div>`;
|
||||
tooltip.innerHTML = `<div>${date}</div>
|
||||
<div class="u-tooltip-data">
|
||||
${marker}${metric.__name__ || ""}: <b>${dataSeries}</b>
|
||||
${marker}${metric.__name__ || ""}: <b class="u-tooltip-data__value">${dataSeries}</b>
|
||||
</div>
|
||||
<div class="u-tooltip__info">${info}</div>`;
|
||||
};
|
||||
|
||||
export const getSeries = (data: MetricResult[]): Series[] => [{}, ...data.map(d => ({
|
||||
label: getNameForMetric(d),
|
||||
width: 1.5,
|
||||
stroke: getColorFromString(getNameForMetric(d))
|
||||
}))];
|
||||
|
||||
export const getLimitsTimes = (data: MetricResult[]): [number, number] => {
|
||||
const allTimes = data.map(d => d.values.map(v => v[0])).flat().sort((a,b) => a-b);
|
||||
return [allTimes[0], allTimes[allTimes.length - 1]];
|
||||
};
|
||||
|
||||
export const getLimitsYaxis = (data: MetricResult[]): [number, number] => {
|
||||
const allValues = data.map(d => d.values.map(v => +v[1])).flat().sort((a,b) => a-b);
|
||||
return [allValues[0], allValues[allValues.length - 1]];
|
||||
};
|
||||
|
||||
export const getDataChart = (data: MetricResult[], times: number[]): AlignedData => {
|
||||
return [times, ...data.map(d => times.map(t => {
|
||||
const v = d.values.find(v => v[0] === t);
|
||||
return v ? +v[1] : null;
|
||||
}))];
|
||||
export const getHideSeries = ({hideSeries, label, metaKey, series}: HideSeriesArgs): string[] => {
|
||||
const include = hideSeries.includes(label);
|
||||
const labels = series.map(s => s.label || "").filter(l => l);
|
||||
if (metaKey && include) {
|
||||
return [...labels.filter(l => l !== label)];
|
||||
} else if (metaKey && !include) {
|
||||
return hideSeries.length === series.length - 2 ? [] : [...labels.filter(l => l !== label)];
|
||||
}
|
||||
return include ? hideSeries.filter(l => l !== label) : [...hideSeries, label];
|
||||
};
|
Loading…
Reference in a new issue