├── .gitignore
├── .travis.yml
├── Demo
├── demo1.png
├── demo2.png
├── timing.png
├── weixin.png
└── zhifubao.png
├── README.md
├── backend_server
├── Dockerfile
├── app.js
├── bin
│ └── init.js
├── business
│ ├── apiInfo.js
│ ├── consoleInfo.js
│ ├── focusClickInfo.js
│ ├── jsError.js
│ ├── perfInfo.js
│ ├── pvInfo.js
│ ├── resourceInfo.js
│ ├── scheduleTask.js
│ ├── site.js
│ └── userInfo.js
├── data
│ └── ip2region.db
├── docker-build.sh
├── models
│ ├── apiModel.js
│ ├── consoleModel.js
│ ├── focusClickModel.js
│ ├── jsModel.js
│ ├── perfModel.js
│ ├── pvModel.js
│ ├── resourceModel.js
│ ├── siteModel.js
│ └── userModel.js
├── package.json
├── public
│ └── stylesheets
│ │ └── style.css
├── routes
│ ├── index.js
│ ├── monitor.js
│ ├── upData.js
│ └── user.js
├── setting.json
├── utils
│ └── util.js
└── views
│ ├── error.jade
│ ├── index.jade
│ └── layout.jade
├── docker-compose.yml
├── docker-start.sh
└── web
├── .editorconfig
├── .gitignore
├── Dockerfile
├── README.md
├── angular.json
├── docker-build.sh
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── extraJs
└── china.js
├── karma.conf.js
├── nginx.conf
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── animations.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── dashboard
│ │ ├── dashboard.module.ts
│ │ └── sys-list
│ │ │ ├── sys-list.component.html
│ │ │ ├── sys-list.component.scss
│ │ │ └── sys-list.component.ts
│ ├── homepage
│ │ ├── home-page
│ │ │ ├── homepage.component.html
│ │ │ ├── homepage.component.scss
│ │ │ └── homepage.component.ts
│ │ └── homepage.module.ts
│ ├── model
│ │ └── entity.ts
│ ├── monitor.common.service.ts
│ ├── public
│ │ ├── advise-bar
│ │ │ ├── advise-bar.component.html
│ │ │ ├── advise-bar.component.scss
│ │ │ └── advise-bar.component.ts
│ │ ├── custom-highchart
│ │ │ ├── custom-highchart.component.html
│ │ │ ├── custom-highchart.component.scss
│ │ │ └── custom-highchart.component.ts
│ │ ├── line-progress
│ │ │ ├── line-progress.component.html
│ │ │ ├── line-progress.component.scss
│ │ │ └── line-progress.component.ts
│ │ ├── monitor-a-blank.directive.ts
│ │ ├── public.module.ts
│ │ └── time-choice-panel
│ │ │ ├── time-choice-panel.component.html
│ │ │ ├── time-choice-panel.component.scss
│ │ │ └── time-choice-panel.component.ts
│ └── web
│ │ ├── web-sys
│ │ ├── api-request
│ │ │ ├── api-request.component.html
│ │ │ ├── api-request.component.scss
│ │ │ └── api-request.component.ts
│ │ ├── backend-log
│ │ │ ├── backend-log.component.html
│ │ │ ├── backend-log.component.scss
│ │ │ └── backend-log.component.ts
│ │ ├── js-error-track
│ │ │ ├── js-error-track.component.html
│ │ │ ├── js-error-track.component.scss
│ │ │ └── js-error-track.component.ts
│ │ ├── js-error
│ │ │ ├── js-error.component.html
│ │ │ ├── js-error.component.scss
│ │ │ └── js-error.component.ts
│ │ ├── resource-load-details
│ │ │ ├── resource-load-details.component.html
│ │ │ ├── resource-load-details.component.scss
│ │ │ └── resource-load-details.component.ts
│ │ ├── sys-index
│ │ │ ├── sys-index.component.html
│ │ │ ├── sys-index.component.scss
│ │ │ └── sys-index.component.ts
│ │ ├── sys-setting
│ │ │ ├── sys-setting.component.html
│ │ │ ├── sys-setting.component.scss
│ │ │ └── sys-setting.component.ts
│ │ ├── user-path
│ │ │ ├── user-path.component.html
│ │ │ ├── user-path.component.scss
│ │ │ └── user-path.component.ts
│ │ ├── visit-details
│ │ │ ├── visit-details.component.html
│ │ │ ├── visit-details.component.scss
│ │ │ └── visit-details.component.ts
│ │ ├── visit-geo
│ │ │ ├── visit-geo.component.html
│ │ │ ├── visit-geo.component.scss
│ │ │ └── visit-geo.component.ts
│ │ ├── visit-os
│ │ │ ├── visit-os.component.html
│ │ │ ├── visit-os.component.scss
│ │ │ └── visit-os.component.ts
│ │ ├── visit-page
│ │ │ ├── visit-page.component.html
│ │ │ ├── visit-page.component.scss
│ │ │ └── visit-page.component.ts
│ │ ├── visit-speed
│ │ │ ├── visit-speed.component.html
│ │ │ ├── visit-speed.component.scss
│ │ │ └── visit-speed.component.ts
│ │ ├── web-sys.component.html
│ │ ├── web-sys.component.scss
│ │ └── web-sys.component.ts
│ │ └── web.module.ts
├── assets
│ ├── .gitkeep
│ └── images
│ │ ├── error404.png
│ │ ├── github.png
│ │ ├── home.jpg
│ │ ├── icon
│ │ ├── SDK.png
│ │ ├── alarm.png
│ │ ├── app.png
│ │ ├── data.png
│ │ ├── m.png
│ │ ├── monitor.png
│ │ ├── multi.png
│ │ ├── q.png
│ │ └── safe.png
│ │ ├── weixin-gongzhonghao.jpg
│ │ ├── weixin.png
│ │ └── zhifubao.png
├── bundle.js
├── environments
│ ├── environment.docker.ts
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # IDEs and editors
4 | .vscode/
5 | package-lock.json
6 | node_modules
7 | /browser_probe
8 | .history
9 | /backend_server/access.log
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | install:
5 | |
6 | npm install -g npm@latest
7 | npm --version
8 | script:
9 | - cd web&&npm install&&npm run test
10 |
--------------------------------------------------------------------------------
/Demo/demo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/Demo/demo1.png
--------------------------------------------------------------------------------
/Demo/demo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/Demo/demo2.png
--------------------------------------------------------------------------------
/Demo/timing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/Demo/timing.png
--------------------------------------------------------------------------------
/Demo/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/Demo/weixin.png
--------------------------------------------------------------------------------
/Demo/zhifubao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/Demo/zhifubao.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # web-monitoring
2 |
3 | ### [功能列表]
4 |
5 | - [x] 允许用户创建多个监测站点
6 | - [x] 从不同维度统计用户访问情况
7 | - [x] 自定义查询时间
8 | - [x] 多种图表展示
9 | - [x] 支持自定义上报(js错误,api请求,性能信息)
10 | - [x] 用户访问路径追踪
11 | - [x] 自定义阈值(注册用户)
12 | - [x] 自动报警功能,发送报警邮件提醒(注册用户)
13 |
14 | ------
15 | ### [技术支持]
16 |
17 | - [x] 前端:Angular5+,ant-design
18 | - [x] 后端:Nodejs+Express
19 | - [x] 数据库:Mongoose+MongoDB
20 | - [x] Docker:一键部署Docker环境
21 |
22 |
23 | ------
24 | 前端监控平台专注于 Web 端体验数据监控。监测 Web 页面的健康度的三个方面:
25 | > * 页面打开速度(测速)
26 | > * 页面稳定性(JS Error)
27 | > * 外部服务调用成功率(API)
28 |
29 | 然后从不同纬度去分析用户体验。
30 |
31 | > - 访问页面
32 | > - 访问速度
33 | > - API请求
34 | > - 地理
35 | > - 终端
36 |
37 |
38 |
39 | 参考:
40 | > http://fex.baidu.com/blog/2014/05/build-performance-monitor-in-7-days
41 |
42 | > 阿里云前端监控
43 |
44 |
45 | ### 页面打开速度
46 |
47 | 网络耗时数据可以借助前面提到 Navigation Timing 接口获取,与之类似的还有Resource Timing,可以获取页面所有静态资源的加载耗时。通过此接口可以轻松获取 DNS、TCP、首字节、html 传输等耗时,Navigation Timing 的接口示意图如下所示:
48 |
49 | 
50 |
51 |
52 | ### API请求
53 |
54 | 默认情况下,使用XMLHTTP拦截用户请求,在请求成功/失败后,统计时间,上报请求。
55 | 用户可使用**__ml.api(api,success, time, code, msg)**手动上报。
56 | ```javascript
57 | api:请求接口
58 | success:上传是否成功(true/false )
59 | time:耗时(ms)
60 | code:返回码
61 | msg:消息(string/object)
62 | ```
63 | ### JS错误
64 |
65 | 默认情况下,使用window.onError去监听用户错误脚本,自动上报。
66 | 用户使用的有些前端框架会捕获js错误,错误信息不会抛至window.onError,这种情况需用户手动调用。
67 | 例如在Angular2+,在你的框架全局捕获错误的地方调用**__ml.error(errorObj)**
68 | ```javascript
69 | export class MyErrorHandler implements ErrorHandler {
70 | handleError(error) {
71 | console.error(error);
72 | window.__ml && window.__ml.error && window.__ml.error(error.stack || error);
73 | }
74 | }
75 | @NgModule({
76 | declarations: [],
77 | imports: [],
78 | providers: [{ provide: ErrorHandler, useClass: MyErrorHandler }],
79 | bootstrap: []
80 | })
81 | export class AppModule { }
82 | ```
83 | 在Vue:
84 | ```javascript
85 | import Vue from 'vue'
86 | const errorHandler = (error, vm)=>{
87 | console.error(error);
88 | window.__ml && window.__ml.error && window.__ml.error(error);
89 | }
90 | Vue.config.errorHandler = errorHandler;
91 | Vue.prototype.$throw = (error)=> errorHandler(error,this);
92 | ```
93 | ### 如何使用(easy!!!)
94 |
95 | 网页地址:WEB-MONITOR
96 |
97 | 第一步:在监控站点中创建一个站点。
98 |
99 | 
100 |
101 | 第二步:复制应用配置中的探针到你需要监控的站点(index.html)页面。大功告成!
102 |
103 | 
104 |
105 | ### 贡献者支持
106 |
107 | 您的支持,是我们最大的前进动力。
108 |
109 | 支付宝:
110 |
111 | 
112 |
113 | 微信:
114 |
115 | 
116 |
117 |
118 |
--------------------------------------------------------------------------------
/backend_server/Dockerfile:
--------------------------------------------------------------------------------
1 | # 基于 node 镜像
2 |
3 | FROM node
4 | RUN rm -rf /www
5 | RUN mkdir /www
6 | WORKDIR /www
7 |
8 | COPY . /www
9 | RUN npm install
10 | RUN npm i -g pm2
11 | EXPOSE 9000
12 | CMD pm2 start bin/init.js --name web-monitoring/backend_server_docker --no-daemon
--------------------------------------------------------------------------------
/backend_server/app.js:
--------------------------------------------------------------------------------
1 | var createError = require("http-errors");
2 | var express = require("express");
3 | var path = require("path");
4 | var cookieParser = require("cookie-parser");
5 | var logger = require("morgan");
6 | var indexRouter = require("./routes/index");
7 | var monitorRouter = require("./routes/monitor");
8 | var upDataRouter = require("./routes/upData");
9 | var userRouter = require("./routes/user");
10 | var userInfo = require("./business/userInfo");
11 | var ScheduleTask = require("./business/scheduleTask");
12 |
13 | var util = require("./utils/util");
14 | var setting = require("./setting.json");
15 | var mongoose = require("mongoose");
16 | var fs = require("fs");
17 |
18 | // 判断是否是docker部署方式
19 | var isDocker = process.env.name === "web-monitoring/backend_server_docker";
20 | console.log(process.env.name,"process.env.name");
21 | mongoose
22 | .connect(
23 | `mongodb://${
24 | isDocker ? setting.mongoDB.dockerAddress : setting.mongoDB.address
25 | }/monitor`,
26 | {
27 | socketTimeoutMS: 0,
28 | keepAlive: true,
29 | useNewUrlParser: true,
30 | reconnectTries: Number.MAX_VALUE,
31 | reconnectInterval: 1000,
32 | }
33 | )
34 | .then(() => {
35 | //默认系统管理员
36 | userInfo.createAdmin("sa@admins.com");
37 | });
38 |
39 | //开启任务
40 | if (setting.nodemailer.enable) {
41 | ScheduleTask.startTask();
42 | }
43 |
44 | var app = express();
45 |
46 | // view engine setup
47 | app.set("views", path.join(__dirname, "views"));
48 | app.set("view engine", "jade");
49 |
50 | //日志
51 | var accessLogStream = fs.createWriteStream(path.join(__dirname, "access.log"), {
52 | flags: "a",
53 | });
54 | app.use(logger("short", { stream: accessLogStream }));
55 | app.use(logger("dev"));
56 |
57 | app.use(express.json());
58 | app.use(express.urlencoded({ extended: false }));
59 | app.use(cookieParser());
60 | app.use(express.static(path.join(__dirname, "public")));
61 |
62 | //cors
63 | app.all("*", function (req, res, next) {
64 | res.header("Access-Control-Allow-Origin", "*");
65 | res.header(
66 | "Access-Control-Allow-Headers",
67 | "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"
68 | );
69 | res.header("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
70 | res.header("X-Powered-By", " 3.2.1");
71 | if (req.method == "OPTIONS") res.sendStatus(200);
72 | /*让options请求快速返回*/ else next();
73 | });
74 |
75 | app.use("/", indexRouter);
76 | app.use("/Monitor/", util.resolveToken, monitorRouter);
77 | app.use("/Up", upDataRouter);
78 | app.use("/User", userRouter);
79 |
80 | // catch 404 and forward to error handler
81 | app.use(function (req, res, next) {
82 | next(createError(404));
83 | });
84 |
85 | // error handler
86 | app.use(function (err, req, res, next) {
87 | // set locals, only providing error in development
88 | res.locals.message = err.message;
89 | res.locals.error = req.app.get("env") === "development" ? err : {};
90 |
91 | // render the error page
92 | res.sendStatus(err.status || 500);
93 | // res.render('error');
94 | });
95 |
96 | module.exports = app;
97 |
--------------------------------------------------------------------------------
/backend_server/bin/init.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('backend-server:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort('9000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string' ?
62 | 'Pipe ' + port :
63 | 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string' ?
87 | 'pipe ' + addr :
88 | 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
--------------------------------------------------------------------------------
/backend_server/business/consoleInfo.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require('mongoose');
2 | var ConsoleModel = require('../models/consoleModel');
3 |
4 | exports.create = (data) => {
5 | var temp = new ConsoleModel(data);
6 | temp.save(function(err, r) {
7 | if (err) {
8 | console.error(err);
9 | }
10 | });
11 | };
--------------------------------------------------------------------------------
/backend_server/business/focusClickInfo.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require('mongoose');
2 | var FocusClickModel = require('../models/focusClickModel');
3 | var _ = require('lodash');
4 | var util = require('../utils/util');
5 |
6 |
7 | exports.create = (data) => {
8 | var temp = new FocusClickModel(data);
9 | temp.save(function(err, r) {
10 | if (err) {
11 | console.error(err);
12 | }
13 | });
14 | };
--------------------------------------------------------------------------------
/backend_server/business/resourceInfo.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require('mongoose');
2 | var ResourceModel = require('../models/resourceModel');
3 | var _ = require('lodash');
4 | var util = require('../utils/util');
5 |
6 | /**
7 | * 资源加载-列表
8 | * @param {*} req
9 | */
10 | exports.list = async(req) => {
11 | let appKey = new Mongoose.Types.ObjectId(req.body.appKey);
12 | let body = util.computeSTimeAndEtime(req.body);
13 | let resJson = {
14 | List: [],
15 | TotalCount: 0
16 | };
17 | let tempCon = '';
18 | tempCon = body.type ? {
19 | "createTime": {
20 | '$gte': body.sTime,
21 | '$lt': body.eTime
22 | },
23 | "appKey": appKey,
24 | "rUrl": {
25 | '$regex': new RegExp(`${body.keywords}.*`, "gi")
26 | },
27 | "rInitiatorType": body.type
28 | } : {
29 | "createTime": {
30 | '$gte': body.sTime,
31 | '$lt': body.eTime
32 | },
33 | "appKey": appKey,
34 | "rUrl": {
35 | '$regex': new RegExp(`${body.keywords}.*`, "gi")
36 | }
37 | };
38 | resJson.TotalCount = await ResourceModel.find(tempCon).countDocuments();
39 | if (resJson.TotalCount) {
40 | resJson.List = await ResourceModel.find(tempCon).sort({ "createTime": -1 }).skip((req.body.pageIndex - 1) * req.body.pageSize).limit(req.body.pageSize);
41 | }
42 | return resJson;
43 | };
44 |
45 | exports.create = (data) => {
46 | var temp = new ResourceModel(data);
47 | temp.save(function(err, r) {
48 | if (err) {
49 | console.error(err);
50 | }
51 | });
52 | };
--------------------------------------------------------------------------------
/backend_server/business/scheduleTask.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require("mongoose");
2 | var Agenda = require("agenda");
3 | var nodemailer = require("nodemailer");
4 | var JsModel = require("../models/jsModel");
5 | var PvModel = require("../models/pvModel");
6 | var ApiModel = require("../models/apiModel");
7 | var PerfModel = require("../models/perfModel");
8 | var SiteModel = require("../models/siteModel");
9 | var _ = require("lodash");
10 | var setting = require("../setting.json");
11 | // 判断是否是docker部署方式
12 | var isDocker = process.env.name === "web-monitoring/backend_server_docker";
13 | const mongoConnectionString = `mongodb://${
14 | isDocker ? setting.mongoDB.dockerAddress : setting.mongoDB.address
15 | }/agendatask`;
16 | const agenda = new Agenda({ db: { address: mongoConnectionString } });
17 | agenda.processEvery("30 seconds");
18 |
19 | const transporter = nodemailer.createTransport({
20 | ...setting.nodemailer,
21 | // host: "smtpdm.aliyun.com",
22 | // port: 25,
23 | // secureConnection: true, // use SSL, the port is 465
24 | // auth: {
25 | // user: "admin@hubing.online",
26 | // pass: "*********",
27 | // },
28 | });
29 |
30 | async function sendEmail(options) {
31 | let appName = options.appKey; //站点名称
32 | let appKey = new Mongoose.Types.ObjectId(options.appKey);
33 | let appInfo = await SiteModel.findOne({ appKey: appKey });
34 | if (appInfo) {
35 | appName = appInfo.appName;
36 | }
37 | let body = "";
38 | let subject = "";
39 | let result;
40 | switch (options.alarmType) {
41 | case "jsError":
42 | result = await checkjsErrorResult(options);
43 | body = `
${
44 | options.email
45 | }
你好,前端监控平台(hubing.online:8083)发现您在平台创建的【${appName}】系统触发JavaScript错误临界值,错误率为:${
46 | result * 100
47 | }%,请前往系统及时处理。
前端监控平台管理员
`;
48 | subject = "JavaScript错误报警提醒";
49 | break;
50 | case "apiError":
51 | result = await checkApiErrorResult(options);
52 | body = `${
53 | options.email
54 | }
你好,前端监控平台(hubing.online:8083)发现您在平台创建的【${
55 | options.appName
56 | }】系统触发API请求错误临界值,错误率为:${
57 | result * 100
58 | }%,请前往系统及时处理。
前端监控平台管理员
`;
59 | subject = "API错误报警提醒";
60 | break;
61 | case "perfSpeed":
62 | result = await checkPerfSpeedResult(options);
63 | body = `${options.email}
你好,前端监控平台(hubing.online:8083)发现您在平台创建的【${options.appName}】系统触发访问速度临界值,目前访问速度平均值为:${result},请前往系统及时处理。
前端监控平台管理员
`;
64 | subject = "访问速度报警提醒";
65 | break;
66 | default:
67 | break;
68 | }
69 | var mailOptions = {
70 | from: "前端监控平台管理员", // sender address mailfrom must be same with the user
71 | to: options.email, // list of receivers
72 | subject: subject, // Subject line
73 | replyTo: "676022504@qq.com", //custom reply address
74 | html: body, // html body
75 | };
76 |
77 | if (result * 100 < options.limitValue) return;
78 |
79 | transporter.sendMail(mailOptions, function (error, info) {
80 | if (error) {
81 | console.log("Message sent: " + error);
82 | }
83 | console.log("Message sent: " + info.response);
84 | });
85 | }
86 |
87 | //任务1:统计用户站点js错误达到临界值
88 | async function checkjsErrorResult(options) {
89 | let appKey = new Mongoose.Types.ObjectId(options.appKey);
90 | let sTime = new Date(
91 | new Date().setMinutes(new Date().getMinutes() - options.times)
92 | );
93 | let eTime = new Date();
94 | let matchCond = {
95 | createTime: {
96 | $gte: sTime,
97 | $lte: eTime,
98 | },
99 | appKey: appKey,
100 | };
101 |
102 | // 查询当前阶段错误率
103 | let r1 = await JsModel.find(matchCond).countDocuments();
104 | let r2 = await PvModel.find(matchCond).countDocuments();
105 | let rate1 = isNaN(r1 / r2) ? 0 : r1 / r2;
106 | return rate1;
107 | }
108 |
109 | //任务2:统计用户站点API错误率达到临界值
110 | async function checkApiErrorResult(options) {
111 | let appKey = new Mongoose.Types.ObjectId(options.appKey);
112 | let sTime = new Date(
113 | new Date().setMinutes(new Date().getMinutes() - options.times)
114 | );
115 | let eTime = new Date();
116 | let matchCond = {
117 | createTime: {
118 | $gte: sTime,
119 | $lte: eTime,
120 | },
121 | appKey: appKey,
122 | };
123 | // 查询当前阶段错误率
124 | let r1 = await ApiModel.find(
125 | _.assign({ success: false }, matchCond)
126 | ).countDocuments();
127 | let r2 = await ApiModel.find(matchCond).countDocuments();
128 | let rate1 = isNaN(r1 / r2) ? 0 : r1 / r2;
129 | return rate1;
130 | }
131 |
132 | //任务3:统计用户站点平均访问速度达到临界值
133 | async function checkPerfSpeedResult(options) {
134 | let appKey = new Mongoose.Types.ObjectId(options.appKey);
135 | let sTime = new Date(
136 | new Date().setMinutes(new Date().getMinutes() - options.times)
137 | );
138 | let eTime = new Date();
139 | let matchCond = {
140 | createTime: {
141 | $gte: sTime,
142 | $lte: eTime,
143 | },
144 | appKey: appKey,
145 | };
146 | // 查询当前阶段加载速度平均值
147 | let r1 = await PerfModel.find(matchCond);
148 | let r1Avg = r1.reduce((acc, val) => acc + val.load, 0) / r1.length;
149 | return r1Avg;
150 | }
151 |
152 | //创建/取消发送邮件任务
153 | /*
154 | options.email 邮件
155 | options.alarmType 邮件类型(jsError,apiError,perfSpeed)
156 | options.times 间隔时间(s)
157 | options.state 任务状态(true/false)
158 | options.limitValue
159 | options.appKey
160 | */
161 | function createTask(options) {
162 | //取消任务
163 | agenda.cancel({
164 | name: `send alarm email`,
165 | "data.appKey": options.appKey,
166 | "data.alarmType": options.alarmType,
167 | });
168 | if (!options.state) {
169 | return;
170 | }
171 | (async function () {
172 | await agenda.start();
173 | let job = agenda.create(`send alarm email`, options);
174 | job.repeatEvery(`${options.times} minutes`, {
175 | skipImmediate: true,
176 | });
177 | await job.save();
178 | })();
179 | }
180 |
181 | exports.startTask = function () {
182 | //定义任务
183 | agenda.define(`send alarm email`, (job, done) => {
184 | sendEmail(job.attrs.data);
185 | done();
186 | });
187 | //开启任务
188 | (async function () {
189 | await agenda.start();
190 | })();
191 | };
192 |
193 | exports.createTask = createTask;
194 |
--------------------------------------------------------------------------------
/backend_server/business/site.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require("mongoose");
2 | var SiteModel = require("../models/siteModel");
3 | var ScheduleTask = require("../business/scheduleTask");
4 | var util = require("../utils/util");
5 | var mongoose = require("mongoose");
6 | /* system listing. */
7 | exports.list = function (req, res, next) {
8 | if (req.userId === util.adminUserId) {
9 | SiteModel.find(function (err, r) {
10 | if (!err) {
11 | res.json(
12 | util.resJson({
13 | IsSuccess: true,
14 | Data: r,
15 | })
16 | );
17 | } else {
18 | res.json(
19 | util.resJson({
20 | IsSuccess: false,
21 | Data: null,
22 | })
23 | );
24 | }
25 | });
26 | } else {
27 | SiteModel.find({ userId: req.userId }, function (err, r) {
28 | if (!err) {
29 | res.json(
30 | util.resJson({
31 | IsSuccess: true,
32 | Data: r,
33 | })
34 | );
35 | } else {
36 | res.json(
37 | util.resJson({
38 | IsSuccess: false,
39 | Data: null,
40 | })
41 | );
42 | }
43 | });
44 | }
45 | };
46 |
47 | exports.create = function (req, res, next) {
48 | var ObjectId = mongoose.Types.ObjectId;
49 | var id1 = new ObjectId();
50 | var temp = new SiteModel({
51 | appName: req.body.appName,
52 | disableHook: false,
53 | disableJS: false,
54 | appKey: id1,
55 | id: id1,
56 | userId: req.userId,
57 | });
58 | temp.save(function (err, r) {
59 | console.log(err);
60 |
61 | if (!err) {
62 | res.json(
63 | util.resJson({
64 | IsSuccess: true,
65 | Data: r,
66 | })
67 | );
68 | } else {
69 | res.json(
70 | util.resJson({
71 | IsSuccess: false,
72 | Data: null,
73 | })
74 | );
75 | }
76 | });
77 | };
78 |
79 | exports.update = function (req, res, next) {
80 | let id = new Mongoose.Types.ObjectId(req.body.id);
81 | SiteModel.findOneAndUpdate(
82 | {
83 | id: id,
84 | },
85 | {
86 | disableHook: req.body.disableHook,
87 | disableJS: req.body.disableJS,
88 | disableResource: req.body.disableResource,
89 | },
90 | function (err, r) {
91 | if (!err) {
92 | res.json(
93 | util.resJson({
94 | IsSuccess: true,
95 | Data: null,
96 | })
97 | );
98 | } else {
99 | res.json(
100 | util.resJson({
101 | IsSuccess: false,
102 | Data: null,
103 | })
104 | );
105 | }
106 | }
107 | );
108 | };
109 |
110 | //js错误报警
111 | exports.alarmJsErrorUpdate = function (req, res, next) {
112 | let id = new Mongoose.Types.ObjectId(req.body.id);
113 | ScheduleTask.createTask({
114 | appKey: req.body.id,
115 | email: req.body.email,
116 | alarmType: "jsError",
117 | times: req.body.alarmTimes,
118 | state: req.body.alarmState,
119 | limitValue: req.body.alarmLimit,
120 | });
121 |
122 | SiteModel.findOneAndUpdate(
123 | {
124 | id: id,
125 | },
126 | {
127 | alarmJsState: req.body.alarmState,
128 | alarmJsLimit: req.body.alarmLimit,
129 | alarmJsTimes: req.body.alarmTimes,
130 | alarmJsEmail: req.body.email,
131 | },
132 | function (err, r) {
133 | if (!err) {
134 | res.json(
135 | util.resJson({
136 | IsSuccess: true,
137 | Data: null,
138 | })
139 | );
140 | } else {
141 | res.json(
142 | util.resJson({
143 | IsSuccess: false,
144 | Data: null,
145 | })
146 | );
147 | }
148 | }
149 | );
150 | };
151 |
152 | //api错误报警
153 | exports.alarmApiErrorUpdate = function (req, res, next) {
154 | let id = new Mongoose.Types.ObjectId(req.body.id);
155 | ScheduleTask.createTask({
156 | appKey: req.body.id,
157 | email: req.body.email,
158 | alarmType: "apiError",
159 | times: req.body.alarmTimes,
160 | state: req.body.alarmState,
161 | limitValue: req.body.alarmLimit,
162 | });
163 |
164 | SiteModel.findOneAndUpdate(
165 | {
166 | id: id,
167 | },
168 | {
169 | alarmApiState: req.body.alarmState,
170 | alarmApiLimit: req.body.alarmLimit,
171 | alarmApiTimes: req.body.alarmTimes,
172 | alarmApiEmail: req.body.email,
173 | },
174 | function (err, r) {
175 | if (!err) {
176 | res.json(
177 | util.resJson({
178 | IsSuccess: true,
179 | Data: null,
180 | })
181 | );
182 | } else {
183 | res.json(
184 | util.resJson({
185 | IsSuccess: false,
186 | Data: null,
187 | })
188 | );
189 | }
190 | }
191 | );
192 | };
193 |
194 | //perf报警
195 | exports.alarmPerfSpeedUpdate = function (req, res, next) {
196 | let id = new Mongoose.Types.ObjectId(req.body.id);
197 | ScheduleTask.createTask({
198 | appKey: req.body.id,
199 | email: req.body.email,
200 | alarmType: "perfSpeed",
201 | times: req.body.alarmTimes,
202 | state: req.body.alarmState,
203 | limitValue: req.body.alarmLimit,
204 | });
205 |
206 | SiteModel.findOneAndUpdate(
207 | {
208 | id: id,
209 | },
210 | {
211 | alarmPerfState: req.body.alarmState,
212 | alarmPerfLimit: req.body.alarmLimit,
213 | alarmPerfTimes: req.body.alarmTimes,
214 | alarmPerfEmail: req.body.email,
215 | },
216 | function (err, r) {
217 | if (!err) {
218 | res.json(
219 | util.resJson({
220 | IsSuccess: true,
221 | Data: null,
222 | })
223 | );
224 | } else {
225 | res.json(
226 | util.resJson({
227 | IsSuccess: false,
228 | Data: null,
229 | })
230 | );
231 | }
232 | }
233 | );
234 | };
235 |
--------------------------------------------------------------------------------
/backend_server/business/userInfo.js:
--------------------------------------------------------------------------------
1 | var Mongoose = require("mongoose");
2 | var UserModel = require("../models/userModel");
3 | var util = require("../utils/util");
4 | var mongoose = require("mongoose");
5 | var AES = require("crypto-js/aes");
6 |
7 | exports.createAdmin = async (email) => {
8 | let r = await UserModel.find({
9 | id: util.adminUserId,
10 | });
11 | if (r && r.length > 0) {
12 | return;
13 | }
14 | var ObjectId = mongoose.Types.ObjectId;
15 | var id1 = new ObjectId(util.adminUserId);
16 | let token = util.encrypt(id1 + "@" + new Date());
17 | var temp = new UserModel({
18 | email: email,
19 | password:`9f2c1604c8ceec9943fd69f96220a8d1`,
20 | id: id1,
21 | token: token,
22 | });
23 |
24 | temp.save(function (err, r) {
25 | console.log(err);
26 | if (!err) {
27 | console.log("管理员初始化成功");
28 | } else {
29 | console.log("管理员初始化失败");
30 | }
31 | });
32 | };
33 |
34 | exports.create = async (req, res, next) => {
35 | let body = req.body;
36 | let r = await UserModel.find({
37 | email: body.email,
38 | });
39 | if (r && r.length > 0) {
40 | res.json(
41 | util.resJson({
42 | IsSuccess: false,
43 | Data: "邮箱已存在",
44 | })
45 | );
46 | return;
47 | }
48 | var ObjectId = mongoose.Types.ObjectId;
49 | var id1 = new ObjectId();
50 | let token = util.encrypt(id1 + "@" + new Date());
51 | var temp = new UserModel({
52 | email: body.email,
53 | password: body.password,
54 | id: id1,
55 | token: token,
56 | });
57 |
58 | temp.save(function (err, r) {
59 | console.log(err);
60 | if (!err) {
61 | res.json(
62 | util.resJson({
63 | IsSuccess: true,
64 | Data: {
65 | userName: body.email,
66 | userId: id1,
67 | token: token,
68 | },
69 | })
70 | );
71 | } else {
72 | res.json(
73 | util.resJson({
74 | IsSuccess: false,
75 | Data: null,
76 | })
77 | );
78 | }
79 | });
80 | };
81 |
82 | exports.login = async (req, res, next) => {
83 | let body = req.body;
84 | let r = await UserModel.find({
85 | email: body.email,
86 | });
87 | if (r && r.length == 0) {
88 | res.json(
89 | util.resJson({
90 | IsSuccess: false,
91 | Data: "用户名不存在",
92 | })
93 | );
94 | return;
95 | }
96 | if (r[0].password != body.password) {
97 | res.json(
98 | util.resJson({
99 | IsSuccess: false,
100 | Data: "用户名或密码错误",
101 | })
102 | );
103 | return;
104 | }
105 |
106 | if (r[0].password == body.password) {
107 | let token = util.encrypt(r[0].id + "@" + new Date());
108 | UserModel.findOneAndUpdate(
109 | {
110 | id: new mongoose.Types.ObjectId(r[0].id),
111 | },
112 | {
113 | token: token,
114 | },
115 | (err) => {
116 | if (!err) {
117 | res.json(
118 | util.resJson({
119 | IsSuccess: true,
120 | Data: {
121 | userName: r[0].email,
122 | userId: r[0].id,
123 | token: token,
124 | },
125 | })
126 | );
127 | } else {
128 | res.json(
129 | util.resJson({
130 | IsSuccess: false,
131 | Data: "更新token失败",
132 | })
133 | );
134 | }
135 | }
136 | );
137 | }
138 | };
139 |
140 | exports.validToken = async (userId) => {
141 | let r = await UserModel.find({
142 | id: new mongoose.Types.ObjectId(userId),
143 | });
144 | if (r && r.length == 0) {
145 | return false;
146 | }
147 | return r[0];
148 | };
149 |
150 | exports.logout = async (req, res, next) => {
151 | let query = req.query;
152 | let r = await UserModel.findOneAndUpdate(
153 | {
154 | id: new mongoose.Types.ObjectId(query.id),
155 | },
156 | {
157 | token: null,
158 | }
159 | );
160 | if (r) {
161 | res.json(
162 | util.resJson({
163 | IsSuccess: true,
164 | Data: null,
165 | })
166 | );
167 | } else {
168 | res.json(
169 | util.resJson({
170 | IsSuccess: false,
171 | Data: "更新出错",
172 | })
173 | );
174 | }
175 | };
176 |
--------------------------------------------------------------------------------
/backend_server/data/ip2region.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/backend_server/data/ip2region.db
--------------------------------------------------------------------------------
/backend_server/docker-build.sh:
--------------------------------------------------------------------------------
1 | docker build -t hubing/monitor-backend .
--------------------------------------------------------------------------------
/backend_server/models/apiModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | api: String,
22 | success: Boolean,
23 | time: Number,
24 | code: Number,
25 | msg: String,
26 | visitedUserId: String,
27 | });
28 |
29 | module.exports = mongoose.model('ApiDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/consoleModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | cType:String,
22 | cMsg: String,
23 | visitedUserId: String,
24 | });
25 |
26 | module.exports = mongoose.model('ConsoleDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/focusClickModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | title: String,
22 | href: String,
23 | text: String,
24 | visitedUserId: String,
25 | });
26 |
27 | module.exports = mongoose.model('FocusClickModel', schema);
--------------------------------------------------------------------------------
/backend_server/models/jsModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | page: String,
7 | type: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | error: String,
22 | visitedUserId: String,
23 | });
24 |
25 | module.exports = mongoose.model('JsDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/perfModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | dns: Number,
22 | tcp: Number,
23 | ssl: Number,
24 | ttfb: Number,
25 | trans: Number,
26 | dom: Number,
27 | res: Number,
28 | firstbyte: Number,
29 | fpt: Number,
30 | tti: Number,
31 | ready: Number,
32 | load: Number,
33 | navt: String,
34 | visitedUserId: String,
35 | });
36 |
37 | module.exports = mongoose.model('PerfDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/pvModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | prePage: String,
9 | appKey: Schema.Types.ObjectId,
10 | os: String,
11 | bs: String,
12 | pageWh: String,
13 | ua: String,
14 | city_nameCN: String,
15 | country_nameCN: String,
16 | latitude: Number,
17 | longitude: Number,
18 | mostSpecificSubdivision_nameCN: String,
19 | onlineip: String,
20 | isp: String,
21 | organizationCN: String,
22 | visitedUserId: String,
23 | });
24 |
25 | module.exports = mongoose.model('PvDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/resourceModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | createTime: { type: Date, default: Date.now },
5 | updateTime: { type: Date, default: Date.now },
6 | type: String,
7 | page: String,
8 | appKey: Schema.Types.ObjectId,
9 | os: String,
10 | bs: String,
11 | pageWh: String,
12 | ua: String,
13 | city_nameCN: String,
14 | country_nameCN: String,
15 | latitude: Number,
16 | longitude: Number,
17 | mostSpecificSubdivision_nameCN: String,
18 | onlineip: String,
19 | isp: String,
20 | organizationCN: String,
21 | rUrl: String,
22 | rEntryType: String,
23 | rInitiatorType: String,
24 | rDuration: Number, //单位ms
25 | rSize: Number,
26 | visitedUserId: String,
27 | });
28 |
29 | module.exports = mongoose.model('ResourceDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/siteModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | appName: String,
5 | disableHook: Boolean,
6 | disableJS: Boolean,
7 | disableResource: Boolean,
8 | createTime: { type: Date, default: Date.now },
9 | updateTime: { type: Date, default: Date.now },
10 | appKey: Schema.Types.ObjectId,
11 | id: Schema.Types.ObjectId,
12 | state: String,
13 | userId: String,
14 |
15 | alarmJsState:{ type: Boolean, default: false },
16 | alarmJsLimit:Number,
17 | alarmJsTimes:Number,
18 | alarmJsEmail:String,
19 |
20 | alarmApiState:{ type: Boolean, default: false },
21 | alarmApiLimit:Number,
22 | alarmApiTimes:Number,
23 | alarmApiEmail:String,
24 |
25 | alarmPerfState:{ type: Boolean, default: false },
26 | alarmPerfLimit:Number,
27 | alarmPerfTimes:Number,
28 | alarmPerfEmail:String,
29 | });
30 |
31 | module.exports = mongoose.model('SiteDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/models/userModel.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var schema = new Schema({
4 | email: String,
5 | password:String,
6 | phone: String,
7 | isAcitve: { type: Boolean, default: false },
8 | createTime: { type: Date, default: Date.now },
9 | id: Schema.Types.ObjectId,
10 | token:String
11 | });
12 |
13 | module.exports = mongoose.model('UserDataInfo', schema);
--------------------------------------------------------------------------------
/backend_server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend-server",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "supervisor ./bin/www",
7 | "start": "pm2 start bin/init.js --name web-monitoring/backend_server",
8 | "stop": "pm2 stop web-monitoring/backend_server",
9 | "restart": "npm run stop && npm run start"
10 | },
11 | "dependencies": {
12 | "agenda": "^2.0.2",
13 | "cookie-parser": "~1.4.3",
14 | "crypto-js": "^3.1.9-1",
15 | "debug": "~2.6.9",
16 | "evenboy-ip2region": "0.0.2",
17 | "express": "~4.16.0",
18 | "http-errors": "~1.6.2",
19 | "jade": "~1.11.0",
20 | "lodash": "^4.17.10",
21 | "mongoose": "^5.2.9",
22 | "morgan": "~1.9.0",
23 | "nodemailer": "^6.3.0",
24 | "supervisor": "^0.12.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/backend_server/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
--------------------------------------------------------------------------------
/backend_server/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function(req, res, next) {
6 | res.render('index', { title: '哈喽' });
7 | });
8 |
9 | module.exports = router;
--------------------------------------------------------------------------------
/backend_server/routes/upData.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var jsError = require('../business/jsError');
4 | var pvInfo = require('../business/pvInfo');
5 | var apiInfo = require('../business/apiInfo');
6 | var perfInfo = require('../business/perfInfo');
7 | var resourceInfo = require('../business/resourceInfo');
8 | var focusClickInfo = require('../business/focusClickInfo');
9 | var consoleInfo = require('../business/consoleInfo');
10 | var util = require('../utils/util');
11 | var _ = require('lodash');
12 |
13 | let uploadDataTaskList = [];
14 |
15 | const solveData = () => {
16 | const tasks = uploadDataTaskList.splice(0, 5);
17 | tasks.forEach(r => {
18 | switch (r.type) {
19 | case 'pv':
20 | pvInfo.create(r);
21 | break;
22 | case 'js':
23 | jsError.create(r);
24 | break;
25 | case 'api':
26 | apiInfo.create(r);
27 | break;
28 | case 'perf':
29 | perfInfo.create(r);
30 | break;
31 | case 'resource':
32 | resourceInfo.create(r);
33 | break;
34 | case 'focusClick':
35 | focusClickInfo.create(r);
36 | break;
37 | case 'console':
38 | consoleInfo.create(r);
39 | break;
40 | default:
41 | break;
42 | }
43 | })
44 | }
45 |
46 | // 每10秒执行一次检查
47 | setInterval(() => {
48 | solveData()
49 | }, 10000);
50 |
51 | /*上传数据 */
52 | router.get('', util.getIp, function (req, res, next) {
53 | let temp = JSON.parse(req.query.paramsJson);
54 | temp = _.extend(temp, req.netInfo);
55 | temp.type = req.query.type;
56 | if (temp.type) {
57 | uploadDataTaskList.push(temp)
58 | }
59 | res.status(200).end();
60 | });
61 |
62 | module.exports = router;
--------------------------------------------------------------------------------
/backend_server/routes/user.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 | var util = require("../utils/util");
4 | var user = require("../business/userInfo");
5 | router.post("/login", user.login);
6 | router.post("/register", util.resolveToken, util.needAdminToken, user.create);
7 | router.post("/logout", util.resolveToken, util.needToken, user.logout);
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/backend_server/setting.json:
--------------------------------------------------------------------------------
1 | {
2 | "mongoDB": {
3 | "address": "127.0.0.1:27017",
4 | "dockerAddress":"database:27017"
5 | },
6 | "nodemailer": {
7 | "enable":false,
8 | "host": "smtpdm.aliyun.com",
9 | "port": 25,
10 | "secureConnection": true,
11 | "auth": {
12 | "user": "admin@hubing.online",
13 | "pass": "*********"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/backend_server/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/backend_server/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= '你好像走错了'
5 | p 这里什么也没有,o(╥﹏╥)o。
6 |
--------------------------------------------------------------------------------
/backend_server/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 | services:
3 | database:
4 | image: mongo
5 | restart: always
6 | volumes:
7 | - ~/data/db:/data/db
8 | networks:
9 | - webapp-network
10 | backend:
11 | image: hubing/monitor-backend
12 | depends_on:
13 | - database
14 | ports:
15 | - 9000:9000
16 | networks:
17 | - webapp-network
18 | web:
19 | image: hubing/monitor-web
20 | ports:
21 | - 9010:80
22 | networks:
23 | - webapp-network
24 | networks:
25 | webapp-network:
26 | driver: bridge
27 |
--------------------------------------------------------------------------------
/docker-start.sh:
--------------------------------------------------------------------------------
1 | docker-compose up -d
--------------------------------------------------------------------------------
/web/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/web/Dockerfile:
--------------------------------------------------------------------------------
1 | # 基于 nginx 镜像(本地打包后,上传dist文件)
2 |
3 | FROM nginx:latest
4 | COPY ./nginx.conf /etc/nginx
5 | COPY ./dist /usr/share/nginx/html
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # MonitorWeb
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.5.3.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
--------------------------------------------------------------------------------
/web/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "monitor-web": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico",
22 | "src/bundle.js",
23 | {
24 | "glob": "**/*",
25 | "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
26 | "output": "/assets/"
27 | }
28 | ],
29 | "styles": [
30 | "src/styles.scss",
31 | "node_modules/ng-zorro-antd/src/ng-zorro-antd.less"
32 | ],
33 | "scripts": []
34 | },
35 | "configurations": {
36 | "production": {
37 | "optimization": true,
38 | "outputHashing": "all",
39 | "sourceMap": false,
40 | "extractCss": true,
41 | "namedChunks": false,
42 | "aot": true,
43 | "extractLicenses": true,
44 | "vendorChunk": false,
45 | "buildOptimizer": true,
46 | "fileReplacements": [
47 | {
48 | "replace": "src/environments/environment.ts",
49 | "with": "src/environments/environment.prod.ts"
50 | }
51 | ]
52 | },
53 | "docker": {
54 | "optimization": true,
55 | "outputHashing": "all",
56 | "sourceMap": false,
57 | "extractCss": true,
58 | "namedChunks": false,
59 | "aot": true,
60 | "extractLicenses": true,
61 | "vendorChunk": false,
62 | "buildOptimizer": true,
63 | "fileReplacements": [
64 | {
65 | "replace": "src/environments/environment.ts",
66 | "with": "src/environments/environment.docker.ts"
67 | }
68 | ]
69 | }
70 | }
71 | },
72 | "serve": {
73 | "builder": "@angular-devkit/build-angular:dev-server",
74 | "options": {
75 | "browserTarget": "monitor-web:build"
76 | },
77 | "configurations": {
78 | "production": {
79 | "browserTarget": "monitor-web:build:production"
80 | }
81 | }
82 | },
83 | "extract-i18n": {
84 | "builder": "@angular-devkit/build-angular:extract-i18n",
85 | "options": {
86 | "browserTarget": "monitor-web:build"
87 | }
88 | },
89 | "test": {
90 | "builder": "@angular-devkit/build-angular:karma",
91 | "options": {
92 | "main": "src/test.ts",
93 | "karmaConfig": "./karma.conf.js",
94 | "polyfills": "src/polyfills.ts",
95 | "tsConfig": "src/tsconfig.spec.json",
96 | "scripts": [],
97 | "styles": [
98 | "src/styles.scss",
99 | "node_modules/ng-zorro-antd/src/ng-zorro-antd.less"
100 | ],
101 | "assets": [
102 | "src/assets",
103 | "src/favicon.ico"
104 | ]
105 | }
106 | },
107 | "lint": {
108 | "builder": "@angular-devkit/build-angular:tslint",
109 | "options": {
110 | "tsConfig": [
111 | "src/tsconfig.app.json",
112 | "src/tsconfig.spec.json"
113 | ],
114 | "exclude": [
115 | "**/node_modules/**"
116 | ]
117 | }
118 | }
119 | }
120 | },
121 | "monitor-web-e2e": {
122 | "root": "e2e",
123 | "sourceRoot": "e2e",
124 | "projectType": "application",
125 | "architect": {
126 | "e2e": {
127 | "builder": "@angular-devkit/build-angular:protractor",
128 | "options": {
129 | "protractorConfig": "./protractor.conf.js",
130 | "devServerTarget": "monitor-web:serve"
131 | }
132 | },
133 | "lint": {
134 | "builder": "@angular-devkit/build-angular:tslint",
135 | "options": {
136 | "tsConfig": [
137 | "e2e/tsconfig.e2e.json"
138 | ],
139 | "exclude": [
140 | "**/node_modules/**"
141 | ]
142 | }
143 | }
144 | }
145 | }
146 | },
147 | "defaultProject": "monitor-web",
148 | "schematics": {
149 | "@schematics/angular:component": {
150 | "prefix": "app",
151 | "styleext": "scss"
152 | },
153 | "@schematics/angular:directive": {
154 | "prefix": "app"
155 | }
156 | }
157 | }
--------------------------------------------------------------------------------
/web/docker-build.sh:
--------------------------------------------------------------------------------
1 | npm install&&npm run build-docker&&docker build -t hubing/monitor-web .
--------------------------------------------------------------------------------
/web/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('monitor-web App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/web/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/web/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/web/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 |
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/web/nginx.conf:
--------------------------------------------------------------------------------
1 |
2 | #user nobody;
3 | worker_processes 1;
4 |
5 | #error_log logs/error.log;
6 | #error_log logs/error.log notice;
7 | #error_log logs/error.log info;
8 | #pid logs/nginx.pid;
9 |
10 |
11 | events {
12 | worker_connections 1024;
13 | }
14 |
15 |
16 | http {
17 | include mime.types;
18 | default_type application/octet-stream;
19 |
20 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
21 | # '$status $body_bytes_sent "$http_referer" '
22 | # '"$http_user_agent" "$http_x_forwarded_for"';
23 |
24 | #access_log logs/access.log main;
25 |
26 | sendfile on;
27 | #tcp_nopush on;
28 |
29 | #keepalive_timeout 0;
30 | keepalive_timeout 65;
31 |
32 | #gzip on;
33 |
34 |
35 |
36 | # another virtual host using mix of IP-, name-, and port-based configuration
37 | #
38 | server {
39 | listen 80;
40 | server_name localhost;
41 |
42 | location / {
43 | root /usr/share/nginx/html;
44 | index index.html;
45 | }
46 | }
47 |
48 |
49 | # HTTPS server
50 | #
51 | #server {
52 | # listen 443 ssl;
53 | # server_name localhost;
54 |
55 | # ssl_certificate cert.pem;
56 | # ssl_certificate_key cert.key;
57 |
58 | # ssl_session_cache shared:SSL:1m;
59 | # ssl_session_timeout 5m;
60 |
61 | # ssl_ciphers HIGH:!aNULL:!MD5;
62 | # ssl_prefer_server_ciphers on;
63 |
64 | # location / {
65 | # root html;
66 | # index index.html index.htm;
67 | # }
68 | #}
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monitor-web",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve --port 9001",
8 | "build": "ng build --prod",
9 | "build-docker": "ng build -c=docker",
10 | "test": "ng test",
11 | "lint": "ng lint",
12 | "e2e": "ng e2e"
13 | },
14 | "private": true,
15 | "dependencies": {
16 | "@angular-devkit/core": "^0.6.8",
17 | "@angular/animations": "^6.1.4",
18 | "@angular/cdk": "^6.4.6",
19 | "@angular/cli": "^6.1.5",
20 | "@angular/common": "^6.1.4",
21 | "@angular/compiler": "^6.1.4",
22 | "@angular/core": "^6.1.4",
23 | "@angular/forms": "^6.1.4",
24 | "@angular/http": "^6.1.4",
25 | "@angular/platform-browser": "^6.1.4",
26 | "@angular/platform-browser-dynamic": "^6.1.4",
27 | "@angular/router": "^6.1.4",
28 | "@types/crypto-js": "^3.1.43",
29 | "classlist.js": "^1.1.20150312",
30 | "codemirror": "^5.48.0",
31 | "core-js": "^2.4.1",
32 | "crypto-js": "^3.1.9-1",
33 | "highcharts": "^6.1.0",
34 | "lodash": "^4.17.10",
35 | "ng-zorro-antd": "^1.4.0",
36 | "ngx-cookie-service": "^1.0.10",
37 | "node-sass": "^6.0.1",
38 | "rxjs": "^6.2.2",
39 | "rxjs-compat": "^6.0.0-rc.0",
40 | "web-animations-js": "^2.3.1",
41 | "zone.js": "^0.8.19"
42 | },
43 | "devDependencies": {
44 | "@angular-devkit/build-angular": "~0.7.0",
45 | "@angular/compiler-cli": "^6.1.4",
46 | "@angular/language-service": "^6.1.4",
47 | "@types/jasmine": "~2.8.3",
48 | "@types/jasminewd2": "~2.0.2",
49 | "@types/node": "~6.0.60",
50 | "codelyzer": "^4.0.1",
51 | "jasmine-core": "~2.8.0",
52 | "jasmine-spec-reporter": "~4.2.1",
53 | "karma": "~2.0.0",
54 | "karma-chrome-launcher": "~2.2.0",
55 | "karma-coverage-istanbul-reporter": "^1.2.1",
56 | "karma-jasmine": "~1.1.0",
57 | "karma-jasmine-html-reporter": "^0.2.2",
58 | "protractor": "~5.1.2",
59 | "ts-node": "~4.1.0",
60 | "tslint": "~5.9.1",
61 | "typescript": "~2.9.2"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/web/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/web/src/app/animations.ts:
--------------------------------------------------------------------------------
1 | import { animate, state, style, transition, trigger, keyframes } from '@angular/animations';
2 |
3 | // Component transition animations
4 | export const slideInDownAnimation =
5 | trigger('routeAnimation', [
6 | state('in', style({ transform: 'rotateX(0)' })),
7 | transition('void => *', [
8 | animate(300, keyframes([ // 回弹的效果
9 | style({ opacity: 0, transform: 'rotateX(90deg)' }),
10 | style({ opacity: 1, transform: 'rotateX(45deg)' }),
11 | style({ opacity: 1, transform: 'rotateX(0)' })
12 | ]))
13 | ])
14 | ]);
15 |
16 | export const slideInDownAnimation1 =
17 | trigger('routeAnimation1', [
18 | state('*',
19 | style({
20 | opacity: 1,
21 | // transform: 'translateY(0%)'
22 | })
23 | ),
24 | transition(':enter', [
25 | style({
26 | opacity: 0.5,
27 | // transform: 'translateY(50%)'
28 | }),
29 | animate('.6s ease-in')
30 | ])
31 | ]);
--------------------------------------------------------------------------------
/web/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | -
10 |
15 | 前端监控平台
20 |
21 |
22 | {{ userName
24 | }}注销
34 |
35 |
43 |
44 |
45 |
46 |
47 |
48 |
60 |
61 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/web/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .ant-layout-header {
2 | padding: 0 !important;
3 | }
4 |
5 | .ant-menu-inline,
6 | .ant-menu-vertical,
7 | .ant-menu-vertical-left {
8 | border-right: none !important;
9 | }
10 |
11 | .left-nav-container {
12 | position: fixed;
13 | left: 0;
14 | overflow-y: auto;
15 | overflow-x: hidden;
16 | // border-right: solid 1px #eee;
17 | background: #EAEDF1;
18 | }
19 |
20 | .ant-menu-dark.ant-menu-horizontal {
21 | border-bottom-color: #373d41;
22 | }
23 |
24 | .ant-menu-dark,
25 | .ant-menu-dark .ant-menu-sub {
26 | background: #373d41;
27 | }
28 |
29 | .ant-menu.ant-menu-root.ant-menu-light.ant-menu-inline {
30 | background: #EAEDF1;
31 | color: #333;
32 | li.ant-menu-item {
33 | margin: 0;
34 | font-size: 12px;
35 | &:hover {
36 | color: #333;
37 | background: #F4F6F8;
38 | }
39 | &:after {
40 | border-right: 3px solid #fff;
41 | }
42 | }
43 | .ant-menu-item-selected {
44 | background-color: #fff;
45 | color: #333;
46 | &:after {
47 | border-right: 1px solid #fff;
48 | margin-right: -1px;
49 | }
50 | }
51 | }
52 |
53 | .ant-layout-header {
54 | height: 50px;
55 | line-height: 50px;
56 | }
57 |
58 | .thanks-for-users {
59 | position: fixed;
60 | left: 4px;
61 | bottom: 4px;
62 | // width: 190px;
63 | right: 0px;
64 | z-index: 1000;
65 | padding: 4px;
66 | background: rgba(191, 191, 191, 0.6588235294117647);
67 | .close-tips {
68 | position: absolute;
69 | right: 4px;
70 | top: 4px;
71 | color: #fff;
72 | cursor: pointer;
73 | }
74 | }
--------------------------------------------------------------------------------
/web/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { CookieService } from "ngx-cookie-service";
2 | import { HttpClient } from "@angular/common/http";
3 | import { NzMessageService } from "ng-zorro-antd";
4 | import { Router } from "@angular/router";
5 | import { UserService, Broadcaster } from "./monitor.common.service";
6 | import { Component, ElementRef, ViewChild, Renderer2 } from "@angular/core";
7 |
8 | @Component({
9 | selector: "app-root",
10 | templateUrl: "./app.component.html",
11 | styleUrls: ["./app.component.scss"],
12 | })
13 | export class AppComponent {
14 | userName = null;
15 | unsubscribe = {
16 | sub0: null,
17 | };
18 | showTips: boolean = true;
19 | dataItems = [
20 | {
21 | url: "https://s.imooc.com/Wxv7KHc",
22 | src: "1.png",
23 | title: "两小时学会 Node.js stream",
24 | },
25 | {
26 | url: "https://s.imooc.com/Wlba9hm",
27 | src: "2.png",
28 | title: "一条龙的 Node·Vue·React 服务器部署",
29 | },
30 | {
31 | url: "https://s.imooc.com/SNTMyFV",
32 | src: "3.png",
33 | title: "纯正商业级应用 Node.js Koa2开发微信小程序服务端",
34 | },
35 | {
36 | url: "https://s.imooc.com/SoXuPiZ",
37 | src: "4.png",
38 | title: "前端下一代开发语言TypeScript 从基础到axios实战",
39 | },
40 | {
41 | url: "https://s.imooc.com/SxjKSih",
42 | src: "5.png",
43 | title: "从基础到实战 手把手带你掌握新版Webpack4.0",
44 | },
45 | {
46 | url: "https://s.imooc.com/SYa6RSU",
47 | src: "6.png",
48 | title: "JavaScript版 数据结构与算法",
49 | },
50 | ];
51 | dataItems1 = [
52 | {
53 | src: "weixin-gongzhonghao.jpg",
54 | title: "微信公众号",
55 | },
56 | ];
57 | constructor(
58 | private user: UserService,
59 | private router: Router,
60 | private msg: NzMessageService,
61 | private http: HttpClient,
62 | private cookie: CookieService,
63 | private broadcaster: Broadcaster
64 | ) {}
65 | ngOnInit(): void {
66 | this.userName = this.user.getUserName();
67 | this.unsubscribe.sub0 = this.broadcaster.on("refreshUser").subscribe(() => {
68 | this.userName = this.user.getUserName();
69 | });
70 | }
71 |
72 | ngOnDestroy(): void {
73 | this.unsubscribe.sub0.unsubscribe();
74 | }
75 |
76 | logout() {
77 | this.http
78 | .post(
79 | "user/logout",
80 | {},
81 | {
82 | params: {
83 | id: this.user.getUserId(),
84 | },
85 | }
86 | )
87 | .subscribe((data: any) => {
88 | if (data.IsSuccess) {
89 | this.cookie.delete("user");
90 | this.userName = null;
91 | this.router.navigate(["home"], { replaceUrl: true });
92 | } else {
93 | this.msg.error(data.Data);
94 | }
95 | });
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/web/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule,ErrorHandler } from '@angular/core';
3 | import { RouterModule, Routes } from '@angular/router';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common';
6 | import { NgZorroAntdModule,NZ_I18N, zh_CN } from 'ng-zorro-antd';
7 | import { registerLocaleData } from '@angular/common';
8 | import zh from '@angular/common/locales/zh';
9 | registerLocaleData(zh);
10 |
11 | import { CookieService } from 'ngx-cookie-service';
12 | import { JwtInterceptorService, Broadcaster, ConfigService, UserService } from './monitor.common.service';
13 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
14 | import { AppComponent } from './app.component';
15 | import { PublicModule } from './public/public.module';
16 | declare var window:any
17 | const routes: Routes = [
18 | { path: '', pathMatch:'full',redirectTo:'home' },
19 | { path: 'home', loadChildren:'app/homepage/homepage.module#HomepageModule' },
20 | { path: 'dashboard', loadChildren:'app/dashboard/dashboard.module#DashboardModule' },
21 | { path: 'sys/:appKey', loadChildren:'app/web/web.module#WebModule' }
22 | ];
23 | export class MyErrorHandler implements ErrorHandler {
24 | handleError(error) {
25 | console.error(error);
26 | window.__ml && window.__ml.error && window.__ml.error(error.stack || error);
27 | }
28 | }
29 | @NgModule({
30 | declarations: [
31 | AppComponent
32 | ],
33 | imports: [
34 | BrowserModule,
35 | BrowserAnimationsModule,
36 | HttpClientModule,
37 | PublicModule,
38 | RouterModule.forRoot(routes),
39 | NgZorroAntdModule.forRoot()
40 | ],
41 | providers: [UserService,CookieService,{ provide: NZ_I18N, useValue: zh_CN },{ provide: ErrorHandler, useClass: MyErrorHandler },ConfigService, { provide: LocationStrategy, useClass: HashLocationStrategy }, { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptorService, multi: true }, Broadcaster],
42 | bootstrap: [AppComponent]
43 | })
44 | export class AppModule { }
45 |
--------------------------------------------------------------------------------
/web/src/app/dashboard/dashboard.module.ts:
--------------------------------------------------------------------------------
1 | import { PublicModule } from './../public/public.module';
2 |
3 | import { NgModule } from '@angular/core';
4 | import { CommonModule } from '@angular/common';
5 | import { RouterModule, Routes } from '@angular/router';
6 | import { FormsModule } from '@angular/forms';
7 | import { NgZorroAntdModule } from 'ng-zorro-antd';
8 | import { SysListComponent } from './sys-list/sys-list.component';
9 | const routes: Routes = [
10 | {path: '', component:SysListComponent}
11 | ];
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | RouterModule,
16 | FormsModule,
17 | NgZorroAntdModule,
18 | PublicModule,
19 | RouterModule.forChild(routes)
20 | ],
21 | declarations: [SysListComponent]
22 | })
23 | export class DashboardModule { }
--------------------------------------------------------------------------------
/web/src/app/dashboard/sys-list/sys-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
13 |
14 |
15 |
23 |
24 |
30 |
31 |
32 | PV:
33 | {{ item.total_pv_uv?.totalPv || "--" }}
34 |
35 |
36 | UV:
37 | {{ item.total_pv_uv?.totalUv || "--" }}
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 | {{ item.appName }}
48 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
71 |
72 | 站点名称
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/web/src/app/dashboard/sys-list/sys-list.component.scss:
--------------------------------------------------------------------------------
1 | .total_pv_uv{
2 | position: absolute;
3 | right: 32px;
4 | color: gray;
5 | z-index: 1;
6 | top: 44px;
7 | p{
8 | margin-bottom: 4px;
9 | font-weight: bold;
10 | font-size: 12px;
11 | span{
12 | color: #52c41a;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/web/src/app/dashboard/sys-list/sys-list.component.ts:
--------------------------------------------------------------------------------
1 | import { UserService } from "./../../monitor.common.service";
2 | import { HighchartConfig } from "./../../model/entity";
3 | import { Router } from "@angular/router";
4 | import { Component, OnInit } from "@angular/core";
5 | import { HttpClient } from "@angular/common/http";
6 | import { NzMessageService } from "ng-zorro-antd";
7 | import * as _ from "lodash";
8 | @Component({
9 | selector: "app-sys-list",
10 | templateUrl: "./sys-list.component.html",
11 | styleUrls: ["./sys-list.component.scss"],
12 | })
13 | export class SysListComponent implements OnInit {
14 | sysItems: Array = [];
15 | isVisible_add: boolean = false;
16 | appName = "";
17 | systemId = "";
18 | isSpinning: boolean = true;
19 | validUser = true;
20 | isSaving: boolean = false;
21 | pv_uv_config: HighchartConfig;
22 | total_pv_uv: any = {};
23 | constructor(
24 | private route: Router,
25 | private http: HttpClient,
26 | private msg: NzMessageService,
27 | private user: UserService
28 | ) {}
29 |
30 | ngOnInit() {
31 | this.list();
32 | }
33 |
34 | gotoSys(item) {
35 | this.isSpinning = true;
36 | this.route.navigate(["sys/" + item.appKey]);
37 | }
38 |
39 | gotoSysSetting(item) {
40 | this.isSpinning = true;
41 | this.route.navigate(["sys/" + item.appKey + "/setting"]);
42 | }
43 |
44 | addSys() {
45 | this.isVisible_add = true;
46 | }
47 |
48 | list() {
49 | this.isSpinning = true;
50 | this.http.post("Monitor/SiteList", {}).subscribe((data: any) => {
51 | if (data.IsSuccess) {
52 | this.sysItems = data.Data;
53 | _.each(this.sysItems, (r) => {
54 | r.isSpinning = true;
55 | this.loadPvUvData(r);
56 | });
57 | }
58 | this.isSpinning = false;
59 | });
60 | }
61 |
62 | save() {
63 | if (!this.appName) {
64 | this.msg.info("站点名称必填");
65 | return;
66 | }
67 |
68 | if (this.sysItems.findIndex((r) => r.appName === this.appName) !== -1) {
69 | this.msg.info("站点名称已存在");
70 | return;
71 | }
72 | this.isSaving = true;
73 | this.http
74 | .post("Monitor/RegisterSite", {
75 | appName: this.appName,
76 | systemId: this.systemId,
77 | })
78 | .subscribe(
79 | (data: any) => {
80 | this.isSaving = false;
81 | if (data.IsSuccess) {
82 | this.msg.success("创建成功");
83 | this.list();
84 | setTimeout(() => {
85 | scrollTo({
86 | top: 10000,
87 | left: 0,
88 | behavior: "smooth",
89 | });
90 | }, 1000);
91 | this.isVisible_add = false;
92 | } else {
93 | this.msg.error("创建失败");
94 | }
95 | },
96 | () => {
97 | this.isSaving = false;
98 | this.msg.error("创建失败");
99 | }
100 | );
101 | }
102 |
103 | cancel() {
104 | this.isVisible_add = false;
105 | }
106 | //加载PV/UV数据
107 | loadPvUvData(data) {
108 | this.http
109 | .post("Monitor/PvAndUvStatis", {
110 | TimeQuantum: 4,
111 | sTime: "",
112 | eTime: "",
113 | appKey: data.appKey,
114 | })
115 | .subscribe((d: any) => {
116 | if (d.IsSuccess) {
117 | this.renderPvUvChart(d.Data, data);
118 | }
119 | });
120 | }
121 | //渲染PV/UV对比图
122 | renderPvUvChart(data, item) {
123 | let tempData = {
124 | pv: [],
125 | uv: [],
126 | };
127 |
128 | item.total_pv_uv = {
129 | totalPv: data.totalPv,
130 | totalUv: data.totalUv,
131 | };
132 | _.each(data.pvAndUvVmList, (val) => {
133 | tempData.pv.push([new Date(val.createTime).getTime(), val.pv]);
134 | tempData.uv.push([new Date(val.createTime).getTime(), val.uv]);
135 | });
136 | item.pv_uv_config = {
137 | type: 10,
138 | ext: {
139 | series: [
140 | {
141 | name: "PV",
142 | data: tempData.pv,
143 | },
144 | {
145 | name: "UV",
146 | data: tempData.uv,
147 | },
148 | ],
149 | legend: false,
150 | chart: {
151 | type: "column",
152 | height: 124,
153 | options3d: {
154 | enabled: true,
155 | depth: 50,
156 | },
157 | },
158 | },
159 | };
160 | item.isSpinning = false;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/web/src/app/homepage/home-page/homepage.component.scss:
--------------------------------------------------------------------------------
1 | #home-section {
2 | margin-top: 50px;
3 | height: 400px;
4 | position: relative;
5 | background-position: center;
6 | background-size: cover;
7 | border-bottom-left-radius: 4px;
8 | border-bottom-right-radius: 4px;
9 |
10 | #deccription {
11 | position: absolute;
12 | top: 30%;
13 | text-align: center;
14 | left: 0;
15 | right: 0;
16 | height: 500px;
17 | margin: -250px 10% auto;
18 | border-radius: 20px;
19 | padding: 8px;
20 | padding-top: 160px;
21 |
22 | .title {
23 | color: #fff;
24 | font-size: 60px;
25 | }
26 |
27 | .desc {
28 | padding: 40px;
29 | font-size: 20px;
30 | color: #bae7ff;
31 | font-weight: 500;
32 | }
33 |
34 | .opt {
35 | button {
36 | width: 200px;
37 | height: 40px;
38 | line-height: 40px;
39 | color: #fff;
40 | border: solid 1px #fff;
41 | border-radius: 18px;
42 | background: rgba(217, 217, 217, 0);
43 |
44 | &:hover {
45 | background: #fff !important;
46 | color: #333;
47 | cursor: pointer;
48 | transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
55 | #home-section2 {
56 | // min-height: 700px;
57 | padding: 50px;
58 |
59 | h4 {
60 | text-align: center;
61 | font-size: 32px;
62 | color: #333;
63 | letter-spacing: 1px;
64 | margin-bottom: 32px;
65 | }
66 | }
67 |
68 | #footer {
69 | height: 200px;
70 | background: #001529;
71 | color: #eee;
72 |
73 | .grid-fluid {
74 | max-width: 600px;
75 | width: 80%;
76 | margin: auto;
77 | height: 200px;
78 | padding-top: 24px;
79 |
80 | a,
81 | h3 {
82 | color: #eee;
83 | margin-bottom: 16px;
84 | }
85 |
86 | .grid {
87 | display: inline-block;
88 | width: 33.33%;
89 | vertical-align: top;
90 |
91 | ul {
92 | list-style: none;
93 | padding-left: 0;
94 | }
95 | }
96 | }
97 | }
98 |
99 |
100 | .section-contanier {
101 | position: relative;
102 | width: 100%;
103 | }
104 |
105 | .section-overlay {
106 | position: absolute;
107 | }
108 |
109 |
110 | .section-block,
111 | .section-overlay {
112 | height: 100%;
113 | width: 100%;
114 | }
115 |
116 | // Extra small devices (portrait phones, less than 576px)
117 | @media (max-width: 575.98px) {
118 | .title {
119 | font-size: 20px !important;
120 | }
121 |
122 | .opt {
123 | button {
124 | width: 100px !important;
125 | height: 24px !important;
126 | line-height: 24px !important;
127 | border-radius: 8px !important;
128 | }
129 | }
130 | }
131 |
132 | [nz-carousel-content] {
133 | text-align: center;
134 | height: 300px;
135 | color: #fff;
136 | overflow: hidden;
137 | }
138 |
--------------------------------------------------------------------------------
/web/src/app/homepage/home-page/homepage.component.ts:
--------------------------------------------------------------------------------
1 | import { debounceTime } from "rxjs/operators";
2 | import { Broadcaster } from "./../../monitor.common.service";
3 | import { CookieService } from "ngx-cookie-service";
4 | import { Router } from "@angular/router";
5 | import { NzMessageService } from "ng-zorro-antd";
6 | import { HttpClient } from "@angular/common/http";
7 | import { Component, OnInit, Renderer2 } from "@angular/core";
8 | import * as CryptoJS from "crypto-js";
9 | import { UserService } from "../../monitor.common.service";
10 | import { Subscription, fromEvent } from "rxjs";
11 |
12 | interface ICardProps {
13 | avatar: string;
14 | title: string;
15 | description: string;
16 | }
17 |
18 | @Component({
19 | selector: "app-homepage",
20 | templateUrl: "./homepage.component.html",
21 | styleUrls: ["./homepage.component.scss"],
22 | })
23 | export class HomepageComponent implements OnInit {
24 | _KEY: "28756942659325487412569845231586"; //32位
25 | _IV: "8536874512548456"; //16位
26 | model = {
27 | email: "",
28 | password: "",
29 | };
30 | model2 = {
31 | email: "",
32 | password: "",
33 | };
34 | bussinessItems: Array = [];
35 | whoUsedItems: Array = [];
36 | usedProcessItems: Array = [];
37 | proItems: Array = [];
38 | isVisible_login: boolean = false;
39 | isVisible_register: boolean = false;
40 | isLogin: boolean = false;
41 | unsubscribe: Subscription;
42 | isAdmin = false;
43 | constructor(
44 | private http: HttpClient,
45 | private msg: NzMessageService,
46 | private router: Router,
47 | private cookie: CookieService,
48 | private user: UserService,
49 | private broadcaster: Broadcaster,
50 | private render: Renderer2
51 | ) {}
52 |
53 | ngOnInit() {
54 | this.isAdmin = this.user.isAdmin();
55 | setTimeout(() => {
56 | this.isLogin = this.user.getToken() ? true : false;
57 | }, 1000);
58 |
59 | this.proItems = [
60 | {
61 | avatar: "safe.png",
62 | title: "无业务侵入影响",
63 | description:
64 | "项目使用探针植入,自动上报数据,不影响业务系统使用。",
65 | },
66 | {
67 | avatar: "multi.png",
68 | title: "捕获采集指标丰富",
69 | description:
70 | "日活跃、用户行为记录、访问日志、JS错误日志、API请求详情、访问性能评估。",
71 | },
72 | {
73 | avatar: "q.png",
74 | title: "快速定位问题",
75 | description:
76 | "提供出错上下文,重现错误场景。",
77 | },
78 | {
79 | avatar: "alarm.png",
80 | title: "智能报警服务",
81 | description:
82 | "灵活设置报警阈值,即时发现问题,不影响用户正常使用。",
83 | },
84 | ];
85 |
86 | this.bussinessItems = [
87 | {
88 | avatar: "",
89 | title: "前端监控平台个性化定制",
90 | description:
91 | "根据用户要求定制开发前端监控系统,个性化设置图表分析,数据展示,助你及时解决线上问题。(微信/QQ:676022504)",
92 | },
93 | {
94 | avatar: "",
95 | title: "个人/企业网站开发",
96 | description:
97 | "专注各类软件开发,打造安全优质的网络平台,价格实惠,低于同类竞品。",
98 | },
99 | {
100 | avatar: "",
101 | title: "小程序开发",
102 | description: "引流新渠道,无需下载,扫码即可使用。",
103 | },
104 | {
105 | avatar: "",
106 | title: "前/后端/架构咨询服务",
107 | description:
108 | "专注解决开发、线上过程中的各种疑难问题,服务费用低至10元。(微信/QQ:676022504)",
109 | },
110 | ];
111 |
112 | this.whoUsedItems = [
113 | {
114 | avatar: "m.png",
115 | title: "前端性能监控平台",
116 | description:
117 | "从不同维度去统计用户真实访问站点的情况,再现用户访问场景,帮助你快速定位问题。",
118 | },
119 | {
120 | avatar: "m.png",
121 | title: "cnbook",
122 | description: "【Kindle中国】_Amazon Kindle 完全购买攻略。",
123 | },
124 | {
125 | avatar: "m.png",
126 | title: "bboss",
127 | description:
128 | "bboss是一个j2ee开源框架,为企业级应用开发提供一站式解决方案,并能有效地支撑移动应用开发。",
129 | },
130 | {
131 | avatar: "m.png",
132 | title: "酷娃利息对比计算工具",
133 | description: "一款帮你选择贷款方式的多功能利息计算工具。",
134 | },
135 | {
136 | avatar: "m.png",
137 | title: "IP转换工具",
138 | description:
139 | "可以快速,准确,可靠的将IP转换成位置的工具,并对外提供接口。",
140 | },
141 | {
142 | avatar: "m.png",
143 | title: "面试一点通",
144 | description:
145 | "全球首个面试资源社区,收录全网面试题,涵盖IT.互联网、金融.财会、娱乐传媒、公务员、教育培训、法律行业、市场营销、职能行政...等等行业。",
146 | },
147 | {
148 | avatar: "m.png",
149 | title: "小贝找房",
150 | description: "新房、二手房专业咨询网站。",
151 | },
152 | {
153 | avatar: "m.png",
154 | title: "More",
155 | description: "更多私有项目入驻。",
156 | },
157 | ];
158 |
159 | this.usedProcessItems = [
160 | {
161 | avatar: "app.png",
162 | title: "步骤1:创建应用",
163 | description: "点击立即体验,进入应用中心,创建应用。",
164 | },
165 | {
166 | avatar: "SDK.png",
167 | title: "步骤2:复制探针",
168 | description: "选择需要监控的指标,复制探针到项目。",
169 | },
170 | {
171 | avatar: "data.png",
172 | title: "步骤3:开启监控",
173 | description: "进入应用详情,查看各项指标数据。",
174 | },
175 | ];
176 | }
177 |
178 | _resizePageHeight() {
179 | this.render.setStyle(
180 | document.querySelector("#home-section"),
181 | "height",
182 | document.body.clientHeight - 50 + "px"
183 | );
184 | }
185 |
186 | login() {
187 | let pwd = this.encrypt(this.model.password);
188 | this.http
189 | .post("User/login", {
190 | email: this.model.email,
191 | password: pwd,
192 | })
193 | .subscribe((r: any) => {
194 | if (r.IsSuccess) {
195 | this.cookie.set(
196 | "user",
197 | JSON.stringify(r.Data),
198 | new Date(new Date().setMonth(new Date().getMonth() + 1))
199 | );
200 | this.broadcaster.broadcast("refreshUser");
201 | this.router.navigate(["dashboard"]);
202 | } else {
203 | this.msg.error(r.Data, { nzDuration: 4000 });
204 | }
205 | });
206 | }
207 |
208 | ngOnDestroy(): void {
209 | // this.unsubscribe.unsubscribe();
210 | }
211 |
212 | register() {
213 | let pwd = this.encrypt(this.model2.password);
214 | this.http
215 | .post("User/register", {
216 | email: this.model2.email,
217 | password: pwd,
218 | })
219 | .subscribe((r: any) => {
220 | if (r.IsSuccess) {
221 | this.cookie.set(
222 | "user",
223 | JSON.stringify(r.Data),
224 | new Date(new Date().setMonth(new Date().getMonth() + 1))
225 | );
226 | this.broadcaster.broadcast("refreshUser");
227 | this.router.navigate(["dashboard"]);
228 | } else {
229 | this.msg.error(r.Data, { nzDuration: 4000 });
230 | }
231 | });
232 | }
233 |
234 | private encrypt(str) {
235 | var key = CryptoJS.enc.Utf8.parse(this._KEY);
236 | var iv = CryptoJS.enc.Utf8.parse(this._IV);
237 | var encrypted = "";
238 | var srcs = CryptoJS.enc.Utf8.parse(str);
239 | encrypted = CryptoJS.AES.encrypt(srcs, key, {
240 | iv: iv,
241 | mode: CryptoJS.mode.CBC,
242 | padding: CryptoJS.pad.Pkcs7,
243 | }).ciphertext;
244 |
245 | return encrypted.toString();
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/web/src/app/homepage/homepage.module.ts:
--------------------------------------------------------------------------------
1 | import { PublicModule } from './../public/public.module';
2 | import { NgZorroAntdModule } from 'ng-zorro-antd';
3 | import { HomepageComponent } from './home-page/homepage.component';
4 |
5 | import { NgModule } from '@angular/core';
6 | import { CommonModule } from '@angular/common';
7 | import { RouterModule, Routes } from '@angular/router';
8 | import { FormsModule } from '@angular/forms';
9 | const routes: Routes = [
10 | {path: '', component:HomepageComponent}
11 | ];
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | RouterModule,
16 | FormsModule,
17 | PublicModule,
18 | NgZorroAntdModule,
19 | RouterModule.forChild(routes)
20 | ],
21 | declarations: [HomepageComponent]
22 | })
23 | export class HomepageModule { }
24 |
--------------------------------------------------------------------------------
/web/src/app/model/entity.ts:
--------------------------------------------------------------------------------
1 | export class UrlNav{
2 | value:string
3 | label:string
4 | isActive:boolean
5 | icon?:string
6 | constructor(label?:string,value?:string,isActive?:boolean,icon?:string){
7 | this.value = value;
8 | this.label = label;
9 | this.isActive = isActive;
10 | this.icon = icon;
11 | }
12 | }
13 |
14 | export class HighchartConfig{
15 | type:number
16 | ext:any
17 | extProps?:any
18 | }
19 |
--------------------------------------------------------------------------------
/web/src/app/public/advise-bar/advise-bar.component.html:
--------------------------------------------------------------------------------
1 |
3 | {{setting?.title}}
5 |
6 |
7 | {{setting?.subTitle}}
8 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/web/src/app/public/advise-bar/advise-bar.component.scss:
--------------------------------------------------------------------------------
1 | .advise-container {
2 | position: fixed;
3 | z-index: 110;
4 | color: #fff;
5 | padding: 16px;
6 | border-radius: 50%;
7 | width: 60px;
8 | height: 60px;
9 | line-height: 1;
10 | font-size: 12px;
11 | border: solid 2px #fff;
12 | box-shadow: 0px 1px 6px 0px rgba(49, 50, 51, 0.47), 0px 1px 3px 0px rgba(186, 218, 255, 0.88);
13 | cursor: pointer;
14 | -webkit-touch-callout: none;
15 | /* iOS Safari */
16 | -webkit-user-select: none;
17 | /* Chrome/Safari/Opera */
18 | -khtml-user-select: none;
19 | /* Konqueror */
20 | -moz-user-select: none;
21 | /* Firefox */
22 | -ms-user-select: none;
23 | /* Internet Explorer/Edge */
24 | user-select: none;
25 |
26 | /* Non-prefixed version, currently not supported by any browser */
27 | .advise {
28 | // width: 600px;
29 | min-height: 200px;
30 | max-height: 400px;
31 | overflow: auto;
32 | position: absolute;
33 | left: 52px;
34 | border: solid 1px #3D93F4;
35 | background: #fff;
36 | color: #3D93F4;
37 | padding:0 4px;
38 | cursor: default;
39 | border-radius: 4px;
40 | box-shadow: 0px 0px 4px;
41 |
42 | a{
43 | width: 250px;
44 | display: inline-block;
45 | margin: 16px;
46 | img{
47 | width: 100%;
48 | }
49 | }
50 | }
51 |
52 | .advise-icon {
53 | position: absolute;
54 | left: 42px;
55 | top: 22px;
56 | font-size: 14px;
57 | }
58 |
59 | animation:mymove 2s infinite;
60 | -webkit-animation:mymove 2s infinite;
61 | /*Safari and Chrome*/
62 | }
63 |
64 | @keyframes mymove {
65 | 0% {
66 | box-shadow: none;
67 | }
68 |
69 | 40% {
70 | box-shadow: 0px 0px 10px #fff;
71 | font-size: 12px;
72 | }
73 |
74 | 80% {
75 | box-shadow: 0px 0px 4px #fff;
76 | }
77 |
78 | 100% {
79 | box-shadow: 0px 0px 2px #fff;
80 | }
81 | }
82 |
83 | @-webkit-keyframes mymove
84 |
85 | /*Safari and Chrome*/
86 | {
87 | 0% {
88 | box-shadow: none;
89 | }
90 |
91 | 40% {
92 | box-shadow: 0px 0px 10px gray;
93 | }
94 |
95 | 80% {
96 | box-shadow: 0px 0px 4px gray;
97 | }
98 |
99 | 100% {
100 | box-shadow: 0px 0px 2px gray;
101 | }
102 | }
103 |
104 | @media screen and (max-width: 500px) {
105 | .advise-container{
106 | font-size: 10px;
107 | display:none;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/web/src/app/public/advise-bar/advise-bar.component.ts:
--------------------------------------------------------------------------------
1 | import { debounceTime } from 'rxjs/operators';
2 | import { Component, OnInit, ViewChild, ElementRef, Renderer2, Input } from '@angular/core';
3 | import { fromEvent, Subscription } from 'rxjs';
4 | import { NzMessageService } from 'ng-zorro-antd';
5 |
6 | @Component({
7 | selector: 'app-advise-bar',
8 | templateUrl: './advise-bar.component.html',
9 | styleUrls: ['./advise-bar.component.scss']
10 | })
11 | export class AdviseBarComponent implements OnInit {
12 | @ViewChild("adviseContainer") adviseContainer: ElementRef;
13 | @Input("setting") setting:{
14 | title:string,
15 | subTitle:string,
16 | color:string,
17 | left:string,
18 | top:string,
19 | with:number
20 | };
21 | @Input("data") data:Array<{
22 | title:string,
23 | url:string,
24 | src:string
25 | }>;
26 | tid;
27 | sub0: Subscription;
28 | showAdvise: boolean = false;
29 | pre = {
30 | pageX: 0,
31 | pageY: 0,
32 | };
33 | cur = {
34 | pageX: 0,
35 | pageY: 0,
36 | };
37 | constructor(
38 | private render: Renderer2,
39 | private msg: NzMessageService
40 | ) { }
41 |
42 | ngOnInit() {
43 | this.render.listen(this.adviseContainer.nativeElement, "mousedown", (event) => {
44 | this.pre.pageX = Math.abs((event as any).pageX);
45 | this.pre.pageY = Math.abs((event as any).pageY);
46 | if (event.target.className == "advise-container")
47 | this.tid = 1;
48 | });
49 | this.render.listen(this.adviseContainer.nativeElement, "mouseup", (event) => {
50 | this.tid = 0;
51 | });
52 |
53 | this.render.listen(window, "mousemove", (event) => {
54 | if (this.tid) {
55 | this.cur.pageX = Math.abs((event as any).pageX);
56 | this.cur.pageY = Math.abs((event as any).pageY);
57 | let hd = this.cur.pageX - this.pre.pageX;
58 | let vd = this.cur.pageY - this.pre.pageY;
59 | this.render.setStyle(this.adviseContainer.nativeElement, "left", this.adviseContainer.nativeElement.offsetLeft + hd + "px");
60 | this.render.setStyle(this.adviseContainer.nativeElement, "top", this.adviseContainer.nativeElement.offsetTop + vd + "px");
61 | this.pre.pageX = Math.abs((event as any).pageX);
62 | this.pre.pageY = Math.abs((event as any).pageY);
63 | }
64 | });
65 | }
66 |
67 | show(event, flag) {
68 | event.stopPropagation();
69 | this.showAdvise = flag;
70 | }
71 |
72 | ngOnDestroy(): void {
73 | if (this.sub0)
74 | this.sub0.unsubscribe();
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/web/src/app/public/custom-highchart/custom-highchart.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/src/app/public/custom-highchart/custom-highchart.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/public/custom-highchart/custom-highchart.component.scss
--------------------------------------------------------------------------------
/web/src/app/public/line-progress/line-progress.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/web/src/app/public/line-progress/line-progress.component.scss:
--------------------------------------------------------------------------------
1 |
2 | .line-progress{
3 | display: inline-block;
4 | }
5 |
6 | .line-progress-left{
7 | width: 15%;
8 | }
9 |
10 | .line-progress-center{
11 | vertical-align: top;
12 | width: 69%;
13 | span{
14 | display: inline-block;
15 | height: 6px;
16 | margin: 8px 0;
17 | transition: all .5s;
18 | }
19 |
20 | }
21 |
22 | .line-progress-right{
23 | width: 10%;
24 | text-align: right;
25 | }
--------------------------------------------------------------------------------
/web/src/app/public/line-progress/line-progress.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, SimpleChanges } from '@angular/core';
2 | import * as _ from 'lodash';
3 | @Component({
4 | selector: 'app-line-progress',
5 | templateUrl: './line-progress.component.html',
6 | styleUrls: ['./line-progress.component.scss']
7 | })
8 | export class LineProgressComponent implements OnInit {
9 | @Input('data') data;
10 | globalColors = [
11 | '#F0574D', '#C55661', '#9A6686', '#866F99', '#D48E4A', '#BC8D58', '#908D74' , '#588D99', '#378DAE', '#3ADDFA', '#46C4D9', '#AB8173', '#C3715A', '#EF5630', '#716ded', '#4b7696', '#4b968a', '#69bf8c', '#7abf69', '#aebf69', '#bf9d69', '#bf6969', '#bf699d', '#ae69bf', '#7a69bf', '#698cbf', '#69bfbf', '#6ccc6f', '#a2cc6c', '#ccbc6c', '#cc826c', '#cc6cc9', '#966ccc', '#32CD32', '#6c7ccc', '#6cb6cc', '#6ccca9'
12 | ];
13 | lineData = [];
14 | constructor() {
15 | }
16 |
17 | ngOnInit() {
18 |
19 | }
20 |
21 | ngOnChanges(changes: SimpleChanges): void {
22 | if (changes.data.currentValue && changes.data.currentValue != changes.data.previousValue) {
23 | this.renderLine();
24 | }
25 | }
26 | private renderLine() {
27 | let totalCount = 0;
28 | _.each(this.data, (d) => {
29 | totalCount += d.val;
30 | });
31 | let frontDataTotal = 0;
32 | let zeroCount=0;
33 | _.each(this.data, (d, index) => {
34 | d.marginL = (frontDataTotal / totalCount) == 0 ? 0 : (frontDataTotal / totalCount).toFixed(3);
35 | d.percent = (d.val / totalCount) == 0 ? 0 : (d.val / totalCount).toFixed(3);
36 | if(d.percent>0.5){
37 | d.percent= d.percent-0.01;
38 | }
39 | d.color = this.globalColors[index];
40 | frontDataTotal += d.val;
41 | // d.percent = index == this.data.length - 1 ? (d.percent - zeroCount*0.002) : d.percent;
42 | });
43 | _.each(this.data, (val, index) => {
44 | setTimeout(() => {
45 | this.lineData[index] = val;
46 | }, (1 + index) * 100 + 500);
47 | });
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/web/src/app/public/monitor-a-blank.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, Host, HostListener, ElementRef, Renderer } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appMonitorABlank]'
5 | })
6 | export class MonitorABlankDirective {
7 |
8 | constructor(
9 | private elementRef: ElementRef
10 | ) { }
11 |
12 | @HostListener('click')
13 | onClick() { // 监听宿主元素的点击事件,设置元素背景色
14 | let temp = (window as any).__ml;
15 | if (temp && temp.focusClick) {
16 | temp.focusClick({
17 | title: this.elementRef.nativeElement.title,
18 | href: this.elementRef.nativeElement.href,
19 | text: this.elementRef.nativeElement.innerText
20 | });
21 | }
22 | }
23 |
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/web/src/app/public/public.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { CustomHighchartComponent } from './custom-highchart/custom-highchart.component';
4 | import { TimeChoicePanelComponent } from './time-choice-panel/time-choice-panel.component';
5 | import { NgZorroAntdModule } from 'ng-zorro-antd';
6 | import { FormsModule } from '@angular/forms';
7 | import { LineProgressComponent } from './line-progress/line-progress.component';
8 | import { AdviseBarComponent } from './advise-bar/advise-bar.component';
9 | import { MonitorABlankDirective } from './monitor-a-blank.directive';
10 |
11 | @NgModule({
12 | imports: [
13 | CommonModule,
14 | NgZorroAntdModule,
15 | FormsModule
16 | ],
17 | declarations: [CustomHighchartComponent, TimeChoicePanelComponent, LineProgressComponent, AdviseBarComponent, MonitorABlankDirective],
18 | exports:[CustomHighchartComponent,TimeChoicePanelComponent,LineProgressComponent,AdviseBarComponent,MonitorABlankDirective]
19 | })
20 | export class PublicModule { }
21 |
--------------------------------------------------------------------------------
/web/src/app/public/time-choice-panel/time-choice-panel.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/web/src/app/public/time-choice-panel/time-choice-panel.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/public/time-choice-panel/time-choice-panel.component.scss
--------------------------------------------------------------------------------
/web/src/app/public/time-choice-panel/time-choice-panel.component.ts:
--------------------------------------------------------------------------------
1 | import { Broadcaster } from './../../monitor.common.service';
2 | import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';
3 | @Component({
4 | selector: 'app-time-choice-panel',
5 | templateUrl: './time-choice-panel.component.html',
6 | styleUrls: ['./time-choice-panel.component.scss']
7 | })
8 | export class TimeChoicePanelComponent implements OnInit {
9 | @Input('time') time:string;
10 | @Output() selectOver = new EventEmitter();
11 | visible:boolean=false;
12 | customTime:any=[]
13 | radioValue:string
14 | constructor(
15 | ) { }
16 | ngOnInit() {
17 | this.radioValue=this.time;
18 | }
19 |
20 | onOk(result: Date): void {
21 | this.selectOver.emit({
22 | type:7,
23 | time:result
24 | });
25 | this.customTime=result;
26 | this.visible=false;
27 | }
28 | ngModelChange(e){
29 | this.radioValue=e;
30 | if(e!=7){
31 | this.selectOver.emit({
32 | type:e
33 | });
34 | this.visible=false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/api-request/api-request.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/api-request/api-request.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/backend-log/backend-log.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 | 编号 |
51 | 日志等级 |
52 | 时间 |
53 | 日志消息 |
54 | 操作 |
55 |
56 |
57 |
58 |
59 | {{data.Id}} |
60 | {{data.Level}} |
61 | {{data.LogDate.substr(6,13)|date:"yyyy/MM/dd HH:mm:ss"}} |
62 | {{data.Message}} |
63 |
64 | {{data.Message?.substr(0,100)}}
65 | |
66 |
67 | 详细
68 | |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 日志等级
78 |
79 | {{logDetails?.Level}}
80 |
81 | 日期
82 |
83 | {{logDetails?.LogDate.substr(6,13)|date:"yyyy/MM/dd HH:mm:ss"}}
84 |
85 | Ip
86 |
87 | {{logDetails?.MessageObj.Ip}}
88 |
89 | 方法
90 |
91 | {{logDetails?.MessageObj.Methods||'未知'}}
92 |
93 | 接口
94 |
95 | {{logDetails?.MessageObj.RequestInterface||'未知'}}
96 |
97 | 参数
98 |
99 | {{logDetails?.MessageObj.Params||'未知'}}
100 |
101 | 详细信息
102 |
103 | {{logDetails?.MessageObj.Exception}}
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/backend-log/backend-log.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/backend-log/backend-log.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/backend-log/backend-log.component.ts:
--------------------------------------------------------------------------------
1 | import { slideInDownAnimation } from './../../../animations';
2 | import { ActivatedRoute } from '@angular/router';
3 | import { HttpClient } from '@angular/common/http';
4 |
5 | import { Component, OnInit } from '@angular/core';
6 | import { NzMessageService,NzModalService } from 'ng-zorro-antd';
7 | import * as _ from 'lodash';
8 | @Component({
9 | selector: 'app-backend-log',
10 | templateUrl: './backend-log.component.html',
11 | styleUrls: ['./backend-log.component.scss'],
12 | animations: [slideInDownAnimation]
13 | })
14 | export class BackendLogComponent implements OnInit {
15 | dataSet = [];
16 | logDetails;
17 | loading = true;
18 | modalLoading = true;
19 | tableScrollSetting = { y: '320px' };
20 | total = 0;
21 | searchModel = {
22 | bussnessId: '',
23 | logEnum: '',
24 | startTime: null,
25 | endTime: null,
26 | pageSize: 50,
27 | pageIndex: 1,
28 | msg:''
29 | };
30 | appKey;
31 | currentSite
32 | constructor(
33 | private http: HttpClient,
34 | private msg: NzMessageService,
35 | private route: ActivatedRoute,
36 | private modalService: NzModalService
37 | ) { }
38 |
39 | ngOnInit() {
40 | this.appKey = this.route.parent.snapshot.paramMap.get("appKey");
41 | this.searchModel.startTime = new Date(new Date().setDate(new Date().getDate() - 1));
42 | this.searchModel.endTime = new Date();
43 | this.getSites();
44 | }
45 |
46 | private getSites() {
47 | this.http.get("Monitor/SiteList").subscribe((d: any) => {
48 | if (d.IsSuccess) {
49 | let currentSys = _.filter(d.Data, { 'appKey': this.appKey });
50 | this.currentSite = currentSys[0];
51 | this.searchModel.bussnessId = this.currentSite.systemId;
52 | if(!this.searchModel.bussnessId){
53 | this.msg.info("请先在应用设置页面添加业务系统ID");
54 | return;
55 | }
56 | this.searchData(true);
57 | }
58 | });
59 | }
60 |
61 | searchModelType(e) {
62 | this.searchModel.logEnum = e;
63 | this.searchData(true);
64 | }
65 |
66 | searchData(reset: boolean = false): void {
67 | if (reset) {
68 | this.searchModel.pageIndex = 1;
69 | }
70 | this.loading = true;
71 | this.http.post("SystemLog/List", this.searchModel).subscribe((data: any) => {
72 | if (data.IsSuccess) {
73 | this.total = data.Data.TotalCount
74 | this.loading = false;
75 | this.dataSet = data.Data.List;
76 | } else {
77 | this.msg.error("数据加载失败");
78 | }
79 | });
80 | }
81 |
82 | onOk(data) {
83 | this.searchModel.startTime = data[0];
84 | this.searchModel.endTime = data[1];
85 | }
86 |
87 | goDetails(data,tplContent){
88 | this.modalLoading=true;
89 | this.modalService.info({
90 | nzTitle: '详情',
91 | nzContent: tplContent,
92 | nzWidth:1000
93 | });
94 | this.http.post("SystemLog/Details", {
95 | errorGuid:data.Id
96 | }).subscribe((data: any) => {
97 | if (data.IsSuccess) {
98 | this.logDetails = data.Data;
99 | } else {
100 | this.msg.error("数据加载失败");
101 | }
102 | this.modalLoading=false;
103 | });
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error-track/js-error-track.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
提示:如果源文件经过压缩,可以使用此功能更快找到出错位置。要求上传对应的 *.map文件,此文件不会上传服务器。
7 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | HTTP请求 {{item.createTime|date:'yyyy-MM-dd HH:mm:ss'}}
50 | Url:{{item.api}}
51 | 状态码:{{item.code}}
52 | 耗时:{{item.time}}ms
53 | 用户IP:{{item.onlineip}}
54 |
55 |
56 | 页面跳转 {{item.createTime|date:'yyyy-MM-dd HH:mm:ss'}}
57 | From:{{item.prePage}}
58 | To:{{item.page}}
59 |
60 |
61 | 控制台信息 {{item.createTime|date:'yyyy-MM-dd HH:mm:ss'}}
62 | Type:{{item.cType}}
63 | Msg:{{item.cMsg}}
64 |
65 |
66 |
67 | JavaScript执行错误 {{data.createTime|date:'yyyy-MM-dd HH:mm:ss'}}
68 | Error:{{data.error?.substr(0,100)}}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error-track/js-error-track.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/js-error-track/js-error-track.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error-track/js-error-track.component.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Component, OnInit, Input } from '@angular/core';
3 | import { UploadXHRArgs, NzMessageService } from 'ng-zorro-antd';
4 | import * as CodeMirror from 'codemirror/lib/codemirror';
5 | import 'codemirror/mode/javascript/javascript';
6 | @Component({
7 | selector: 'app-js-error-track',
8 | templateUrl: './js-error-track.component.html',
9 | styleUrls: ['./js-error-track.component.scss']
10 | })
11 | export class JsErrorTrackComponent implements OnInit {
12 | @Input("data") data;
13 | currentFile = {
14 | lineColumn: ''
15 | };
16 | result;
17 | dataSet = [];
18 | codeMirrorEditor;
19 | constructor(
20 | private msg: NzMessageService,
21 | private http: HttpClient
22 | ) { }
23 |
24 | ngOnInit() {
25 | setTimeout(() => {
26 | let myTextarea = document.getElementById('code');
27 | this.codeMirrorEditor = CodeMirror.fromTextArea(myTextarea, {
28 | mode: 'javascript',//编辑器语言
29 | theme: 'monokai', //编辑器主题
30 | extraKeys: { "Ctrl": "autocomplete" },//ctrl可以弹出选择项
31 | lineNumbers: true//显示行号
32 | });
33 | });
34 | }
35 |
36 | resolve() {
37 | if (!this.currentFile.lineColumn) {
38 | this.msg.info('行号/列号不能为空');
39 | return;
40 | }
41 | let files = (document.getElementById("sourceMapFile") as any).files;
42 | if (files.length < 1) {
43 | this.msg.info('请上传对应*.map文件');
44 | return;
45 | }
46 | let [line, column] = this.currentFile.lineColumn.split(':');
47 | const fileReader = new FileReader();
48 | fileReader.onloadend = () => {
49 | const rawSourceMap = fileReader.result;
50 | // 查找
51 | (window as any).sourceMap.SourceMapConsumer.with(rawSourceMap, null, consumer => {
52 | let result = consumer.originalPositionFor({
53 | source: "./",
54 | line: +line,
55 | column: +column
56 | });
57 | let index = consumer._absoluteSources.indexOf(result.source);
58 | this.result = result;
59 | if (index != -1) {
60 | let sourceContent = consumer.sourcesContent[index];
61 | this.codeMirrorEditor.setValue(sourceContent);
62 | }
63 | });
64 | };
65 | fileReader.readAsText(files[0]);
66 | }
67 |
68 | getPath() {
69 | this.http.post("Monitor/JsErrorTrackPath", {
70 | createTime: this.data.createTime,
71 | onlineip: this.data.onlineip,
72 | os: this.data.os,
73 | pageWh: this.data.pageWh,
74 | ua: this.data.ua,
75 | bs: this.data.bs,
76 | appKey: this.data.appKey
77 | }).subscribe((data: any) => {
78 | if (data.IsSuccess) {
79 | data.Data.List.forEach(element => {
80 | if(element.type=='console'){
81 | element.cMsg=decodeURIComponent(element.cMsg);
82 | }
83 | });
84 | this.dataSet = data.Data.List;
85 | } else {
86 | this.msg.error("数据加载失败");
87 | }
88 | })
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error/js-error.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
按JS错误时间排行
9 |
14 |
20 |
21 |
22 |
25 |
26 |
27 |
33 | {{
34 | item.error.substr(0, 60)
35 | }}
36 |
37 | {{ item.createTime | date: "yyyy-MM-dd HH:mm" }}
38 |
39 |
40 |
41 |
46 | 加载更多
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 | IP:{{ currentSelected?.onlineip }}
62 |
63 | 标识:{{ currentSelected?.visitedUserId }}
64 |
65 |
66 |
67 |
68 |
69 | {{ currentSelected?.os }}
70 |
71 |
72 |
73 |
74 | {{ currentSelected?.bs }}
75 |
76 |
77 |
78 |
79 | {{ currentSelected?.pageWh }}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | {{ currentSelected?.country_nameCN }}
91 |
92 |
93 |
94 |
95 | {{ currentSelected?.mostSpecificSubdivision_nameCN }}
96 |
97 |
98 |
99 |
100 | {{ currentSelected?.city_nameCN }}
101 |
102 |
103 |
104 |
105 | {{ currentSelected?.isp }}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error/js-error.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/js-error/js-error.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/js-error/js-error.component.ts:
--------------------------------------------------------------------------------
1 | import { JsErrorTrackComponent } from './../js-error-track/js-error-track.component';
2 |
3 |
4 | import { fromEvent as observableFromEvent, Observable } from 'rxjs';
5 | import { debounceTime } from 'rxjs/operators';
6 | import { ActivatedRoute } from '@angular/router';
7 | import { Broadcaster } from './../../../monitor.common.service';
8 | import { HttpClient } from '@angular/common/http';
9 | import { Component, OnInit, ViewChildren, ElementRef, Renderer2 } from '@angular/core';
10 | import * as _ from 'lodash';
11 | import { NzMessageService, NzModalService } from 'ng-zorro-antd';
12 | declare var window: any
13 | @Component({
14 | selector: 'app-js-error',
15 | templateUrl: './js-error.component.html',
16 | styleUrls: ['./js-error.component.scss']
17 | })
18 | export class JsErrorComponent implements OnInit {
19 | @ViewChildren('mydetailsContent') mydetailsContent: Array
20 | unsubscribe = {
21 | sub0: null,
22 | sub1: null
23 | }
24 | isSpinning = {
25 | spin1: true,
26 | spin2: true
27 | };
28 | appKey;
29 | keywords;
30 | pageIndex=1;
31 | pageSize=50;
32 | jsErrorListData = [];
33 | jsErrorListDataTotal = 0;
34 | currentSelected
35 | constructor(
36 | private render: Renderer2,
37 | private http: HttpClient,
38 | private broadcaster: Broadcaster,
39 | private route: ActivatedRoute,
40 | private msg:NzMessageService,
41 | private modalService:NzModalService
42 | ) { }
43 |
44 | ngOnInit() {
45 |
46 | this.appKey = this.route.parent.snapshot.paramMap.get("appKey");
47 | this.unsubscribe.sub0 = observableFromEvent(window, "resize").pipe(
48 | debounceTime(100))
49 | .subscribe((event) => {
50 | this._resizePageHeight();
51 | });
52 |
53 | this.unsubscribe.sub1 = this.broadcaster.on("choiceTimeToRender").subscribe((data: any) => {
54 | this.loadUserPathList(data.time, data.type);
55 | });
56 | if (window.globalTime) {
57 | this.loadUserPathList(window.globalTime.time, window.globalTime.type);
58 | } else {
59 | this.loadUserPathList(null, 4);
60 | }
61 |
62 | }
63 |
64 | search() {
65 | this.currentSelected = null;
66 | if (window.globalTime) {
67 | this.loadUserPathList(window.globalTime.time, window.globalTime.type);
68 | } else {
69 | this.loadUserPathList(null, 4);
70 | }
71 | }
72 |
73 |
74 | loadMore() {
75 | let time;
76 | let type;
77 | if (window.globalTime) {
78 | time=window.globalTime.time;
79 | type=window.globalTime.type;
80 | } else {
81 | time=null;
82 | type=4;
83 | }
84 | this.isSpinning.spin1 = true;
85 | this.pageIndex+=1;
86 | this.http.post("Monitor/List", {
87 | TimeQuantum: type == '7' ? '' : type,
88 | type: "js",
89 | sTime: type == '7' ? time[0] : '',
90 | eTime: type == '7' ? time[1] : '',
91 | appKey: this.appKey,
92 | keywords: this.keywords||"",
93 | pageSize: this.pageSize,
94 | pageIndex: this.pageIndex,
95 | }).subscribe((data: any) => {
96 | if (data.IsSuccess) {
97 | if (data.Data.List && data.Data.List.length > 0) {
98 | _.each(data.Data.List, (d) => {
99 | d.error = decodeURIComponent(d.error).replace(/\\n/g, "
");
100 | });
101 | this.jsErrorListData = [...this.jsErrorListData,...data.Data.List];
102 | }
103 | }
104 | this.isSpinning.spin1 = false;
105 | });
106 | }
107 |
108 |
109 | //获取jsError列表
110 | loadUserPathList(time, type) {
111 | this.isSpinning.spin1 = true;
112 | this.pageIndex=1;
113 | this.http.post("Monitor/List", {
114 | TimeQuantum: type == '7' ? '' : type,
115 | type: "js",
116 | sTime: type == '7' ? time[0] : '',
117 | eTime: type == '7' ? time[1] : '',
118 | appKey: this.appKey,
119 | keywords: this.keywords||"",
120 | pageSize: this.pageSize,
121 | pageIndex: this.pageIndex,
122 | }).subscribe((data: any) => {
123 | if (data.IsSuccess) {
124 | if (data.Data.List && data.Data.List.length > 0) {
125 | data.Data.List[0]['select'] = true;
126 | _.each(data.Data.List, (d) => {
127 | d.error = decodeURIComponent(d.error).replace(/\\n/g, "
");
128 | });
129 | this.jsErrorListData = data.Data.List;
130 | this.jsErrorListDataTotal=data.Data.TotalCount;
131 | this.selectListItem(data.Data.List[0]);
132 | } else {
133 | this.currentSelected=null;
134 | this.jsErrorListData = [];
135 | }
136 | }
137 | this.isSpinning.spin1 = false;
138 | });
139 | }
140 |
141 | selectListItem(data) {
142 | _.each(this.jsErrorListData, (val) => {
143 | val.select = false;
144 | });
145 | data.select = true;
146 | this.currentSelected = data;
147 | }
148 |
149 | ngAfterViewInit() {
150 | this.broadcaster.broadcast('showGlobalTimer', true);
151 | this._resizePageHeight();
152 | }
153 |
154 | ngOnDestroy(): void {
155 | this.unsubscribe.sub0.unsubscribe();
156 | this.unsubscribe.sub1.unsubscribe();
157 | }
158 |
159 | private _resizePageHeight() {
160 | this.mydetailsContent.forEach(element => {
161 | this.render.setStyle(element.nativeElement, "height", window.innerHeight - 50 - 70 + "px");
162 | });
163 | }
164 |
165 | errorTrack(){
166 | if(!this.currentSelected){
167 | this.msg.info('选择一条错误数据');
168 | return;
169 | }
170 | const modal= this.modalService.create({
171 | nzTitle: '错误场景还原',
172 | nzMaskClosable:false,
173 | nzWidth:1366,
174 | nzContent: JsErrorTrackComponent,
175 | nzZIndex:3000,
176 | nzBodyStyle:{
177 | padding:0
178 | },
179 | nzComponentParams: {
180 | data:this.currentSelected
181 | },
182 | nzFooter:null
183 | });
184 | }
185 | }
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/resource-load-details/resource-load-details.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 | Name |
30 | DateTime |
31 | 访客IP |
32 | 用户标识 |
33 | InitiatorType |
34 | EntryType |
35 | Size |
36 | Time |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {{data.rUrl?.substr(0,100)}}
44 |
45 | {{data.rUrl}}
46 |
47 |
48 | |
49 | {{data.createTime|date:"yyyy/MM/dd HH:mm:ss"}} |
50 | {{data.onlineip}} |
51 | {{ data.visitedUserId }} |
52 | {{data.rInitiatorType}} |
53 | {{data.rEntryType}} |
54 | {{(data.rSize||data.rSize==0)&&(data.rSize===0?'from cache':data.rSize+'KB')||''}} |
55 | {{data.rDuration}}ms |
56 |
57 |
58 |
59 |
共 {{total}} 条数据
60 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/resource-load-details/resource-load-details.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/resource-load-details/resource-load-details.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/resource-load-details/resource-load-details.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Broadcaster } from './../../../monitor.common.service';
3 | import { slideInDownAnimation } from './../../../animations';
4 | import { ActivatedRoute } from '@angular/router';
5 | import { HttpClient } from '@angular/common/http';
6 | import { Component, OnInit } from '@angular/core';
7 | import { NzMessageService } from 'ng-zorro-antd';
8 | import * as _ from 'lodash';
9 | @Component({
10 | selector: 'app-resource-load-details',
11 | templateUrl: './resource-load-details.component.html',
12 | styleUrls: ['./resource-load-details.component.scss'],
13 | animations: [slideInDownAnimation]
14 | })
15 | export class ResourceLoadDetailsComponent implements OnInit {
16 | dataSet = [];
17 | loading = true;
18 | tableScrollSetting={ y: '280px',x:'1000px' };
19 | total=0;
20 | appKey;
21 | searchModel={
22 | keywords:'',
23 | type:'',
24 | sTime:null,
25 | eTime:null,
26 | appKey:'',
27 | pageSize:100,
28 | pageIndex:1
29 | };
30 | constructor(
31 | private http:HttpClient,
32 | private msg:NzMessageService,
33 | private route: ActivatedRoute,
34 | private broadcaster:Broadcaster
35 | ) { }
36 | ngOnInit() {
37 |
38 | this.appKey = this.route.parent.snapshot.paramMap.get("appKey");
39 | this.searchModel.sTime=new Date(new Date().setDate(new Date().getDate()-1));
40 | this.searchModel.eTime=new Date();
41 | this.searchModel.appKey=this.appKey;
42 | this.searchData(true);
43 | }
44 |
45 | searchModelType(e){
46 | this.searchModel.type=e;
47 | this.searchData(true);
48 | }
49 |
50 | searchData(reset:boolean=false): void {
51 | if (reset) {
52 | this.searchModel.pageIndex = 1;
53 | }
54 | this.loading = true;
55 | this.searchModel.keywords=this.searchModel.keywords.trim();
56 | this.http.post("Monitor/resourceList",this.searchModel).subscribe((data:any) => {
57 | if (data.IsSuccess) {
58 | this.loading = false;
59 | this.total=data.Data.TotalCount;
60 | data.Data.List.forEach(element => {
61 | if(element.rSize){
62 | element.rSize=new Number(element.rSize/1024).toFixed(2);
63 | }
64 | if(element.rDuration){
65 | element.rDuration=new Number(element.rDuration).toFixed(2);
66 | }else{
67 | element.rDuration=0;
68 | }
69 | });
70 | this.dataSet=data.Data.List;
71 | } else {
72 | this.msg.error("数据加载失败");
73 | }
74 | })
75 | }
76 |
77 | ngAfterContentInit(): void {
78 | this.broadcaster.broadcast('showGlobalTimer',false);
79 | (window as any).globalTime=null;
80 | }
81 |
82 | onOk(data){
83 | this.searchModel.sTime=data[0];
84 | this.searchModel.eTime=data[1];
85 | }
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/sys-index/sys-index.component.scss:
--------------------------------------------------------------------------------
1 | .total_pv_uv{
2 | position: absolute;
3 | right: 32px;
4 | color: gray;
5 | z-index: 1;
6 | p{
7 | margin-bottom: 4px;
8 | font-weight: bold;
9 | span{
10 | color: #52c41a;
11 | padding-left: 4px;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/sys-setting/sys-setting.component.html:
--------------------------------------------------------------------------------
1 |
2 |
SDK扩展配置项
3 |
4 | 注意:更改以下配置项后,BI探针内容也会相应的变化,需要将BI探针内容重新复制/粘贴到页面HTML中,并且部署后才会生效。
5 |
6 |
7 |
8 | 站点ID:
9 |
10 |
11 |
12 | 手动上报API数据:
13 |
19 |
20 |
21 | 手动上传JS错误:
22 |
28 |
29 |
30 | 上报资源加载情况:
31 |
37 |
38 |
39 |
40 |
报警设置
41 |
注意:修改报警配置后,需要点击保存配置才会生效。
42 |
43 | 站点ID:
44 |
45 |
46 |
47 |
48 | JS错误率报警:
54 |
55 |
78 |
79 |
80 |
81 | API错误率报警:
87 |
88 |
89 |
112 |
113 |
114 |
115 | Perf访问速度报警:
121 |
122 |
123 |
146 |
147 |
148 |
149 |
复制/粘贴BI探针
150 |
151 | 复制下方的代码,将其粘贴在页面HTML的body中。需要将代码粘贴在body内容的第一行,重新部署后生效。如需帮助请联系作者。
152 |
153 |
154 |
155 | {{ setting.code }}
156 |
157 |
158 |
159 |
手动上报,勾选上面手动上报配置后,可以使用下面接口手动上报数据:
160 |
手动上报API数据
161 |
162 |
163 | __ml.api(api,success, time, code, msg):
164 | api:请求接口
165 | success:上传是否成功(true/false )
166 | time:耗时(ms)
167 | code:返回码
168 | msg:消息(string/object)
169 |
170 |
171 |
172 |
手动上传JS错误
173 |
174 |
175 | __ml.error(errorobj):
176 | errorobj:js错误对象
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/sys-setting/sys-setting.component.scss:
--------------------------------------------------------------------------------
1 |
2 | .setting-gutter{
3 | margin: 16px 8px;
4 | font-size: 14px;
5 | color: #333;
6 | label{
7 | margin-left: 8px;
8 | }
9 | }
10 |
11 | .setting-code{
12 | background-color: #f5f5f6;
13 | padding: 16px;
14 | }
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/user-path/user-path.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
按用户访问路径数排行
9 |
14 |
20 |
21 |
22 |
25 |
26 |
27 |
33 | {{ item.onlineip }}({{ item.mostSpecificSubdivision_nameCN }})
36 |
37 | 总路径:{{ item.count }}
38 |
39 |
40 |
41 |
46 | 加载更多
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 | IP:{{ currentSelectedUserPath?.onlineip }}
62 |
63 | 标识:{{ currentSelectedUserPath?.visitedUserId }}
64 |
65 |
66 |
67 |
68 |
69 | {{ currentSelectedUserPath?.os }}
70 |
71 |
72 |
73 |
74 | {{ currentSelectedUserPath?.bs }}
75 |
76 |
77 |
78 |
79 | {{ currentSelectedUserPath?.pageWh }}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | {{ currentSelectedUserPath?.country_nameCN }}
91 |
92 |
93 |
94 |
95 |
96 | {{ currentSelectedUserPath?.mostSpecificSubdivision_nameCN }}
97 |
98 |
99 |
100 |
101 |
102 | {{ currentSelectedUserPath?.city_nameCN }}
103 |
104 |
105 |
106 |
107 | {{ currentSelectedUserPath?.isp }}
108 |
109 |
110 |
111 |
112 |
113 |
118 |
用户关键访问路径记录:
119 |
120 |
121 |
126 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/user-path/user-path.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/user-path/user-path.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/user-path/user-path.component.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { fromEvent as observableFromEvent, Observable } from 'rxjs';
4 | import { debounceTime } from 'rxjs/operators';
5 | import { ActivatedRoute } from '@angular/router';
6 | import { Broadcaster } from './../../../monitor.common.service';
7 | import { HttpClient } from '@angular/common/http';
8 | import { Component, OnInit, ViewChildren, ElementRef, Renderer2 } from '@angular/core';
9 | import * as _ from 'lodash';
10 | declare var window: any
11 | @Component({
12 | selector: 'app-user-path',
13 | templateUrl: './user-path.component.html',
14 | styleUrls: ['./user-path.component.scss']
15 | })
16 | export class UserPathComponent implements OnInit {
17 | @ViewChildren('mydetailsContent') mydetailsContent: Array
18 | unsubscribe = {
19 | sub0: null,
20 | sub1: null
21 | }
22 | isSpinning = {
23 | spin1: true,
24 | spin2: true
25 | };
26 | appKey;
27 | keywords;
28 | pageIndex=1;
29 | pageSize=100;
30 | userPathListData={
31 | data:[],
32 | total:0
33 | };
34 | currentSelectedUserPath
35 | constructor(
36 | private render: Renderer2,
37 | private http: HttpClient,
38 | private broadcaster: Broadcaster,
39 | private route: ActivatedRoute
40 | ) { }
41 |
42 | ngOnInit() {
43 |
44 | this.appKey = this.route.parent.snapshot.paramMap.get("appKey");
45 | this.unsubscribe.sub0 = observableFromEvent(window, "resize").pipe(
46 | debounceTime(100))
47 | .subscribe((event) => {
48 | this._resizePageHeight();
49 | });
50 |
51 | this.unsubscribe.sub1 = this.broadcaster.on("choiceTimeToRender").subscribe((data: any) => {
52 | this.loadUserPathList(data.time, data.type);
53 | });
54 | if (window.globalTime) {
55 | this.loadUserPathList(window.globalTime.time, window.globalTime.type);
56 | } else {
57 | this.loadUserPathList(null, 4);
58 | }
59 |
60 | }
61 |
62 | search() {
63 | this.currentSelectedUserPath = null;
64 | if (window.globalTime) {
65 | this.loadUserPathList(window.globalTime.time, window.globalTime.type);
66 | } else {
67 | this.loadUserPathList(null, 4);
68 | }
69 | }
70 |
71 | loadMore() {
72 | let time;
73 | let type;
74 | if (window.globalTime) {
75 | time=window.globalTime.time;
76 | type=window.globalTime.type;
77 | } else {
78 | time=null;
79 | type=4;
80 | }
81 | this.isSpinning.spin1 = true;
82 | this.pageIndex+=1;
83 | this.http.post("Monitor/userPathListStatis", {
84 | TimeQuantum: type == '7' ? '' : type,
85 | sTime: type == '7' ? time[0] : '',
86 | eTime: type == '7' ? time[1] : '',
87 | appKey: this.appKey,
88 | keywords: this.keywords,
89 | pageIndex:this.pageIndex,
90 | pageSize:this.pageSize
91 | }).subscribe((d: any) => {
92 | if (d.IsSuccess) {
93 | if (d.Data && d.Data.data.length > 0) {
94 | this.userPathListData.data = [...this.userPathListData.data,...d.Data.data];
95 | }
96 | }
97 | this.isSpinning.spin1 = false;
98 | });
99 | }
100 |
101 |
102 | //获取用户访问列表
103 | loadUserPathList(time, type) {
104 | this.isSpinning.spin1 = true;
105 | this.pageIndex=1;
106 | this.http.post("Monitor/userPathListStatis", {
107 | TimeQuantum: type == '7' ? '' : type,
108 | sTime: type == '7' ? time[0] : '',
109 | eTime: type == '7' ? time[1] : '',
110 | appKey: this.appKey,
111 | keywords: this.keywords,
112 | pageIndex:this.pageIndex,
113 | pageSize:this.pageSize
114 | }).subscribe((d: any) => {
115 | if (d.IsSuccess) {
116 | if (d.Data && d.Data.data.length > 0) {
117 | d.Data.data[0]['select'] = true;
118 | this.userPathListData = d.Data;
119 | this.selectUserPathListItem(d.Data.data[0]);
120 | } else {
121 | this.userPathListData.data = [];
122 | this.userPathListData.total =0;
123 | }
124 |
125 | }
126 | this.isSpinning.spin1 = false;
127 | });
128 | }
129 |
130 | selectUserPathListItem(data) {
131 | _.each(this.userPathListData.data, (val) => {
132 | val.select = false;
133 | });
134 | data.select = true;
135 | this.currentSelectedUserPath = data;
136 | }
137 |
138 |
139 | ngAfterViewInit() {
140 | this.broadcaster.broadcast('showGlobalTimer', true);
141 | this._resizePageHeight();
142 | }
143 | ngOnDestroy(): void {
144 | this.unsubscribe.sub0.unsubscribe();
145 | this.unsubscribe.sub1.unsubscribe();
146 | }
147 |
148 | private _resizePageHeight() {
149 | this.mydetailsContent.forEach(element => {
150 | this.render.setStyle(element.nativeElement, "height", window.innerHeight - 50 - 70 + "px");
151 | });
152 |
153 | }
154 |
155 | }
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-details/visit-details.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
70 |
71 |
72 | 时间 |
73 | 日志类型 |
74 | 页面地址 |
75 | 浏览器 |
76 | 设备 |
77 | UA |
78 | 地域 |
79 | 用户标识 |
80 | API |
81 | Msg |
82 | Code |
83 | IsSuccess |
84 | Time |
85 | Error |
86 |
87 |
88 |
89 |
90 | {{ data.createTime | date: "yyyy/MM/dd HH:mm:ss" }} |
91 | {{ data.type }} |
92 | {{ data.page }} |
93 | {{ data.bs }} |
94 | {{ data.os }} |
95 | {{ data.ua }} |
96 |
97 | {{ data.mostSpecificSubdivision_nameCN }}
98 | {{ data.onlineip }}
99 | |
100 | {{ data.visitedUserId }} |
101 | {{ data.api }} |
102 |
103 |
104 | {{ data.msg?.substr(0, 100) }}
105 |
106 | {{ data.msg }}
107 |
108 |
109 | |
110 | {{ data.code }} |
111 | {{ data.success }} |
112 | {{ data.time }}ms |
113 |
114 |
115 | 错误场景还原
118 |
125 | |
126 |
127 |
128 |
129 |
共 {{ total }} 条数据
130 |
131 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-details/visit-details.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/visit-details/visit-details.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-details/visit-details.component.ts:
--------------------------------------------------------------------------------
1 | import { JsErrorTrackComponent } from './../js-error-track/js-error-track.component';
2 | import { Broadcaster } from './../../../monitor.common.service';
3 | import { slideInDownAnimation } from './../../../animations';
4 | import { ActivatedRoute } from '@angular/router';
5 | import { HttpClient } from '@angular/common/http';
6 | import { Component, OnInit } from '@angular/core';
7 | import { NzMessageService, NzModalService } from 'ng-zorro-antd';
8 | import * as _ from 'lodash';
9 | @Component({
10 | selector: 'app-visit-details',
11 | templateUrl: './visit-details.component.html',
12 | styleUrls: ['./visit-details.component.scss'],
13 | animations: [slideInDownAnimation]
14 | })
15 | export class VisitDetailsComponent implements OnInit {
16 | dataSet = [];
17 | loading = true;
18 | tableScrollSetting = { y: '320px', x: '1000px' };
19 | total = 0;
20 | appKey
21 | searchModel = {
22 | keywords: '',
23 | type: 'pv',
24 | sTime: null,
25 | eTime: null,
26 | pageSize: 100,
27 | pageIndex: 1,
28 | appKey: ''
29 | };
30 | constructor(
31 | private http: HttpClient,
32 | private msg: NzMessageService,
33 | private route: ActivatedRoute,
34 | private broadcaster: Broadcaster,
35 | private modalService:NzModalService
36 | ) { }
37 | ngOnInit() {
38 | this.appKey = this.route.parent.snapshot.paramMap.get("appKey");
39 | this.searchModel.type = this.route.snapshot.queryParams["type"] || 'pv';
40 | this.searchModel.keywords = this.route.snapshot.queryParams["keywords"]==undefined ? '' : decodeURIComponent(this.route.snapshot.queryParams["keywords"]);
41 | this.searchModel.sTime =this.route.snapshot.queryParams["sTime"]==undefined?new Date(new Date().setDate(new Date().getDate() - 1)):this.route.snapshot.queryParams["sTime"];
42 | this.searchModel.eTime =this.route.snapshot.queryParams["sTime"]==undefined? new Date():new Date(new Date(this.route.snapshot.queryParams["sTime"]).setSeconds(new Date(this.route.snapshot.queryParams["sTime"]).getSeconds() +1));
43 | this.searchModel.appKey = this.appKey;
44 | this.searchData(true);
45 | }
46 |
47 | searchModelType(e) {
48 | if (e == 'api') {
49 | this.tableScrollSetting = { y: '320px', x: '1500px' };
50 | } else {
51 | this.tableScrollSetting = { y: '320px', x: '1000px' };
52 | }
53 | this.searchModel.type = e;
54 | this.searchData(true);
55 | }
56 |
57 | searchData(reset: boolean = false): void {
58 | if (reset) {
59 | this.searchModel.pageIndex = 1;
60 | }
61 | this.loading = true;
62 | this.searchModel.keywords = this.searchModel.keywords.trim();
63 | this.http.post("Monitor/List", this.searchModel).subscribe((data: any) => {
64 | if (data.IsSuccess) {
65 | this.total = data.Data.TotalCount
66 | this.loading = false;
67 | if (this.searchModel.type == 'js') {
68 | _.each(data.Data.List, (d) => {
69 | d.error = decodeURIComponent(d.error).replace(/\\n/g,"
");
70 | });
71 | }
72 | this.dataSet = data.Data.List;
73 | } else {
74 | this.msg.error("数据加载失败");
75 | }
76 | })
77 | }
78 |
79 | ngAfterContentInit(): void {
80 | this.broadcaster.broadcast('showGlobalTimer', false);
81 | (window as any).globalTime = null;
82 | }
83 |
84 | onOk(data) {
85 | this.searchModel.sTime = data[0];
86 | this.searchModel.eTime = data[1];
87 | }
88 |
89 | errorTrack(data){
90 | const modal= this.modalService.create({
91 | nzTitle: '错误场景还原',
92 | nzMaskClosable:false,
93 | nzWidth:1366,
94 | nzContent: JsErrorTrackComponent,
95 | nzZIndex:3000,
96 | nzBodyStyle:{
97 | padding:0
98 | },
99 | nzComponentParams: {
100 | data:data
101 | },
102 | nzFooter:null
103 | });
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-geo/visit-geo.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
按访问量排行
4 |
5 |
6 | {{item.geo}}
7 |
8 | {{item.count}} ({{item.percent}}%)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 0" [nzSpinning]="isSpinning.spin2">
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 0" [nzSpinning]="isSpinning.spin3">
33 |
34 |
35 |
36 |
37 | JS错误率
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 0" [nzSpinning]="isSpinning.spin4">
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-geo/visit-geo.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/visit-geo/visit-geo.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-os/visit-os.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{item.terminal}}
8 |
9 | {{item.count}}({{(item.percent)}}%)
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{item.terminal}}
19 |
20 | {{item.count}}({{(item.percent)}}%)
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {{item.terminal}}
30 |
31 | {{item.count}}({{(item.percent)}}%)
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 0" [nzSpinning]="isSpinning.spin2">
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 0" [nzSpinning]="isSpinning.spin3">
57 |
58 |
59 |
60 |
61 | JS错误率
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | 0" [nzSpinning]="isSpinning.spin4">
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-os/visit-os.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/visit-os/visit-os.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-page/visit-page.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/visit-page/visit-page.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-speed/visit-speed.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
页面访问速度排行
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 | {{item.page}}
15 |
16 | {{item.avgLoad}}ms
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 0" [nzSpinning]="isSpinning.spin2">
29 |
30 |
31 |
32 |
33 | 0" [nzSpinning]="isSpinning.spin3">
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 0" [nzSpinning]="isSpinning.spin4">
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
66 |
67 |
68 |
69 | 0" [nzSpinning]="isSpinning.spin6">
70 |
73 |
74 |
75 |
76 |
77 | 地域 |
78 | 首次渲染(ms) |
79 |
80 |
81 |
82 |
83 | {{data.name}} |
84 | {{data.fpt}}ms |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | 0" [nzSpinning]="isSpinning.spin7">
102 |
103 |
104 |
105 | 浏览器 |
106 | 首次渲染(ms) |
107 |
108 |
109 |
110 |
111 | {{data.terminal}} |
112 | {{data.speed}}ms |
113 |
114 |
115 |
116 |
117 |
118 |
119 | 0" [nzSpinning]="isSpinning.spin8">
120 |
121 |
122 |
123 | 操作系统 |
124 | 首次渲染(ms) |
125 |
126 |
127 |
128 |
129 | {{data.terminal}} |
130 | {{data.speed}}ms |
131 |
132 |
133 |
134 |
135 |
136 |
137 | 0" [nzSpinning]="isSpinning.spin9">
138 |
139 |
140 |
141 | 分辨率 |
142 | 首次渲染(ms) |
143 |
144 |
145 |
146 |
147 | {{data.terminal}} |
148 | {{data.speed}}ms |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/visit-speed/visit-speed.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/app/web/web-sys/visit-speed/visit-speed.component.scss
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/web-sys.component.html:
--------------------------------------------------------------------------------
1 |
9 |
13 | << 返回系统列表
14 |
15 |
38 |
39 |
40 |
关注也是情,说出您的想法!
41 |
42 |

43 |
44 |
45 |
一分也是爱,期望您的支持!
46 |
47 |
微信:
48 |

49 |
50 |
51 |
支付宝:
52 |

53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | -
61 |
62 |
63 | {{item.label}}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/web-sys.component.scss:
--------------------------------------------------------------------------------
1 | .ant-layout-header{
2 | padding: 0 !important;
3 | }
4 |
5 | .ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left{
6 | border-right: none !important;
7 | }
8 |
9 | .left-nav-container{
10 | position: fixed;
11 | left: 0;
12 | overflow-y: auto;
13 | overflow-x: hidden;
14 | // border-right: solid 1px #eee;
15 | background:#EAEDF1;
16 | }
17 |
18 | .ant-menu-dark.ant-menu-horizontal{
19 | border-bottom-color: #373d41;
20 | }
21 |
22 | .ant-menu-dark, .ant-menu-dark .ant-menu-sub{
23 | background: #373d41;
24 | }
25 | .ant-menu.ant-menu-root.ant-menu-light.ant-menu-inline{
26 | background:#EAEDF1;
27 | color: #333;
28 | li.ant-menu-item{
29 | margin: 0;
30 | font-size: 12px;
31 | &:hover{
32 | color: #333;
33 | background: #F4F6F8;
34 | }
35 | &:after{
36 | border-right: 3px solid #fff;
37 | }
38 | }
39 | .ant-menu-item-selected{
40 | background-color: #fff;
41 | color: #333;
42 | &:after{
43 | border-right: 1px solid #fff;
44 | margin-right: -1px;
45 | }
46 | }
47 | }
48 |
49 | .ant-layout-header{
50 | height: 50px;
51 | line-height: 50px;
52 | }
53 |
54 | .sys-header{
55 | margin-left: 200px;height: 70px;background:#fff;
56 | line-height: 70px;border-bottom: solid 2px #e6ebef;
57 | border-left: solid 16px #fff;
58 | border-right: solid 16px #fff;margin-top:50px;border-radius: 24px;
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/web/src/app/web/web-sys/web-sys.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { fromEvent as observableFromEvent, Observable } from 'rxjs';
3 |
4 | import { debounceTime } from 'rxjs/operators';
5 | import { Broadcaster } from './../../monitor.common.service';
6 | import { ActivatedRoute, Router } from '@angular/router';
7 | import { HttpClient } from '@angular/common/http';
8 | import { UrlNav } from './../../model/entity';
9 |
10 | import { Component, ElementRef, ViewChild, Renderer2 } from '@angular/core';
11 |
12 |
13 | import { NzMessageService } from 'ng-zorro-antd';
14 | declare var window: any
15 | @Component({
16 | selector: 'app-web-sys',
17 | templateUrl: './web-sys.component.html',
18 | styleUrls: ['./web-sys.component.scss']
19 | })
20 | export class WebSysComponent {
21 | @ViewChild('centerContent') centerContent: ElementRef
22 | @ViewChild('leftNav') leftNav: ElementRef
23 | leftNavItems: Array = [];
24 | sysItems: Array = [];
25 | currentSelectedSys;
26 | showGlobalTimer = true;
27 | isFullScreen: boolean = false;
28 | unsubscribe = {
29 | sub0: null,
30 | sub1: null
31 | };
32 | constructor(
33 | private render: Renderer2,
34 | private http: HttpClient,
35 | private msg: NzMessageService,
36 | private route: ActivatedRoute,
37 | private router: Router,
38 | private broadcaster: Broadcaster
39 | ) {
40 | this.leftNavItems = [{
41 | label: '应用总览',
42 | value: 'index',
43 | isActive: false,
44 | icon: 'laptop'
45 | }, {
46 | label: '访问页面',
47 | value: 'visitPage',
48 | isActive: false,
49 | icon: 'switcher'
50 | }, {
51 | label: '访问速度',
52 | value: 'visitSpeed',
53 | isActive: false,
54 | icon: 'line-chart'
55 | },
56 | {
57 | label: 'JS错误',
58 | value: 'jsError',
59 | isActive: false,
60 | icon: 'exception'
61 | },
62 | {
63 | label: 'API请求',
64 | value: 'apiReq',
65 | isActive: false,
66 | icon: 'api'
67 | }, {
68 | label: '地理',
69 | value: 'visitGeo',
70 | isActive: false,
71 | icon: 'global'
72 | }, {
73 | label: '终端',
74 | value: 'visitOs',
75 | isActive: false,
76 | icon: 'ie'
77 | }, {
78 | label: '用户行为追踪',
79 | value: 'userPath',
80 | isActive: false,
81 | icon: 'radius-setting'
82 | }, {
83 | label: '资源加载详情',
84 | value: 'resourceDetails',
85 | isActive: false,
86 | icon: 'radar-chart'
87 | }, {
88 | label: '访问明细',
89 | value: 'visitDetails',
90 | isActive: false,
91 | icon: 'file'
92 | }, {
93 | label: '应用设置',
94 | value: 'setting',
95 | isActive: false,
96 | icon: 'setting'
97 | }];
98 | }
99 | ngOnInit(): void {
100 | this.currentSelectedSys = this.route.snapshot.paramMap.get('appKey');
101 | this.getSites();
102 | this.unsubscribe.sub0 = observableFromEvent(window, "resize").pipe(
103 | debounceTime(100))
104 | .subscribe((event) => {
105 | this._resizePageHeight();
106 | });
107 | this.unsubscribe.sub0 = observableFromEvent(document, "fullscreenchange")
108 | .subscribe((event) => {
109 | this.isFullScreen = this.isfull();
110 | });
111 | let temp2 = observableFromEvent(document, "mozfullscreenchange")
112 | .subscribe((event) => {
113 | this.isFullScreen = this.isfull();
114 | });
115 | this.unsubscribe.sub0.add(temp2);
116 | let temp3 = observableFromEvent(document, "webkitfullscreenchange")
117 | .subscribe((event) => {
118 | this.isFullScreen = this.isfull();
119 | });
120 | this.unsubscribe.sub0.add(temp3);
121 | let temp4 = observableFromEvent(document, "msfullscreenchange")
122 | .subscribe((event) => {
123 | this.isFullScreen = this.isfull();
124 | });
125 |
126 | }
127 | ngAfterViewInit() {
128 | this.unsubscribe.sub1 = this.broadcaster.on("showGlobalTimer").subscribe((r: boolean) => {
129 | let temp = setTimeout(() => {
130 | this.showGlobalTimer = r;
131 | clearTimeout(temp);
132 | });
133 | });
134 | this._resizePageHeight();
135 | }
136 |
137 | ngOnDestroy(): void {
138 | this.unsubscribe.sub0.unsubscribe();
139 | this.unsubscribe.sub1.unsubscribe();
140 | }
141 |
142 | selectedSysChange(e) {
143 | this.router.navigate(['/sys/' + e + '/index']);
144 | setTimeout(() => {
145 | location.reload();
146 | });
147 | }
148 |
149 | selectOver(e) {
150 | window.globalTime = e;
151 | this.broadcaster.broadcast("choiceTimeToRender", e);
152 | }
153 |
154 | fullScreen() {
155 | var el: any = document.documentElement;
156 | var rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen;
157 | if (typeof rfs != "undefined" && rfs) {
158 | rfs.call(el);
159 | this.isFullScreen = true;
160 | };
161 | }
162 |
163 | exitFullScreen() {
164 | if (document.exitFullscreen) {
165 | document.exitFullscreen();
166 | this.isFullScreen = false;
167 | }
168 | }
169 |
170 | private _resizePageHeight() {
171 | this.render.setStyle(this.centerContent.nativeElement, "height", window.innerHeight - 50 - 70 + "px");
172 | this.render.setStyle(this.leftNav.nativeElement, "height", window.innerHeight - 50 - 70 + "px");
173 | }
174 |
175 | private getSites() {
176 | this.http.get("Monitor/SiteList").subscribe((d: any) => {
177 | if (d.IsSuccess) {
178 | this.sysItems = d.Data;
179 | } else {
180 | this.msg.error("系统列表加载失败");
181 | }
182 | })
183 | }
184 | private isfull() {
185 | return (
186 | (document as any).fullscreen ||
187 | (document as any).fullscreenElement ||
188 | (document as any).mozFullScreenElement ||
189 | (document as any).webkitFullscreenElement ||
190 | null);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/web/src/app/web/web.module.ts:
--------------------------------------------------------------------------------
1 |
2 | import { BackendLogComponent } from './web-sys/backend-log/backend-log.component';
3 | import { PublicModule } from './../public/public.module';
4 |
5 | import { NgModule } from '@angular/core';
6 | import { CommonModule } from '@angular/common';
7 | import { RouterModule, Routes } from '@angular/router';
8 | import { FormsModule } from '@angular/forms';
9 | import { NgZorroAntdModule } from 'ng-zorro-antd';
10 | import { WebSysComponent } from './web-sys/web-sys.component';
11 | import { SysIndexComponent } from './web-sys/sys-index/sys-index.component';
12 | import { SysSettingComponent } from './web-sys/sys-setting/sys-setting.component';
13 | import { VisitPageComponent } from './web-sys/visit-page/visit-page.component';
14 | import { VisitSpeedComponent } from './web-sys/visit-speed/visit-speed.component';
15 | import { JsErrorComponent } from './web-sys/js-error/js-error.component';
16 | import { ApiRequestComponent } from './web-sys/api-request/api-request.component';
17 | import { VisitDetailsComponent } from './web-sys/visit-details/visit-details.component';
18 | import { VisitGeoComponent } from './web-sys/visit-geo/visit-geo.component';
19 | import { VisitOsComponent } from './web-sys/visit-os/visit-os.component';
20 | import { ResourceLoadDetailsComponent } from './web-sys/resource-load-details/resource-load-details.component';
21 | import { UserPathComponent } from './web-sys/user-path/user-path.component';
22 | import { JsErrorTrackComponent } from './web-sys/js-error-track/js-error-track.component';
23 | const routes: Routes = [
24 | {
25 | path: '', component: WebSysComponent, children: [
26 | { path: '', redirectTo: 'index' },
27 | //应用总览
28 | { path: 'index', component: SysIndexComponent },
29 | // 设置
30 | { path: 'setting', component: SysSettingComponent },
31 | // 用户访问路径
32 | { path: 'userPath', component: UserPathComponent },
33 | // 访问页面
34 | { path: 'visitPage', component: VisitPageComponent },
35 | // 访问速度
36 | { path: 'visitSpeed', component: VisitSpeedComponent },
37 | // 访问明细
38 | { path: 'visitDetails', component: VisitDetailsComponent },
39 | // js错误
40 | { path: 'jsError', component: JsErrorComponent },
41 | // api请求
42 | { path: 'apiReq', component: ApiRequestComponent },
43 | // 地理
44 | { path: 'visitGeo', component: VisitGeoComponent },
45 | // 终端
46 | { path: 'visitOs', component: VisitOsComponent },
47 | // 资源加载详情
48 | { path: 'resourceDetails', component: ResourceLoadDetailsComponent },
49 | // 后端日志
50 | { path: 'backendLog', component: BackendLogComponent }
51 | ]
52 | }
53 | ];
54 | @NgModule({
55 | imports: [
56 | CommonModule,
57 | RouterModule,
58 | FormsModule,
59 | NgZorroAntdModule,
60 | RouterModule.forChild(routes),
61 | PublicModule
62 | ],
63 | entryComponents:[JsErrorTrackComponent],
64 | declarations: [BackendLogComponent,WebSysComponent, SysIndexComponent, SysSettingComponent, VisitPageComponent, VisitSpeedComponent, JsErrorComponent, ApiRequestComponent, ResourceLoadDetailsComponent, VisitDetailsComponent, VisitGeoComponent, VisitOsComponent, UserPathComponent, JsErrorTrackComponent]
65 | })
66 | export class WebModule { }
--------------------------------------------------------------------------------
/web/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/.gitkeep
--------------------------------------------------------------------------------
/web/src/assets/images/error404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/error404.png
--------------------------------------------------------------------------------
/web/src/assets/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/github.png
--------------------------------------------------------------------------------
/web/src/assets/images/home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/home.jpg
--------------------------------------------------------------------------------
/web/src/assets/images/icon/SDK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/SDK.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/alarm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/alarm.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/app.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/data.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/m.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/m.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/monitor.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/multi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/multi.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/q.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/q.png
--------------------------------------------------------------------------------
/web/src/assets/images/icon/safe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/icon/safe.png
--------------------------------------------------------------------------------
/web/src/assets/images/weixin-gongzhonghao.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/weixin-gongzhonghao.jpg
--------------------------------------------------------------------------------
/web/src/assets/images/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/weixin.png
--------------------------------------------------------------------------------
/web/src/assets/images/zhifubao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/assets/images/zhifubao.png
--------------------------------------------------------------------------------
/web/src/environments/environment.docker.ts:
--------------------------------------------------------------------------------
1 |
2 | export const environment = {
3 | production: true,
4 | apiUrl:'http://localhost:9000/',
5 | uploadDataUrl:'http://localhost:9000/Up',
6 | jsHackUrl:'http://localhost:9010/bundle.js'
7 | };
--------------------------------------------------------------------------------
/web/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 |
2 | export const environment = {
3 | production: true,
4 | apiUrl:'https://backsite.hubing.online/',
5 | uploadDataUrl:'https://backsite.hubing.online/Up',
6 | jsHackUrl:'https://hubing.online/bundle.js'
7 | };
--------------------------------------------------------------------------------
/web/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | apiUrl: "http://localhost:9000/",
9 | uploadDataUrl: "http://localhost:9000/Up",
10 | jsHackUrl: "http://localhost:8080/bundle.js",
11 | // jsHackUrl: "https://hubing.online/bundle.js",
12 | };
13 |
--------------------------------------------------------------------------------
/web/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kisslove/web-monitoring/770572cbed4687158e4d405e27e055e090ad3424/web/src/favicon.ico
--------------------------------------------------------------------------------
/web/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 前端监控平台
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/web/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic().bootstrapModule(AppModule)
11 | .catch(err => console.log(err));
12 |
--------------------------------------------------------------------------------
/web/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | import 'core-js/es6/symbol';
23 | import 'core-js/es6/object';
24 | import 'core-js/es6/function';
25 | import 'core-js/es6/parse-int';
26 | import 'core-js/es6/parse-float';
27 | import 'core-js/es6/number';
28 | import 'core-js/es6/math';
29 | import 'core-js/es6/string';
30 | import 'core-js/es6/date';
31 | import 'core-js/es6/array';
32 | import 'core-js/es6/regexp';
33 | import 'core-js/es6/map';
34 | import 'core-js/es6/weak-map';
35 | import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Required to support Web Animations `@angular/platform-browser/animations`.
51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 | **/
53 | import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 |
56 |
57 | /***************************************************************************************************
58 | * Zone JS is required by default for Angular itself.
59 | */
60 | import 'zone.js/dist/zone'; // Included with Angular CLI.
61 |
62 |
63 |
64 | /***************************************************************************************************
65 | * APPLICATION IMPORTS
66 | */
67 |
--------------------------------------------------------------------------------
/web/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import 'codemirror/lib/codemirror.css';
3 | @import 'codemirror/theme/monokai.css';
4 | ::-webkit-scrollbar {
5 | height: 6px;
6 | width: 6px;
7 | }
8 |
9 |
10 | ::-webkit-scrollbar-thumb {
11 | background-color: #7777777a;
12 | outline: 1px solid #333;
13 | }
14 |
15 | :focus {
16 | outline: none;
17 | }
18 |
19 | html,body{
20 | min-width: 1000px !important;
21 | }
22 |
23 | .ant-select-selection,
24 | .ant-input,
25 | .ant-popover-inner,
26 | .ant-btn,
27 | input,
28 | .ant-modal-content,
29 | .ant-input-number,
30 | .ant-radio-button-wrapper:first-child,
31 | .ant-radio-button-wrapper:last-child {
32 | border-radius: 0 !important;
33 | }
34 |
35 | .ant-radio-button-wrapper {
36 | margin: 0 4px !important;
37 | }
38 |
39 | .custom-select {
40 | .ant-select-selection {
41 | color: #000;
42 | font-size: 14px;
43 | border: none !important;
44 | .ant-select-arrow {
45 | color: #000;
46 | font-weight: 900;
47 | }
48 | }
49 | &.ant-select-open .ant-select-selection {
50 | border: none !important;
51 | box-shadow: none !important;
52 | }
53 | }
54 |
55 | .ant-card-head-title {
56 | font-size: 12px !important;
57 | }
58 |
59 | .my-list-card {
60 | .ant-card-body {
61 | padding: 0 !important;
62 | }
63 | }
64 |
65 | .ant-card-head {
66 | min-height: 40px !important;
67 | height: 40px !important;
68 | line-height: 20px !important;
69 | }
70 |
71 | .ant-card {
72 | min-height: 200px !important;
73 | }
74 |
75 | .custom-default-list {
76 | background-color: #f6fafe;
77 | cursor: pointer;
78 | height: 40px;
79 | margin: 4px 0;
80 | padding: 8px;
81 | .left{
82 | overflow: hidden;
83 | text-overflow: ellipsis;
84 | display: inline-block;
85 | // word-break: break-all;
86 | height: 20px;
87 | }
88 | .right {
89 | position: absolute;
90 | right: 0;
91 |
92 | }
93 | &.selected {
94 | background-color: #40a9ff;
95 | color: #fff;
96 | cursor: default;
97 | }
98 | &:hover:not(.selected) {
99 | background-color: #E6F9FC;
100 | }
101 | }
102 |
103 | .my-ellipsis{
104 | text-overflow: ellipsis;
105 | overflow: hidden;
106 | display: inline-block;
107 | height: 20px;
108 | word-break: break-all;
109 | }
110 |
111 | .ant-table-thead > tr > th, .ant-table-tbody > tr > td{
112 | padding: 8px !important;
113 | }
114 |
115 | .ant-tabs-nav .ant-tabs-tab{
116 | margin: 0 0 0 0 !important;
117 | }
118 |
119 | .ant-list-item-content{
120 | text-overflow: ellipsis;
121 | overflow: hidden;
122 | word-break: break-all;
123 | }
124 |
125 | .ant-layout-header,.ant-menu-dark, .ant-menu-dark .ant-menu-sub {
126 | color: rgba(255, 255, 255, 0.65);
127 | background: #096dd9 !important;
128 | }
129 |
130 | .CodeMirror{
131 | height: 220px;
132 | }
133 |
134 | .custom-card{
135 | display: inline-block !important;
136 | vertical-align: top;
137 | margin: 16px !important;
138 | box-shadow: 0 0 20px 0 rgb(223,229,232);
139 | transition: all .3s;
140 | transform: translateY(0);
141 | border: none !important;
142 | &:hover{
143 | box-shadow: 0 10px 10px #aaa;
144 | transform: translateY(-8px);
145 | }
146 | }
--------------------------------------------------------------------------------
/web/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/web/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/web/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "types": [
8 | "jasmine",
9 | "node"
10 | ]
11 | },
12 | "files": [
13 | "test.ts",
14 | "polyfills.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/web/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "dom"
17 | ],
18 | "module": "es2015",
19 | "baseUrl": "./"
20 | }
21 | }
--------------------------------------------------------------------------------
/web/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | true,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-shadowed-variable": true,
69 | "no-string-literal": false,
70 | "no-string-throw": true,
71 | "no-switch-case-fall-through": true,
72 | "no-trailing-whitespace": true,
73 | "no-unnecessary-initializer": true,
74 | "no-unused-expression": true,
75 | "no-use-before-declare": true,
76 | "no-var-keyword": true,
77 | "object-literal-sort-keys": false,
78 | "one-line": [
79 | true,
80 | "check-open-brace",
81 | "check-catch",
82 | "check-else",
83 | "check-whitespace"
84 | ],
85 | "prefer-const": true,
86 | "quotemark": [
87 | true,
88 | "single"
89 | ],
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always"
94 | ],
95 | "triple-equals": [
96 | true,
97 | "allow-null-check"
98 | ],
99 | "typedef-whitespace": [
100 | true,
101 | {
102 | "call-signature": "nospace",
103 | "index-signature": "nospace",
104 | "parameter": "nospace",
105 | "property-declaration": "nospace",
106 | "variable-declaration": "nospace"
107 | }
108 | ],
109 | "unified-signatures": true,
110 | "variable-name": false,
111 | "whitespace": [
112 | true,
113 | "check-branch",
114 | "check-decl",
115 | "check-operator",
116 | "check-separator",
117 | "check-type"
118 | ],
119 | "directive-selector": [
120 | true,
121 | "attribute",
122 | "app",
123 | "camelCase"
124 | ],
125 | "component-selector": [
126 | true,
127 | "element",
128 | "app",
129 | "kebab-case"
130 | ],
131 | "no-output-on-prefix": true,
132 | "use-input-property-decorator": true,
133 | "use-output-property-decorator": true,
134 | "use-host-property-decorator": true,
135 | "no-input-rename": true,
136 | "no-output-rename": true,
137 | "use-life-cycle-interface": true,
138 | "use-pipe-transform-interface": true,
139 | "component-class-suffix": true,
140 | "directive-class-suffix": true
141 | }
142 | }
143 |
--------------------------------------------------------------------------------