diff --git a/package-lock.json b/package-lock.json index 1768634235..3556238d94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2403,6 +2403,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bootstrap": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.2.1.tgz", + "integrity": "sha512-tt/7vIv3Gm2mnd/WeDx36nfGGHleil0Wg8IeB7eMrVkY0fZ5iTaBisSh8oNANc2IBsCc6vCgCNTIM/IEN0+50Q==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2872,6 +2877,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", @@ -3973,6 +3983,14 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -4067,6 +4085,23 @@ "safer-buffer": "^2.1.0" } }, + "echarts": { + "version": "4.2.0-rc.2", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-4.2.0-rc.2.tgz", + "integrity": "sha512-5Y4Kyi4eNsRM9Cnl7Q8C6PFVjznBJv1VIiMm/VSQ9zyqeo+ce1695GqUd9v4zfVx+Ow1gnwMJX67h0FNvarScw==", + "requires": { + "zrender": "4.0.5" + } + }, + "echarts-for-react": { + "version": "2.0.15-beta.0", + "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-2.0.15-beta.0.tgz", + "integrity": "sha512-brgogznteCirkvOvl/ZOozkpvBt612LBtDiSGE/pANTIO2+YyGpNfFXAIjEIDAY6yTvgdj+Ei7JISAfflXEn5Q==", + "requires": { + "fast-deep-equal": "^2.0.1", + "size-sensor": "^0.2.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8329,6 +8364,11 @@ "topo": "2.x.x" } }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -8650,6 +8690,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8682,6 +8732,11 @@ "lodash._reinterpolate": "~3.0.0" } }, + "lodash.tonumber": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz", + "integrity": "sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -9756,6 +9811,11 @@ "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.1.0.tgz", "integrity": "sha512-CPCdcFxx7fEcDMWTDjXe2Wypt4JuMt4q5Q2UrpTcyBBkLiCIyPEh/mCGmUWIcNkKGyXwQ9Y2wVhlKm6ketiBNQ==" }, + "popper.js": { + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz", + "integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==" + }, "portfinder": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", @@ -13601,6 +13661,20 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.2.tgz", "integrity": "sha512-7kEBKwU9R8fKnZJBRa5RSIfay4KJwnYvKB6gODGicUmDSAhQJ7Tdnll5S0RLtYrzRfMVXlqYw61rzrSpP4ThLQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-popper": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz", + "integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=", + "requires": { + "popper.js": "^1.14.1", + "prop-types": "^15.6.1" + } + }, "react-scripts": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.3.tgz", @@ -13656,6 +13730,48 @@ "workbox-webpack-plugin": "3.6.3" } }, + "react-transition-group": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.3.tgz", + "integrity": "sha512-2DGFck6h99kLNr8pOFk+z4Soq3iISydwOFeeEVPjTN6+Y01CmvbWmnN02VuTWyFdnRtIDPe+wy2q6Ui8snBPZg==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + }, + "reactstrap": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-7.1.0.tgz", + "integrity": "sha512-wtc4RkgnGn1TsZ0AxOZ2OqT+b8YmCWZj/tErPujWLepxzlEEhveZGC+uDerdaHVSAzJUP2DTk605iper7hutQQ==", + "requires": { + "@babel/runtime": "^7.2.0", + "classnames": "^2.2.3", + "lodash.isfunction": "^3.0.9", + "lodash.isobject": "^3.0.2", + "lodash.tonumber": "^4.0.3", + "prop-types": "^15.5.8", + "react-lifecycles-compat": "^3.0.4", + "react-popper": "^0.10.4", + "react-transition-group": "^2.3.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -14986,6 +15102,11 @@ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==" }, + "size-sensor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-0.2.2.tgz", + "integrity": "sha512-dL/IdBhGDvCHlxQgxryqJNEnSWQz4xvKntsW028CgaJLBLSw8rpi7oUVSM4+xnaHbH+BFkXz6H5aMStfH8v2Pg==" + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -17137,6 +17258,11 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "zrender": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-4.0.5.tgz", + "integrity": "sha512-SintgipGEJPT9Sz2ABRoE4ZD7Yzy7oR7j7KP6H+C9FlbHWnLUfGVK7E8UV27pGwlxAMB0EsnrqhXx5XjAfv/KA==" } } } diff --git a/package.json b/package.json index b5c6f1e0f0..f7fa704b33 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,14 @@ "version": "0.1.0", "private": true, "dependencies": { + "bootstrap": "^4.2.1", + "echarts": "^4.2.0-rc.2", + "echarts-for-react": "^2.0.15-beta.0", + "jquery": "^3.3.1", "react": "^16.7.0", "react-dom": "^16.7.0", - "react-scripts": "2.1.3" + "react-scripts": "2.1.3", + "reactstrap": "^7.1.0" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000000..6fa5d36c2f --- /dev/null +++ b/src/App.css @@ -0,0 +1,30 @@ +body { + padding-top: 10px; /* TODO remove */ +} + +.expression-input { + margin-bottom: 10px; +} + +.alert.alert-danger { + margin-bottom: 10px; +} + +.nav-tabs .nav-link { + cursor: pointer; +} + +.tab-content { + border-left: 1px solid #dee2e6; + border-right: 1px solid #dee2e6; + border-bottom: 1px solid #dee2e6; +} + +.data-table.table { + white-space: pre; + margin: 2px 0 2px 0; +} + +.data-table > tbody > tr > td { + padding: 6px 16px 6px 16px; +} diff --git a/src/App.js b/src/App.js index 9c723ac188..5913efd549 100755 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,14 @@ import React, { Component } from 'react'; +import { Alert, Button, Col, Container, InputGroup, InputGroupAddon, Input, Nav, NavItem, NavLink, Row, TabContent, TabPane, Table } from 'reactstrap'; +import ReactEcharts from 'echarts-for-react'; +import './App.css' class App extends Component { render() { return ( - + + + ); } } @@ -46,7 +51,7 @@ class PanelList extends Component { return ( <> {this.state.panels} - + ); } @@ -58,7 +63,7 @@ class Panel extends Component { this.state = { expr: '', - type: 'graph', // TODO enum? + type: 'table', // TODO enum? range: '1h', endTime: null, data: null, @@ -72,9 +77,38 @@ class Panel extends Component { } execute() { + if (this.state.expr === "") { + return; + } + this.setState({loading: true}); - fetch('http://demo.robustperception.io:9090/api/v1/query?query=' + encodeURIComponent(this.state.expr)) + let url = new URL('http://demo.robustperception.io:9090/');//window.location.href); + let params = { + 'query': this.state.expr, + }; + switch (this.state.type) { + case 'graph': + url.pathname = '/api/v1/query_range' + Object.assign(params, { + start: '1549118381', + end: '1549119381', + step: 10, + }) + // TODO path prefix here and elsewhere. + break; + case 'table': + url.pathname = '/api/v1/query' + Object.assign(params, { + time: '1549119381', + }) + break; + default: + // TODO + } + Object.keys(params).forEach(key => url.searchParams.append(key, params[key])) + + fetch(url) .then(resp => { if (resp.ok) { return resp.json(); @@ -85,6 +119,7 @@ class Panel extends Component { }) .then(json => this.setState({ + error: null, data: json.data, loading: false, }) @@ -103,13 +138,58 @@ class Panel extends Component { render() { return ( -
- - {this.state.loading && "Loading..."} - {this.state.error} - - -
+ <> + + + + + + + + {this.state.loading && "Loading..."} + + + + + {this.state.error && {this.state.error.toString()}} + + + + + + + + + + + + + + + + + + + + + ); } } @@ -128,34 +208,167 @@ class ExpressionInput extends Component { } } + numRows() { + // TODO: Not ideal. This doesn't handle long lines. + return this.props.value.split(/\r\n|\r|\n/).length; + } + render() { return ( -
-