├── .gitignore
├── LICENSE
├── README.md
├── client
├── dev.config.js
├── index.html
├── index.js
├── package.json
├── src
│ ├── components
│ │ ├── react-data-inline-block
│ │ │ ├── index.js
│ │ │ └── style.less
│ │ ├── react-echarts
│ │ │ ├── index.js
│ │ │ └── style.less
│ │ └── react-tab-info
│ │ │ ├── index.js
│ │ │ └── style.less
│ ├── global
│ │ ├── conf
│ │ │ └── location.js
│ │ ├── js
│ │ │ ├── date.js
│ │ │ └── request.js
│ │ ├── layout
│ │ │ ├── GlobalHeader
│ │ │ │ ├── index.js
│ │ │ │ └── style.less
│ │ │ └── SideNav
│ │ │ │ ├── index.js
│ │ │ │ └── style.less
│ │ └── less
│ │ │ ├── color.less
│ │ │ ├── global.less
│ │ │ ├── mixins.less
│ │ │ └── reset.less
│ └── pages
│ │ ├── dbInfo
│ │ ├── index.js
│ │ └── style.less
│ │ ├── default
│ │ ├── index.js
│ │ └── style.less
│ │ ├── memoryInfo
│ │ ├── index.js
│ │ └── style.less
│ │ └── qpsInfo
│ │ ├── index.js
│ │ └── style.less
└── webpack.config.js
├── package.json
└── server
├── app.js
├── bin
└── index.js
├── common
├── logger.js
├── middleware
│ └── response-time.js
├── redis-client.js
├── response.js
└── util
│ └── index.js
├── config
└── index.js
├── controller
├── instance-list-ctl.js
└── instance-status-ctl.js
├── public
├── favicon.ico
├── index.html
└── js
│ └── build
│ ├── 0.build.js
│ ├── 1.build.js
│ └── build.js
├── router
├── index.js
├── instance-list-router.js
└── instance-status-router.js
├── schedule
├── create-redis-server-info.js
└── index.js
└── service
├── api
└── redis.js
├── instance-service.js
├── memory-management-service.js
└── qps-management-service.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | server/logs
3 | logs
4 | *.log
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | client/node-modules
30 | *.sw*
31 | *.un~
32 | .idea
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 John
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # redis-monitor
2 | koa+react+antd写的一个简单redis状态监控工具. 目前主要监控内存使用情况、命令处理情况及实例中keys的分布情况
3 |
4 |
5 | ## 技术栈
6 |
7 | ### 前端
8 | 打包工具: webpack
9 | 前端框架: react
10 | ui库: antd
11 |
12 | ### 后端
13 | node版本: >=7.6.0
14 | web框架: koa2
15 | 数据存储: redis
16 |
17 | ## 部署
18 |
19 | ### 前端
20 |
21 | client目录下
22 | 前端部分为页面展示(已打包在server/public/js/build目录下)
23 | 打包发布:
24 | cd client/
25 | npm install .
26 | npm run dev (此命令会将项目打包到server/public/js/build目录下)
27 |
28 |
29 | ### 后端
30 | 后端启动分两部分(定时任务和服务接口)
31 |
32 | 开发环境下:
33 | npm installl .
34 | 定时任务启动收集数据:
35 | npm run dev
36 | 启动服务
37 | npm run dev-schedule
38 |
39 |
40 |
--------------------------------------------------------------------------------
/client/dev.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path =require('path');
3 | var config = {
4 | entry: [
5 | './index.js'
6 | ],
7 | output: {
8 | path: path.join(__dirname ,'../server/public/js/build'),
9 | //使用本系统静态资源地址(ip端口必须和server启动地址一致)
10 | publicPath: "http://127.0.0.1:12000/js/build/",
11 | filename: 'build.js'
12 | },
13 | resolve: {
14 | extensions: ['.js', '.jsx', '.json', '.less']
15 | },
16 | module: {
17 | rules: [{
18 | test: /\.js$/,
19 | exclude: /node_modules/,
20 | loader: 'babel-loader',
21 | query: {
22 | cacheDirectory: true,
23 | plugins: [['import', {'libraryName': 'antd', 'style': true}]],
24 | presets: ['es2015', 'react', 'stage-0', 'stage-1']
25 | }
26 | }, {
27 | test: /\.less$/,
28 | loader: 'style-loader!css-loader!less-loader'
29 | }]
30 | },
31 | devServer: {
32 | host: 'localhost',
33 | port: 9999,
34 | proxy: {
35 | '/mointor/*': {
36 | target: 'http://127.0.0.1:12000'
37 | }
38 | }
39 | },
40 | };
41 |
42 | module.exports = config;
43 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | redis服务监控
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Router, Route, Link, hashHistory } from 'react-router';
4 |
5 | import './src/global/less/global.less';
6 |
7 | import GlobalHeader from './src/global/layout/GlobalHeader/index';
8 | import SideNav from './src/global/layout/SideNav/index';
9 |
10 | export default class Layout extends React.Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 | {this.props.children}
17 |
18 | );
19 | }
20 | }
21 |
22 | const routes = {
23 | path: '/',
24 | component: Layout,
25 | getIndexRoute(history, callback) {
26 | require.ensure([], function (require) {
27 | callback(null, require('./src/pages/default/index').default);
28 | });
29 | },
30 | getChildRoutes(history, callback) {
31 | if (history.location.pathname === '/') {
32 | require.ensure([], function (require) {
33 | callback(null, [
34 | require('./src/pages/default/index').default
35 | ]);
36 | });
37 | } else if (history.location.pathname === '/dbInfo' || history.location.pathname === '/memoryInfo' ||
38 | history.location.pathname === '/qpsInfo') {
39 | require.ensure([], function (require) {
40 | callback(null, [
41 | require('./src/pages/dbInfo/index').default,
42 | require('./src/pages/memoryInfo/index').default,
43 | require('./src/pages/qpsInfo/index').default
44 | ]);
45 | });
46 | }
47 | }
48 | };
49 |
50 | ReactDOM.render(
51 | ,
52 | document.getElementById('app')
53 | );
54 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redis-monitor-client",
3 | "version": "1.0.0",
4 | "description": "redis-monitor-client",
5 | "scripts": {
6 | "dev": "webpack --config dev.config.js --progress --colors",
7 | "start": "webpack-dev-server --hot --inline --history-api-fallback --progress"
8 | },
9 | "author": "jizhuofeng",
10 | "license": "MIT",
11 | "dependencies": {
12 | "antd": "^2.8.3",
13 | "babel-core": "^6.21.0",
14 | "babel-loader": "^6.2.10",
15 | "babel-preset-es2015": "^6.18.0",
16 | "babel-preset-react": "^6.16.0",
17 | "babel-preset-stage-0": "^6.16.0",
18 | "babel-preset-stage-1": "^6.16.0",
19 | "css-loader": "^0.26.1",
20 | "echarts-for-react": "^1.2.0",
21 | "less": "^2.7.2",
22 | "less-loader": "^2.2.3",
23 | "mockjs": "^1.0.1-beta3",
24 | "moment": "^2.18.1",
25 | "react": "^15.4.2",
26 | "react-dom": "^15.4.2",
27 | "react-hot-loader": "^1.3.1",
28 | "react-router": "^3.0.0",
29 | "style-loader": "^0.13.1",
30 | "superagent": "^3.5.2",
31 | "webpack": "^2.3.3",
32 | "webpack-dev-server": "^2.4.2"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "https://github.com/jizhuofeng/redis-monitor.git"
37 | },
38 | "devDependencies": {
39 | "babel-plugin-import": "^1.1.0",
40 | "react-router": "^3.0.0"
41 | },
42 | "keywords": [
43 | "redis monitor react node.js"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/client/src/components/react-data-inline-block/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './style';
4 |
5 | export class DataInlineBlock extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | if (this.props.data.length === 0) {
12 | return (
13 |
14 | );
15 | } else {
16 | let tempNodeArr = this.props.data.map((data, index) => {
17 | let typeClass = data.type === 'up' ? "data-block up" : "data-block down";
18 | let typeArrow = data.type === 'up' ? "↑" : "↓";
19 |
20 | let typeClass2 = data.type2 === 'up' ? "data-block up" : "data-block down";
21 | let typeArrow2 = data.type2 === 'up' ? "↑" : "↓";
22 |
23 | if(data.name2 != null) {
24 | return (
25 |
26 |
27 |
{data.name}
28 |
{data.number}
29 |
较前一{this.props.timeType === 'day' ? '天' : '月'}
30 |
{typeArrow} {data.rate}
31 |
32 |
33 |
{data.name2}
34 |
{data.number2}
35 |
较前一{this.props.timeType2 === 'day' ? '天' : '月'}
36 |
{typeArrow2} {data.rate2}
37 |
38 |
39 | );
40 | }else {
41 | return (
42 |
43 |
44 |
{data.name}
45 |
{data.number}
46 |
较前一{this.props.timeType === 'day' ? '天' : '月'}
47 |
{typeArrow} {data.rate}
48 |
49 |
50 |
店长:{data.seller}
51 |
买家:{data.buyer}
52 |
53 |
54 | );
55 | }
56 | });
57 |
58 | return (
59 |
60 | {tempNodeArr}
61 |
62 | );
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/client/src/components/react-data-inline-block/style.less:
--------------------------------------------------------------------------------
1 | @import "../../global/less/mixins";
2 | @import "../../global/less/color";
3 | .data-inline-block {
4 | position: relative;
5 | width: 100%;
6 | height: auto;
7 | .clearfix();
8 | .data-block {
9 | position: relative;
10 | float: left;
11 | width: 24%;
12 | height: 115px;
13 | margin-bottom: 10px;
14 | padding-left: 10px;
15 | margin-left: 1.33%;
16 | background-color: #fff;
17 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1);
18 | overflow: hidden;
19 | &:nth-child(1),
20 | &:nth-child(4n+1) {
21 | margin-left: 0;
22 | }
23 | &.up {
24 | .data-content {
25 | .sub-number {
26 | color: #d9534f;
27 | }
28 |
29 | }
30 | .glyphicon {
31 | right: -8px;
32 | top: 70px;
33 | color: #d9534f;
34 | }
35 | }
36 | &.down {
37 | .data-content {
38 | .sub-number {
39 | color: #5cb85c;
40 | }
41 | }
42 | .glyphicon {
43 | right: -8px;
44 | top: 0;
45 | color: #5cb85c;
46 | }
47 | }
48 | }
49 | .data-content {
50 | width: 115px;
51 | height: 100%;
52 | padding-top: 7px;
53 | &.grid-left {
54 | width: 50%;
55 | float: left;
56 | }
57 | &.grid-right {
58 | width: 50%;
59 | float: left;
60 | padding-left: 10px;
61 | background: #d8e1eb
62 | }
63 | &.center {
64 | padding-top: 20%
65 | }
66 | .title {
67 | margin-bottom: 2px;
68 | font: normal 16px/17px 'Arial';
69 | color: #555;
70 | }
71 | .number {
72 | margin-bottom: 5px;
73 | padding-bottom: 5px;
74 | font: bolder 24px/35px 'Arial';
75 | color: @main;
76 | border-bottom: 1px solid #EEE;
77 | }
78 | .sub-title {
79 | margin-bottom: 2px;
80 | font: normal 12px/15px 'Arial';
81 | color: #555;
82 | }
83 | .sub-number {
84 | font: normal 14px/23px 'Arial';
85 | }
86 | .rate {
87 | margin-bottom: 2px;
88 | font: normal 13px/15px 'Arial';
89 | color: @main;
90 | }
91 | }
92 | .glyphicon {
93 | position: absolute;
94 | font-size: 50px;
95 | opacity: 0.3;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/client/src/components/react-echarts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactEcharts from 'echarts-for-react';
3 |
4 | import './style';
5 |
6 | export class Charts extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
{this.props.title}
15 |
16 |
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/client/src/components/react-echarts/style.less:
--------------------------------------------------------------------------------
1 | .charts {
2 | position: relative;
3 | width: 100%;
4 | height: auto;
5 | padding: 10px 10px 25px;
6 | background-color: #ffffff;
7 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1);
8 | .charts-name {
9 | width: 100%;
10 | height: 35px;
11 | padding-left: 7px;
12 | margin-bottom: 5px;
13 | font: bolder 20px/35px 'Arial';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/components/react-tab-info/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './style';
4 |
5 | export class TabInfo extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | if (this.props.data.length !== 0) {
12 | let tabBlock = this.props.data.map((data, index) => {
13 | if(this.props.selected == index + 1) {
14 | return (
15 |
18 |
{data.number}
19 |
{data.name}
20 |
21 | );
22 | }else {
23 | return (
24 |
27 |
{data.number}
28 |
{data.name}
29 |
30 | );
31 | }
32 | });
33 |
34 | return (
35 |
36 | {tabBlock}
37 |
38 | );
39 | } else {
40 | return (
41 |
42 |
43 |

44 |
数据加载中,请稍候…
45 |
46 |
47 | );
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/client/src/components/react-tab-info/style.less:
--------------------------------------------------------------------------------
1 | @import "../../global/less/mixins";
2 | @import "../../global/less/color";
3 | .tab-info {
4 | position: relative;
5 | width: 100%;
6 | height: 115px;
7 | padding: 10px 5px;
8 | margin-bottom: 10px;
9 | background-color: #fff;
10 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1);
11 | .clearfix();
12 | .tab-block {
13 | float: left;
14 | height: 100%;
15 | padding-top: 13px;
16 | border-left: 1px solid #eeeeee;
17 | text-align: center;
18 | .number {
19 | width: 100%;
20 | height: auto;
21 | margin-bottom: 5px;
22 | font: bolder 26px/45px 'Arial';
23 | color: @main;
24 | }
25 | .info-name {
26 | width: 100%;
27 | height: auto;
28 | font: normal 16px/20px 'Arial';
29 | color: #555;
30 | }
31 | &:nth-child(1) {
32 | border-left: 0;
33 | }
34 | &.selected {
35 | background-color: #eee;
36 | }
37 | }
38 | .loading {
39 | position: absolute;
40 | top: 50%;
41 | left: 50%;
42 | margin-top: -35px;
43 | margin-left: -75px;
44 | width: 150px;
45 | height: 70px;
46 | background-color: rgba(0, 0, 0, 0.7);
47 | border-radius: 5px;
48 | text-align: center;
49 | .loading-img {
50 | position: absolute;
51 | top: 10px;
52 | left: 50%;
53 | width: 30px;
54 | height: 30px;
55 | margin-left: -15px;
56 | animation: loading-rotate 2s linear infinite;
57 | -webkit-animation: loading-rotate 2s linear infinite;
58 | }
59 | .loading-content {
60 | position: absolute;
61 | bottom: 5px;
62 | left: 50%;
63 | width: 140px;
64 | height: 20px;
65 | margin-left: -70px;
66 | font: normal 12px/20px 'Arial';
67 | color: #ced3cd;
68 | }
69 | }
70 | }
71 | @-webkit-keyframes loading-rotate {
72 | 0% {
73 | transform: rotate(0deg);
74 | -ms-transform: rotate(0deg);
75 | -moz-transform: rotate(0deg);
76 | -webkit-transform: rotate(0deg);
77 | -o-transform: rotate(0deg);
78 | }
79 | 100% {
80 | transform: rotate(360deg);
81 | -ms-transform: rotate(360deg);
82 | -moz-transform: rotate(360deg);
83 | -webkit-transform: rotate(360deg);
84 | -o-transform: rotate(360deg);
85 | }
86 | }
87 | @keyframes loading-rotate {
88 | 0% {
89 | transform: rotate(0deg);
90 | -ms-transform: rotate(0deg);
91 | -moz-transform: rotate(0deg);
92 | -webkit-transform: rotate(0deg);
93 | -o-transform: rotate(0deg);
94 | }
95 | 100% {
96 | transform: rotate(360deg);
97 | -ms-transform: rotate(360deg);
98 | -moz-transform: rotate(360deg);
99 | -webkit-transform: rotate(360deg);
100 | -o-transform: rotate(360deg);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/client/src/global/conf/location.js:
--------------------------------------------------------------------------------
1 | let locationProps = [{
2 | listTitle: '实例管理',
3 | listIcon: 'database',
4 | pageUrl: '/'
5 | }, {
6 | listTitle: '状态监控',
7 | listIcon: 'area-chart',
8 | list: [{
9 | pageName: '实例信息',
10 | pageUrl: '/dbInfo'
11 | }, {
12 | pageName: '内存变化',
13 | pageUrl: '/memoryInfo'
14 | }, {
15 | pageName: '吞吐率',
16 | pageUrl: '/qpsInfo'
17 | }]
18 |
19 | }];
20 |
21 | export default locationProps;
--------------------------------------------------------------------------------
/client/src/global/js/date.js:
--------------------------------------------------------------------------------
1 | /*
2 | * date.js 公共时间处理函数集
3 | * */
4 |
5 | /*
6 | * Glo_setDateZero(number)
7 | * 小于10的数字前加0,返回字符串
8 | * */
9 | export function Glo_setDateZero(number) {
10 | return number < 10 ? '0' + number : '' + number;
11 | }
12 |
13 | /*
14 | * Glo_getCurrentDate()
15 | * 获取当前日期(yyyy-mm-dd)
16 | * */
17 | export function Glo_getCurrentDate() {
18 | let dateObj = new Date();
19 | return dateObj.getFullYear() + '-' + Glo_setDateZero(dateObj.getMonth() + 1) + '-' + Glo_setDateZero(dateObj.getDate());
20 | }
21 |
22 | /*
23 | * Glo_getCurrentMonth()
24 | * 获取当前年月(yyyy-mm)
25 | * */
26 | export function Glo_getCurrentMonth() {
27 | let dateObj = new Date();
28 | return dateObj.getFullYear() + '-' + Glo_setDateZero(dateObj.getMonth() + 1);
29 | }
30 |
31 | /*
32 | * Glo_timeStampFormat(timeStamp)
33 | * 格式化时间戳(yyyy-mm-dd hh:mm:ss)
34 | * */
35 | export function Glo_timeStampFormat(timeStamp) {
36 | var time = new Date(timeStamp);
37 | var y = time.getFullYear();
38 | var m = time.getMonth() + 1;
39 | var d = time.getDate();
40 | var h = time.getHours();
41 | var mm = time.getMinutes();
42 | var s = time.getSeconds();
43 | return y + '-' + Glo_setDateZero(m) + '-' + Glo_setDateZero(d) + ' ' + Glo_setDateZero(h) + ':' + Glo_setDateZero(mm) + ':' + Glo_setDateZero(s);
44 | }
45 |
46 | /*
47 | * Glo_getPreDate(currentDate, daysNum)
48 | * 获取N天前的日期
49 | * */
50 | export function Glo_getPreDate(currentDate, daysNum) {
51 | let currentDateArr = currentDate.split('-').map(data => {
52 | return Number(data);
53 | });
54 |
55 | if (currentDateArr[2] < daysNum) {
56 | if (currentDateArr[1] === 1) {
57 | currentDateArr[0]--;
58 | currentDateArr[1] = 12;
59 | currentDateArr[2] = 31 - (daysNum - currentDateArr[2]) + 1;
60 | } else if (currentDateArr[1] === 3) {
61 | currentDateArr[1] = '02';
62 | if (((currentDateArr[0] % 100 === 0) && (currentDateArr[0] % 400 === 0)) || (currentDateArr[0] % 100 !== 0 && currentDateArr[0] % 4 === 0)) {
63 | currentDateArr[2] = 29 - (daysNum - currentDateArr[2]) + 1;
64 | } else {
65 | currentDateArr[2] = 28 - (daysNum - currentDateArr[2]) + 1;
66 | }
67 | } else if (currentDateArr[1] === 5 || currentDateArr[1] === 7 || currentDateArr[1] === 8 || currentDateArr[1] === 10 || currentDateArr[1] === 12) {
68 | currentDateArr[1] = Glo_setDateZero(--currentDateArr[1]);
69 | currentDateArr[2] = 30 - (daysNum - currentDateArr[2]) + 1;
70 | } else {
71 | currentDateArr[1] = Glo_setDateZero(--currentDateArr[1]);
72 | currentDateArr[2] = 30 - (daysNum - currentDateArr[2]) + 1;
73 | }
74 | } else {
75 | currentDateArr[1] = Glo_setDateZero(currentDateArr[1]);
76 | currentDateArr[2] = Glo_setDateZero(currentDateArr[2] - daysNum + 1);
77 | }
78 | return currentDateArr.join('-');
79 | }
--------------------------------------------------------------------------------
/client/src/global/js/request.js:
--------------------------------------------------------------------------------
1 | /*
2 | * request.js 公共请求函数集
3 | * */
4 |
5 | import request from 'superagent';
6 | import { notification } from 'antd';
7 | /*
8 | * GET request method
9 | * reqObj{
10 | * context - 方法调用上下文环境(this)
11 | * init - 是否只调用一次(初始化
12 | * }
13 | * */
14 | export function Glo_getRequest(reqObj) {
15 | let tempState = Object.assign({}, reqObj.context.state);
16 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) {
17 | let reqTime = 1;
18 | if (tempState[reqObj.url + '_reqTime']) {
19 | reqTime = ++tempState[reqObj.url + '_reqTime'];
20 | } else {
21 | tempState[reqObj.url + '_reqTime'] = reqTime;
22 | }
23 | if (reqObj.init && !tempState[reqObj.url + 'init']) {
24 | tempState[reqObj.url + 'init'] = true;
25 | }
26 | reqObj.context.setState(tempState, () => {
27 | request.get(reqObj.url).query(reqObj.data).end((err, res) => {
28 | if (res.status === 200) {
29 | if (reqTime === reqObj.context.state[reqObj.url + '_reqTime']) {
30 | reqObj.response.call(reqObj.context, err, res);
31 | }
32 | } else {
33 | notification['warning']({
34 | message: '对不起,系统升级中',
35 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。',
36 | duration: 7
37 | });
38 | }
39 | });
40 | });
41 | }
42 | }
43 |
44 | /**
45 | * PUT request method
46 | * reqObj {
47 | * context - 方法调用上下文环境(this)
48 | * init - 是否只调用一次(初始化
49 | * }
50 | */
51 | export function Glo_putRequest(reqObj) {
52 | let tempState = Object.assign({}, reqObj.context.state);
53 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) {
54 | if (reqObj.init && !tempState[reqObj.url + 'init']) {
55 | tempState[reqObj.url + 'init'] = true;
56 | reqObj.context.setState(tempState);
57 | }
58 | request.put(reqObj.url).send(reqObj.data).end((err, res) => {
59 | if (res.status === 200) {
60 | reqObj.response.call(reqObj.context, err, res);
61 | } else {
62 | notification['warning']({
63 | message: '对不起,系统升级中',
64 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。',
65 | duration: 7
66 | });
67 | }
68 | });
69 | }
70 | }
71 |
72 | /*
73 | * POST request method
74 | * reqObj{
75 | * context - 方法调用上下文环境(this)
76 | * init - 是否只调用一次(初始化
77 | * }
78 | * */
79 | export function Glo_postRequest(reqObj) {
80 | let tempState = Object.assign({}, reqObj.context.state);
81 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) {
82 | if (reqObj.init && !tempState[reqObj.url + 'init']) {
83 | tempState[reqObj.url + 'init'] = true;
84 | reqObj.context.setState(tempState);
85 | }
86 | request.post(reqObj.url).send(reqObj.data).end((err, res) => {
87 | if (res.status === 200) {
88 | reqObj.response.call(reqObj.context, err, res);
89 | } else {
90 | notification['warning']({
91 | message: '对不起,系统升级中',
92 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。',
93 | duration: 7
94 | });
95 | }
96 | });
97 | }
98 | }
99 |
100 | /*
101 | * PATCH request method
102 | * reqObj{
103 | * context - 方法调用上下文环境(this)
104 | * init - 是否只调用一次(初始化
105 | * }
106 | * */
107 | export function Glo_patchRequest(reqObj) {
108 | let tempState = Object.assign({}, reqObj.context.state);
109 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) {
110 | if (reqObj.init && !tempState[reqObj.url + 'init']) {
111 | tempState[reqObj.url + 'init'] = true;
112 | reqObj.context.setState(tempState);
113 | }
114 | request.patch(reqObj.url).send(reqObj.data).end((err, res) => {
115 | if (res.status === 200) {
116 | reqObj.response.call(reqObj.context, err, res);
117 | } else {
118 | notification['warning']({
119 | message: '对不起,系统升级中',
120 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。',
121 | duration: 7
122 | });
123 | }
124 | });
125 | }
126 | }
127 |
128 | /*
129 | * DELETE request method
130 | * reqObj{
131 | * context - 方法调用上下文环境(this)
132 | * }
133 | * */
134 | export function Glo_deleteRequest(reqObj) {
135 | request.del(reqObj.url).send(reqObj.data).end((err, res) => {
136 | if (res.status === 200) {
137 | reqObj.response.call(reqObj.context, err, res);
138 | } else {
139 | notification['warning']({
140 | message: '对不起,系统升级中',
141 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。',
142 | duration: 7
143 | });
144 | }
145 | });
146 | }
--------------------------------------------------------------------------------
/client/src/global/layout/GlobalHeader/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 |
4 | import './style';
5 |
6 | class GlobalHeader extends React.Component {
7 | render() {
8 | return (
9 |
14 | );
15 | }
16 | }
17 |
18 | export default GlobalHeader;
19 |
--------------------------------------------------------------------------------
/client/src/global/layout/GlobalHeader/style.less:
--------------------------------------------------------------------------------
1 | @import "../../less/color";
2 | .global-header {
3 | position: absolute;
4 | top: 0;
5 | left: 0;
6 | z-index: 99;
7 | width: 100%;
8 | height: 45px;
9 | background-color: #323232;
10 | .logo {
11 | position: relative;
12 | width: 20%;
13 | height: 100%;
14 | padding: 0 10px 0 12px;
15 | background-size: 90px auto;
16 | }
17 | }
--------------------------------------------------------------------------------
/client/src/global/layout/SideNav/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {hashHistory} from 'react-router';
3 | import {Icon} from 'antd';
4 |
5 | import locationProps from '../../conf/location';
6 |
7 | import './style';
8 |
9 | class SideNav extends React.Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | let sideNavProps = locationProps;
14 | let currentUrl = this.props.pathName;
15 |
16 | sideNavProps.forEach(data => {
17 | data.listShow = false;
18 | data.currentTitle = false;
19 | if (data.list) {
20 | data.list.forEach((dataList) => {
21 | dataList.current = false;
22 | if (dataList.pageUrl === currentUrl) {
23 | data.listShow = true;
24 | data.currentTitle = true;
25 | dataList.current = true;
26 | }
27 | });
28 | } else if (data.pageUrl) {
29 | if (data.pageUrl === currentUrl) {
30 | data.listShow = true;
31 | data.currentTitle = true;
32 | }
33 | }
34 | });
35 |
36 | this.state = {
37 | sideNavProps: sideNavProps
38 | };
39 | }
40 |
41 | setListShow = (data, index) => {
42 | let tempState = Object.assign({}, this.state);
43 | let tempListShow = tempState.sideNavProps[index].listShow;
44 | if (tempState.sideNavProps[index].list) {
45 | tempState.sideNavProps.forEach((value, key) => {
46 | if (index === key) {
47 | value.listShow = !tempListShow;
48 | } else {
49 | value.listShow = false;
50 | }
51 | });
52 | this.setState(tempState);
53 | } else {
54 | tempState.sideNavProps.forEach((value, key) => {
55 | if (index === key) {
56 | value.listShow = !tempListShow;
57 | value.currentTitle = true;
58 | } else {
59 | value.listShow = false;
60 | value.currentTitle = false;
61 | value.list.forEach(tempListData => {
62 | tempListData.current = false;
63 | });
64 | }
65 | });
66 |
67 | this.setState(tempState, ()=> {
68 | hashHistory.push(tempState.sideNavProps[index].pageUrl);
69 | });
70 | }
71 | };
72 |
73 | setSideNavListCurrent = (titleIndex, listIndex, url) => {
74 | let tempState = Object.assign({}, this.state);
75 | tempState.sideNavProps.forEach((data, index) => {
76 | if (index === titleIndex) {
77 | data.listShow = true;
78 | data.currentTitle = true;
79 | data.list.forEach((tempListData, tempListIndex) => {
80 | tempListData.current = tempListIndex === listIndex;
81 | });
82 | } else {
83 | data.listShow = false;
84 | data.currentTitle = false;
85 | if (data.list) {
86 | data.list.forEach(tempListData => {
87 | tempListData.current = false;
88 | });
89 | }
90 | }
91 | });
92 | this.setState(tempState, () => {
93 | hashHistory.push(url);
94 | });
95 | };
96 |
97 | renderSideNavNode = (sideNavProps) => {
98 | let sideBarNode = [];
99 |
100 | sideNavProps.forEach((data, index) => {
101 | if (data.list) {
102 | let operateNode =
103 | {data.listShow ? '收起' : '展开'}
104 |
;
105 |
106 | sideBarNode.push(
107 | this.setListShow(data, index)}>
109 | {data.listTitle}
110 | {operateNode}
111 |
112 | );
113 |
114 | data.list.forEach((dataList, key) => {
115 | if (data.listShow) {
116 | if (dataList.current) {
117 | sideBarNode.push(
118 |
119 | {dataList.pageName}
120 |
121 | );
122 | } else {
123 | sideBarNode.push(
124 | this.setSideNavListCurrent(index, key, dataList.pageUrl)}>
126 | {dataList.pageName}
127 |
128 | );
129 | }
130 | } else {
131 | sideBarNode.push(
132 | this.setSideNavListCurrent(index, key, dataList.pageUrl)}>
134 | {dataList.pageName}
135 |
136 | );
137 | }
138 | });
139 |
140 | } else if (data.pageUrl) {
141 | sideBarNode.push(
142 | this.setListShow(data, index)}>
144 | {data.listTitle}
145 |
146 | );
147 | }
148 | });
149 |
150 | return sideBarNode;
151 | };
152 |
153 | render() {
154 | let tempSideNavProps = this.state.sideNavProps;
155 |
156 | return (
157 |
158 | {this.renderSideNavNode(tempSideNavProps)}
159 |
160 | );
161 | }
162 | }
163 |
164 | export default SideNav;
165 |
--------------------------------------------------------------------------------
/client/src/global/layout/SideNav/style.less:
--------------------------------------------------------------------------------
1 | @import "../../less/color";
2 | .side-nav {
3 | float: left;
4 | width: 20%;
5 | height: 100%;
6 | background-color: @grey;
7 | overflow-y: scroll;
8 | color: #ced3cd;
9 | li {
10 | position: relative;
11 | width: 100%;
12 | height: 55px;
13 | padding: 0 12px;
14 | font: normal 15px/55px 'Arial';
15 | cursor: pointer;
16 | transition: all 0.3s;
17 | &.list-node {
18 | padding-left: 40px;
19 | white-space: nowrap;
20 | }
21 | &.title-active {
22 | background-color: #1b1b1b;
23 | color: @main;
24 | }
25 | &.active {
26 | color: @main;
27 | background-color: #1b1b1b;
28 | }
29 | &.list-hide {
30 | height: 0;
31 | opacity: 0;
32 | border-bottom: none;
33 | overflow: hidden;
34 | }
35 | .operate {
36 | position: absolute;
37 | top: 50%;
38 | right: 10px;
39 | width: 60px;
40 | height: 55px;
41 | margin-top: -55px/2;
42 | font: normal 13px/55px 'Arial';
43 | }
44 | &:hover {
45 | color: @main;
46 | &.list-node {
47 | padding-left: 45px;
48 | background-color: #1b1b1b;
49 | }
50 | &.active {
51 | padding-left: 40px;
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/client/src/global/less/color.less:
--------------------------------------------------------------------------------
1 | @main: #3889cc;
2 | @grey: #323232;
--------------------------------------------------------------------------------
/client/src/global/less/global.less:
--------------------------------------------------------------------------------
1 | @import "./color";
2 | @import "./reset";
3 | @import "./mixins";
4 | #app {
5 | position: relative;
6 | width: 100%;
7 | min-width: 980px;
8 | height: 100%;
9 | overflow: hidden;
10 | background-color: #eee;
11 | padding-top: 45px;
12 | }
13 | .layout {
14 | width: 100%;
15 | height: 100%;
16 | .clearfix();
17 | }
18 | .main-content {
19 | float: left;
20 | width: 80%;
21 | height: 100%;
22 | padding: 12px;
23 | overflow-y: scroll;
24 | .content-block {
25 | position: relative;
26 | width: 100%;
27 | height: auto;
28 | min-height: 45px;
29 | margin-bottom: 10px;
30 | background-color: #fff;
31 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1);
32 | .clearfix();
33 | }
34 | }
35 | .ant-table-middle .ant-table-thead {
36 | th {
37 | text-align: center;
38 | }
39 | }
40 | .ant-table-body {
41 | td {
42 | text-align: center;
43 | }
44 | }
45 | .ant-btn {
46 | &.red-btn {
47 | border-color: #ff5d5d;
48 | background-color: #ff5d5d;
49 | &:hover {
50 | border-color: #ff7776;
51 | background-color: #ff7776;
52 | }
53 | }
54 | &.green-btn {
55 | border-color: #87d068;
56 | background-color: #87d068;
57 | &:hover {
58 | border-color: #9fd096;
59 | background-color: #9fd096;
60 | }
61 | }
62 | }
63 | .ant-pagination {
64 | margin-bottom: 10px;
65 | }
66 | .ant-card {
67 | .label-name {
68 | font: bolder 12px/12px 'Arial';
69 | margin-right: 8.5px;
70 | }
71 | }
--------------------------------------------------------------------------------
/client/src/global/less/mixins.less:
--------------------------------------------------------------------------------
1 | .clearfix() {
2 | *zoom: 1;
3 | &:before,
4 | &:after {
5 | display: table;
6 | content: "";
7 | }
8 | &:after {
9 | clear: both;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/client/src/global/less/reset.less:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
6 | html {
7 | height: 100%;
8 | }
9 | body {
10 | height: 100%;
11 | }
12 | ul {
13 | list-style-type: none;
14 | }
15 | a {
16 | text-decoration: none;
17 | }
18 | button {
19 | border: none;
20 | outline: none;
21 | margin: 0;
22 | padding: 0;
23 | background-color: transparent;
24 | cursor: pointer;
25 | }
--------------------------------------------------------------------------------
/client/src/pages/dbInfo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Input, Button, Spin, Form, Row, Col, message, Tabs, Icon, Tag,
4 | Modal, Select, Dropdown, Card} from 'antd';
5 |
6 | const FormItem = Form.Item;
7 | const TabPane = Tabs.TabPane;
8 | const InputGroup = Input.Group;
9 | const Option = Select.Option;
10 |
11 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request';
12 | import { Glo_timeStampFormat } from '../../global/js/date';
13 | import {Charts} from '../../components/react-echarts/index';
14 |
15 | import './style';
16 |
17 | //page framework
18 | class PageContent extends React.Component {
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | instances: [],
24 | currentInstance: {
25 |
26 | },
27 | loading: false,
28 | chartOption: {
29 | tooltip: {
30 | trigger: 'axis'
31 | },
32 | legend: {
33 | data: ['keys']
34 | },
35 | grid: {
36 | left: '3%',
37 | right: '3%',
38 | bottom: '2%',
39 | containLabel: true
40 | },
41 | xAxis: [
42 | {
43 | type: 'category',
44 | data: []
45 | }
46 | ],
47 | yAxis: [
48 | {
49 | type: 'value'
50 | }
51 | ],
52 | series: [
53 | {
54 | name: 'keys',
55 | type: 'bar',
56 | data: []
57 | }
58 | ]
59 | }
60 | };
61 | }
62 | renderCharts = (value) => {
63 | let tempState = Object.assign({}, this.state);
64 | tempState.loading = false;
65 | let dbs = [];
66 | let dbkeys = [];
67 | if(value.data.Keyspace) {
68 | for(let info in value.data.Keyspace) {
69 | dbs.push(info);
70 | let tmp = value.data.Keyspace[info];
71 | let keyNum = tmp.split(',')[0].split('=')[1];
72 | dbkeys.push(keyNum);
73 | }
74 | }
75 | tempState.chartOption.xAxis[0].data = dbs;
76 | tempState.chartOption.series[0].data = dbkeys;
77 | this.setState(tempState);
78 | }
79 | getInstanceBaseInfo = (instance) => {
80 | let _self = this;
81 | let tempState = Object.assign({}, this.state);
82 | tempState.loading = true;
83 | _self.setState(tempState);
84 | let data = {
85 | name: instance.name
86 | }
87 | //获取实例基本信息
88 | Glo_getRequest({
89 | context: _self,
90 | init: false,
91 | data: data,
92 | url: '/monitor/server/base-info',
93 | response: (err, res) => {
94 | let responseResult = JSON.parse(res.text);
95 | if (responseResult.code === 1) {
96 | _self.renderCharts(responseResult);
97 | } else {
98 | let tempState = Object.assign({}, this.state);
99 | tempState.loading = false;
100 | _self.setState(tempState);
101 | message.error(responseResult.message);
102 | }
103 | }
104 | });
105 | }
106 | instanceChange = (value) => {
107 | let tempState = Object.assign({}, this.state);
108 | for(let i = 0; i < tempState.instances.length; i++) {
109 | if(tempState.instances[i].showName == value) {
110 | tempState.currentInstance = tempState.instances[i];
111 | break;
112 | }
113 | }
114 | this.setState(tempState, () => {
115 | this.getInstanceBaseInfo(tempState.currentInstance);
116 | });
117 | }
118 | initInstances = (value) => {
119 | let tempState = Object.assign({}, this.state);
120 | tempState.instances = [];
121 | for(let key in value.data) {
122 | tempState.instances.push({
123 | showName: key + '(' + value.data[key].host +
124 | ':' + value.data[key].port + ')',
125 | name: key,
126 | host: value.data[key].host,
127 | port: value.data[key].port
128 | })
129 | }
130 | if(tempState.instances[0]) {
131 | tempState.currentInstance = tempState.instances[0];
132 | }
133 |
134 | this.setState(tempState, () => {
135 | if(tempState.instances.length > 0) {
136 | this.getInstanceBaseInfo(this.state.instances[0]);
137 | }
138 | });
139 |
140 | }
141 | componentWillMount() {
142 | let _self = this;
143 | Glo_getRequest({
144 | context: _self,
145 | init: true,
146 | url: '/monitor/instance',
147 | response: (err, res) => {
148 | let responseResult = JSON.parse(res.text);
149 | if (responseResult.code === 1) {
150 | _self.initInstances(responseResult);
151 | } else {
152 | message.error(responseResult.message);
153 | }
154 | }
155 | });
156 | }
157 |
158 | render() {
159 | let _self = this;
160 | return (
161 |
162 |
163 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | 实例中每个db包含的key的总数量
179 |
180 |
181 |
182 |
183 | );
184 | }
185 | }
186 |
187 | let route = {
188 | path: '/dbInfo',
189 | component: PageContent
190 | };
191 |
192 | export default route;
--------------------------------------------------------------------------------
/client/src/pages/dbInfo/style.less:
--------------------------------------------------------------------------------
1 | @import "../../global/less/mixins";
2 | .top-block {
3 | padding: 8.5px 8px;
4 | }
5 | .title {
6 | font: normal 20px/70px 'Arial';
7 | color: #000;
8 | }
--------------------------------------------------------------------------------
/client/src/pages/default/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {Button, Spin, Table, message, Modal, Form, Input} from 'antd';
4 | const FormItem = Form.Item;
5 | import {Glo_getRequest, Glo_deleteRequest, Glo_postRequest} from '../../global/js/request';
6 |
7 | import './style';
8 |
9 | class PageContent extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | addModalVisible: false,
14 | addSending: false,
15 | editModalVisible: false,
16 | editSending: false,
17 | modalKey: 0,
18 | currentInstance: {
19 |
20 | },
21 | addInstance: {
22 |
23 | },
24 | tableData: []
25 | };
26 | }
27 | addInstanceChange = (key, value) => {
28 | let tempState = Object.assign({}, this.state);
29 | tempState.addInstance[key] = value;
30 | this.setState(tempState);
31 | }
32 | currentInstanceChange = (key, value) => {
33 | let tempState = Object.assign({}, this.state);
34 | tempState.currentInstance["newName"] = value;
35 | this.setState(tempState);
36 | }
37 | showAddModal = () => {
38 | let tempState = Object.assign({}, this.state);
39 | tempState.addModalVisible = true;
40 | this.setState(tempState);
41 | }
42 | hideAddModal = () => {
43 | let tempState = Object.assign({}, this.state);
44 | tempState.addModalVisible = false;
45 | tempState.addSending = false;
46 | this.setState(tempState);
47 | }
48 | showEditModal = (text, record, id) => {
49 |
50 | let tempState = Object.assign({}, this.state);
51 | tempState.modalKey++;
52 | tempState.currentInstance = Object.assign({}, record);
53 | tempState.editModalVisible = true;
54 | this.setState(tempState);
55 | }
56 | hideEditModal = () => {
57 | let tempState = Object.assign({}, this.state);
58 | tempState.editSending = false;
59 | tempState.editModalVisible = false;
60 | this.setState(tempState);
61 | }
62 |
63 | renderRenameResult = () => {
64 | let tempState = Object.assign({}, this.state);
65 | tempState.editSending = false;
66 | tempState.editModalVisible = false;
67 | tempState.tableData[tempState.currentInstance["index"]]["name"] = tempState.currentInstance["newName"];
68 | tempState.currentInstance = Object.assign({}, tempState.tableData[tempState.currentInstance["index"]]);
69 | this.setState(tempState);
70 | }
71 | renderDeleteResult = (id) => {
72 | let tempState = Object.assign({}, this.state);
73 | tempState.tableData.splice(id, 1);
74 | this.setState(tempState);
75 | }
76 | renameInstance = () => {
77 | let _self = this;
78 | let tempState = Object.assign({}, this.state);
79 | tempState.editSending = true;
80 | this.setState(tempState);
81 | let data = {
82 | name: _self.state.currentInstance["newName"],
83 | oldName: _self.state.currentInstance["name"]
84 | }
85 | Glo_postRequest({
86 | context: _self,
87 | init: false,
88 | data: data,
89 | url: '/monitor/instance/rename',
90 | response: (err, res) => {
91 | let responseResult = JSON.parse(res.text);
92 | if (responseResult.code === 1) {
93 | _self.renderRenameResult();
94 | } else {
95 | _self.hideEditModal();
96 | message.error("修改失败(" + responseResult.message + ")");
97 | }
98 | }
99 | });
100 | }
101 | deleteInstance = (text, record, id) => {
102 | let _self = this;
103 | let data = {
104 | name: record["name"]
105 | }
106 | Glo_deleteRequest({
107 | context: _self,
108 | init: true,
109 | data: data,
110 | url: '/monitor/instance',
111 | response: (err, res) => {
112 | let responseResult = JSON.parse(res.text);
113 | if (responseResult.code === 1) {
114 | _self.renderDeleteResult(text);
115 | } else {
116 | message.error("删除失败(" + responseResult.message + ")");
117 | }
118 | }
119 | });
120 | }
121 | renderAddResult = () => {
122 | let _self = this;
123 | //添加后重新load实例数据
124 | let tempState = Object.assign({}, this.state);
125 | tempState.addModalVisible = false;
126 | tempState.loading = true;
127 | _self.setState(tempState);
128 | Glo_getRequest({
129 | context: _self,
130 | init: false,
131 | url: '/monitor/instance',
132 | response: (err, res) => {
133 | let tempState = Object.assign({}, this.state);
134 | tempState.loading = false;
135 | this.setState(tempState);
136 | let responseResult = JSON.parse(res.text);
137 | if (responseResult.code === 1) {
138 | this.renderTable(responseResult);
139 | } else {
140 | message.error(responseResult.message);
141 | }
142 | }
143 | });
144 | }
145 | addInstance = () => {
146 | let _self = this;
147 | let data = {
148 | "host": _self.state.addInstance["host"],
149 | "port": _self.state.addInstance["port"]
150 | }
151 | if(_self.state.addInstance["name"]) {
152 | data["name"] = _self.state.addInstance["name"];
153 | }
154 | if(_self.state.addInstance["password"]) {
155 | data["password"] = _self.state.addInstance["password"];
156 | }
157 | Glo_postRequest({
158 | context: _self,
159 | data: data,
160 | url: '/monitor/instance',
161 | response: (err, res) => {
162 | let responseResult = JSON.parse(res.text);
163 | if (responseResult.code === 1) {
164 | _self.renderAddResult();
165 | } else {
166 | _self.hideAddModal();
167 | message.error("添加失败(" + responseResult.message + ")");
168 | }
169 | }
170 | });
171 | }
172 | renderTable = (value) => {
173 | let tempState = Object.assign({}, this.state);
174 | tempState.loading = false;
175 | let data = [];
176 | let index = 0;
177 | for(let key in value.data) {
178 | data.push({
179 | name: key,
180 | host: value.data[key].host,
181 | port: value.data[key].port,
182 | index: index
183 | })
184 | index++;
185 | }
186 |
187 | tempState.tableData = data;
188 | tempState.totalPage = value.pageCount;
189 | tempState.currentPage = value.pageCurrent;
190 | this.setState(tempState);
191 | };
192 |
193 | componentWillMount(){
194 | let _self = this;
195 | Glo_getRequest({
196 | context: _self,
197 | init: true,
198 | url: '/monitor/instance',
199 | response: (err, res) => {
200 | let responseResult = JSON.parse(res.text);
201 | if (responseResult.code === 1) {
202 | _self.renderTable(responseResult);
203 | } else {
204 | message.error(responseResult.message);
205 | }
206 | }
207 | });
208 | }
209 |
210 | render() {
211 | const columns = [{
212 | title: '实例名',
213 | dataIndex: 'name',
214 | key: 'name'
215 | }, {
216 | title: '地址',
217 | dataIndex: 'host',
218 | key: 'host'
219 | }, {
220 | title: '端口',
221 | dataIndex: 'port',
222 | key: 'port'
223 | },{
224 | title: '操作',
225 | dataIndex: 'index',
226 | render: (text, record, id) => {
227 | return (
228 |
229 |
230 |
232 |
233 | );
234 | },
235 | key: 'index'
236 | }];
237 |
238 | const formItemLayout = {
239 | labelCol: {span: 6},
240 | wrapperCol: {span: 14},
241 | };
242 |
243 | return (
244 |
245 |
246 |
248 |
249 |
250 |
251 |
253 |
254 |
this.renameInstance()}
259 | confirmLoading={this.state.editSending}
260 | onCancel={() => this.hideEditModal()}
261 | afterClose={() => this.hideEditModal()}>
262 |
276 |
277 |
this.addInstance()}
281 | confirmLoading={this.state.addSending}
282 | onCancel={() => this.hideAddModal()}>
283 |
309 |
310 |
311 |
312 | );
313 | }
314 | }
315 |
316 | let route = {
317 | component: PageContent
318 | };
319 |
320 | export default route;
--------------------------------------------------------------------------------
/client/src/pages/default/style.less:
--------------------------------------------------------------------------------
1 | .current-date {
2 | position: absolute;
3 | left: 16px;
4 | top: 11px;
5 | width: 100%;
6 | height: 30px;
7 | margin-bottom: 10px;
8 | font: normal 18px/30px 'Arial';
9 | color: #666;
10 | }
--------------------------------------------------------------------------------
/client/src/pages/memoryInfo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 |
4 | import { Input, Button, Spin, Form, Row, Col, DatePicker, message, Tabs, Icon, Tag,
5 | Modal, Select, Dropdown, Card} from 'antd';
6 |
7 | const FormItem = Form.Item;
8 | const TabPane = Tabs.TabPane;
9 | const InputGroup = Input.Group;
10 | const Option = Select.Option;
11 | const RangePicker = DatePicker.RangePicker;
12 |
13 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request';
14 | import { Glo_timeStampFormat } from '../../global/js/date';
15 | import {Charts} from '../../components/react-echarts/index';
16 | import {Glo_getCurrentDate, Glo_getPreDate} from '../../global/js/date';
17 | import './style';
18 |
19 | //page framework
20 | class PageContent extends React.Component {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | loading: false,
25 | beginTime: moment().hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'),
26 | endTime: moment().format('YYYY-MM-DD HH:mm:ss'),
27 | type: '1',
28 | instances: [],
29 | currentInstance: {
30 |
31 | },
32 | chartOption: {
33 | tooltip: {
34 | trigger: 'axis'
35 | },
36 | legend: {
37 | data: ['memory(M)']
38 | },
39 | grid: {
40 | left: '4%',
41 | right: '3%',
42 | bottom: '2%',
43 | containLabel: true
44 | },
45 | xAxis: [
46 | {
47 | type: 'category',
48 | data: []
49 | }
50 | ],
51 | yAxis: [
52 | {
53 | type: 'value'
54 | }
55 | ],
56 | series: [
57 | {
58 | name: 'memory(M)',
59 | type: 'line',
60 | data: []
61 | }
62 | ]
63 | }
64 | };
65 | }
66 | rangePickChange = (timeArr) => {
67 | let tempState = Object.assign({}, this.state);
68 | tempState.beginTime = timeArr[0];
69 | tempState.endTime = timeArr[1];
70 | this.setState(tempState);
71 | };
72 | typeChange = (value) => {
73 | let tempState = Object.assign({}, this.state);
74 | tempState.type = value;
75 | this.setState(tempState);
76 | }
77 | renderCharts = (value) => {
78 | let tempState = Object.assign({}, this.state);
79 | tempState.loading = false;
80 | let times = [];
81 | let memory = [];
82 | if(value.data) {
83 | for(let i = 0; i < value.data.length; i++) {
84 | let item = value.data[i];
85 | times.push(item.time);
86 | memory.push(item.value/1024/1024)
87 | }
88 | }
89 | tempState.chartOption.xAxis[0].data = times;
90 | tempState.chartOption.series[0].data = memory;
91 | this.setState(tempState);
92 | }
93 | instanceChange = (value) => {
94 | let tempState = Object.assign({}, this.state);
95 | for(let i = 0; i < tempState.instances.length; i++) {
96 | if(tempState.instances[i].showName == value) {
97 | tempState.currentInstance = tempState.instances[i];
98 | break;
99 | }
100 | }
101 | this.setState(tempState);
102 | }
103 | initInstances = (value) => {
104 | let tempState = Object.assign({}, this.state);
105 | tempState.instances = [];
106 | for(let key in value.data) {
107 | tempState.instances.push({
108 | showName: key + '(' + value.data[key].host +
109 | ':' + value.data[key].port + ')',
110 | name: key,
111 | host: value.data[key].host,
112 | port: value.data[key].port
113 | })
114 | }
115 | if(tempState.instances[0]) {
116 | tempState.currentInstance = tempState.instances[0];
117 | }
118 |
119 | this.setState(tempState, () => {
120 | this.reqMemoryData();
121 | });
122 | }
123 | reqMemoryData = () => {
124 | let _self = this;
125 | let data = {
126 | name: this.state.currentInstance.name,
127 | type: this.state.type,
128 | beginTime: this.state.beginTime,
129 | endTime: this.state.endTime
130 | }
131 | Glo_getRequest({
132 | context: _self,
133 | init: false,
134 | data: data,
135 | url: '/monitor/server/memory-change',
136 | response: (err, res) => {
137 | let responseResult = JSON.parse(res.text);
138 | if (responseResult.code === 1) {
139 | _self.renderCharts(responseResult);
140 | } else {
141 | message.error(responseResult.message);
142 | }
143 | }
144 | });
145 |
146 | }
147 | componentWillMount() {
148 | let _self = this;
149 | let tempState = Object.assign({}, this.state);
150 | tempState.beginTime = moment(this.currentDate).hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss');
151 | tempState.endTime = moment(this.currentDate).hour(23).minute(59).second(59).format('YYYY-MM-DD HH:mm:ss');
152 | this.setState(tempState);
153 | Glo_getRequest({
154 | context: _self,
155 | init: true,
156 | url: '/monitor/instance',
157 | response: (err, res) => {
158 | let responseResult = JSON.parse(res.text);
159 | if (responseResult.code === 1) {
160 | _self.initInstances(responseResult);
161 | } else {
162 | message.error(responseResult.message);
163 | }
164 | }
165 | });
166 | }
167 |
168 | render() {
169 | let _self = this;
170 | return (
171 |
172 |
173 |
181 |
188 | this.rangePickChange(dateString)}
190 | defaultValue={[moment(this.state.beginTime, "YYYY-MM-DD HH:mm:ss"),
191 | moment(this.state.endTime, "YYYY-MM-DD HH:mm:ss")]}/>
192 |
193 |
194 |
195 |
196 |
197 | 当前时间点实例使用的内存大小
198 |
199 |
200 |
201 |
202 | );
203 | }
204 | }
205 |
206 | let route = {
207 | path: '/memoryInfo',
208 | component: PageContent
209 | };
210 |
211 | export default route;
--------------------------------------------------------------------------------
/client/src/pages/memoryInfo/style.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/client/src/pages/memoryInfo/style.less
--------------------------------------------------------------------------------
/client/src/pages/qpsInfo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 |
4 | import { Input, Button, Spin, Form, Row, Col, DatePicker, message, Tabs, Icon, Tag,
5 | Modal, Select, Dropdown, Card} from 'antd';
6 |
7 | const FormItem = Form.Item;
8 | const TabPane = Tabs.TabPane;
9 | const InputGroup = Input.Group;
10 | const Option = Select.Option;
11 | const RangePicker = DatePicker.RangePicker;
12 |
13 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request';
14 | import { Glo_timeStampFormat } from '../../global/js/date';
15 | import {Charts} from '../../components/react-echarts/index';
16 | import {Glo_getCurrentDate, Glo_getPreDate} from '../../global/js/date';
17 | import './style';
18 |
19 | //page framework
20 | class PageContent extends React.Component {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | loading: false,
25 | beginTime: moment().hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'),
26 | endTime: moment().format('YYYY-MM-DD HH:mm:ss'),
27 | type: '1',
28 | instances: [],
29 | currentInstance: {
30 |
31 | },
32 | chartOption: {
33 | tooltip: {
34 | trigger: 'axis'
35 | },
36 | legend: {
37 | data: ['吞吐率']
38 | },
39 | grid: {
40 | left: '4%',
41 | right: '3%',
42 | bottom: '2%',
43 | containLabel: true
44 | },
45 | xAxis: [
46 | {
47 | type: 'category',
48 | data: []
49 | }
50 | ],
51 | yAxis: [
52 | {
53 | type: 'value'
54 | }
55 | ],
56 | series: [
57 | {
58 | name: '吞吐率',
59 | type: 'line',
60 | data: []
61 | }
62 | ]
63 | }
64 | };
65 | }
66 | rangePickChange = (timeArr) => {
67 | let tempState = Object.assign({}, this.state);
68 | tempState.beginTime = timeArr[0];
69 | tempState.endTime = timeArr[1];
70 | this.setState(tempState);
71 | };
72 | typeChange = (value) => {
73 | let tempState = Object.assign({}, this.state);
74 | tempState.type = value;
75 | this.setState(tempState);
76 | }
77 | renderCharts = (value) => {
78 | let tempState = Object.assign({}, this.state);
79 | tempState.loading = false;
80 | let times = [];
81 | let memory = [];
82 | if(value.data) {
83 | for(let i = 0; i < value.data.length; i++) {
84 | let item = value.data[i];
85 | times.push(item.time);
86 | memory.push(item.value);
87 | }
88 | }
89 | tempState.chartOption.xAxis[0].data = times;
90 | tempState.chartOption.series[0].data = memory;
91 | this.setState(tempState);
92 | }
93 | instanceChange = (value) => {
94 | let tempState = Object.assign({}, this.state);
95 | for(let i = 0; i < tempState.instances.length; i++) {
96 | if(tempState.instances[i].showName == value) {
97 | tempState.currentInstance = tempState.instances[i];
98 | break;
99 | }
100 | }
101 | this.setState(tempState);
102 | }
103 | initInstances = (value) => {
104 | let tempState = Object.assign({}, this.state);
105 | tempState.instances = [];
106 | for(let key in value.data) {
107 | tempState.instances.push({
108 | showName: key + '(' + value.data[key].host +
109 | ':' + value.data[key].port + ')',
110 | name: key,
111 | host: value.data[key].host,
112 | port: value.data[key].port
113 | })
114 | }
115 | if(tempState.instances[0]) {
116 | tempState.currentInstance = tempState.instances[0];
117 | }
118 |
119 | this.setState(tempState);
120 | this.reqQpsData();
121 | }
122 | reqQpsData = () => {
123 | let _self = this;
124 | let data = {
125 | name: this.state.currentInstance.name,
126 | type: this.state.type,
127 | beginTime: this.state.beginTime,
128 | endTime: this.state.endTime
129 | }
130 | Glo_getRequest({
131 | context: _self,
132 | init: false,
133 | data: data,
134 | url: '/monitor/server/qps',
135 | response: (err, res) => {
136 | let responseResult = JSON.parse(res.text);
137 | if (responseResult.code === 1) {
138 | _self.renderCharts(responseResult);
139 | } else {
140 | message.error(responseResult.message);
141 | }
142 | }
143 | });
144 |
145 | }
146 | componentWillMount() {
147 | let _self = this;
148 | let tempState = Object.assign({}, this.state);
149 | tempState.beginTime = moment(this.currentDate).hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss');
150 | tempState.endTime = moment(this.currentDate).hour(23).minute(59).second(59).format('YYYY-MM-DD HH:mm:ss');
151 | this.setState(tempState);
152 | Glo_getRequest({
153 | context: _self,
154 | init: true,
155 | url: '/monitor/instance',
156 | response: (err, res) => {
157 | let responseResult = JSON.parse(res.text);
158 | if (responseResult.code === 1) {
159 | _self.initInstances(responseResult);
160 | } else {
161 | message.error(responseResult.message);
162 | }
163 | }
164 | });
165 | }
166 |
167 | render() {
168 | let _self = this;
169 | return (
170 |
171 |
172 |
180 |
187 | this.rangePickChange(dateString)}
189 | defaultValue={[moment(this.state.beginTime, "YYYY-MM-DD HH:mm:ss"),
190 | moment(this.state.endTime, "YYYY-MM-DD HH:mm:ss")]}/>
191 |
192 |
193 |
194 |
195 |
196 | 单位时间执行的命令数
197 |
198 |
199 |
200 |
201 | );
202 | }
203 | }
204 |
205 | let route = {
206 | path: '/qpsInfo',
207 | component: PageContent
208 | };
209 |
210 | export default route;
--------------------------------------------------------------------------------
/client/src/pages/qpsInfo/style.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/client/src/pages/qpsInfo/style.less
--------------------------------------------------------------------------------
/client/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path =require('path');
3 | var config = {
4 | entry: [
5 | 'webpack/hot/only-dev-server',
6 | './index.js'
7 | ],
8 | output: {
9 | path: path.join(__dirname ,'../server/public/js/build'),
10 | filename: 'build.js'
11 | },
12 | resolve: {
13 | extensions: ['.js', '.jsx', '.json', '.less']
14 | },
15 | module: {
16 | rules: [{
17 | test: /\.js$/,
18 | exclude: /node_modules/,
19 | loader: 'babel-loader',
20 | query: {
21 | cacheDirectory: true,
22 | plugins: [['import', {'libraryName': 'antd', 'style': true}]],
23 | presets: ['es2015', 'react', 'stage-0', 'stage-1']
24 | }
25 | }, {
26 | test: /\.less$/,
27 | loader: 'style-loader!css-loader!less-loader'
28 | }]
29 | },
30 | devServer: {
31 | host: '127.0.0.1',
32 | port: 9999,
33 | proxy: {
34 | '/monitor/*': {
35 | target: 'http://127.0.0.1:12000'
36 | }
37 | }
38 | },
39 | };
40 |
41 | module.exports = config;
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redis-monitor",
3 | "version": "1.0.0",
4 | "description": "koa+react+antd写的一个简单redis状态监控工具",
5 | "main": "./server/bin/index.js",
6 | "scripts": {
7 | "start": "supervisor ./server/bin/index.js",
8 | "dev": "supervisor ./server/bin/index.js ",
9 | "dev-schedule": "supervisor ./server/schedule/index.js",
10 | "product": "pm2 start ./server/bin/index.js --name redis-monitor",
11 | "product-schedule": "pm2 start ./server/schedule/index.js --name redis-monitor-schedule",
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "dependencies": {
15 | "koa": "^2.2.0",
16 | "koa-bodyparser": "^4.2.0",
17 | "koa-favicon": "^2.0.0",
18 | "koa-router": "^7.0.1",
19 | "koa-static": "^3.0.0",
20 | "log4js": "^1.1.1",
21 | "moment": "^2.18.1",
22 | "node-schedule": "^1.2.1",
23 | "redis": "^2.7.1"
24 | },
25 | "devDependencies": {},
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/jizhuofeng/redis-monitor.git"
29 | },
30 | "keywords": [
31 | "redis",
32 | "monitor"
33 | ],
34 | "author": "jizhuofeng",
35 | "license": "MIT"
36 | }
37 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 |
5 | 'use strict';
6 | const Koa = require('koa');
7 | const path = require('path');
8 | const bodyparser = require('koa-bodyparser');
9 | const staticServer = require('koa-static');
10 | const responseTime = require('./common/middleware/response-time');
11 | const router = require('./router');
12 | const app = new Koa();
13 |
14 | app.use(bodyparser());
15 | app.use(responseTime);
16 | //静态资源配置
17 | app.use(staticServer(__dirname + '/public'));
18 | //应用路由
19 | router(app);
20 | module.exports = app;
--------------------------------------------------------------------------------
/server/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | let app = require('../app');
8 | let config = require('../config');
9 | let logger = require('../common/logger').logger;
10 | let http = require('http');
11 |
12 | /**
13 | * Get port from environment and store in Express.
14 | */
15 |
16 | var port = normalizePort(process.env.PORT || config.serverPort);
17 | // app.set('port', port);
18 |
19 | /**
20 | * Create HTTP server.
21 | */
22 |
23 | var server = http.createServer(app.callback());
24 |
25 | /**
26 | * Listen on provided port, on all network interfaces.
27 | */
28 |
29 | server.listen(port);
30 | server.on('error', onError);
31 | server.on('listening', onListening);
32 |
33 | /**
34 | * Normalize a port into a number, string, or false.
35 | */
36 |
37 | function normalizePort(val) {
38 | var port = parseInt(val, 10);
39 |
40 | if (isNaN(port)) {
41 | // named pipe
42 | return val;
43 | }
44 |
45 | if (port >= 0) {
46 | // port number
47 | return port;
48 | }
49 |
50 | return false;
51 | }
52 |
53 | /**
54 | * Event listener for HTTP server "error" event.
55 | */
56 |
57 | function onError(error) {
58 | if (error.syscall !== 'listen') {
59 | throw error;
60 | }
61 |
62 | var bind = typeof port === 'string'
63 | ? 'Pipe ' + port
64 | : 'Port ' + port;
65 |
66 | // handle specific listen errors with friendly messages
67 | switch (error.code) {
68 | case 'EACCES':
69 | logger.error(bind + ' requires elevated privileges');
70 | process.exit(1);
71 | break;
72 | case 'EADDRINUSE':
73 | logger.error(bind + ' is already in use');
74 | process.exit(1);
75 | break;
76 | default:
77 | throw error;
78 | }
79 | }
80 |
81 | /**
82 | * Event listener for HTTP server "listening" event.
83 | */
84 |
85 | function onListening() {
86 | var addr = server.address();
87 | var bind = typeof addr === 'string'
88 | ? 'pipe ' + addr
89 | : 'port ' + addr.port;
90 | logger.info('Listening on ' + bind);
91 | }
92 |
--------------------------------------------------------------------------------
/server/common/logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 |
6 | let log4js = require('log4js');
7 | let path = require('path');
8 | log4js.configure({
9 | "replaceConsole": true,
10 | "appenders":[{
11 | "category": "console",
12 | "type": "console"
13 | },
14 | {
15 | "type": "file",
16 | "category": "logger",
17 | "filename": path.join(__dirname, "../logs/redis-monitor.log"),
18 | "pattern": "-yyyy-MM-dd",
19 | "alwaysIncludePattern": true,
20 | "layout":{
21 | "type":"pattern",
22 | "pattern" : "%d(%x{pid})[%p][%c] %m",
23 | "tokens":{
24 | "pid": function(){return process.pid;}
25 | }
26 | }
27 | }
28 | ],
29 | "levels":{
30 | "[all]": "DEBUG"
31 | }
32 | })
33 |
34 | module.exports = {
35 | 'logger': log4js.getLogger('logger')
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/server/common/middleware/response-time.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const logger = require('../logger').logger;
3 |
4 | module.exports = async function (ctx, next){
5 | let start = new Date();
6 | await next();
7 | let ms = new Date() - start;
8 | logger.info(`request : ${ctx.method} ${ctx.url} used ${ms}ms`);
9 | }
--------------------------------------------------------------------------------
/server/common/redis-client.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/29.
3 | */
4 | 'use strict';
5 |
6 | const redis = require('redis');
7 | const config = require('../config');
8 | const logger = require('./logger').logger;
9 | const redisClient= redis.createClient(config.redis);
10 |
11 | redisClient.on('error', function (err) {
12 | logger.error('redis error event - ' + config.redis.host + ':' +
13 | config.redis.port + ' - ' + err);
14 | });
15 |
16 | module.exports = redisClient;
17 |
18 |
--------------------------------------------------------------------------------
/server/common/response.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = new class {
4 | constructor() {
5 |
6 | }
7 | SUCCESS(ctx, data = [], pageCount = 1, pageCurrent = 1) {
8 | if(!data) {
9 | data = {};
10 | }
11 | ctx.body = {
12 | code: 1,
13 | message: 'success',
14 | data: data,
15 | pageCount: pageCount,
16 | pageCurrent: pageCurrent
17 | }
18 | }
19 | FAILED(ctx, message = 'failed') {
20 | ctx.body = {
21 | code: 0,
22 | message: message
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/server/common/util/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/29.
3 | */
4 | 'use strict';
5 |
6 | //解析redis info命令返回的数据结构
7 | exports.parseRedisServerInfo = function (info) {
8 |
9 | let data = {
10 | }
11 | if(!info) {
12 | return data;
13 | }
14 | let tmp = info.split('#');
15 | for(let i = 0; i < tmp.length; i++) {
16 | if(tmp[i] == '') {
17 | continue;
18 | }
19 | let items = tmp[i].split('\r\n');
20 | let resTmp = {
21 |
22 | }
23 | for(let k = 1; k < items.length; k++) {
24 | if(items[k] == '') {
25 | continue;
26 | }
27 | let item = items[k].split(':');
28 | resTmp[item[0]] = item[1];
29 |
30 | }
31 | data[items[0].substr(1, items[0].length)] = resTmp
32 | }
33 | return data;
34 | }
--------------------------------------------------------------------------------
/server/config/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 |
6 | module.exports = {
7 | serverPort: 12000,
8 | redis: {
9 | host: '127.0.0.1',
10 | port: 6379,
11 | db: 2
12 | }
13 | }
--------------------------------------------------------------------------------
/server/controller/instance-list-ctl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | let InstanceService = require('../service/instance-service');
6 | let logger = require('../common/logger').logger;
7 | let Response = require('../common/response');
8 |
9 | module.exports = new class {
10 | constructor() {
11 | }
12 | //获取redis实例信息
13 | async getInstance(ctx, next) {
14 |
15 | let [name] = [];
16 | if(ctx.query) {
17 | ({name} = ctx.query);
18 | }
19 | logger.info('get instance list request body %s', JSON.stringify(ctx.query));
20 | try {
21 | let info = await InstanceService.getInstanceList(name);
22 | logger.info('get instance list %s', JSON.stringify(info));
23 | Response.SUCCESS(ctx, info);
24 | } catch(err) {
25 | logger.error('get instance list error %s', err.toString());
26 | Response.FAILED(ctx, '获取失败');
27 | }
28 |
29 | }
30 | //添加redis实例
31 | async addInstance(ctx, next) {
32 |
33 | let [name, host, port, password] = [];
34 | if(ctx.request.body) {
35 | ({name, host, port, password} = ctx.request.body);
36 | }
37 | logger.info('add instance request body %s', JSON.stringify(ctx.request.body));
38 | if(!host || !port) {
39 | logger.warn('add instance less params host or port');
40 | Response.FAILED(ctx, '缺少参数');
41 | return;
42 | }
43 | if(!name) {
44 | name = host + ':' + port;
45 | }
46 | try {
47 |
48 | let res = await InstanceService.addInstance(name, host, port, password);
49 | logger.info('add instance result %s', res);
50 | if(res != 'success') {
51 | Response.FAILED(ctx, '添加失败(' + res + ')');
52 | return;
53 | }
54 | Response.SUCCESS(ctx);
55 | } catch(err) {
56 | logger.error('add instance error %s', err.toString());
57 | Response.FAILED(ctx, '添加失败');
58 | }
59 | }
60 | async deleteInstance(ctx, next) {
61 | let [name] = [];
62 | if(ctx.request.body) {
63 | ({name} = ctx.request.body);
64 | }
65 | logger.info('delete instance request body %s', JSON.stringify(ctx.request.body));
66 | if(!name) {
67 | logger.warn('delete instance less params name');
68 | Response.FAILED(ctx, '缺少参数');
69 | return;
70 | }
71 | try {
72 | let res = await InstanceService.deleteInstance(name);
73 | logger.info('delete instance result %s', res);
74 | if(res != 'success') {
75 | Response.FAILED(ctx, '删除失败(' + res + ')');
76 | return;
77 | }
78 | Response.SUCCESS(ctx);
79 |
80 | } catch(err) {
81 | logger.error('delete instance error %s', err.toString());
82 | Response.FAILED(ctx, '删除失败');
83 | }
84 |
85 |
86 | }
87 | async renameInstance(ctx, next) {
88 | let [name, oldName] = [];
89 | if(ctx.request.body) {
90 | ({name, oldName} = ctx.request.body);
91 | }
92 | logger.info('rename instance request body %s', JSON.stringify(ctx.request.body));
93 | if(!name || !oldName) {
94 | logger.warn('rename instance less params name or oldName');
95 | Response.FAILED(ctx, '缺少参数');
96 | return;
97 | }
98 | try {
99 | let res = await InstanceService.renameInstance(name, oldName);
100 | logger.info('rename instance result %s', res);
101 | if(res != 'success') {
102 | Response.FAILED(ctx, '重命名失败(' + res + ')');
103 | return;
104 | }
105 | Response.SUCCESS(ctx);
106 | } catch(err) {
107 | logger.error('rename instance error %s', err.toString());
108 | Response.FAILED(ctx, '重命名失败');
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/server/controller/instance-status-ctl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | let InstanceService = require('../service/instance-service');
6 | let MemoryService = require('../service/memory-management-service');
7 | let QpsService = require('../service/qps-management-service');
8 | let logger = require('../common/logger').logger;
9 | let Response = require('../common/response');
10 |
11 | module.exports = new class {
12 | constructor() {
13 | }
14 | async getServerBaseInfo(ctx, next) {
15 | //获取实例info命令返回的数据
16 | let [name] = [];
17 | if(ctx.query) {
18 | ({name} = ctx.query);
19 | }
20 | logger.info('get server base info request body %s', JSON.stringify(ctx.query));
21 | if(!name) {
22 | logger.warn('get server base info less params name');
23 | Response.FAILED(ctx, '缺少参数');
24 | return;
25 | }
26 | try {
27 | let info = await InstanceService.getServerBaseInfo(name);
28 | logger.info('get server base info %s', JSON.stringify(info));
29 | Response.SUCCESS(ctx, info);
30 | } catch(err) {
31 | logger.error('get server base info error %s', err.toString());
32 | Response.FAILED(ctx, '获取失败');
33 | }
34 | }
35 | async getMemoryChangeInfo(ctx, next) {
36 | let [name, type, beginTime, endTime] = [];
37 | if(ctx.query) {
38 | ({name, type, beginTime, endTime} = ctx.query);
39 | }
40 | //type 1=分钟, 2=小时,3=天
41 | //type=1时时间区间限制为一天内的数据(beginTime当天的数据)
42 | //type=2时时间区间限制为一周内的数据(beginTime之后一周内的数据)
43 | //type=3时时间区间无限制
44 | if(!name || !type || !beginTime || !endTime) {
45 | logger.warn('get memory info less params type or beginTime or endTime');
46 | Response.FAILED(ctx, '缺少参数');
47 | return;
48 | }
49 | type = parseInt(type, 10);
50 | try {
51 | let data = [];
52 | data = await MemoryService.getMemoryChangeInfo(name, type, beginTime, endTime);
53 | Response.SUCCESS(ctx, data);
54 | } catch(err) {
55 | logger.error('get memory info error %s', err.toString());
56 | Response.FAILED(ctx, '获取失败');
57 | }
58 | }
59 | async getQpsInfo(ctx, next) {
60 | let [name, type, beginTime, endTime] = [];
61 | if(ctx.query) {
62 | ({name, type, beginTime, endTime} = ctx.query);
63 | }
64 | //type 1=分钟, 2=小时,3=天
65 | //type=1时时间区间限制为一天内的数据(beginTime当天的数据)
66 | //type=2时时间区间限制为一周内的数据(beginTime之后一周内的数据)
67 | //type=3时时间区间无限制
68 | if(!name || !type || !beginTime || !endTime) {
69 | logger.warn('get qps info less params type or beginTime');
70 | Response.FAILED(ctx, '缺少参数');
71 | return;
72 | }
73 | type = parseInt(type, 10);
74 | try {
75 | let data = [];
76 | data = await QpsService.getQpsInfo(name, type, beginTime, endTime);
77 | Response.SUCCESS(ctx, data);
78 | } catch(err) {
79 | logger.error('get qps info error %s', err.toString());
80 | Response.FAILED(ctx, '获取失败');
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/server/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/server/public/favicon.ico
--------------------------------------------------------------------------------
/server/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | redis服务监控
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/server/router/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | const Router = require('koa-router');
6 | const fs = require('fs');
7 |
8 | const appRouter = new Router();
9 | //动态引入router目录下路由文件
10 | let files = fs.readdirSync(__dirname);
11 | if (files && files.length > 0) {
12 | for(let i = 0; i < files.length; i++) {
13 | let file = files[i];
14 | let pos = file.lastIndexOf('.');
15 | if (pos == -1) {
16 | continue;
17 | }
18 | let fileName = file.substr(0, pos);
19 | let fileExt = file.substr(pos+1);
20 | if (fileName === 'index') {
21 | continue;
22 | }
23 | let router = require('./' + fileName);
24 | appRouter.use('/monitor', router.routes(), router.allowedMethods());
25 | }
26 | }
27 | function router(app){
28 | app.use(appRouter.routes());
29 | }
30 |
31 | module.exports = router;
--------------------------------------------------------------------------------
/server/router/instance-list-router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | const Router = require('koa-router');
6 | const ILCtl = require('../controller/instance-list-ctl');
7 |
8 | const router = new Router({
9 | });
10 | //监控系统监控的实例列表管理
11 | //获取实例列表
12 | router.get('/instance', ILCtl.getInstance);
13 | //添加实例
14 | router.post('/instance', ILCtl.addInstance);
15 | //删除实例
16 | router.del('/instance', ILCtl.deleteInstance);
17 | //实例重命名
18 | router.post('/instance/rename', ILCtl.renameInstance);
19 |
20 | module.exports = router;
21 |
--------------------------------------------------------------------------------
/server/router/instance-status-router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | const Router = require('koa-router');
6 | const ISCtl = require('../controller/instance-status-ctl');
7 |
8 |
9 | const router = new Router({
10 | });
11 | //实例运行状态信息获取
12 | router.get('/server/base-info', ISCtl.getServerBaseInfo);
13 | //获取实例内存变化
14 | router.get('/server/memory-change', ISCtl.getMemoryChangeInfo);
15 | //获取实例处理请求的变化
16 | router.get('/server/qps', ISCtl.getQpsInfo);
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/schedule/create-redis-server-info.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 | const moment = require('moment');
6 | const redis = require('redis');
7 | const InstanceService = require('../service/instance-service');
8 | const logger = require('../common/logger').logger;
9 | const util = require('../common/util');
10 |
11 | //同步redis数据
12 | exports.createData = async function () {
13 | try {
14 | //获取实例列表
15 | let redisList = await InstanceService.getInstanceList(null, true);
16 | logger.debug('create data redis list %s', JSON.stringify(redisList));
17 | for(let key in redisList) {
18 | //遍历实例
19 | let res = await InstanceService.syncRedisInstanceInfo(redisList[key]);
20 | logger.info('create data result %s', res);
21 | }
22 | } catch (err) {
23 | logger.error('create data occour error %s', err.toString());
24 |
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/server/schedule/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/28.
3 | */
4 | 'use strict';
5 |
6 | let schedule = require('node-schedule');
7 | let CRSI = require('./create-redis-server-info');
8 | let logger = require('../common/logger').logger;
9 |
10 |
11 | let rule = '0 * * * * *';
12 | schedule.scheduleJob(rule, CRSI.createData);
13 | logger.debug('schedule start');
14 |
15 |
--------------------------------------------------------------------------------
/server/service/api/redis.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/29.
3 | */
4 | 'use strict';
5 | let redisClient = require('../../common/redis-client');
6 |
7 | //redis各种api接口封装
8 | //如果提供client参数则使用提供的client, 若不提供client则默认使用全局的redisClient
9 | module.exports = new class {
10 | constructor() {
11 |
12 | }
13 | //hgetall
14 | hgetall(key, client = redisClient) {
15 | return new Promise(function(resolve, reject) {
16 | client.hgetall(key, function(err, res) {
17 | if(!err && res) {
18 | resolve(res);
19 | return;
20 | }
21 | resolve(null);
22 | })
23 | })
24 | }
25 | //hget
26 | hget(key, field, client = redisClient) {
27 | return new Promise(function(resolve, reject) {
28 | client.hget(key, field, function(err, res) {
29 | if(!err && res) {
30 | resolve(res);
31 | return;
32 | }
33 | resolve(null);
34 | })
35 | })
36 | }
37 | //hmget
38 | hmget(key, fields, client = redisClient) {
39 | return new Promise(function(resolve, reject) {
40 | client.hmget(key, fields, function(err, res) {
41 | if(!err && res) {
42 | resolve(res);
43 | return;
44 | }
45 | resolve(null);
46 | })
47 | })
48 | }
49 | //hset
50 | hset(key, field, value, client = redisClient) {
51 | return new Promise(function(resolve, reject) {
52 | client.hset(key, field, value, function(err, res) {
53 | if(!err && res) {
54 | resolve(res);
55 | return;
56 | }
57 | resolve(null);
58 | })
59 | })
60 | }
61 | //hdel
62 | hdel(key, field, client = redisClient) {
63 | return new Promise(function(resolve, reject) {
64 | client.hdel(key, field, function(err, res) {
65 | if(!err && res) {
66 | resolve(res);
67 | return;
68 | }
69 | resolve(null);
70 | })
71 | })
72 | }
73 | //info
74 | info(client) {
75 | return new Promise(function(resolve, reject) {
76 | client.info(function(err, res) {
77 | if(!err && res) {
78 | resolve(res);
79 | return;
80 | }
81 | resolve(null);
82 | })
83 | })
84 | }
85 | }
--------------------------------------------------------------------------------
/server/service/instance-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jason on 2017/3/29.
3 | */
4 | 'use strict';
5 | let moment = require('moment');
6 | let redis = require('redis');
7 | let RedisApi = require('./api/redis');
8 | let util = require('../common/util');
9 | let logger = require('../common/logger').logger;
10 |
11 | module.exports = new class {
12 | constructor() {
13 |
14 | }
15 | //检查实例是否可以已存在
16 | async checkInstanceExists(host, port) {
17 | let insList = await this.getInstanceList();
18 | if(insList) {
19 | for(let key in insList) {
20 | let ins = insList[key];
21 | if(ins.host == host && ins.port == port) {
22 | return true;
23 | }
24 | }
25 | }
26 | return false;
27 | }
28 | //检查实例是否可以正常连接
29 | async checkInstanceConnect(host, port, password) {
30 | let config = {
31 | host: host,
32 | port: port
33 | }
34 | if(password) {
35 | config.password = password;
36 | }
37 | return new Promise(function(resolve, reject) {
38 | let client = redis.createClient(config);
39 | client.on('error', function(err) {
40 | client.quit();
41 | resolve(false);
42 | });
43 | client.on('ready', function() {
44 | client.quit();
45 | resolve(true);
46 | });
47 | });
48 | }
49 | //获取实例info命令数据
50 | async getServerBaseInfo(name) {
51 | let key = 'server-instance-list';
52 | let insConfig = await RedisApi.hget(key, name);
53 | if(!insConfig) {
54 | logger.info('get server base info instance not exists');
55 | return null;
56 | }
57 | let config = JSON.parse(insConfig);
58 | let canConnect = await this.checkInstanceConnect(config.host, config.port, config.password);
59 | if(!canConnect) {
60 | logger.info('get server base info instance can not connect');
61 | return null;
62 | }
63 | let client = redis.createClient(config);
64 | client.on('error', function(err) {
65 | logger.error('get server info connect redis %s', err.toString());
66 | client.quit();
67 | });
68 | let info = await RedisApi.info(client);
69 | //解析redis info 命令返回的数据
70 | let infoObj = util.parseRedisServerInfo(info);
71 | return infoObj;
72 | }
73 | //实例重命名
74 | async renameInstance(name, oldName) {
75 | let key = 'server-instance-list';
76 | let field = name;
77 | let fieldOld = oldName;
78 | let newInfo = await RedisApi.hget(key, field);
79 | if(newInfo) {
80 | return 'instance has exists';
81 | }
82 | let oldInfo = await RedisApi.hget(key, fieldOld);
83 | if(oldInfo) {
84 | //重新添加
85 | let addRes = await RedisApi.hset(key, field, oldInfo);
86 | //删除
87 | let delRes = await RedisApi.hdel(key, fieldOld);
88 | return 'success';
89 | } else {
90 | return 'instance not exists';
91 | }
92 | }
93 | //删除实例
94 | async deleteInstance(name) {
95 |
96 | let field = name;
97 | let key = 'server-instance-list';
98 | let ins = await RedisApi.hget(key, field);
99 | if(!ins) {
100 | //实例不存在
101 | logger.info('delete instance instance not exists');
102 | return 'instance not exists';
103 | }
104 | let res = await RedisApi.hdel(key, field);
105 | return 'success';
106 | }
107 |
108 | //添加实例
109 | async addInstance(name, host, port, password) {
110 | //检察是否已存在
111 | let exists = await this.checkInstanceExists(host, port);
112 | if(exists) {
113 | logger.info('add instance instance has exists');
114 | return 'instance has exists';
115 | }
116 | //检查实例是否可正常连接
117 | // let canConnect = await this.checkInstanceConnect(host, port, password);
118 | // if(!canConnect) {
119 | // logger.info('add instance instance can not connect');
120 | // return 'can not connect';
121 | // }
122 | let info = {
123 | host: host,
124 | port: port
125 | }
126 | if(password) {
127 | info.password = password;
128 | }
129 | let field = name;
130 | let key = 'server-instance-list';
131 | let value = JSON.stringify(info);
132 | let res = await RedisApi.hset(key, field, value);
133 | return 'success';
134 | }
135 | //获取实例列表
136 | async getInstanceList(name, flag = false) {
137 | let key = 'server-instance-list';
138 | let list = {};
139 | if(name) {
140 | let tmp = await RedisApi.hget(key, name);
141 | if(tmp) {
142 | let ins = JSON.parse(tmp)
143 | list[name] = {
144 | host: ins.host,
145 | port: ins.port
146 | }
147 | if(flag && ins.password) {
148 | list[name].password = ins.password;
149 | }
150 | }
151 |
152 | } else {
153 | let tmp = await RedisApi.hgetall(key);
154 | if(tmp) {
155 | for(let key in tmp) {
156 | let ins = JSON.parse(tmp[key])
157 | list[key] = {
158 | host: ins.host,
159 | port: ins.port
160 | }
161 | if(flag && ins.password) {
162 | list[key].password = ins.password;
163 | }
164 | }
165 | }
166 | }
167 | return list;
168 | }
169 | //同步redis实例状态信息
170 | async syncRedisInstanceInfo(config) {
171 | try {
172 | let nowT = moment();
173 | let client = redis.createClient(config);
174 | client.on('error', function(err) {
175 | logger.error('sync redis instance info connect redis %s', err.toString());
176 | client.quit();
177 | });
178 | let info = await RedisApi.info(client);
179 | //解析redis info 命令返回的数据
180 | let infoObj = util.parseRedisServerInfo(info);
181 | //logger.debug('instance info %s', JSON.stringify(infoObj));
182 | let res = null;
183 | let dayT = nowT.clone().hour(0).minute(0).second(0).unix();
184 | let hourT = nowT.clone().minute(0).second(0).unix();
185 | let minuteT = nowT.clone().second(0).unix();
186 | let preKey = config.host + ':' + config.port;
187 | let usedMemory = infoObj.Memory.used_memory;
188 | let totalCommand = infoObj.Stats.total_commands_processed;
189 | //内存数据
190 | //分钟信息
191 | await RedisApi.hset(preKey + ':M:M',
192 | minuteT, usedMemory);
193 | //小时信息
194 | let hourV = await RedisApi.hget(preKey + ':M:H', hourT);
195 | if(!hourV || parseInt(hourV) < parseInt(usedMemory)) {
196 | //更新小时内最大值
197 | await RedisApi.hset(preKey + ':M:H',
198 | hourT, usedMemory);
199 | }
200 | //天信息
201 | let dayV = await RedisApi.hget(preKey + ':M:D', dayT);
202 | if(!dayV || parseInt(dayV) < parseInt(usedMemory)) {
203 | //更新天内最大值
204 | await RedisApi.hset(preKey + ':M:D',
205 | dayT, usedMemory);
206 | }
207 | //执行命令数统计
208 | await RedisApi.hset(preKey+":CP:M", minuteT, totalCommand);
209 | client.quit();
210 | return "success";
211 | } catch(err) {
212 | logger.error('sync redis instance info error %s', err);
213 | return 'sync failed';
214 | }
215 |
216 | }
217 |
218 | }
--------------------------------------------------------------------------------
/server/service/memory-management-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | let moment = require('moment');
3 | let redis = require('redis');
4 | let RedisApi = require('./api/redis');
5 | let util = require('../common/util');
6 | let logger = require('../common/logger').logger;
7 |
8 | //实例内存数据
9 | module.exports = new class {
10 | constructor() {
11 |
12 | }
13 | //按分钟获取
14 | async getMemoryChangeInfoByMinute(config, beginTime, endTime) {
15 | let beginT = moment(beginTime);
16 | let endT = moment(endTime);
17 | let fields = [];
18 | let dateArr = [];
19 | let count = 0;
20 | while(!beginT.isAfter(endT, 'minute')) {
21 | if(count >= 1440) {
22 | break;
23 | }
24 | let tmpT = beginT.clone().second(0);
25 | dateArr.push(tmpT.format('YYYY-MM-DD HH:mm'));
26 | fields.push(tmpT.unix());
27 | beginT.add(1, 'minutes');
28 | count++;
29 | }
30 | logger.debug('fields %s', JSON.stringify(fields));
31 | let key = config.host + ':' + config.port + ':M:M';
32 | let values = await RedisApi.hmget(key, fields);
33 | let data = [];
34 | if(values) {
35 | for(let i = 0; i < values.length; i++) {
36 | let tmp = {
37 | time: dateArr[i],
38 | value: 0
39 | }
40 | if(values[i]) {
41 | tmp.value = parseInt(values[i]) ;
42 | }
43 | data.push(tmp);
44 | }
45 | }
46 | return data;
47 | }
48 | //按小时获取
49 | async getMemoryChangeInfoByHour(config, beginTime, endTime) {
50 | let beginT = moment(beginTime);
51 | let endT = moment(endTime);
52 | let fields = [];
53 | let dateArr = [];
54 | let count = 0;
55 | while(!beginT.isAfter(endT, 'hour')) {
56 | if(count > 200) {
57 | break;
58 | }
59 | let tmpT = beginT.clone().minute(0).second(0);
60 | dateArr.push(tmpT.format('YYYY-MM-DD HH'));
61 | fields.push(tmpT.unix());
62 | beginT.add(1, 'hours');
63 | count++;
64 | }
65 | logger.debug('fields %s', JSON.stringify(fields));
66 | let key = config.host + ':' + config.port + ':M:H';
67 | let values = await RedisApi.hmget(key, fields);
68 | let data = [];
69 | if(values) {
70 | for(let i = 0; i < values.length; i++) {
71 | let tmp = {
72 | time: dateArr[i],
73 | value: 0
74 | }
75 | if(values[i]) {
76 | tmp.value = parseInt(values[i]) ;
77 | }
78 | data.push(tmp);
79 | }
80 | }
81 | return data;
82 | }
83 | //按天获取
84 | async getMemoryChangeInfoByDate(config, beginTime, endTime) {
85 | let beginT = moment(beginTime);
86 | let endT = moment(endTime);
87 | let fields = [];
88 | let dateArr = [];
89 | while(!beginT.isAfter(endT, 'day')) {
90 | let tmpT = beginT.clone().hour(0).minute(0).second(0);
91 | dateArr.push(tmpT.format('YYYY-MM-DD'));
92 | fields.push(tmpT.unix());
93 | beginT.add(1, 'days');
94 | }
95 | logger.debug('fields %s', JSON.stringify(fields));
96 | let key = config.host + ':' + config.port + ':M:D';
97 | let values = await RedisApi.hmget(key, fields);
98 | let data = [];
99 | if(values) {
100 | for(let i = 0; i < values.length; i++) {
101 | let tmp = {
102 | time: dateArr[i],
103 | value: 0
104 | }
105 | if(values[i]) {
106 | tmp.value = parseInt(values[i]) ;
107 | }
108 | data.push(tmp);
109 | }
110 | }
111 | return data;
112 | }
113 | //获取内存变化情况
114 | async getMemoryChangeInfo(name, type, beginTime, endTime) {
115 | let key = 'server-instance-list';
116 | let insConfig = await RedisApi.hget(key, name);
117 | if(!insConfig) {
118 | logger.info('get memory change info instance not exists');
119 | return null;
120 | }
121 | let config = JSON.parse(insConfig);
122 | let data = null;
123 | switch(type) {
124 | case 1:
125 | data = await this.getMemoryChangeInfoByMinute(config, beginTime, endTime);
126 | break;
127 | case 2:
128 | data = await this.getMemoryChangeInfoByHour(config, beginTime, endTime);
129 | break;
130 | case 3:
131 | data = await this.getMemoryChangeInfoByDate(config, beginTime, endTime);
132 | break;
133 | }
134 | return data;
135 |
136 | }
137 | }
--------------------------------------------------------------------------------
/server/service/qps-management-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | let moment = require('moment');
3 | let redis = require('redis');
4 | let RedisApi = require('./api/redis');
5 | let util = require('../common/util');
6 | let logger = require('../common/logger').logger;
7 |
8 | //实例qps数据
9 | module.exports = new class {
10 | constructor() {
11 |
12 | }
13 | //按分钟获取
14 | async getQpsInfoByMinute(config, beginTime, endTime) {
15 | let beginT = moment(beginTime);
16 | let endT = moment(endTime);
17 | let fields = [];
18 | let dateArr = [];
19 | let count = 0;
20 | while(!beginT.isAfter(endT, 'minute')) {
21 | if(count >= 1440) {
22 | break;
23 | }
24 | let tmpT = beginT.clone().second(0);
25 | dateArr.push(tmpT.format('YYYY-MM-DD HH:mm'));
26 | fields.push(tmpT.unix());
27 | beginT.add(1, 'minutes');
28 | count++;
29 | }
30 | logger.debug('fields %s', JSON.stringify(fields));
31 | let key = config.host + ':' + config.port + ':CP:M';
32 | let values = await RedisApi.hmget(key, fields);
33 | let data = [];
34 | if(values) {
35 | for(let i = 0; i < values.length - 1 ; i++) {
36 | let tmp = {
37 | time: dateArr[i],
38 | value: 0
39 | }
40 | if(values[i+1]) {
41 | let nowAllCP = parseInt(values[i+1], 10);
42 | let lastAllCP = nowAllCP;
43 | if(values[i]) {
44 | lastAllCP = parseInt(values[i], 10);
45 | }
46 |
47 | tmp.value = nowAllCP - lastAllCP;
48 | }
49 | data.push(tmp);
50 | }
51 | }
52 | return data;
53 | }
54 | //按小时获取
55 | async getQpsInfoByHour(config, beginTime, endTime) {
56 | let beginT = moment(beginTime);
57 | let endT = moment(endTime);
58 | let fields = [];
59 | let dateArr = [];
60 | let count = 0;
61 | while(!beginT.isAfter(endT, 'hour')) {
62 | if(count > 200) {
63 | break;
64 | }
65 | let tmpT = beginT.clone().minute(0).second(0);
66 | dateArr.push(tmpT.format('YYYY-MM-DD HH'));
67 | fields.push(tmpT.unix());
68 | beginT.add(1, 'hours');
69 | count++;
70 | }
71 | logger.debug('fields %s', JSON.stringify(fields));
72 | let key = config.host + ':' + config.port + ':CP:M';
73 | let values = await RedisApi.hmget(key, fields);
74 | let data = [];
75 | if(values) {
76 | for(let i = 0; i < values.length - 1 ; i++) {
77 | let tmp = {
78 | time: dateArr[i],
79 | value: 0
80 | }
81 | if(values[i+1]) {
82 | let nowAllCP = parseInt(values[i+1], 10);
83 | let lastAllCP = nowAllCP;
84 | if(values[i]) {
85 | lastAllCP = parseInt(values[i], 10);
86 | }
87 |
88 | tmp.value = nowAllCP - lastAllCP;
89 | }
90 | data.push(tmp);
91 | }
92 | }
93 |
94 | return data;
95 | }
96 | //按天获取
97 | async getQpsInfoByDate(config, beginTime, endTime) {
98 | let beginT = moment(beginTime);
99 | let endT = moment(endTime);
100 | let fields = [];
101 | let dateArr = [];
102 | while(!beginT.isAfter(endT, 'day')) {
103 | let tmpT = beginT.clone().hour(0).minute(0).second(0);
104 | dateArr.push(tmpT.format('YYYY-MM-DD'));
105 | fields.push(tmpT.unix());
106 | beginT.add(1, 'days');
107 | }
108 | logger.debug('fields %s', JSON.stringify(fields));
109 | let key = config.host + ':' + config.port + ':CP:M';
110 | let values = await RedisApi.hmget(key, fields);
111 | let data = [];
112 | if(values) {
113 | for(let i = 0; i < values.length - 1 ; i++) {
114 | let tmp = {
115 | time: dateArr[i],
116 | value: 0
117 | }
118 | if(values[i+1]) {
119 | let nowAllCP = parseInt(values[i+1], 10);
120 | let lastAllCP = nowAllCP;
121 | if(values[i]) {
122 | lastAllCP = parseInt(values[i], 10);
123 | }
124 |
125 | tmp.value = nowAllCP - lastAllCP;
126 | }
127 | data.push(tmp);
128 | }
129 | }
130 | return data;
131 | }
132 | //获取内存变化情况
133 | async getQpsInfo(name, type, beginTime, endTime) {
134 | let key = 'server-instance-list';
135 | let insConfig = await RedisApi.hget(key, name);
136 | if(!insConfig) {
137 | logger.info('get qps info instance not exists');
138 | return null;
139 | }
140 | let config = JSON.parse(insConfig);
141 | let data = null;
142 | switch(type) {
143 | case 1:
144 | data = await this.getQpsInfoByMinute(config, beginTime, endTime);
145 | break;
146 | case 2:
147 | data = await this.getQpsInfoByHour(config, beginTime, endTime);
148 | break;
149 | case 3:
150 | data = await this.getQpsInfoByDate(config, beginTime, endTime);
151 | break;
152 | }
153 | return data;
154 |
155 | }
156 | }
--------------------------------------------------------------------------------