diff --git a/heimdall-api/src/main/resources/liquibase/changelog/20181018114923-add-privileges-metrics-to-role-admin.xml b/heimdall-api/src/main/resources/liquibase/changelog/20181018114923-add-privileges-metrics-to-role-admin.xml new file mode 100644 index 00000000..c922aed1 --- /dev/null +++ b/heimdall-api/src/main/resources/liquibase/changelog/20181018114923-add-privileges-metrics-to-role-admin.xml @@ -0,0 +1,14 @@ + + + + + + + + + INSERT INTO roles_privileges (role_id, privilege_id) select rol.id, priv.id from roles rol, (select PRIVILEGES.id from PRIVILEGES except select privilege_id from roles_privileges where role_id = 1) priv where rol.name = 'ADMIN' + + + diff --git a/heimdall-api/src/main/resources/liquibase/master.xml b/heimdall-api/src/main/resources/liquibase/master.xml index ec8dc14d..547b3ad4 100644 --- a/heimdall-api/src/main/resources/liquibase/master.xml +++ b/heimdall-api/src/main/resources/liquibase/master.xml @@ -27,8 +27,9 @@ + - + diff --git a/heimdall-core/src/main/java/br/com/conductor/heimdall/core/util/MongoLogConnector.java b/heimdall-core/src/main/java/br/com/conductor/heimdall/core/util/MongoLogConnector.java index d2b312c8..64e05047 100644 --- a/heimdall-core/src/main/java/br/com/conductor/heimdall/core/util/MongoLogConnector.java +++ b/heimdall-core/src/main/java/br/com/conductor/heimdall/core/util/MongoLogConnector.java @@ -194,9 +194,11 @@ private Query prepareRange(Query query, Periods date) { switch(date) { case TODAY: { query.field(insertedOnDate).containsIgnoreCase(LocalDate.now().format(DateTimeFormatter.ISO_DATE)); + break; } case YESTERDAY: { query.field(insertedOnDate).containsIgnoreCase(LocalDate.now().minusDays(1).format(DateTimeFormatter.ISO_DATE)); + break; } case THIS_WEEK: { Map week = CalendarUtils.firstAndLastDaysOfWeek(LocalDate.now()); diff --git a/heimdall-frontend/package-lock.json b/heimdall-frontend/package-lock.json index 06beb364..4858e55c 100644 --- a/heimdall-frontend/package-lock.json +++ b/heimdall-frontend/package-lock.json @@ -3121,6 +3121,30 @@ "jsbn": "~0.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" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4267,8 +4291,8 @@ "bundled": true, "optional": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ansi-regex": { @@ -4323,7 +4347,7 @@ "bundled": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "block-stream": { @@ -4559,10 +4583,10 @@ "version": "3.1.3", "bundled": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "hoek": { @@ -4640,7 +4664,7 @@ "bundled": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -4811,10 +4835,10 @@ "bundled": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -4896,7 +4920,7 @@ "version": "1.0.9", "bundled": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "sshpk": { @@ -4904,15 +4928,15 @@ "bundled": true, "optional": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -4926,16 +4950,16 @@ "version": "1.0.2", "bundled": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { "version": "1.0.1", "bundled": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "^5.0.1" } }, "stringstream": { @@ -4947,7 +4971,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -4984,7 +5008,7 @@ "bundled": true, "optional": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -4992,7 +5016,7 @@ "bundled": true, "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -10196,7 +10220,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-2.1.0.tgz", "integrity": "sha1-ISINnGOvx3qMkhEeARr3Bc4MaQE=", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } } } @@ -10217,7 +10241,7 @@ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", "requires": { - "lodash.keys": "3.1.2" + "lodash.keys": "^3.1.2" } } } @@ -11509,6 +11533,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "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", @@ -13288,6 +13317,11 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" } } + }, + "zrender": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-4.0.5.tgz", + "integrity": "sha512-SintgipGEJPT9Sz2ABRoE4ZD7Yzy7oR7j7KP6H+C9FlbHWnLUfGVK7E8UV27pGwlxAMB0EsnrqhXx5XjAfv/KA==" } } } diff --git a/heimdall-frontend/package.json b/heimdall-frontend/package.json index 1e8d070e..adbf1101 100644 --- a/heimdall-frontend/package.json +++ b/heimdall-frontend/package.json @@ -11,6 +11,8 @@ "cross-fetch": "^1.1.1", "css-animation": "^1.4.1", "dotenv": "^5.0.1", + "echarts": "^4.2.0-rc.2", + "echarts-for-react": "^2.0.15-beta.0", "less": "2.7.3", "lodash.flow": "^3.5.0", "moment": "^2.22.1", diff --git a/heimdall-frontend/src/actions/analytics.js b/heimdall-frontend/src/actions/analytics.js new file mode 100644 index 00000000..5ca668bb --- /dev/null +++ b/heimdall-frontend/src/actions/analytics.js @@ -0,0 +1,62 @@ +import { AnalyticsConstants } from '../constants/actions-types' +import { analyticsService } from '../services/AnalyticsService' + +export const initLoading = () => dispatch => { + dispatch({ type: AnalyticsConstants.ANALYTICS_LOADING }) +} + +export const finishLoading = () => dispatch => { + dispatch({ type: AnalyticsConstants.ANALYTICS_LOADING_FINISH }) +} + +export const sendNotification = notification => dispatch => { + dispatch({ type: AnalyticsConstants.ANALYTICS_NOTIFICATION, notification }) +} + +export const getTopApps = (limit, period) => dispatch => { + analyticsService.getAppsTop(limit, period) + .then(data => { + dispatch({ type: AnalyticsConstants.ANALYTICS_TOP_APPS, topApps: data }) + dispatch(finishLoading()) + }) + .catch(error => { + console.log(error) + dispatch(finishLoading()) + }) +} + +export const getTopApis = (limit, period) => dispatch => { + analyticsService.getApisTop(limit, period) + .then(data => { + dispatch({ type: AnalyticsConstants.ANALYTICS_TOP_APIS, topApis: data }) + dispatch(finishLoading()) + }) + .catch(error => { + console.log(error) + dispatch(finishLoading()) + }) +} + +export const getTopAccessTokens = (limit, period) => dispatch => { + analyticsService.getAccessTokensTop(limit, period) + .then(data => { + dispatch({ type: AnalyticsConstants.ANALYTICS_TOP_ACCESS_TOKENS, topAccessTokens: data }) + dispatch(finishLoading()) + }) + .catch(error => { + console.log(error) + dispatch(finishLoading()) + }) +} + +export const getTopResultStatus = (limit, period) => dispatch => { + analyticsService.getResultStatusTop(limit, period) + .then(data => { + dispatch({ type: AnalyticsConstants.ANALYTICS_TOP_RESULT_STATUS, topResultStatus: data }) + dispatch(finishLoading()) + }) + .catch(error => { + console.log(error) + dispatch(finishLoading()) + }) +} \ No newline at end of file diff --git a/heimdall-frontend/src/components/ui/Chart.js b/heimdall-frontend/src/components/ui/Chart.js new file mode 100644 index 00000000..72f3fac5 --- /dev/null +++ b/heimdall-frontend/src/components/ui/Chart.js @@ -0,0 +1,73 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ReactEcharts from 'echarts-for-react' + +import Loading from "./Loading" + +class Chart extends React.Component { + + state = { + color: 'red' + } + + componentDidMount() { + this.setState({...this.state, color: this.props.color}) + } + + getOption = () => { + const { metrics } = this.props + + let dataMetrics = [] + let dataMetricsValues = [] + + if (metrics && Array.isArray(metrics)) { + + dataMetrics = metrics.map(metric => { + return metric.metric + }) + + dataMetricsValues = metrics.map(metric => { + return metric.value + }) + + } + + return { + title: { + text: this.props.title, + }, + tooltip: {}, + xAxis: { + data: dataMetrics + }, + yAxis: {}, + series: [{ + name: 'requests', + type: 'bar', + data: dataMetricsValues + }], + color: this.state.color + + } + } + + render() { + + const { metrics } = this.props + + if (!metrics) { + return + } + + return ( + + ) + } +} + +Chart.propTypes = { + metrics: PropTypes.array, + title: PropTypes.string.isRequired, +} + +export default Chart diff --git a/heimdall-frontend/src/components/ui/SideBar.js b/heimdall-frontend/src/components/ui/SideBar.js index eaef1480..d5fe47c6 100644 --- a/heimdall-frontend/src/components/ui/SideBar.js +++ b/heimdall-frontend/src/components/ui/SideBar.js @@ -56,6 +56,10 @@ class SideBar extends Component { + + + + diff --git a/heimdall-frontend/src/constants/actions-types.js b/heimdall-frontend/src/constants/actions-types.js index 568168f8..948655d3 100644 --- a/heimdall-frontend/src/constants/actions-types.js +++ b/heimdall-frontend/src/constants/actions-types.js @@ -178,4 +178,14 @@ export const CacheConstants = { CACHE_LOADING: 'CACHE_LOADING', CACHE_LOADING_FINISH: 'CACHE_LOADING_FINISH', CACHE_NOTIFICATION: 'CACHE_NOTIFICATION' +} + +export const AnalyticsConstants = { + ANALYTICS_LOADING: 'ANALYTICS_LOADING', + ANALYTICS_LOADING_FINISH: 'ANALYTICS_LOADING_FINISH', + ANALYTICS_TOP_APPS: 'ANALYTICS_TOP_APPS', + ANALYTICS_TOP_APIS: 'ANALYTICS_TOP_APIS', + ANALYTICS_TOP_ACCESS_TOKENS: 'ANALYTICS_TOP_ACCESS_TOKENS', + ANALYTICS_TOP_RESULT_STATUS: 'ANALYTICS_TOP_RESULT_STATUS', + ANALYTICS_NOTIFICATION: 'ANALYTICS_NOTIFICATION' } \ No newline at end of file diff --git a/heimdall-frontend/src/containers/Analytics.js b/heimdall-frontend/src/containers/Analytics.js new file mode 100644 index 00000000..e6e6eb97 --- /dev/null +++ b/heimdall-frontend/src/containers/Analytics.js @@ -0,0 +1,142 @@ +import React from 'react' +import { connect } from 'react-redux' +import {Button, Card, Col, Form, Row, InputNumber, Select, notification} from 'antd' + +import Chart from "../components/ui/Chart" +import PageHeader from "../components/ui/PageHeader" +import Loading from "../components/ui/Loading" +import { getTopAccessTokens, getTopApis, getTopApps, getTopResultStatus, sendNotification} from "../actions/analytics" + +class Analytics extends React.Component { + + state = { + limit: 5, + period: "THIS_MONTH", + topApps: [], + topApis: [], + topResultStatus: [], + topAccessTokens: [] + } + + componentDidMount() { + + const { limit, period } = this.state + this.getCharts(limit, period) + } + + componentWillUpdate(nextProps, nextState) { + const { limit, period } = this.state + + if (nextState.limit !== limit || nextState.period !== period) { + this.getCharts(nextState.limit, nextState.period) + } + } + + componentWillReceiveProps(newProps) { + if (newProps.notification && newProps.notification !== this.props.notification) { + const { type, message, description } = newProps.notification + notification[type]({ message, description }) + } + } + + onSearchForm = () => { + this.props.form.validateFieldsAndScroll((err, payload) => { + if (!err) { + this.updateLimitAndPeriod(payload.limit, payload.period) + } + }); + } + + updateLimitAndPeriod = (limit, period) => { + this.setState({...this.state, limit: limit, period: period }) + } + + getCharts = (limit, period) => { + this.props.dispatch(getTopApps(limit, period)) + this.props.dispatch(getTopApis(limit, period)) + this.props.dispatch(getTopAccessTokens(limit, period)) + this.props.dispatch(getTopResultStatus(limit, period)) + this.props.dispatch(sendNotification({ type: 'success', message: 'Metrics update!' })) + } + + render() { + const { topApps, topResultStatus, topAccessTokens, topApis, loading } = this.props + + const {getFieldDecorator} = this.props.form + + if (loading) { + return + } + + return ( +
+ + + + +
+ + + {getFieldDecorator('limit', { + initialValue: this.state.limit + })()} + + + {getFieldDecorator('period', { + initialValue: this.state.period + })( + + )} + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ ) + } +} + +const AnalyticsWrapped = Form.create({})(Analytics) + +const mapStateToProps = state => { + return { + topApps: state.analytics.topApps, + topApis: state.analytics.topApis, + topAccessTokens: state.analytics.topAccessTokens, + topResultStatus: state.analytics.topResultStatus, + loading: state.analytics.loading, + notification: state.analytics.notification + } +} + +export default connect(mapStateToProps)(AnalyticsWrapped) \ No newline at end of file diff --git a/heimdall-frontend/src/reducers/Analytics.js b/heimdall-frontend/src/reducers/Analytics.js new file mode 100644 index 00000000..7a409e3b --- /dev/null +++ b/heimdall-frontend/src/reducers/Analytics.js @@ -0,0 +1,22 @@ +import {AnalyticsConstants} from "../constants/actions-types" + +export default (state = {}, action) => { + switch (action.type) { + case AnalyticsConstants.ANALYTICS_LOADING: + return { ...state, loading: true } + case AnalyticsConstants.ANALYTICS_LOADING_FINISH: + return { ...state, loading: false } + case AnalyticsConstants.ANALYTICS_TOP_APPS: + return { ...state, topApps: action.topApps } + case AnalyticsConstants.ANALYTICS_TOP_APIS: + return { ...state, topApis: action.topApis } + case AnalyticsConstants.ANALYTICS_TOP_ACCESS_TOKENS: + return { ...state, topAccessTokens: action.topAccessTokens } + case AnalyticsConstants.ANALYTICS_TOP_RESULT_STATUS: + return { ...state, topResultStatus: action.topResultStatus } + case AnalyticsConstants.ANALYTICS_NOTIFICATION: + return { ...state, notification: action.notification } + default: + return state + } +} \ No newline at end of file diff --git a/heimdall-frontend/src/reducers/index.js b/heimdall-frontend/src/reducers/index.js index fe92d6b1..449ca230 100644 --- a/heimdall-frontend/src/reducers/index.js +++ b/heimdall-frontend/src/reducers/index.js @@ -16,6 +16,7 @@ import roles from './Roles' import caches from './Caches' import middlewares from './middlewares' import traces from './Traces' +import analytics from './Analytics' export default combineReducers({ apis, @@ -33,5 +34,6 @@ export default combineReducers({ roles, middlewares, caches, - traces + traces, + analytics, }) \ No newline at end of file diff --git a/heimdall-frontend/src/routes/index.js b/heimdall-frontend/src/routes/index.js index 20782fe4..73371fce 100644 --- a/heimdall-frontend/src/routes/index.js +++ b/heimdall-frontend/src/routes/index.js @@ -37,6 +37,7 @@ import Users from '../containers/Users'; import SingleUser from '../containers/SingleUser'; import Traces from "../containers/Traces"; import SingleTrace from "../containers/SingleTrace"; +import Analytics from '../containers/Analytics'; const routes = ({ history }) => ( @@ -69,6 +70,7 @@ const routes = ({ history }) => ( + {/* routes not finded or 404 */} diff --git a/heimdall-frontend/src/services/AnalyticsService.js b/heimdall-frontend/src/services/AnalyticsService.js new file mode 100644 index 00000000..89916c35 --- /dev/null +++ b/heimdall-frontend/src/services/AnalyticsService.js @@ -0,0 +1,76 @@ +import { HTTPv1 } from "../utils/Http" + +const getAppsTop = (limit, period) => { + const params = { + params: { + limit: limit, + period: period + } + } + return HTTPv1.get('/metrics/apps/top', params) + .then(result => { + return Promise.resolve(result.data) + }) + .catch(error => { + console.log(error) + return Promise.reject(error) + }) +} + +const getApisTop = (limit, period) => { + const params = { + params: { + limit: limit, + period: period + } + } + return HTTPv1.get('/metrics/apis/top', params) + .then(result => { + return Promise.resolve(result.data) + }) + .catch(error => { + console.log(error) + return Promise.reject(error) + }) +} + +const getAccessTokensTop = (limit, period) => { + const params = { + params: { + limit: limit, + period: period + } + } + return HTTPv1.get('/metrics/access-tokens/top', params) + .then(result => { + return Promise.resolve(result.data) + }) + .catch(error => { + console.log(error) + return Promise.reject(error) + }) +} + +const getResultStatusTop = (limit, period) => { + const params = { + params: { + limit: limit, + period: period + } + } + return HTTPv1.get('/metrics/result-status/top', params) + .then(result => { + return Promise.resolve(result.data) + }) + .catch(error => { + console.log(error) + return Promise.reject(error) + }) +} + +export const analyticsService = { + getAppsTop, + getApisTop, + getAccessTokensTop, + getResultStatusTop +} \ No newline at end of file diff --git a/heimdall-frontend/src/utils/ColorUtils.js b/heimdall-frontend/src/utils/ColorUtils.js index d2445700..486a3232 100644 --- a/heimdall-frontend/src/utils/ColorUtils.js +++ b/heimdall-frontend/src/utils/ColorUtils.js @@ -61,9 +61,14 @@ const getColorActivate = (active) => { } } +const getRandomColor = () => { + return '#' + ("000000" + Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6); +} + export default { getColorMethod, getColorLevel, getColorStatus, - getColorActivate + getColorActivate, + getRandomColor }