mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
vmui: multiple queries (#1916)
* feat: change duration by "enter"
* fix: optimize data processing for chart
* feat: set minimum step to 1ms
* update dependencies
* feat: remove save the last query to local storage
* fix: handle an error in a table with subqueries
* feat: store display type in URL
* Revert "feat: store display type in URL"
This reverts commit ccc242c69a
.
* feat: store display type in URL
* refactor: move the time setting to a folder
* refactor: move the query configurator to a folder
* refactor: move the auth settings to a folder
* feat: improve styles
* feat: add multi query
* update package-lock
* feat: add display multiple queries
* feat: add limits for multiple queries
* update dependencies
* feat: add history for multiple queries
* feat: add line type to legend
* feat: change style for switch
* feat: change the logic for axes limits for multiple queries
* update package-lock.json
* update dependencies
* feat: add the filter to legend
* wip
* lib/httpserver: add missing 127.0.0.1 hostname to the logged address for http and pprof server if the address starts with ':'
This allows copy-pasting the url to http server from logs.
* lib/httpserver: add missing 127.0.0.1 hostname to the logged address for http and pprof server if the address starts with ':'
This allows copy-pasting the url to http server from logs.
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
0288078cfb
commit
c1fd93e8a0
@ -1217,7 +1217,8 @@ Consider setting the following command-line flags:
|
||||
* `-snapshotAuthKey` for protecting `/snapshot*` endpoints. See [how to work with snapshots](#how-to-work-with-snapshots).
|
||||
* `-forceMergeAuthKey` for protecting `/internal/force_merge` endpoint. See [force merge docs](#forced-merge).
|
||||
* `-search.resetCacheAuthKey` for protecting `/internal/resetRollupResultCache` endpoint. See [backfilling](#backfilling) for more details.
|
||||
* `-configAuthKey` for pretecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
* `-configAuthKey` for protecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
- `-pprofAuthKey` for protecting `/debug/pprof/*` endpoints, which can be used for [profiling](#profiling).
|
||||
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`.
|
||||
|
@ -73,7 +73,6 @@ func main() {
|
||||
}()
|
||||
}
|
||||
|
||||
logger.Infof("starting http server for exporting metrics at http://%q/metrics", *httpListenAddr)
|
||||
go httpserver.Serve(*httpListenAddr, nil)
|
||||
|
||||
srcFS, err := newSrcFS()
|
||||
|
@ -36,7 +36,6 @@ func main() {
|
||||
buildinfo.Init()
|
||||
logger.Init()
|
||||
|
||||
logger.Infof("starting http server for exporting metrics at http://%q/metrics", *httpListenAddr)
|
||||
go httpserver.Serve(*httpListenAddr, nil)
|
||||
|
||||
srcFS, err := newSrcFS()
|
||||
|
@ -1,19 +1,19 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.674f8c98.chunk.css",
|
||||
"main.js": "./static/js/main.06277491.chunk.js",
|
||||
"runtime-main.js": "./static/js/runtime-main.f698388d.js",
|
||||
"main.css": "./static/css/main.1b10b1ed.chunk.css",
|
||||
"main.js": "./static/js/main.9f03a409.chunk.js",
|
||||
"runtime-main.js": "./static/js/runtime-main.66a19bd8.js",
|
||||
"static/css/2.77671664.chunk.css": "./static/css/2.77671664.chunk.css",
|
||||
"static/js/2.e53c287a.chunk.js": "./static/js/2.e53c287a.chunk.js",
|
||||
"static/js/3.e51afffb.chunk.js": "./static/js/3.e51afffb.chunk.js",
|
||||
"static/js/2.bc8706fc.chunk.js": "./static/js/2.bc8706fc.chunk.js",
|
||||
"static/js/3.7c3472dc.chunk.js": "./static/js/3.7c3472dc.chunk.js",
|
||||
"index.html": "./index.html",
|
||||
"static/js/2.e53c287a.chunk.js.LICENSE.txt": "./static/js/2.e53c287a.chunk.js.LICENSE.txt"
|
||||
"static/js/2.bc8706fc.chunk.js.LICENSE.txt": "./static/js/2.bc8706fc.chunk.js.LICENSE.txt"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.f698388d.js",
|
||||
"static/js/runtime-main.66a19bd8.js",
|
||||
"static/css/2.77671664.chunk.css",
|
||||
"static/js/2.e53c287a.chunk.js",
|
||||
"static/css/main.674f8c98.chunk.css",
|
||||
"static/js/main.06277491.chunk.js"
|
||||
"static/js/2.bc8706fc.chunk.js",
|
||||
"static/css/main.1b10b1ed.chunk.css",
|
||||
"static/js/main.9f03a409.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.77671664.chunk.css" rel="stylesheet"><link href="./static/css/main.674f8c98.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],f=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(l&&l(r);p.length;)p.shift()();return u.push.apply(u,f||[]),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:"e51afffb"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(f);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 f=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 f=0;f<a.length;f++)r(a[f]);var l=c;t()}([])</script><script src="./static/js/2.e53c287a.chunk.js"></script><script src="./static/js/main.06277491.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.77671664.chunk.css" rel="stylesheet"><link href="./static/css/main.1b10b1ed.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:"7c3472dc"}[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.bc8706fc.chunk.js"></script><script src="./static/js/main.9f03a409.chunk.js"></script></body></html>
|
1
app/vmselect/vmui/static/css/main.1b10b1ed.chunk.css
Normal file
1
app/vmselect/vmui/static/css/main.1b10b1ed.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: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:40px}.cm-gutters{border-radius:4px 0 0 4px;height:100%}.multi-line-scroll .cm-content,.multi-line-scroll .cm-gutters{min-height:38px!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{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));grid-gap:20px;margin-top:20px;cursor:default}.legendGroup{margin-bottom:24px}.legendGroupTitle{display:flex;align-items:center;padding:10px 0 5px;font-size:11px}.legendGroupLine{margin:0 10px}.legendItem{display:inline-grid;grid-template-columns:auto auto;grid-gap:6px;align-items:start;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;margin:3px 0}.legendLabel{font-size:11px;font-weight:400}
|
@ -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:64px!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}
|
2
app/vmselect/vmui/static/js/2.bc8706fc.chunk.js
Normal file
2
app/vmselect/vmui/static/js/2.bc8706fc.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 @@
|
||||
(this.webpackJsonpvmui=this.webpackJsonpvmui||[]).push([[3],{351: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],{355: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)}}}]);
|
File diff suppressed because one or more lines are too long
1
app/vmselect/vmui/static/js/main.9f03a409.chunk.js
Normal file
1
app/vmselect/vmui/static/js/main.9f03a409.chunk.js
Normal file
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],f=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(l&&l(r);p.length;)p.shift()();return u.push.apply(u,f||[]),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:"e51afffb"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(f);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 f=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 f=0;f<a.length;f++)r(a[f]);var l=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:"7c3472dc"}[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()}([]);
|
406
app/vmui/packages/vmui/package-lock.json
generated
406
app/vmui/packages/vmui/package-lock.json
generated
@ -40,9 +40,10 @@
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"qs": "^6.10.1",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-measure": "^2.5.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "~4.5.2",
|
||||
@ -1809,9 +1810,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
|
||||
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
@ -3129,17 +3130,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/base/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.2.0.tgz",
|
||||
@ -3161,17 +3151,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab": {
|
||||
"version": "5.0.0-alpha.58",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.58.tgz",
|
||||
@ -3222,17 +3201,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.2.2.tgz",
|
||||
@ -3277,17 +3245,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.2.2.tgz",
|
||||
@ -3314,17 +3271,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.2.0.tgz",
|
||||
@ -3355,17 +3301,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styles": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.2.2.tgz",
|
||||
@ -3406,17 +3341,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styles/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.2.2.tgz",
|
||||
@ -3456,17 +3380,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz",
|
||||
@ -3498,17 +3411,6 @@
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -4305,9 +4207,9 @@
|
||||
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ=="
|
||||
},
|
||||
"node_modules/@types/testing-library__jest-dom": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz",
|
||||
"integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==",
|
||||
"version": "5.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.2.tgz",
|
||||
"integrity": "sha512-vehbtyHUShPxIa9SioxDwCvgxukDMH//icJG90sXQBUm5lJOHLT5kNeU9tnivhnA/TkOFMzGIXN2cTc4hY8/kg==",
|
||||
"dependencies": {
|
||||
"@types/jest": "*"
|
||||
}
|
||||
@ -6052,14 +5954,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@ -7893,9 +7787,9 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz",
|
||||
"integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==",
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz",
|
||||
"integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==",
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
@ -9745,12 +9639,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/filesize": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
||||
@ -12193,29 +12081,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.0.tgz",
|
||||
"integrity": "sha512-fdXgpnyQH4LNSnYgRfHN/g413bqbPspWIAZPlXrdNISehDih1VNDtuRvlzGQJ4Go+fur1HKB2IyI25t6cWi5EA==",
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz",
|
||||
"integrity": "sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.4.0",
|
||||
"jest-get-type": "^27.4.0",
|
||||
"pretty-format": "^27.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/@jest/types": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.0.tgz",
|
||||
"integrity": "sha512-jIsLdASXMf8GS7P7oGFGwobNse/6Ewq3GBPHoo0i6XRmja+NrUoDqJm4a1ffF2bHGleKJizxokcp1sCqSktP3g==",
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^16.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
"diff-sequences": "^27.0.6",
|
||||
"jest-get-type": "^27.3.1",
|
||||
"pretty-format": "^27.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
@ -12236,31 +12109,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/pretty-format": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.0.tgz",
|
||||
"integrity": "sha512-n0QR6hMREfp6nLzfVksXMAfIxk1ffOOfbb/FzKHFmRtn9iJKaZXB8WMzLr8a72IASShEAhqK06nlwp1gVWgqKg==",
|
||||
"dependencies": {
|
||||
"@jest/types": "^27.4.0",
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-docblock": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz",
|
||||
@ -12457,9 +12305,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz",
|
||||
"integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==",
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz",
|
||||
"integrity": "sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg==",
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
@ -14550,11 +14398,6 @@
|
||||
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.15.0",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.1.30",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
|
||||
@ -16972,9 +16815,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz",
|
||||
"integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
@ -17382,6 +17225,19 @@
|
||||
"react": "17.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-draggable": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz",
|
||||
"integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0",
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-overlay": {
|
||||
"version": "6.0.9",
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||
@ -23684,9 +23540,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
|
||||
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
@ -24763,16 +24619,6 @@
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/icons-material": {
|
||||
@ -24781,16 +24627,6 @@
|
||||
"integrity": "sha512-NvyrVaGKpP4R1yFw8BCnE0QcsQ67RtpgxPr4FtH8q60MDYPuPVczLOn5Ash5CFavoDWur/NfM/4DpT54yf3InA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/lab": {
|
||||
@ -24811,16 +24647,6 @@
|
||||
"react-is": "^17.0.2",
|
||||
"react-transition-group": "^4.4.2",
|
||||
"rifm": "^0.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/material": {
|
||||
@ -24840,16 +24666,6 @@
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-transition-group": "^4.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/private-theming": {
|
||||
@ -24860,16 +24676,6 @@
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@mui/utils": "^5.2.2",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/styled-engine": {
|
||||
@ -24880,16 +24686,6 @@
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@emotion/cache": "^11.6.0",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/styles": {
|
||||
@ -24914,16 +24710,6 @@
|
||||
"jss-plugin-rule-value-function": "^10.8.2",
|
||||
"jss-plugin-vendor-prefixer": "^10.8.2",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/system": {
|
||||
@ -24939,16 +24725,6 @@
|
||||
"clsx": "^1.1.1",
|
||||
"csstype": "^3.0.10",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/types": {
|
||||
@ -24967,16 +24743,6 @@
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
@ -25573,9 +25339,9 @@
|
||||
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ=="
|
||||
},
|
||||
"@types/testing-library__jest-dom": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz",
|
||||
"integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==",
|
||||
"version": "5.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.2.tgz",
|
||||
"integrity": "sha512-vehbtyHUShPxIa9SioxDwCvgxukDMH//icJG90sXQBUm5lJOHLT5kNeU9tnivhnA/TkOFMzGIXN2cTc4hY8/kg==",
|
||||
"requires": {
|
||||
"@types/jest": "*"
|
||||
}
|
||||
@ -26901,14 +26667,6 @@
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"optional": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@ -28384,9 +28142,9 @@
|
||||
}
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz",
|
||||
"integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww=="
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz",
|
||||
"integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ=="
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
@ -29796,12 +29554,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"optional": true
|
||||
},
|
||||
"filesize": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
||||
@ -31689,28 +31441,16 @@
|
||||
}
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.0.tgz",
|
||||
"integrity": "sha512-fdXgpnyQH4LNSnYgRfHN/g413bqbPspWIAZPlXrdNISehDih1VNDtuRvlzGQJ4Go+fur1HKB2IyI25t6cWi5EA==",
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz",
|
||||
"integrity": "sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.4.0",
|
||||
"jest-get-type": "^27.4.0",
|
||||
"pretty-format": "^27.4.0"
|
||||
"diff-sequences": "^27.0.6",
|
||||
"jest-get-type": "^27.3.1",
|
||||
"pretty-format": "^27.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/types": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.0.tgz",
|
||||
"integrity": "sha512-jIsLdASXMf8GS7P7oGFGwobNse/6Ewq3GBPHoo0i6XRmja+NrUoDqJm4a1ffF2bHGleKJizxokcp1sCqSktP3g==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^16.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@ -31719,24 +31459,6 @@
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.0.tgz",
|
||||
"integrity": "sha512-n0QR6hMREfp6nLzfVksXMAfIxk1ffOOfbb/FzKHFmRtn9iJKaZXB8WMzLr8a72IASShEAhqK06nlwp1gVWgqKg==",
|
||||
"requires": {
|
||||
"@jest/types": "^27.4.0",
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -31897,9 +31619,9 @@
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "27.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz",
|
||||
"integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ=="
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz",
|
||||
"integrity": "sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg=="
|
||||
},
|
||||
"jest-haste-map": {
|
||||
"version": "26.6.2",
|
||||
@ -33528,11 +33250,6 @@
|
||||
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.15.0",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.30",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
|
||||
@ -35484,9 +35201,9 @@
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz",
|
||||
"integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
@ -35787,6 +35504,15 @@
|
||||
"scheduler": "^0.20.2"
|
||||
}
|
||||
},
|
||||
"react-draggable": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz",
|
||||
"integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==",
|
||||
"requires": {
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-error-overlay": {
|
||||
"version": "6.0.9",
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||
|
@ -36,9 +36,10 @@
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"qs": "^6.10.1",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-measure": "^2.5.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "~4.5.2",
|
||||
|
@ -4,44 +4,15 @@ import HomeLayout from "./components/Home/HomeLayout";
|
||||
import {StateProvider} from "./state/common/StateContext";
|
||||
import {AuthStateProvider} from "./state/auth/AuthStateContext";
|
||||
import {GraphStateProvider} from "./state/graph/GraphStateContext";
|
||||
import { ThemeProvider, Theme, StyledEngineProvider, createTheme } from "@mui/material/styles";
|
||||
|
||||
import { ThemeProvider, StyledEngineProvider } from "@mui/material/styles";
|
||||
import THEME from "./theme/theme";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
|
||||
import LocalizationProvider from "@mui/lab/LocalizationProvider";
|
||||
// pick a date util library
|
||||
import DayjsUtils from "@date-io/dayjs";
|
||||
|
||||
|
||||
declare module "@mui/styles/defaultTheme" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface DefaultTheme extends Theme {}
|
||||
}
|
||||
|
||||
|
||||
const App: FC = () => {
|
||||
|
||||
const THEME = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#3F51B5"
|
||||
},
|
||||
secondary: {
|
||||
main: "#F50057"
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiSwitch: {
|
||||
defaultProps: {
|
||||
color: "secondary"
|
||||
}
|
||||
}
|
||||
},
|
||||
typography: {
|
||||
"fontSize": 10
|
||||
}
|
||||
});
|
||||
|
||||
return <>
|
||||
<CssBaseline /> {/* CSS Baseline: kind of normalize.css made by materialUI team - can be scoped */}
|
||||
<LocalizationProvider dateAdapter={DayjsUtils}> {/* Allows datepicker to work with DayJS */}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export interface MetricBase {
|
||||
group: number;
|
||||
metric: {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
@ -26,8 +26,8 @@ import TabPanel from "./AuthTabPanel";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import {useAuthDispatch, useAuthState} from "../../../state/auth/AuthStateContext";
|
||||
import {AUTH_METHOD, WithCheckbox} from "../../../state/auth/reducer";
|
||||
import {useAuthDispatch, useAuthState} from "../../../../state/auth/AuthStateContext";
|
||||
import {AUTH_METHOD, WithCheckbox} from "../../../../state/auth/reducer";
|
||||
|
||||
// TODO: make generic when creating second dialog
|
||||
export interface DialogProps {
|
@ -0,0 +1,42 @@
|
||||
import React, {FC, useCallback, useMemo} from "react";
|
||||
import {Box, FormControlLabel, TextField} from "@mui/material";
|
||||
import {useGraphDispatch, useGraphState} from "../../../../state/graph/GraphStateContext";
|
||||
import debounce from "lodash.debounce";
|
||||
import BasicSwitch from "../../../../theme/switch";
|
||||
|
||||
const AxesLimitsConfigurator: FC = () => {
|
||||
|
||||
const { yaxis } = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const axes = useMemo(() => Object.keys(yaxis.limits.range), [yaxis.limits.range]);
|
||||
|
||||
const onChangeYaxisLimits = () => { graphDispatch({type: "TOGGLE_ENABLE_YAXIS_LIMITS"}); };
|
||||
|
||||
const onChangeLimit = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, axis: string, index: number) => {
|
||||
const newLimits = yaxis.limits.range;
|
||||
newLimits[axis][index] = +e.target.value;
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: newLimits});
|
||||
};
|
||||
const debouncedOnChangeLimit = useCallback(debounce(onChangeLimit, 500), [yaxis.limits.range]);
|
||||
|
||||
return <Box display="grid" alignItems="center" gap={2}>
|
||||
<FormControlLabel
|
||||
control={<BasicSwitch checked={yaxis.limits.enable} onChange={onChangeYaxisLimits}/>}
|
||||
label="Fix the limits for y-axis"
|
||||
/>
|
||||
<Box display="grid" alignItems="center" gap={4}>
|
||||
{axes.map(axis => <Box display="grid" gridTemplateColumns="120px 120px" gap={1} key={axis}>
|
||||
<TextField label={`Min ${axis}`} type="number" size="small" variant="outlined"
|
||||
disabled={!yaxis.limits.enable}
|
||||
defaultValue={yaxis.limits.range[axis][0]}
|
||||
onChange={(e) => debouncedOnChangeLimit(e, axis, 0)}/>
|
||||
<TextField label={`Max ${axis}`} type="number" size="small" variant="outlined"
|
||||
disabled={!yaxis.limits.enable}
|
||||
defaultValue={yaxis.limits.range[axis][1]}
|
||||
onChange={(e) => debouncedOnChangeLimit(e, axis, 1)} />
|
||||
</Box>)}
|
||||
</Box>
|
||||
</Box>;
|
||||
};
|
||||
|
||||
export default AxesLimitsConfigurator;
|
@ -0,0 +1,62 @@
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import React, {FC, useState, useRef} from "react";
|
||||
import AxesLimitsConfigurator from "./AxesLimitsConfigurator";
|
||||
import {Box, Button, IconButton, Paper, Typography} from "@mui/material";
|
||||
import Draggable from "react-draggable";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
popover: {
|
||||
position: "absolute",
|
||||
display: "grid",
|
||||
gridGap: "16px",
|
||||
padding: "0 0 25px",
|
||||
zIndex: 2,
|
||||
},
|
||||
popoverHeader: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "#3F51B5",
|
||||
padding: "6px 6px 6px 12px",
|
||||
borderRadius: "4px 4px 0 0",
|
||||
color: "#FFF",
|
||||
cursor: "move",
|
||||
},
|
||||
popoverBody: {
|
||||
padding: "0 14px"
|
||||
}
|
||||
});
|
||||
|
||||
const GraphSettings: FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const position = { x: 173, y: 0 };
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return <Box display="flex" px={2}>
|
||||
<Button onClick={() => setOpen((old) => !old)} variant="outlined">
|
||||
<SettingsIcon sx={{fontSize: 16, marginRight: "4px"}}/>
|
||||
<span style={{lineHeight: 1, paddingTop: "1px"}}>{open ? "Hide" : "Show"} graph settings</span>
|
||||
</Button>
|
||||
{open && (
|
||||
<Draggable nodeRef={draggableRef} defaultPosition={position} handle="#handle">
|
||||
<Paper elevation={3} className={classes.popover} ref={draggableRef}>
|
||||
<div id="handle" className={classes.popoverHeader}>
|
||||
<Typography variant="body1"><b>Graph Settings</b></Typography>
|
||||
<IconButton size="small" onClick={() => setOpen(false)}>
|
||||
<CloseIcon style={{color: "white"}}/>
|
||||
</IconButton>
|
||||
</div>
|
||||
<Box className={classes.popoverBody}>
|
||||
<AxesLimitsConfigurator/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Draggable>
|
||||
)}
|
||||
</Box>;
|
||||
};
|
||||
|
||||
export default GraphSettings;
|
@ -0,0 +1,36 @@
|
||||
import React, {FC} from "react";
|
||||
import {Box, FormControlLabel} from "@mui/material";
|
||||
import {saveToStorage} from "../../../../utils/storage";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import BasicSwitch from "../../../../theme/switch";
|
||||
|
||||
const AdditionalSettings: FC = () => {
|
||||
|
||||
const {queryControls: {autocomplete, nocache}} = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onChangeAutocomplete = () => {
|
||||
dispatch({type: "TOGGLE_AUTOCOMPLETE"});
|
||||
saveToStorage("AUTOCOMPLETE", !autocomplete);
|
||||
};
|
||||
|
||||
const onChangeCache = () => {
|
||||
dispatch({type: "NO_CACHE"});
|
||||
saveToStorage("NO_CACHE", !nocache);
|
||||
};
|
||||
|
||||
return <Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Enable cache"
|
||||
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
||||
/>
|
||||
</Box>
|
||||
</Box>;
|
||||
};
|
||||
|
||||
export default AdditionalSettings;
|
@ -0,0 +1,136 @@
|
||||
import React, {FC, useRef, useState} from "react";
|
||||
import {
|
||||
Accordion, AccordionDetails, AccordionSummary, Box, Grid, IconButton, Typography, Tooltip, Button
|
||||
} from "@mui/material";
|
||||
import QueryEditor from "./QueryEditor";
|
||||
import {TimeSelector} from "../Time/TimeSelector";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
||||
import Portal from "@mui/material/Portal";
|
||||
import ServerConfigurator from "./ServerConfigurator";
|
||||
import AdditionalSettings from "./AdditionalSettings";
|
||||
|
||||
const QueryConfigurator: FC = () => {
|
||||
|
||||
const {serverUrl, query, queryHistory, time: {duration}, queryControls: {autocomplete}} = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [queryString, _setQueryString] = useState(query);
|
||||
const queryStringRef = useRef(queryString);
|
||||
const queryContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const setQueryString = (data: string[]) => {
|
||||
queryStringRef.current = data;
|
||||
_setQueryString(data);
|
||||
};
|
||||
|
||||
const onSetDuration = (dur: string) => dispatch({type: "SET_DURATION", payload: dur});
|
||||
|
||||
const onRunQuery = () => {
|
||||
const history = queryHistory.map((h, i) => {
|
||||
const lastQueryEqual = queryString[i] === h.values[h.values.length - 1];
|
||||
return {
|
||||
index: h.values.length - Number(lastQueryEqual),
|
||||
values: lastQueryEqual ? h.values : [...h.values, queryString[i]]
|
||||
};
|
||||
});
|
||||
dispatch({type: "RUN_QUERY"});
|
||||
dispatch({type: "SET_QUERY_HISTORY", payload: history});
|
||||
dispatch({type: "SET_QUERY", payload: queryStringRef.current});
|
||||
};
|
||||
|
||||
const onAddQuery = () => {
|
||||
const value = [...queryString, ""];
|
||||
setQueryString(value);
|
||||
dispatch({type: "SET_QUERY", payload: value});
|
||||
};
|
||||
|
||||
const onRemoveQuery = (index: number) => {
|
||||
const value = [...queryString];
|
||||
value.splice(index, 1);
|
||||
setQueryString(value);
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onSetQuery = (value: string, index: number) => {
|
||||
const newQuery = [...queryStringRef.current];
|
||||
newQuery[index] = value;
|
||||
setQueryString(newQuery);
|
||||
};
|
||||
|
||||
const setHistoryIndex = (step: number, indexQuery: number) => {
|
||||
const {index, values} = queryHistory[indexQuery];
|
||||
const newIndexHistory = index + step;
|
||||
if (newIndexHistory < 0 || newIndexHistory >= values.length) return;
|
||||
const newQuery = values[newIndexHistory] || "";
|
||||
onSetQuery(newQuery, indexQuery);
|
||||
dispatch({
|
||||
type: "SET_QUERY_HISTORY_BY_INDEX",
|
||||
payload: {value: {values, index: newIndexHistory}, queryNumber: indexQuery}
|
||||
});
|
||||
};
|
||||
|
||||
return <>
|
||||
<Accordion expanded={expanded} onChange={() => setExpanded(prev => !prev)}>
|
||||
<AccordionSummary
|
||||
expandIcon={<IconButton><ExpandMoreIcon/></IconButton>}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
sx={{alignItems: "flex-start", padding: "15px"}}
|
||||
>
|
||||
<Box mr={2}>
|
||||
<Typography variant="h6" component="h2">Query Configuration</Typography>
|
||||
</Box>
|
||||
<Box flexGrow={1} onClick={e => e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}>
|
||||
<Portal disablePortal={!expanded} container={queryContainer.current}>
|
||||
{query.map((q, i) =>
|
||||
<Box key={`${i}_${q}`} display="grid" gridTemplateColumns="1fr auto" gap="4px" width="100%"
|
||||
mb={i === query.length-1 ? 0 : 2}>
|
||||
<QueryEditor server={serverUrl} query={queryString[i]} index={i} oneLiner={!expanded}
|
||||
autocomplete={autocomplete}
|
||||
queryHistory={queryHistory[i]} setHistoryIndex={setHistoryIndex}
|
||||
runQuery={onRunQuery}
|
||||
setQuery={onSetQuery}/>
|
||||
{i === 0 && <Tooltip title="Execute Query">
|
||||
<IconButton onClick={onRunQuery}>
|
||||
<PlayCircleOutlineIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>}
|
||||
{i > 0 && <Tooltip title="Remove Query">
|
||||
<IconButton onClick={() => onRemoveQuery(i)}>
|
||||
<HighlightOffIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>}
|
||||
</Box>)}
|
||||
</Portal>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container columnSpacing={2}>
|
||||
<Grid item xs={6} minWidth={400}>
|
||||
<ServerConfigurator/>
|
||||
{/* for portal QueryEditor */}
|
||||
<div ref={queryContainer}/>
|
||||
{query.length < 2 && <Box display="inline-block" minHeight="40px" mt={2}>
|
||||
<Button onClick={onAddQuery} variant="outlined">
|
||||
<AddIcon sx={{fontSize: 16, marginRight: "4px"}}/>
|
||||
<span style={{lineHeight: 1, paddingTop: "1px"}}>Query</span>
|
||||
</Button>
|
||||
</Box>}
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<TimeSelector setDuration={onSetDuration} duration={duration}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} pt={1}>
|
||||
<AdditionalSettings/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default QueryConfigurator;
|
@ -4,13 +4,14 @@ import {defaultKeymap} from "@codemirror/commands";
|
||||
import React, {FC, useEffect, useRef, useState} from "react";
|
||||
import { PromQLExtension } from "codemirror-promql";
|
||||
import { basicSetup } from "@codemirror/basic-setup";
|
||||
import {QueryHistory} from "../../../state/common/reducer";
|
||||
import {QueryHistory} from "../../../../state/common/reducer";
|
||||
|
||||
export interface QueryEditorProps {
|
||||
setHistoryIndex: (step: number) => void;
|
||||
setQuery: (query: string) => void;
|
||||
setHistoryIndex: (step: number, index: number) => void;
|
||||
setQuery: (query: string, index: number) => void;
|
||||
runQuery: () => void;
|
||||
query: string;
|
||||
query: string,
|
||||
index: number;
|
||||
queryHistory: QueryHistory;
|
||||
server: string;
|
||||
oneLiner?: boolean;
|
||||
@ -18,7 +19,15 @@ export interface QueryEditorProps {
|
||||
}
|
||||
|
||||
const QueryEditor: FC<QueryEditorProps> = ({
|
||||
query, queryHistory, setHistoryIndex, setQuery, runQuery, server, oneLiner = false, autocomplete
|
||||
index,
|
||||
query,
|
||||
queryHistory,
|
||||
setHistoryIndex,
|
||||
setQuery,
|
||||
runQuery,
|
||||
server,
|
||||
oneLiner = false,
|
||||
autocomplete
|
||||
}) => {
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@ -45,7 +54,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||
|
||||
const listenerExtension = EditorView.updateListener.of(editorUpdate => {
|
||||
if (editorUpdate.docChanged) {
|
||||
setQuery(editorUpdate.state.doc.toJSON().map(el => el.trim()).join(""));
|
||||
setQuery(editorUpdate.state.doc.toJSON().map(el => el.trim()).join(""), index);
|
||||
}
|
||||
});
|
||||
|
||||
@ -66,9 +75,9 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||
if (key === "Enter" && ctrlMetaKey) {
|
||||
runQuery();
|
||||
} else if (key === "ArrowUp" && ctrlMetaKey) {
|
||||
setHistoryIndex(-1);
|
||||
setHistoryIndex(-1, index);
|
||||
} else if (key === "ArrowDown" && ctrlMetaKey) {
|
||||
setHistoryIndex(1);
|
||||
setHistoryIndex(1, index);
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,35 @@
|
||||
import React, {FC, useState} from "react";
|
||||
import {Box, TextField, Tooltip, IconButton} from "@mui/material";
|
||||
import SecurityIcon from "@mui/icons-material/Security";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import {AuthDialog} from "../Auth/AuthDialog";
|
||||
|
||||
|
||||
const ServerConfigurator: FC = () => {
|
||||
|
||||
const {serverUrl} = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onSetServer = ({target: {value}}: {target: {value: string}}) => {
|
||||
dispatch({type: "SET_SERVER", payload: value});
|
||||
};
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
return <>
|
||||
<Box display="grid" gridTemplateColumns="1fr auto" gap="4px" alignItems="center" width="100%" mb={2} minHeight={50}>
|
||||
<TextField variant="outlined" fullWidth label="Server URL" value={serverUrl}
|
||||
inputProps={{style: {fontFamily: "Monospace"}}}
|
||||
onChange={onSetServer}/>
|
||||
<Box>
|
||||
<Tooltip title="Request Auth Settings">
|
||||
<IconButton onClick={() => setDialogOpen(true)}>
|
||||
<SecurityIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<AuthDialog open={dialogOpen} onClose={() => setDialogOpen(false)}/>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default ServerConfigurator;
|
@ -1,13 +1,13 @@
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import {getQueryRangeUrl, getQueryUrl} from "../../../api/query-range";
|
||||
import {useAppState} from "../../../state/common/StateContext";
|
||||
import {InstantMetricResult, MetricResult} from "../../../api/types";
|
||||
import {isValidHttpUrl} from "../../../utils/url";
|
||||
import {useAuthState} from "../../../state/auth/AuthStateContext";
|
||||
import {TimeParams} from "../../../types";
|
||||
import {getQueryRangeUrl, getQueryUrl} from "../../../../api/query-range";
|
||||
import {useAppState} from "../../../../state/common/StateContext";
|
||||
import {InstantMetricResult, MetricBase, MetricResult} from "../../../../api/types";
|
||||
import {isValidHttpUrl} from "../../../../utils/url";
|
||||
import {useAuthState} from "../../../../state/auth/AuthStateContext";
|
||||
import {TimeParams} from "../../../../types";
|
||||
|
||||
export const useFetchQuery = (): {
|
||||
fetchUrl?: string,
|
||||
fetchUrl?: string[],
|
||||
isLoading: boolean,
|
||||
graphData?: MetricResult[],
|
||||
liveData?: InstantMetricResult[],
|
||||
@ -40,7 +40,7 @@ export const useFetchQuery = (): {
|
||||
}, [period]);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!fetchUrl) return;
|
||||
if (!fetchUrl?.length) return;
|
||||
setIsLoading(true);
|
||||
setPrevPeriod(period);
|
||||
|
||||
@ -53,14 +53,23 @@ export const useFetchQuery = (): {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(fetchUrl, { headers });
|
||||
const responses = await Promise.all(fetchUrl.map(url => fetch(url, {headers})));
|
||||
const tempData = [];
|
||||
let counter = 1;
|
||||
for await (const response of responses) {
|
||||
if (response.ok) {
|
||||
const resp = await response.json();
|
||||
setError(undefined);
|
||||
displayType === "chart" ? setGraphData(resp.data.result) : setLiveData(resp.data.result);
|
||||
tempData.push(...resp.data.result.map((d: MetricBase) => {
|
||||
d.group = counter;
|
||||
return d;
|
||||
}));
|
||||
counter++;
|
||||
} else {
|
||||
setError((await response.json())?.error);
|
||||
}
|
||||
}
|
||||
displayType === "chart" ? setGraphData(tempData) : setLiveData(tempData);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
}
|
||||
@ -72,14 +81,14 @@ export const useFetchQuery = (): {
|
||||
if (!period) return;
|
||||
if (!serverUrl) {
|
||||
setError("Please enter Server URL");
|
||||
} else if (!query.trim()) {
|
||||
} else if (query.every(q => !q.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);
|
||||
return query.filter(q => q.trim()).map(q => displayType === "chart"
|
||||
? getQueryRangeUrl(serverUrl, q, bufferPeriod, nocache)
|
||||
: getQueryUrl(serverUrl, q, period));
|
||||
} else {
|
||||
setError("Please provide a valid URL");
|
||||
}
|
||||
@ -88,6 +97,7 @@ export const useFetchQuery = (): {
|
||||
|
||||
useEffect(() => {
|
||||
setPrevPeriod(undefined);
|
||||
fetchData();
|
||||
}, [query]);
|
||||
|
||||
// TODO: this should depend on query as well, but need to decide when to do the request.
|
@ -1,148 +0,0 @@
|
||||
import React, {FC, useRef, useState} from "react";
|
||||
import { Accordion, AccordionDetails, AccordionSummary, Box, Grid, IconButton, TextField, Typography, FormControlLabel,
|
||||
Tooltip, Switch } from "@mui/material";
|
||||
import QueryEditor from "./QueryEditor";
|
||||
import {TimeSelector} from "./TimeSelector";
|
||||
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import SecurityIcon from "@mui/icons-material/Security";
|
||||
import {AuthDialog} from "./AuthDialog";
|
||||
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
||||
import Portal from "@mui/material/Portal";
|
||||
import {saveToStorage} from "../../../utils/storage";
|
||||
import {useGraphDispatch, useGraphState} from "../../../state/graph/GraphStateContext";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
const QueryConfigurator: FC = () => {
|
||||
const {serverUrl, query, queryHistory, time: {duration}, queryControls: {autocomplete, nocache}} = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onChangeAutocomplete = () => {
|
||||
dispatch({type: "TOGGLE_AUTOCOMPLETE"});
|
||||
saveToStorage("AUTOCOMPLETE", !autocomplete);
|
||||
};
|
||||
const onChangeCache = () => {
|
||||
dispatch({type: "NO_CACHE"});
|
||||
saveToStorage("NO_CACHE", !nocache);
|
||||
};
|
||||
|
||||
const { yaxis } = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
|
||||
const onChangeYaxisLimits = () => { graphDispatch({type: "TOGGLE_ENABLE_YAXIS_LIMITS"}); };
|
||||
|
||||
const setMinLimit = ({target: {value}}: {target: {value: string}}) => {
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: [+value, yaxis.limits.range[1]]});
|
||||
};
|
||||
const setMaxLimit = ({target: {value}}: {target: {value: string}}) => {
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: [yaxis.limits.range[0], +value]});
|
||||
};
|
||||
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
|
||||
const queryContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onSetDuration = (dur: string) => dispatch({type: "SET_DURATION", payload: dur});
|
||||
|
||||
const onRunQuery = () => {
|
||||
const { values } = queryHistory;
|
||||
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]});
|
||||
};
|
||||
const onSetQuery = (newQuery: string) => {
|
||||
if (query === newQuery) return;
|
||||
dispatch({type: "SET_QUERY", payload: newQuery});
|
||||
};
|
||||
const setHistoryIndex = (step: number) => {
|
||||
const index = queryHistory.index + step;
|
||||
if (index < -1 || index > queryHistory.values.length) return;
|
||||
dispatch({type: "SET_QUERY_HISTORY_INDEX", payload: index});
|
||||
onSetQuery(queryHistory.values[index] || "");
|
||||
};
|
||||
const onSetServer = ({target: {value}}: {target: {value: string}}) => {
|
||||
dispatch({type: "SET_SERVER", payload: value});
|
||||
};
|
||||
|
||||
return <>
|
||||
<Accordion expanded={expanded} onChange={() => setExpanded(prev => !prev)}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon/>}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Box display="flex" alignItems="center" mr={2}><Typography variant="h6" component="h2">Query Configuration</Typography></Box>
|
||||
<Box flexGrow={1} onClick={e => e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}>
|
||||
<Portal disablePortal={!expanded} container={queryContainer.current}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box width="100%">
|
||||
<QueryEditor server={serverUrl} query={query} oneLiner={!expanded} autocomplete={autocomplete}
|
||||
queryHistory={queryHistory} setHistoryIndex={setHistoryIndex} runQuery={onRunQuery} setQuery={onSetQuery}/>
|
||||
</Box>
|
||||
<Tooltip title="Execute Query">
|
||||
<IconButton onClick={onRunQuery} size="large"><PlayCircleOutlineIcon /></IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Portal>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box display="grid" gap={2} gridTemplateRows="auto 1fr">
|
||||
<Box display="flex" alignItems="center">
|
||||
<TextField variant="outlined" fullWidth label="Server URL" value={serverUrl}
|
||||
inputProps={{style: {fontFamily: "Monospace"}}}
|
||||
onChange={onSetServer}/>
|
||||
<Box>
|
||||
<Tooltip title="Request Auth Settings">
|
||||
<IconButton onClick={() => setDialogOpen(true)} size="large"><SecurityIcon/></IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flexGrow={1} ><div ref={queryContainer} />{/* for portal QueryEditor */}</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={8} md={6} >
|
||||
<Box style={{
|
||||
minHeight: "128px",
|
||||
padding: "10px 0",
|
||||
borderRadius: "4px",
|
||||
borderColor: "#b9b9b9",
|
||||
borderStyle: "solid",
|
||||
borderWidth: "1px"}}>
|
||||
<TimeSelector setDuration={onSetDuration} duration={duration}/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Box px={1} display="flex" alignItems="center" minHeight={52}>
|
||||
<Box><FormControlLabel label="Enable autocomplete"
|
||||
control={<Switch size="small" checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/></Box>
|
||||
<Box ml={2}><FormControlLabel label="Enable cache"
|
||||
control={<Switch size="small" checked={!nocache} onChange={onChangeCache}/>}
|
||||
/></Box>
|
||||
<Box ml={2} display="flex" alignItems="center">
|
||||
<FormControlLabel
|
||||
control={<Switch size="small" checked={yaxis.limits.enable} onChange={onChangeYaxisLimits}/>}
|
||||
label="Fix the limits for y-axis"
|
||||
/>
|
||||
{yaxis.limits.enable && <Box display="grid" gridTemplateColumns="120px 120px" gap={1}>
|
||||
<TextField label="Min" type="number" size="small" variant="outlined"
|
||||
defaultValue={yaxis.limits.range[0]} onChange={debounce(setMinLimit, 750)}/>
|
||||
<TextField label="Max" type="number" size="small" variant="outlined"
|
||||
defaultValue={yaxis.limits.range[1]} onChange={debounce(setMaxLimit, 750)}/>
|
||||
</Box>}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<AuthDialog open={dialogOpen} onClose={() => setDialogOpen(false)}/>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default QueryConfigurator;
|
@ -1,9 +1,10 @@
|
||||
import React, {FC, useEffect, useState} from "react";
|
||||
import {Box, FormControlLabel, IconButton, Switch, Tooltip} from "@mui/material";
|
||||
import {Box, FormControlLabel, IconButton, Tooltip} from "@mui/material";
|
||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
||||
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
|
||||
import CircularProgressWithLabel from "../../common/CircularProgressWithLabel";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import CircularProgressWithLabel from "../../../common/CircularProgressWithLabel";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import BasicSwitch from "../../../../theme/switch";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
colorizing: {
|
||||
@ -69,7 +70,7 @@ export const ExecutionControls: FC = () => {
|
||||
|
||||
return <Box display="flex" alignItems="center">
|
||||
{<FormControlLabel
|
||||
control={<Switch size="small" className={classes.colorizing} checked={autoRefresh} onChange={handleChange} />}
|
||||
control={<BasicSwitch className={classes.colorizing} checked={autoRefresh} onChange={handleChange} />}
|
||||
label="Auto-refresh"
|
||||
/>}
|
||||
|
||||
@ -78,7 +79,9 @@ export const ExecutionControls: FC = () => {
|
||||
onClick={() => {iterateDelays();}} />
|
||||
<Tooltip title="Change delay refresh">
|
||||
<Box ml={1}>
|
||||
<IconButton onClick={() => {iterateDelays();}} size="large"><EqualizerIcon style={{color: "white"}} /></IconButton>
|
||||
<IconButton onClick={() => {iterateDelays();}}>
|
||||
<EqualizerIcon style={{color: "white"}} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</>}
|
@ -1,6 +1,6 @@
|
||||
import React, {FC} from "react";
|
||||
import {Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
|
||||
import {supportedDurations} from "../../../utils/time";
|
||||
import {supportedDurations} from "../../../../utils/time";
|
||||
|
||||
export const TimeDurationPopover: FC = () => {
|
||||
|
@ -2,17 +2,33 @@ import React, {FC, useEffect, useState} from "react";
|
||||
import {Box, Popover, TextField, Typography} from "@mui/material";
|
||||
import DateTimePicker from "@mui/lab/DateTimePicker";
|
||||
import {TimeDurationPopover} from "./TimeDurationPopover";
|
||||
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
|
||||
import {checkDurationLimit, dateFromSeconds, formatDateForNativeInput} from "../../../utils/time";
|
||||
import {InlineBtn} from "../../common/InlineBtn";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import {checkDurationLimit, dateFromSeconds, formatDateForNativeInput} from "../../../../utils/time";
|
||||
import {InlineBtn} from "../../../common/InlineBtn";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
|
||||
interface TimeSelectorProps {
|
||||
setDuration: (str: string) => void;
|
||||
duration: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto auto",
|
||||
height: "100%",
|
||||
padding: "18px 14px",
|
||||
borderRadius: "4px",
|
||||
borderColor: "#b9b9b9",
|
||||
borderStyle: "solid",
|
||||
borderWidth: "1px"
|
||||
}
|
||||
});
|
||||
|
||||
export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const [durationStringFocused, setFocused] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
|
||||
const [until, setUntil] = useState<string>();
|
||||
@ -60,7 +76,7 @@ export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return <Box m={1} flexDirection="row" display="flex">
|
||||
return <Box className={classes.container}>
|
||||
{/*setup duration*/}
|
||||
<Box px={1}>
|
||||
<Box>
|
||||
@ -72,7 +88,7 @@ export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||
onFocus={() => {setFocused(true);}}
|
||||
/>
|
||||
</Box>
|
||||
<Box my={2}>
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2">
|
||||
<span aria-owns={open ? "mouse-over-popover" : undefined}
|
||||
aria-haspopup="true"
|
||||
@ -119,7 +135,7 @@ export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box my={2}>
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2">
|
||||
Will be changed to current time for auto-refresh mode.
|
||||
<InlineBtn handler={() => dispatch({type: "RUN_QUERY_TO_NOW"})} text="Switch to now"/>
|
@ -1,20 +1,19 @@
|
||||
import React, {FC} from "react";
|
||||
import {Alert, AppBar, Box, CircularProgress, Fade, Link, Toolbar, Typography} from "@mui/material";
|
||||
import {ExecutionControls} from "./Configurator/ExecutionControls";
|
||||
import {ExecutionControls} from "./Configurator/Time/ExecutionControls";
|
||||
import {DisplayTypeSwitch} from "./Configurator/DisplayTypeSwitch";
|
||||
import GraphView from "./Views/GraphView";
|
||||
import TableView from "./Views/TableView";
|
||||
import {useAppState} from "../../state/common/StateContext";
|
||||
import QueryConfigurator from "./Configurator/QueryConfigurator";
|
||||
import {useFetchQuery} from "./Configurator/useFetchQuery";
|
||||
import QueryConfigurator from "./Configurator/Query/QueryConfigurator";
|
||||
import {useFetchQuery} from "./Configurator/Query/useFetchQuery";
|
||||
import JsonView from "./Views/JsonView";
|
||||
import {UrlCopy} from "./UrlCopy";
|
||||
|
||||
const HomeLayout: FC = () => {
|
||||
|
||||
const {displayType, time: {period}} = useAppState();
|
||||
|
||||
const {fetchUrl, isLoading, liveData, graphData, error} = useFetchQuery();
|
||||
const {isLoading, liveData, graphData, error} = useFetchQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -46,10 +45,9 @@ const HomeLayout: FC = () => {
|
||||
<ExecutionControls/>
|
||||
</Box>
|
||||
<DisplayTypeSwitch/>
|
||||
<UrlCopy url={fetchUrl}/>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Box p={2} display="grid" gridTemplateRows="auto 1fr" gap={"20px"} style={{minHeight: "calc(100vh - 64px)"}}>
|
||||
<Box p={4} display="grid" gridTemplateRows="auto 1fr" gap={"20px"} style={{minHeight: "calc(100vh - 64px)"}}>
|
||||
<Box>
|
||||
<QueryConfigurator/>
|
||||
</Box>
|
||||
@ -68,7 +66,7 @@ const HomeLayout: FC = () => {
|
||||
<CircularProgress/>
|
||||
</Box>
|
||||
</Fade>}
|
||||
{<Box height={"100%"} p={3} bgcolor={"#fff"}>
|
||||
{<Box height={"100%"} bgcolor={"#fff"}>
|
||||
{error &&
|
||||
<Alert color="error" severity="error" style={{fontSize: "14px"}}>
|
||||
{error}
|
||||
|
@ -2,50 +2,51 @@ 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 {useGraphDispatch, useGraphState} from "../../../state/graph/GraphStateContext";
|
||||
import {getHideSeries, getLegendItem, getLimitsYAxis, getSeriesItem, getTimeSeries} from "../../../utils/uPlot";
|
||||
import Legend from "../../Legend/Legend";
|
||||
import {useGraphDispatch} from "../../../state/graph/GraphStateContext";
|
||||
import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series";
|
||||
import {getLimitsYAxis, getTimeSeries} from "../../../utils/uplot/axes";
|
||||
import {LegendItem} from "../../../utils/uplot/types";
|
||||
import {AxisRange} from "../../../state/graph/reducer";
|
||||
import GraphSettings from "../Configurator/Graph/GraphSettings";
|
||||
|
||||
export interface GraphViewProps {
|
||||
data?: MetricResult[];
|
||||
}
|
||||
|
||||
const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
const { yaxis } = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
|
||||
const [dataChart, setDataChart] = useState<uPlotData>([[]]);
|
||||
const [series, setSeries] = useState<uPlotSeries[]>([]);
|
||||
const [legend, setLegend] = useState<LegendItem[]>([]);
|
||||
const [hideSeries, setHideSeries] = useState<string[]>([]);
|
||||
const [valuesLimit, setValuesLimit] = useState<[number, number]>([0, 1]);
|
||||
const [valuesLimit, setValuesLimit] = useState<AxisRange>({"1": [0, 1]});
|
||||
|
||||
const setLimitsYaxis = (values: number[]) => {
|
||||
if (!yaxis.limits.enable || (yaxis.limits.range.every(item => !item))) {
|
||||
const setLimitsYaxis = (values: {[key: string]: number[]}) => {
|
||||
const limits = getLimitsYAxis(values);
|
||||
setValuesLimit(limits);
|
||||
graphDispatch({type: "SET_YAXIS_LIMITS", payload: limits});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeLegend = (label: string, metaKey: boolean) => {
|
||||
setHideSeries(getHideSeries({hideSeries, label, metaKey, series}));
|
||||
const onChangeLegend = (legend: LegendItem, metaKey: boolean) => {
|
||||
setHideSeries(getHideSeries({hideSeries, legend, metaKey, series}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const tempTimes: number[] = [];
|
||||
const tempValues: number[] = [];
|
||||
const tempValues: {[key: string]: number[]} = {};
|
||||
const tempLegend: LegendItem[] = [];
|
||||
const tempSeries: uPlotSeries[] = [];
|
||||
|
||||
data?.forEach(d => {
|
||||
data?.forEach((d) => {
|
||||
const seriesItem = getSeriesItem(d, hideSeries);
|
||||
tempSeries.push(seriesItem);
|
||||
tempLegend.push(getLegendItem(seriesItem));
|
||||
tempLegend.push(getLegendItem(seriesItem, d.group));
|
||||
|
||||
d.values.forEach(v => {
|
||||
tempTimes.push(v[0]);
|
||||
tempValues.push(+v[1]);
|
||||
tempValues[d.group] ? tempValues[d.group].push(+v[1]) : tempValues[d.group] = [+v[1]];
|
||||
});
|
||||
});
|
||||
|
||||
@ -68,7 +69,7 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
data?.forEach(d => {
|
||||
const seriesItem = getSeriesItem(d, hideSeries);
|
||||
tempSeries.push(seriesItem);
|
||||
tempLegend.push(getLegendItem(seriesItem));
|
||||
tempLegend.push(getLegendItem(seriesItem, d.group));
|
||||
});
|
||||
setSeries([{}, ...tempSeries]);
|
||||
setLegend(tempLegend);
|
||||
@ -77,6 +78,7 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
return <>
|
||||
{(data.length > 0)
|
||||
? <div>
|
||||
<GraphSettings/>
|
||||
<LineChart data={dataChart} series={series} metrics={data} limits={valuesLimit}/>
|
||||
<Legend labels={legend} onChange={onChangeLegend}/>
|
||||
</div>
|
||||
|
@ -1,31 +1,48 @@
|
||||
import React, {FC} from "react";
|
||||
import React, {FC, useMemo} from "react";
|
||||
import {hexToRGB} from "../../utils/color";
|
||||
import {useAppState} from "../../state/common/StateContext";
|
||||
import {LegendItem} from "../../utils/uplot/types";
|
||||
import "./legend.css";
|
||||
|
||||
export interface LegendItem {
|
||||
label: string;
|
||||
color: string;
|
||||
checked: boolean;
|
||||
}
|
||||
import {getDashLine} from "../../utils/uplot/helpers";
|
||||
|
||||
export interface LegendProps {
|
||||
labels: LegendItem[];
|
||||
onChange: (legend: string, metaKey: boolean) => void;
|
||||
onChange: (item: LegendItem, metaKey: boolean) => void;
|
||||
}
|
||||
|
||||
export const Legend: FC<LegendProps> = ({labels, onChange}) => {
|
||||
const Legend: FC<LegendProps> = ({labels, onChange}) => {
|
||||
const {query} = useAppState();
|
||||
|
||||
const groups = useMemo(() => {
|
||||
return Array.from(new Set(labels.map(l => l.group)));
|
||||
}, [labels]);
|
||||
|
||||
return <div className="legendWrapper">
|
||||
{labels.map((legendItem: LegendItem) =>
|
||||
{groups.map((group) => <div className="legendGroup" key={group}>
|
||||
<div className="legendGroupTitle">
|
||||
<svg className="legendGroupLine" width="33" height="3" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<line strokeWidth="3" x1="0" y1="0" x2="33" y2="0" stroke="#363636"
|
||||
strokeDasharray={getDashLine(group).join(",")}
|
||||
/>
|
||||
</svg>
|
||||
<b>"{query[group - 1]}"</b>:
|
||||
</div>
|
||||
<div>
|
||||
{labels.filter(l => l.group === group).map((legendItem: LegendItem) =>
|
||||
<div className={legendItem.checked ? "legendItem" : "legendItem legendItemHide"}
|
||||
key={legendItem.label}
|
||||
onClick={(e) => onChange(legendItem.label, e.ctrlKey || e.metaKey)}>
|
||||
key={`${legendItem.group}.${legendItem.label}`}
|
||||
onClick={(e) => onChange(legendItem, 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 className="legendLabel">{legendItem.label}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default Legend;
|
@ -1,12 +1,31 @@
|
||||
.legendWrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
grid-gap: 20px;
|
||||
margin-top: 20px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.legendGroup {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.legendGroupTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.legendGroupLine {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.legendItem {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 4px;
|
||||
align-items: center;
|
||||
grid-gap: 6px;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
padding: 5px 10px;
|
||||
background-color: #FFF;
|
||||
@ -30,9 +49,10 @@
|
||||
border-style: solid;
|
||||
box-sizing: border-box;
|
||||
transition: 0.2s ease;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.legendLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
}
|
@ -1,45 +1,47 @@
|
||||
import React, {FC, useCallback, useEffect, useRef, useState} from "react";
|
||||
import {useAppDispatch, useAppState} from "../../state/common/StateContext";
|
||||
import uPlot, {AlignedData as uPlotData, Options as uPlotOptions, Series as uPlotSeries, Range} from "uplot";
|
||||
import uPlot, {AlignedData as uPlotData, Options as uPlotOptions, Series as uPlotSeries, Range, Scales, Scale} from "uplot";
|
||||
import {useGraphState} from "../../state/graph/GraphStateContext";
|
||||
import {defaultOptions, dragChart, setTooltip} from "../../utils/uPlot";
|
||||
import {defaultOptions} from "../../utils/uplot/helpers";
|
||||
import {dragChart} from "../../utils/uplot/events";
|
||||
import {getAxes} from "../../utils/uplot/axes";
|
||||
import {setTooltip} from "../../utils/uplot/tooltip";
|
||||
import {MetricResult} from "../../api/types";
|
||||
import {limitsDurations} from "../../utils/time";
|
||||
import throttle from "lodash.throttle";
|
||||
import "uplot/dist/uPlot.min.css";
|
||||
import "./tooltip.css";
|
||||
import {AxisRange} from "../../state/graph/reducer";
|
||||
|
||||
export interface LineChartProps {
|
||||
metrics: MetricResult[]
|
||||
metrics: MetricResult[];
|
||||
data: uPlotData;
|
||||
series: uPlotSeries[],
|
||||
limits: [number, number]
|
||||
series: uPlotSeries[];
|
||||
limits: AxisRange;
|
||||
}
|
||||
|
||||
enum typeChartUpdate { xRange = "xRange", yRange = "yRange", data = "data" }
|
||||
enum typeChartUpdate {xRange = "xRange", yRange = "yRange", data = "data"}
|
||||
|
||||
const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {time: {period}} = useAppState();
|
||||
const { yaxis } = useGraphState();
|
||||
const {yaxis} = useGraphState();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const uPlotRef = useRef<HTMLDivElement>(null);
|
||||
const [isPanning, setPanning] = useState(false);
|
||||
const [zoomPos, setZoomPos] = useState(0);
|
||||
const [xRange, setXRange] = useState({ min: period.start, max: period.end });
|
||||
const [xRange, setXRange] = useState({min: period.start, max: period.end});
|
||||
const [uPlotInst, setUPlotInst] = useState<uPlot>();
|
||||
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.className = "u-tooltip";
|
||||
const tooltipIdx = { seriesIdx: 1, dataIdx: 0 };
|
||||
const tooltipOffset = { left: 0, top: 0 };
|
||||
const tooltipIdx = {seriesIdx: 1, dataIdx: 0};
|
||||
const tooltipOffset = {left: 0, top: 0};
|
||||
|
||||
const setScale = ({min, max}: {min: number, max: number}): void => {
|
||||
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 throttledSetScale = useCallback(throttle(setScale, 500), []);
|
||||
|
||||
const setPlotScale = ({u, min, max}: {u: uPlot, min: number, max: number}) => {
|
||||
const setPlotScale = ({u, min, max}: { u: uPlot, min: number, max: number }) => {
|
||||
const delta = (max - min) * 1000;
|
||||
if ((delta < limitsDurations.min) || (delta > limitsDurations.max)) return;
|
||||
u.setScale("x", {min, max});
|
||||
@ -52,12 +54,8 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
||||
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 => {
|
||||
dragChart({u, e, setPanning, setPlotScale, factor});
|
||||
});
|
||||
|
||||
u.over.addEventListener("mousedown", e => dragChart({u, e, setPanning, setPlotScale, factor}));
|
||||
// wheel scroll zoom
|
||||
u.over.addEventListener("wheel", e => {
|
||||
if (!e.ctrlKey && !e.metaKey) return;
|
||||
@ -67,7 +65,7 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
||||
const xVal = u.posToVal(zoomPos, "x");
|
||||
const oxRange = (u.scales.x.max || 0) - (u.scales.x.min || 0);
|
||||
const nxRange = e.deltaY < 0 ? oxRange * factor : oxRange / factor;
|
||||
const min = xVal - (zoomPos/width) * nxRange;
|
||||
const min = xVal - (zoomPos / width) * nxRange;
|
||||
const max = min + nxRange;
|
||||
u.batch(() => setPlotScale({u, min, max}));
|
||||
});
|
||||
@ -88,25 +86,27 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
||||
? setTooltip({u, tooltipIdx, metrics, series, tooltip, tooltipOffset})
|
||||
: tooltip.style.display = "none";
|
||||
};
|
||||
|
||||
const getRangeY = (u: uPlot, min = 0, max = 1): Range.MinMax => {
|
||||
if (yaxis.limits.enable) return yaxis.limits.range;
|
||||
return min && max ? [min - (min * 0.05), max + (max * 0.05)] : limits;
|
||||
const getRangeX = (): Range.MinMax => [xRange.min, xRange.max];
|
||||
const getRangeY = (u: uPlot, min = 0, max = 1, axis: string): Range.MinMax => {
|
||||
if (yaxis.limits.enable) return yaxis.limits.range[axis];
|
||||
return min && max ? [min - (min * 0.05), max + (max * 0.05)] : limits[axis];
|
||||
};
|
||||
|
||||
const getRangeX = (): Range.MinMax => {
|
||||
return [xRange.min, xRange.max];
|
||||
const getScales = (): Scales => {
|
||||
const scales: { [key: string]: { range: Scale.Range } } = {x: {range: getRangeX}};
|
||||
Object.keys(yaxis.limits.range).forEach(axis => {
|
||||
scales[axis] = {range: (u: uPlot, min = 0, max = 1) => getRangeY(u, min, max, axis)};
|
||||
});
|
||||
return scales;
|
||||
};
|
||||
|
||||
const options: uPlotOptions = {
|
||||
...defaultOptions,
|
||||
width: containerRef.current ? containerRef.current.offsetWidth : 400,
|
||||
series,
|
||||
plugins: [{ hooks: { ready: onReadyChart, setCursor, setSeries: seriesFocus }}],
|
||||
scales: {
|
||||
x: { range: getRangeX },
|
||||
y: { range: getRangeY }
|
||||
}
|
||||
axes: getAxes(series),
|
||||
scales: {...getScales()},
|
||||
width: containerRef.current ? containerRef.current.offsetWidth : 400,
|
||||
plugins: [{hooks: {ready: onReadyChart, setCursor, setSeries: seriesFocus}}],
|
||||
};
|
||||
|
||||
const updateChart = (type: typeChartUpdate): void => {
|
||||
@ -116,7 +116,10 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
||||
uPlotInst.scales.x.range = getRangeX;
|
||||
break;
|
||||
case typeChartUpdate.yRange:
|
||||
uPlotInst.scales.y.range = getRangeY;
|
||||
Object.keys(yaxis.limits.range).forEach(axis => {
|
||||
if (!uPlotInst.scales[axis]) return;
|
||||
uPlotInst.scales[axis].range = (u: uPlot, min = 0, max = 1) => getRangeY(u, min, max, axis);
|
||||
});
|
||||
break;
|
||||
case typeChartUpdate.data:
|
||||
uPlotInst.setData(data);
|
||||
@ -125,13 +128,13 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = [], limits}) =>
|
||||
uPlotInst.redraw();
|
||||
};
|
||||
|
||||
useEffect(() => setXRange({ min: period.start, max: period.end }), [period]);
|
||||
useEffect(() => setXRange({min: period.start, max: period.end}), [period]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!uPlotRef.current) return;
|
||||
const u = new uPlot(options, data, uPlotRef.current);
|
||||
setUPlotInst(u);
|
||||
setXRange({ min: period.start, max: period.end });
|
||||
setXRange({min: period.start, max: period.end});
|
||||
return u.destroy;
|
||||
}, [uPlotRef.current, series]);
|
||||
|
||||
|
@ -14,7 +14,7 @@ code {
|
||||
/*Material UI global classes*/
|
||||
|
||||
.MuiAccordionSummary-content {
|
||||
margin: 10px 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* TODO: find better way to override codemirror styles */
|
||||
@ -30,7 +30,7 @@ code {
|
||||
}
|
||||
|
||||
.one-line-scroll .cm-editor {
|
||||
height: 24px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
@ -40,7 +40,7 @@ code {
|
||||
|
||||
.multi-line-scroll .cm-content,
|
||||
.multi-line-scroll .cm-gutters {
|
||||
min-height: 64px !important;
|
||||
min-height: 38px !important;
|
||||
}
|
||||
|
||||
.one-line-scroll .cm-content,
|
||||
|
@ -4,7 +4,7 @@ import {TimeParams, TimePeriod} from "../../types";
|
||||
import {dateFromSeconds, formatDateToLocal, getDateNowUTC, getDurationFromPeriod, getTimeperiodForDuration} from "../../utils/time";
|
||||
import {getFromStorage} from "../../utils/storage";
|
||||
import {getDefaultServer} from "../../utils/default-server-url";
|
||||
import {getQueryStringValue} from "../../utils/query-string";
|
||||
import {getQueryArray, getQueryStringValue} from "../../utils/query-string";
|
||||
|
||||
export interface TimeState {
|
||||
duration: string;
|
||||
@ -19,9 +19,9 @@ export interface QueryHistory {
|
||||
export interface AppState {
|
||||
serverUrl: string;
|
||||
displayType: DisplayType;
|
||||
query: string;
|
||||
query: string[];
|
||||
time: TimeState;
|
||||
queryHistory: QueryHistory,
|
||||
queryHistory: QueryHistory[],
|
||||
queryControls: {
|
||||
autoRefresh: boolean;
|
||||
autocomplete: boolean,
|
||||
@ -32,9 +32,9 @@ export interface AppState {
|
||||
export type Action =
|
||||
| { type: "SET_DISPLAY_TYPE", payload: DisplayType }
|
||||
| { type: "SET_SERVER", payload: string }
|
||||
| { type: "SET_QUERY", payload: string }
|
||||
| { type: "SET_QUERY_HISTORY_INDEX", payload: number }
|
||||
| { type: "SET_QUERY_HISTORY_VALUES", payload: string[] }
|
||||
| { type: "SET_QUERY", payload: string[] }
|
||||
| { type: "SET_QUERY_HISTORY_BY_INDEX", payload: {value: QueryHistory, queryNumber: number} }
|
||||
| { type: "SET_QUERY_HISTORY", payload: QueryHistory[] }
|
||||
| { type: "SET_DURATION", payload: string }
|
||||
| { type: "SET_UNTIL", payload: Date }
|
||||
| { type: "SET_PERIOD", payload: TimePeriod }
|
||||
@ -46,13 +46,13 @@ export type Action =
|
||||
|
||||
const duration = getQueryStringValue("g0.range_input", "1h") as string;
|
||||
const endInput = formatDateToLocal(getQueryStringValue("g0.end_input", getDateNowUTC()) as Date);
|
||||
const query = getQueryStringValue("g0.expr", "") as string;
|
||||
const query = getQueryArray();
|
||||
|
||||
export const initialState: AppState = {
|
||||
serverUrl: getDefaultServer(),
|
||||
displayType: getQueryStringValue("tab", "chart") as DisplayType,
|
||||
query: query, // demo_memory_usage_bytes
|
||||
queryHistory: { index: 0, values: [query] },
|
||||
queryHistory: query.map(q => ({index: 0, values: [q]})),
|
||||
time: {
|
||||
duration,
|
||||
period: getTimeperiodForDuration(duration, new Date(endInput))
|
||||
@ -81,21 +81,15 @@ export function reducer(state: AppState, action: Action): AppState {
|
||||
...state,
|
||||
query: action.payload
|
||||
};
|
||||
case "SET_QUERY_HISTORY_INDEX":
|
||||
case "SET_QUERY_HISTORY":
|
||||
return {
|
||||
...state,
|
||||
queryHistory: {
|
||||
...state.queryHistory,
|
||||
index: action.payload
|
||||
}
|
||||
queryHistory: action.payload
|
||||
};
|
||||
case "SET_QUERY_HISTORY_VALUES":
|
||||
case "SET_QUERY_HISTORY_BY_INDEX":
|
||||
return {
|
||||
...state,
|
||||
queryHistory: {
|
||||
...state.queryHistory,
|
||||
values: action.payload
|
||||
}
|
||||
queryHistory: state.queryHistory.splice(action.payload.queryNumber, 1, action.payload.value)
|
||||
};
|
||||
case "SET_DURATION":
|
||||
return {
|
||||
|
@ -1,7 +1,11 @@
|
||||
export interface AxisRange {
|
||||
[key: string]: [number, number]
|
||||
}
|
||||
|
||||
export interface YaxisState {
|
||||
limits: {
|
||||
enable: boolean,
|
||||
range: [number, number]
|
||||
range: AxisRange
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,11 +15,11 @@ export interface GraphState {
|
||||
|
||||
export type GraphAction =
|
||||
| { type: "TOGGLE_ENABLE_YAXIS_LIMITS" }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: [number, number] }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: {[key: string]: [number, number]} }
|
||||
|
||||
export const initialGraphState: GraphState = {
|
||||
yaxis: {
|
||||
limits: {enable: false, range: [0, 0]}
|
||||
limits: {enable: false, range: {"1": [0, 0]}}
|
||||
}
|
||||
};
|
||||
|
||||
|
25
app/vmui/packages/vmui/src/theme/switch.ts
Normal file
25
app/vmui/packages/vmui/src/theme/switch.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {styled} from "@mui/material/styles";
|
||||
import Switch from "@mui/material/Switch";
|
||||
|
||||
const BasicSwitch = styled(Switch)(() => ({
|
||||
padding: 10,
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 14,
|
||||
"&:before, &:after": {
|
||||
content: "\"\"",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
},
|
||||
"& .MuiSwitch-thumb": {
|
||||
boxShadow: "none",
|
||||
width: 12,
|
||||
height: 12,
|
||||
margin: 4,
|
||||
},
|
||||
}));
|
||||
|
||||
export default BasicSwitch;
|
56
app/vmui/packages/vmui/src/theme/theme.ts
Normal file
56
app/vmui/packages/vmui/src/theme/theme.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {createTheme} from "@mui/material/styles";
|
||||
|
||||
const THEME = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#3F51B5"
|
||||
},
|
||||
secondary: {
|
||||
main: "#F50057"
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiSwitch: {
|
||||
defaultProps: {
|
||||
color: "secondary"
|
||||
}
|
||||
},
|
||||
MuiAccordion: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px;"
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
elevation3: {
|
||||
boxShadow: "rgba(0, 0, 0, 0.2) 0px 3px 8px;"
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
defaultProps: {
|
||||
size: "large",
|
||||
},
|
||||
styleOverrides: {
|
||||
sizeLarge: {
|
||||
borderRadius: "20%",
|
||||
height: "40px",
|
||||
width: "41px"
|
||||
},
|
||||
sizeMedium: {
|
||||
borderRadius: "20%",
|
||||
},
|
||||
sizeSmall: {
|
||||
borderRadius: "20%",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
typography: {
|
||||
"fontSize": 10
|
||||
}
|
||||
});
|
||||
|
||||
export default THEME;
|
@ -2,10 +2,9 @@ import qs from "qs";
|
||||
import get from "lodash.get";
|
||||
|
||||
const stateToUrlParams = {
|
||||
"query": "g0.expr",
|
||||
"time.duration": "g0.range_input",
|
||||
"time.period.date": "g0.end_input",
|
||||
"time.period.step": "g0.step_input",
|
||||
"time.duration": "range_input",
|
||||
"time.period.date": "end_input",
|
||||
"time.period.step": "step_input",
|
||||
"displayType": "tab"
|
||||
};
|
||||
|
||||
@ -39,15 +38,19 @@ export const setQueryStringWithoutPageReload = (qsValue: string): void => {
|
||||
|
||||
export const setQueryStringValue = (newValue: Record<string, unknown>): void => {
|
||||
const queryMap = new Map(Object.entries(stateToUrlParams));
|
||||
const query = get(newValue, "query", "") as string[];
|
||||
const newQsValue: string[] = [];
|
||||
query.forEach((q, i) => {
|
||||
queryMap.forEach((queryKey, stateKey) => {
|
||||
// const queryKeyEncoded = encodeURIComponent(queryKey);
|
||||
const value = get(newValue, stateKey, "") as string;
|
||||
if (value) {
|
||||
const valueEncoded = encodeURIComponent(value);
|
||||
newQsValue.push(`${queryKey}=${valueEncoded}`);
|
||||
newQsValue.push(`g${i}.${queryKey}=${valueEncoded}`);
|
||||
}
|
||||
});
|
||||
newQsValue.push(`g${i}.expr=${q}`);
|
||||
});
|
||||
|
||||
setQueryStringWithoutPageReload(newQsValue.join("&"));
|
||||
};
|
||||
|
||||
@ -59,3 +62,10 @@ export const getQueryStringValue = (
|
||||
const values = qs.parse(queryString, { ignoreQueryPrefix: true });
|
||||
return get(values, key, defaultValue || "");
|
||||
};
|
||||
|
||||
export const getQueryArray = (): string[] => {
|
||||
const queryLength = window.location.search.match(/g\d+.expr/gmi)?.length || 1;
|
||||
return new Array(queryLength).fill(1).map((q, i) => {
|
||||
return getQueryStringValue(`g${i}.expr`, "") as string;
|
||||
});
|
||||
};
|
||||
|
@ -1,147 +0,0 @@
|
||||
import uPlot, {Series as uPlotSeries, Series} from "uplot";
|
||||
import {getColorFromString} from "./color";
|
||||
import dayjs from "dayjs";
|
||||
import {MetricResult} from "../api/types";
|
||||
import {LegendItem} from "../components/Legend/Legend";
|
||||
import {getNameForMetric} from "./metric";
|
||||
import {getMaxFromArray, getMinFromArray} from "./math";
|
||||
import {roundTimeSeconds} from "./time";
|
||||
import numeral from "numeral";
|
||||
|
||||
interface SetupTooltip {
|
||||
u: uPlot,
|
||||
metrics: MetricResult[],
|
||||
series: Series[],
|
||||
tooltip: HTMLDivElement,
|
||||
tooltipOffset: {left: number, top: number},
|
||||
tooltipIdx: {seriesIdx: number, dataIdx: number}
|
||||
}
|
||||
|
||||
interface HideSeriesArgs {
|
||||
hideSeries: string[],
|
||||
label: string,
|
||||
metaKey: boolean,
|
||||
series: Series[]
|
||||
}
|
||||
|
||||
interface DragArgs {
|
||||
e: MouseEvent,
|
||||
u: uPlot,
|
||||
factor: number,
|
||||
setPanning: (enable: boolean) => void,
|
||||
setPlotScale: ({u, min, max}: {u: uPlot, min: number, max: number}) => void
|
||||
}
|
||||
|
||||
const stub = (): null => null;
|
||||
|
||||
export const defaultOptions = {
|
||||
height: 500,
|
||||
legend: { show: false },
|
||||
axes: [
|
||||
{ space: 80 },
|
||||
{
|
||||
show: true,
|
||||
font: "10px Arial",
|
||||
values: (self: uPlot, ticks: number[]): (string | number)[] => ticks.map(n => n > 1000 ? numeral(n).format("0.0a") : n)
|
||||
}
|
||||
],
|
||||
cursor: {
|
||||
drag: { x: false, y: false },
|
||||
focus: { prox: 30 },
|
||||
bind: { mouseup: stub, mousedown: stub, click: stub, dblclick: stub, mouseenter: stub }
|
||||
},
|
||||
};
|
||||
|
||||
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 = metrics[seriesIdx - 1]?.metric || {};
|
||||
const color = getColorFromString(series[seriesIdx].label || "");
|
||||
|
||||
const {width, height} = u.over.getBoundingClientRect();
|
||||
const top = u.valToPos((dataSeries || 0), "y");
|
||||
const lft = u.valToPos(dataTime, "x");
|
||||
const {width: tooltipWidth, height: tooltipHeight} = tooltip.getBoundingClientRect();
|
||||
const overflowX = lft + tooltipWidth >= width;
|
||||
const overflowY = top + tooltipHeight >= height;
|
||||
|
||||
tooltip.style.display = "grid";
|
||||
tooltip.style.top = `${tooltipOffset.top + top + 10 - (overflowY ? tooltipHeight + 10 : 0)}px`;
|
||||
tooltip.style.left = `${tooltipOffset.left + lft + 10 - (overflowX ? tooltipWidth + 20 : 0)}px`;
|
||||
const date = dayjs(new Date(dataTime * 1000)).format("YYYY-MM-DD HH:mm:ss:SSS (Z)");
|
||||
const info = Object.keys(metric).filter(k => k !== "__name__").map(k => `<div><b>${k}</b>: ${metric[k]}</div>`).join("");
|
||||
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 class="u-tooltip-data__value">${dataSeries}</b>
|
||||
</div>
|
||||
<div class="u-tooltip__info">${info}</div>`;
|
||||
};
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
export const getTimeSeries = (times: number[]): number[] => {
|
||||
const allTimes = Array.from(new Set(times)).sort((a,b) => a-b);
|
||||
const step = getMinFromArray(allTimes.map((t, i) => allTimes[i + 1] - t));
|
||||
const length = allTimes.length;
|
||||
const startTime = allTimes[0] || 0;
|
||||
return new Array(length).fill(startTime).map((d, i) => roundTimeSeconds(d + (step * i)));
|
||||
};
|
||||
|
||||
export const getLimitsYAxis = (values: number[]): [number, number] => {
|
||||
const min = getMinFromArray(values);
|
||||
const max = getMaxFromArray(values);
|
||||
return [min - (min * 0.05), max + (max * 0.05)];
|
||||
};
|
||||
|
||||
export const getSeriesItem = (d: MetricResult, hideSeries: string[]): Series => {
|
||||
const label = getNameForMetric(d);
|
||||
return {
|
||||
label,
|
||||
width: 1.5,
|
||||
stroke: getColorFromString(label),
|
||||
show: !hideSeries.includes(label),
|
||||
scale: "y"
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegendItem = (s: uPlotSeries): LegendItem => ({
|
||||
label: s.label || "",
|
||||
color: s.stroke as string,
|
||||
checked: s.show || false
|
||||
});
|
||||
|
||||
export const dragChart = ({e, factor = 0.85, u, setPanning, setPlotScale}: DragArgs): void => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
setPanning(true);
|
||||
const leftStart = e.clientX;
|
||||
const xUnitsPerPx = u.posToVal(1, "x") - u.posToVal(0, "x");
|
||||
const scXMin = u.scales.x.min || 0;
|
||||
const scXMax = u.scales.x.max || 0;
|
||||
|
||||
const mouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const dx = xUnitsPerPx * ((e.clientX - leftStart) * factor);
|
||||
setPlotScale({u, min: scXMin - dx, max: scXMax - dx});
|
||||
};
|
||||
|
||||
const mouseUp = () => {
|
||||
setPanning(false);
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
document.removeEventListener("mouseup", mouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
document.addEventListener("mouseup", mouseUp);
|
||||
};
|
30
app/vmui/packages/vmui/src/utils/uplot/axes.ts
Normal file
30
app/vmui/packages/vmui/src/utils/uplot/axes.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Axis, Series} from "uplot";
|
||||
import {getMaxFromArray, getMinFromArray} from "../math";
|
||||
import {roundTimeSeconds} from "../time";
|
||||
import {AxisRange} from "../../state/graph/reducer";
|
||||
import {formatTicks} from "./helpers";
|
||||
|
||||
export const getAxes = (series: Series[]): Axis[] => Array.from(new Set(series.map(s => s.scale))).map(a => {
|
||||
const axis = {scale: a, show: true, font: "10px Arial", values: formatTicks};
|
||||
if (!a) return {space: 80};
|
||||
if (!(Number(a) % 2)) return {...axis, side: 1};
|
||||
return axis;
|
||||
});
|
||||
|
||||
export const getTimeSeries = (times: number[]): number[] => {
|
||||
const allTimes = Array.from(new Set(times)).sort((a, b) => a - b);
|
||||
const step = getMinFromArray(allTimes.map((t, i) => allTimes[i + 1] - t));
|
||||
const startTime = allTimes[0] || 0;
|
||||
return new Array(allTimes.length).fill(startTime).map((d, i) => roundTimeSeconds(d + (step * i)));
|
||||
};
|
||||
|
||||
export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange => {
|
||||
const result: AxisRange = {};
|
||||
for (const key in values) {
|
||||
const numbers = values[key];
|
||||
const min = getMinFromArray(numbers);
|
||||
const max = getMaxFromArray(numbers);
|
||||
result[key] = [min - (min * 0.05), max + (max * 0.05)];
|
||||
}
|
||||
return result;
|
||||
};
|
25
app/vmui/packages/vmui/src/utils/uplot/events.ts
Normal file
25
app/vmui/packages/vmui/src/utils/uplot/events.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {DragArgs} from "./types";
|
||||
|
||||
export const dragChart = ({e, factor = 0.85, u, setPanning, setPlotScale}: DragArgs): void => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
setPanning(true);
|
||||
const leftStart = e.clientX;
|
||||
const xUnitsPerPx = u.posToVal(1, "x") - u.posToVal(0, "x");
|
||||
const scXMin = u.scales.x.min || 0;
|
||||
const scXMax = u.scales.x.max || 0;
|
||||
|
||||
const mouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const dx = xUnitsPerPx * ((e.clientX - leftStart) * factor);
|
||||
setPlotScale({u, min: scXMin - dx, max: scXMax - dx});
|
||||
};
|
||||
const mouseUp = () => {
|
||||
setPanning(false);
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
document.removeEventListener("mouseup", mouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
document.addEventListener("mouseup", mouseUp);
|
||||
};
|
34
app/vmui/packages/vmui/src/utils/uplot/helpers.ts
Normal file
34
app/vmui/packages/vmui/src/utils/uplot/helpers.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import uPlot from "uplot";
|
||||
import numeral from "numeral";
|
||||
import {getColorFromString} from "../color";
|
||||
|
||||
export const defaultOptions = {
|
||||
height: 500,
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
cursor: {
|
||||
drag: {
|
||||
x: false,
|
||||
y: false
|
||||
},
|
||||
focus: {
|
||||
prox: 30
|
||||
},
|
||||
bind: {
|
||||
mouseup: (): null => null,
|
||||
mousedown: (): null => null,
|
||||
click: (): null => null,
|
||||
dblclick: (): null => null,
|
||||
mouseenter: (): null => null
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const formatTicks = (u: uPlot, ticks: number[]): (string | number)[] => {
|
||||
return ticks.map(n => n > 1000 ? numeral(n).format("0.0a") : n);
|
||||
};
|
||||
|
||||
export const getColorLine = (scale: number, label: string): string => getColorFromString(`${scale}${label}`);
|
||||
|
||||
export const getDashLine = (group: number): number[] => group <= 1 ? [] : [group*4, group*1.2];
|
41
app/vmui/packages/vmui/src/utils/uplot/series.ts
Normal file
41
app/vmui/packages/vmui/src/utils/uplot/series.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {MetricResult} from "../../api/types";
|
||||
import {Series} from "uplot";
|
||||
import {getNameForMetric} from "../metric";
|
||||
import {LegendItem} from "./types";
|
||||
import {getColorLine, getDashLine} from "./helpers";
|
||||
import {HideSeriesArgs} from "./types";
|
||||
|
||||
export const getSeriesItem = (d: MetricResult, hideSeries: string[]): Series => {
|
||||
const label = getNameForMetric(d);
|
||||
return {
|
||||
label,
|
||||
dash: getDashLine(d.group),
|
||||
width: 1.5,
|
||||
stroke: getColorLine(d.group, label),
|
||||
show: !includesHideSeries(label, d.group, hideSeries),
|
||||
scale: String(d.group)
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegendItem = (s: Series, group: number): LegendItem => ({
|
||||
group,
|
||||
label: s.label || "",
|
||||
color: s.stroke as string,
|
||||
checked: s.show || false
|
||||
});
|
||||
|
||||
export const getHideSeries = ({hideSeries, legend, metaKey, series}: HideSeriesArgs): string[] => {
|
||||
const label = `${legend.group}.${legend.label}`;
|
||||
const include = includesHideSeries(legend.label, legend.group, hideSeries);
|
||||
const labels = series.map(s => `${s.scale}.${s.label}`);
|
||||
if (metaKey && include) {
|
||||
return [...labels.filter(l => l !== label)];
|
||||
} else if (metaKey && !include) {
|
||||
return hideSeries.length >= series.length - 1 ? [] : [...labels.filter(l => l !== label)];
|
||||
}
|
||||
return include ? hideSeries.filter(l => l !== label) : [...hideSeries, label];
|
||||
};
|
||||
|
||||
export const includesHideSeries = (label: string, group: string | number, hideSeries: string[]): boolean => {
|
||||
return hideSeries.includes(`${group}.${label}`);
|
||||
};
|
30
app/vmui/packages/vmui/src/utils/uplot/tooltip.ts
Normal file
30
app/vmui/packages/vmui/src/utils/uplot/tooltip.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import dayjs from "dayjs";
|
||||
import {SetupTooltip} from "./types";
|
||||
import {getColorLine} from "./helpers";
|
||||
|
||||
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 = metrics[seriesIdx - 1]?.metric || {};
|
||||
const color = getColorLine(Number(series[seriesIdx].scale || 0), series[seriesIdx].label || "");
|
||||
|
||||
const {width, height} = u.over.getBoundingClientRect();
|
||||
const top = u.valToPos((dataSeries || 0), series[seriesIdx]?.scale || "1");
|
||||
const lft = u.valToPos(dataTime, "x");
|
||||
const {width: tooltipWidth, height: tooltipHeight} = tooltip.getBoundingClientRect();
|
||||
const overflowX = lft + tooltipWidth >= width;
|
||||
const overflowY = top + tooltipHeight >= height;
|
||||
|
||||
tooltip.style.display = "grid";
|
||||
tooltip.style.top = `${tooltipOffset.top + top + 10 - (overflowY ? tooltipHeight + 10 : 0)}px`;
|
||||
tooltip.style.left = `${tooltipOffset.left + lft + 10 - (overflowX ? tooltipWidth + 20 : 0)}px`;
|
||||
const date = dayjs(new Date(dataTime * 1000)).format("YYYY-MM-DD HH:mm:ss:SSS (Z)");
|
||||
const info = Object.keys(metric).filter(k => k !== "__name__").map(k => `<div><b>${k}</b>: ${metric[k]}</div>`).join("");
|
||||
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 class="u-tooltip-data__value">${dataSeries}</b>
|
||||
</div>
|
||||
<div class="u-tooltip__info">${info}</div>`;
|
||||
};
|
39
app/vmui/packages/vmui/src/utils/uplot/types.ts
Normal file
39
app/vmui/packages/vmui/src/utils/uplot/types.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import uPlot, {Series} from "uplot";
|
||||
import {MetricResult} from "../../api/types";
|
||||
|
||||
export interface SetupTooltip {
|
||||
u: uPlot,
|
||||
metrics: MetricResult[],
|
||||
series: Series[],
|
||||
tooltip: HTMLDivElement,
|
||||
tooltipOffset: {
|
||||
left: number,
|
||||
top: number
|
||||
},
|
||||
tooltipIdx: {
|
||||
seriesIdx: number,
|
||||
dataIdx: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface HideSeriesArgs {
|
||||
hideSeries: string[],
|
||||
legend: LegendItem,
|
||||
metaKey: boolean,
|
||||
series: Series[]
|
||||
}
|
||||
|
||||
export interface DragArgs {
|
||||
e: MouseEvent,
|
||||
u: uPlot,
|
||||
factor: number,
|
||||
setPanning: (enable: boolean) => void,
|
||||
setPlotScale: ({u, min, max}: { u: uPlot, min: number, max: number }) => void
|
||||
}
|
||||
|
||||
export interface LegendItem {
|
||||
group: number;
|
||||
label: string;
|
||||
color: string;
|
||||
checked: boolean;
|
||||
}
|
@ -1217,7 +1217,8 @@ Consider setting the following command-line flags:
|
||||
* `-snapshotAuthKey` for protecting `/snapshot*` endpoints. See [how to work with snapshots](#how-to-work-with-snapshots).
|
||||
* `-forceMergeAuthKey` for protecting `/internal/force_merge` endpoint. See [force merge docs](#forced-merge).
|
||||
* `-search.resetCacheAuthKey` for protecting `/internal/resetRollupResultCache` endpoint. See [backfilling](#backfilling) for more details.
|
||||
* `-configAuthKey` for pretecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
* `-configAuthKey` for protecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
- `-pprofAuthKey` for protecting `/debug/pprof/*` endpoints, which can be used for [profiling](#profiling).
|
||||
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`.
|
||||
|
@ -1221,7 +1221,8 @@ Consider setting the following command-line flags:
|
||||
* `-snapshotAuthKey` for protecting `/snapshot*` endpoints. See [how to work with snapshots](#how-to-work-with-snapshots).
|
||||
* `-forceMergeAuthKey` for protecting `/internal/force_merge` endpoint. See [force merge docs](#forced-merge).
|
||||
* `-search.resetCacheAuthKey` for protecting `/internal/resetRollupResultCache` endpoint. See [backfilling](#backfilling) for more details.
|
||||
* `-configAuthKey` for pretecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
* `-configAuthKey` for protecting `/config` endpoint, since it may contain sensitive information such as passwords.
|
||||
- `-pprofAuthKey` for protecting `/debug/pprof/*` endpoints, which can be used for [profiling](#profiling).
|
||||
|
||||
Explicitly set internal network interface for TCP and UDP ports for data ingestion with Graphite and OpenTSDB formats.
|
||||
For example, substitute `-graphiteListenAddr=:2003` with `-graphiteListenAddr=<internal_iface_ip>:2003`.
|
||||
|
@ -84,8 +84,12 @@ func Serve(addr string, rh RequestHandler) {
|
||||
if *tlsEnable {
|
||||
scheme = "https"
|
||||
}
|
||||
logger.Infof("starting http server at %s://%s/", scheme, addr)
|
||||
logger.Infof("pprof handlers are exposed at %s://%s/debug/pprof/", scheme, addr)
|
||||
hostAddr := addr
|
||||
if strings.HasPrefix(hostAddr, ":") {
|
||||
hostAddr = "127.0.0.1" + hostAddr
|
||||
}
|
||||
logger.Infof("starting http server at %s://%s/", scheme, hostAddr)
|
||||
logger.Infof("pprof handlers are exposed at %s://%s/debug/pprof/", scheme, hostAddr)
|
||||
lnTmp, err := netutil.NewTCPListener(scheme, addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start http server at %s: %s", addr, err)
|
||||
|
Loading…
Reference in New Issue
Block a user