├── src
└── app
│ ├── components
│ ├── .gitkeep
│ ├── Footer
│ │ ├── style.css
│ │ └── index.tsx
│ ├── Title
│ │ ├── style.css
│ │ └── index.tsx
│ ├── ContentHeader
│ │ ├── style.css
│ │ └── index.tsx
│ ├── Detail
│ │ ├── style.css
│ │ └── index.tsx
│ └── Echart
│ │ ├── SmoothedLine
│ │ └── index.tsx
│ │ ├── PvUvLine
│ │ └── index.tsx
│ │ ├── Bar
│ │ └── index.tsx
│ │ ├── DoughnutPie
│ │ └── index.tsx
│ │ └── ChinaMap
│ │ └── index.tsx
│ ├── constants
│ └── .gitkeep
│ ├── containers
│ ├── Modules
│ │ ├── Error
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── PageMsg
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── User
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── Referrer
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── System
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── AssetsMsg
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── RealTime
│ │ │ ├── index.css
│ │ │ └── overView.tsx
│ │ ├── WebPerformance
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── Home
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ └── BaseMsg
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ ├── Website
│ │ ├── index.css
│ │ └── index.tsx
│ ├── Root
│ │ └── index.tsx
│ ├── Auth
│ │ ├── style.css
│ │ ├── signIn.tsx
│ │ └── signUp.tsx
│ ├── Container
│ │ └── index.tsx
│ ├── View2.tsx
│ ├── View.tsx
│ └── App
│ │ └── index.tsx
│ ├── models
│ └── index.ts
│ ├── index.css
│ ├── api
│ ├── realtime.ts
│ ├── pages.ts
│ ├── assets.ts
│ ├── commonData.ts
│ ├── auth.ts
│ ├── axios.ts
│ └── host.ts
│ ├── layout
│ ├── Header
│ │ ├── style.css
│ │ └── index.tsx
│ └── Sider
│ │ └── index.tsx
│ ├── stores
│ ├── TokenStore.ts
│ ├── TimerStore.ts
│ ├── RouterStore.ts
│ ├── PageStore.ts
│ ├── index.ts
│ ├── RealTimeStore.ts
│ ├── OverViewStore.ts
│ ├── AuthStore.ts
│ ├── HostStore.ts
│ ├── AssetsStore.ts
│ └── CommonDataStore.ts
│ ├── config
│ └── path.ts
│ ├── index.tsx
│ ├── reset.css
│ ├── interfaces
│ └── index.ts
│ ├── registerServiceWorker.ts
│ └── macarons.js
├── public
├── favicon.ico
├── images
│ └── tinylog.png
├── manifest.json
└── index.html
├── docs
└── 20180505210245.png
├── tsconfig.test.json
├── config
├── jest
│ ├── typescriptTransform.js
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── .gitignore
├── .circleci
└── config.yml
├── nginx-site.conf
├── scripts
├── test.js
├── start.js
└── build.js
├── tsconfig.json
├── Dockerfile
├── tslint.json
├── package.json
└── README.md
/src/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/constants/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/containers/Modules/Error/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/containers/Modules/PageMsg/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/containers/Modules/PageMsg/index.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinylog/tinylog-ui/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/docs/20180505210245.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinylog/tinylog-ui/HEAD/docs/20180505210245.png
--------------------------------------------------------------------------------
/public/images/tinylog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinylog/tinylog-ui/HEAD/public/images/tinylog.png
--------------------------------------------------------------------------------
/src/app/models/index.ts:
--------------------------------------------------------------------------------
1 | export interface IUser {
2 | email: string,
3 | token: string,
4 | id: string
5 | }
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------
/src/app/index.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 | @import './reset.css';
3 |
4 | #root, #root > div {
5 | height: 100%;
6 | }
--------------------------------------------------------------------------------
/src/app/components/Footer/style.css:
--------------------------------------------------------------------------------
1 | .tl-footer {
2 | text-align: center;
3 | font-size: 12px;
4 | color: rgba(0, 0, 0, 0.65);
5 | }
--------------------------------------------------------------------------------
/src/app/components/Title/style.css:
--------------------------------------------------------------------------------
1 | h3 {
2 | color: #1890ff;
3 | font-size: 15px;
4 | padding: 0 4px;
5 | padding-bottom: 8px;
6 | }
--------------------------------------------------------------------------------
/src/app/containers/Website/index.css:
--------------------------------------------------------------------------------
1 | .website-page {
2 | padding: 40px;
3 | }
4 |
5 | .action-panel {
6 | margin-bottom: 16px;
7 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/User/index.css:
--------------------------------------------------------------------------------
1 | .user-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/Referrer/index.css:
--------------------------------------------------------------------------------
1 | .referrer-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/System/index.css:
--------------------------------------------------------------------------------
1 | .system-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/AssetsMsg/index.css:
--------------------------------------------------------------------------------
1 | .assets-msg-data-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/RealTime/index.css:
--------------------------------------------------------------------------------
1 | .realtime-msg-data-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/WebPerformance/index.css:
--------------------------------------------------------------------------------
1 | .web-performance-data-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px 0;
5 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/Home/index.css:
--------------------------------------------------------------------------------
1 | .home-logo {
2 | width: 600px;
3 | height: 200px;
4 | background: url(/images/tinylog.png) no-repeat;
5 | background-size: 100% 100%;
6 | margin: 150px auto;
7 | }
--------------------------------------------------------------------------------
/config/jest/typescriptTransform.js:
--------------------------------------------------------------------------------
1 | // Copyright 2004-present Facebook. All Rights Reserved.
2 |
3 | 'use strict';
4 |
5 | const tsJestPreprocessor = require('ts-jest/preprocessor');
6 |
7 | module.exports = tsJestPreprocessor;
8 |
--------------------------------------------------------------------------------
/src/app/containers/Modules/BaseMsg/index.css:
--------------------------------------------------------------------------------
1 | .base-msg-data-cont {
2 | display: flex;
3 | justify-content: space-around;
4 | padding-bottom: 10px;
5 | border-bottom: 1px solid #e8e8e8;
6 | margin-bottom: 10px;
7 | border-bottom: 1px solid #e8e8e8;
8 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './index.css';
3 |
4 | class Home extends React.Component<{}, {}> {
5 | render () {
6 | return (
7 |
8 | )
9 | }
10 | }
11 |
12 | export default Home;
--------------------------------------------------------------------------------
/src/app/components/Title/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './style.css';
3 |
4 | class Title extends React.Component<{title: string}, {}> {
5 | render () {
6 | return (
7 | {this.props.title}
8 | );
9 | }
10 | }
11 |
12 | export default Title;
--------------------------------------------------------------------------------
/src/app/components/ContentHeader/style.css:
--------------------------------------------------------------------------------
1 | .content-header {
2 | margin: 0 16px;
3 | margin-top: 16px;
4 | padding: 0 16px;
5 | background: rgb(255, 255, 255);
6 | }
7 |
8 | .content-header h3 {
9 | height: 52px;
10 | font-size: 18px;
11 | font-weight: bold;
12 | line-height: 50px;
13 | }
--------------------------------------------------------------------------------
/src/app/api/realtime.ts:
--------------------------------------------------------------------------------
1 | import rest from './axios';
2 | import { IRealTimeQuery } from '../interfaces';
3 |
4 | const getOverViewRealTime = (query: IRealTimeQuery) => {
5 | return rest.request({
6 | method: 'get',
7 | url: `/realtime/${query.id}/overview`
8 | })
9 | }
10 |
11 | export default {
12 | getOverViewRealTime
13 | }
--------------------------------------------------------------------------------
/src/app/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './style.css';
3 |
4 | class Footer extends React.Component<{}, {}> {
5 | render () {
6 | return (
7 |
10 | );
11 | }
12 | }
13 |
14 | export default Footer;
--------------------------------------------------------------------------------
/src/app/layout/Header/style.css:
--------------------------------------------------------------------------------
1 | .header ul li:last-child {
2 | position: absolute;
3 | right: 0;
4 | }
5 |
6 | .header .logo {
7 | width: 120px;
8 | height: 31px;
9 | margin: 16px 28px 16px 0;
10 | color: #fff;
11 | text-align: center;
12 | font-size: 15px;
13 | line-height: 31px;
14 | background: #e6f7ff34;
15 | float: left;
16 | }
--------------------------------------------------------------------------------
/src/app/stores/TokenStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, action } from 'mobx';
2 |
3 | class TokenStore {
4 | @observable token;
5 | constructor () {
6 | this.token = '';
7 | }
8 | @action resetToken () {
9 | this.token = '';
10 | }
11 | @action setToken (token: string) {
12 | this.token = token;
13 | }
14 | }
15 |
16 | export default TokenStore;
--------------------------------------------------------------------------------
/src/app/components/ContentHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './style.css';
3 |
4 | class ContentHeader extends React.Component<{ title: string }, {}> {
5 | render () {
6 | return (
7 |
8 | {this.props.title}
9 |
10 | );
11 | }
12 | }
13 |
14 | export default ContentHeader;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/src/app/api/pages.ts:
--------------------------------------------------------------------------------
1 | import rest from './axios';
2 | import { IPageQuery } from '../interfaces';
3 |
4 | const getPages = (query: IPageQuery) => {
5 | return rest.request({
6 | method: 'get',
7 | url: `/host/${query.id}/pages/slow`,
8 | params: {
9 | from: query.from,
10 | to: query.to
11 | }
12 | })
13 | }
14 |
15 | export default {
16 | getPages
17 | }
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/app/api/assets.ts:
--------------------------------------------------------------------------------
1 | import rest from './axios';
2 | import { IAssetsQuery } from '../interfaces';
3 |
4 | const getAssetsSlow = (query: IAssetsQuery) => {
5 | return rest.request({
6 | method: 'get',
7 | url: `/host/${query.id}/assets/slow`,
8 | params: {
9 | from: query.from,
10 | to: query.to
11 | }
12 | })
13 | }
14 |
15 | export default {
16 | getAssetsSlow
17 | }
--------------------------------------------------------------------------------
/src/app/stores/TimerStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, action } from 'mobx';
2 |
3 | class TimerStore {
4 | @observable timer;
5 | constructor () {
6 | this.timer = 0;
7 | }
8 | @action resetTimer () {
9 | this.timer = 0;
10 | }
11 |
12 | @action addTimer () {
13 | this.timer++;
14 | }
15 |
16 | @action subTimer () {
17 | this.timer--;
18 | }
19 | }
20 |
21 | export default TimerStore;
--------------------------------------------------------------------------------
/src/app/api/commonData.ts:
--------------------------------------------------------------------------------
1 | import rest from './axios';
2 | import { ICommonDataQuery } from './../interfaces/index';
3 |
4 | const getCommonDataQuery = (query: ICommonDataQuery) => {
5 | return rest.request({
6 | method: 'get',
7 | url: `/host/${query.id}/distribution/${query.type}`,
8 | params: {
9 | from: query.from,
10 | to: query.to
11 | }
12 | })
13 | }
14 |
15 | export default {
16 | getCommonDataQuery
17 | }
--------------------------------------------------------------------------------
/src/app/stores/RouterStore.ts:
--------------------------------------------------------------------------------
1 | import { History } from 'history';
2 | import { RouterStore as BaseRouterStore, syncHistoryWithStore } from 'mobx-react-router';
3 |
4 | // 路由状态同步
5 | class RouterStore extends BaseRouterStore {
6 | public history;
7 | constructor(history?: History) {
8 | super();
9 | if (history) {
10 | this.history = syncHistoryWithStore(history, this);
11 | }
12 | }
13 | }
14 |
15 | export default RouterStore;
--------------------------------------------------------------------------------
/src/app/api/auth.ts:
--------------------------------------------------------------------------------
1 | import { ISignIn, ISignUp } from './../interfaces/index';
2 | import rest from './axios';
3 |
4 | const signIn = (data: ISignIn) => {
5 | return rest.request({
6 | method: 'post',
7 | url: '/user/login',
8 | data
9 | });
10 | };
11 |
12 | const signUp = (data: ISignUp) => {
13 | return rest.request({
14 | method: 'post',
15 | url: '/user/register',
16 | data
17 | });
18 | };
19 |
20 | export default {
21 | signIn,
22 | signUp
23 | }
--------------------------------------------------------------------------------
/src/app/containers/Modules/Error/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './index.css';
3 | import Detail from '../../../components/Detail';
4 |
5 | class Error extends React.Component<{}, {}> {
6 | render () {
7 | const title = '错误信息'
8 | const messages = [
9 | { name: '错误次数', value: 0 }
10 | ]
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
19 | export default Error;
--------------------------------------------------------------------------------
/src/app/containers/Root/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export class Root extends React.Component<{}, {}> {
4 |
5 | renderDevTool() {
6 | if (process.env.NODE_ENV !== 'production') {
7 | const DevTools = require('mobx-react-devtools').default;
8 | return ();
9 | }
10 | return null;
11 | }
12 |
13 | render() {
14 | return (
15 |
16 | {this.props.children}
17 | {this.renderDevTool()}
18 |
19 | );
20 | }
21 | }
--------------------------------------------------------------------------------
/src/app/components/Detail/style.css:
--------------------------------------------------------------------------------
1 | .tl-detail-container {
2 | overflow: hidden;
3 | padding-bottom: 10px;
4 | border-bottom: 1px solid #e8e8e8;
5 | margin-bottom: 10px;
6 | }
7 |
8 | .tl-detail-form {
9 | overflow: hidden;
10 | }
11 |
12 | .tl-detail-container .tl-detail-form p {
13 | float: left;
14 | width: 33.3%;
15 | box-sizing: border-box;
16 | padding: 4px;
17 | }
18 |
19 | .tl-detail-container .tl-detail-form span {
20 | display: inline-block;
21 | min-width: 150px;
22 | padding-right: 4px;
23 | }
--------------------------------------------------------------------------------
/src/app/stores/PageStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, runInAction } from 'mobx';
2 | import { IPageQuery, IPage } from '../interfaces';
3 | import api from '../api/pages';
4 |
5 | class PageStore {
6 | @observable pages: (IPage)[];
7 | constructor () {
8 | this.pages = [];
9 | }
10 | @action async getPages (query: IPageQuery) {
11 | const { data: res } = await api.getPages(query);
12 | runInAction(() => {
13 | this.pages = res.data;
14 | })
15 | return res;
16 | }
17 | }
18 |
19 | export default PageStore;
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | image_config: &image_config
2 |
3 | IMAGE_NAME: tinylog-ui
4 |
5 | IMAGE_TAG: latest
6 |
7 | version: 2
8 | jobs:
9 |
10 | build:
11 | machine: true
12 |
13 | environment:
14 | <<: *image_config
15 |
16 | steps:
17 | - checkout
18 |
19 | - run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
20 |
21 | - run: docker build -t $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_TAG .
22 |
23 | - run: docker push $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_TAG && sleep 10
24 |
25 | - store_artifacts:
26 | path: Dockerfile
27 |
--------------------------------------------------------------------------------
/src/app/containers/Auth/style.css:
--------------------------------------------------------------------------------
1 | .auth {
2 | position: relative;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | width: 100%;
7 | height: 100%;
8 | }
9 |
10 | .auth .login-form {
11 | width: 300px;
12 | }
13 |
14 | .auth .login-form-forgot {
15 | float: right;
16 | }
17 |
18 | .auth .login-form-button {
19 | width: 100%;
20 | }
21 |
22 | .auth-logo {
23 | position: absolute;
24 | width: 400px;
25 | height: 140px;
26 | background: url(/images/tinylog.png) no-repeat;
27 | background-size: 100% 100%;
28 | margin: auto;
29 | top: 100px;
30 | left: 0;
31 | right: 0;
32 | }
--------------------------------------------------------------------------------
/src/app/stores/index.ts:
--------------------------------------------------------------------------------
1 | import TimerStore from './TimerStore';
2 | import RouterStore from './RouterStore';
3 | import TokenStore from './TokenStore';
4 | import AuthStore from './AuthStore';
5 | import HostStore from './HostStore';
6 | import OverViewStore from './OverViewStore';
7 | import AssetsStore from './AssetsStore';
8 | import CommonDataStore from './CommonDataStore'
9 | import PageStore from './PageStore';
10 | import RealTimeStore from './RealTimeStore';
11 |
12 | export {
13 | TimerStore,
14 | RouterStore,
15 | TokenStore,
16 | AuthStore,
17 | HostStore,
18 | OverViewStore,
19 | AssetsStore,
20 | PageStore,
21 | CommonDataStore,
22 | RealTimeStore
23 | };
--------------------------------------------------------------------------------
/src/app/stores/RealTimeStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, runInAction } from 'mobx';
2 | import api from '../api/realtime';
3 | import { IRealTimeQuery, IRealTime } from '../interfaces/index';
4 |
5 | class RealTimeStore {
6 | @observable realtime: IRealTime;
7 | constructor () {
8 | this.realtime = {
9 | count: 0,
10 | referrer: [],
11 | browserName: [],
12 | deviceType: [],
13 | country: []
14 | }
15 | }
16 | @action async getRealTime (query: IRealTimeQuery) {
17 | const { data: res} = await api.getOverViewRealTime(query);
18 | runInAction (() => {
19 | this.realtime = res.data;
20 | })
21 | return res;
22 | }
23 | }
24 |
25 | export default RealTimeStore;
--------------------------------------------------------------------------------
/nginx-site.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80 default_server;
3 | server_name _;
4 | root /usr/share/nginx/html;
5 | index index.html;
6 |
7 | gzip on;
8 | gzip_disable "msie6";
9 |
10 | gzip_vary on;
11 | gzip_proxied any;
12 | gzip_comp_level 6;
13 | gzip_buffers 16 8k;
14 | gzip_http_version 1.1;
15 | gzip_min_length 256;
16 | gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
17 |
18 | location / {
19 | try_files $uri $uri/ /index.html =404;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/containers/Container/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Header from '../../layout/Header';
3 | import { IAuth } from '../../interfaces';
4 | import { Route, Switch } from 'react-router';
5 | import { inject, observer } from 'mobx-react';
6 | import App from '../App';
7 | import Website from '../Website';
8 |
9 | interface Container extends IAuth {
10 | }
11 |
12 | @inject('router', 'auth')
13 | @observer
14 | class Container extends React.Component {
15 | render () {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | export default Container;
--------------------------------------------------------------------------------
/src/app/stores/OverViewStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, runInAction } from 'mobx';
2 | import api from '../api/host';
3 | import { IOverview, IOverviewQuery } from '../interfaces';
4 | import moment from 'moment';
5 |
6 | // 网站的基本数据信息
7 | class OverViewStore {
8 | @observable overviews: (IOverview)[];
9 | constructor () {
10 | this.overviews = []
11 | }
12 | @action async getOverview (query: IOverviewQuery) {
13 | const { data: res } = await api.getOverview(query)
14 | runInAction (() => {
15 | // mobx 的数据被封装过,使用需要slice一下: console.log(this.overviews.slice())
16 | this.overviews = res.data.map(item => {
17 | item.date = moment(item.date).format('YY-MM-DD')
18 | return item
19 | })
20 | })
21 | return res;
22 | }
23 | }
24 |
25 | export default OverViewStore
--------------------------------------------------------------------------------
/src/app/components/Detail/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Title from '../Title';
3 | import './style.css';
4 |
5 | interface IMessages {
6 | name: string;
7 | value: string|number;
8 | }
9 |
10 | interface DetailProps {
11 | title: string;
12 | messages: (IMessages)[];
13 | }
14 |
15 | class Detail extends React.Component {
16 | render () {
17 | const messages = this.props.messages.map((item, index) => {
18 | return (
19 | {item.name}{item.value}
20 | )
21 | })
22 | return (
23 |
24 |
25 |
26 | {messages}
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | export default Detail;
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/dist",
4 | "module": "esnext",
5 | "target": "es5",
6 | "lib": ["es6", "dom"],
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "jsx": "react",
10 | "moduleResolution": "node",
11 | "rootDir": "src",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": true,
15 | "noImplicitAny": false,
16 | "strictNullChecks": true,
17 | "experimentalDecorators": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "allowSyntheticDefaultImports": true
21 | },
22 | "exclude": [
23 | "node_modules",
24 | "build",
25 | "scripts",
26 | "acceptance-tests",
27 | "webpack",
28 | "jest",
29 | "src/setupTests.ts"
30 | ],
31 | "types": [
32 | "typePatches"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { message } from 'antd';
3 |
4 | axios.interceptors.request.use(function (config: any) {
5 | config.baseURL = 'https://tinylog.ruiming.me/api';
6 | config.headers = {
7 | authorization: `Bearer ${localStorage.getItem('token')}`
8 | }
9 | return config;
10 | }, function (error: any) {
11 | return Promise.reject(error);
12 | });
13 |
14 | axios.interceptors.response.use(function (response: any) {
15 | const data = response.data;
16 | response.data = {}
17 | response.data.code = 200;
18 | response.data.data = data;
19 | return response;
20 | }, function (error: any) {
21 | console.dir(error)
22 | const msg = (error.response && error.response.data && error.response.data.message) || '服务器连接异常';
23 | message.info(msg);
24 | return Promise.reject({
25 | code: (error.response && error.response.status) || 500,
26 | msg
27 | });
28 | });
29 |
30 | export default axios;
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/api/host.ts:
--------------------------------------------------------------------------------
1 | import rest from './axios';
2 | import { IOverviewQuery } from '../interfaces'
3 | import { IHostBody } from '../interfaces';
4 |
5 | const getHost = () => {
6 | return rest.request({
7 | method: 'get',
8 | url: '/host'
9 | });
10 | };
11 |
12 | const postHost = (data: IHostBody) => {
13 | return rest.request({
14 | method: 'post',
15 | url: '/host/create',
16 | data
17 | })
18 | }
19 |
20 | const patchHost = (data: IHostBody) => {
21 | return rest.request({
22 | method: 'patch',
23 | url: `/host/${data.id}`
24 | })
25 | }
26 |
27 | const deleteHost = (data: IHostBody) => {
28 | return rest.request({
29 | method: 'delete',
30 | url: '/host',
31 | data: {
32 | list: [data.id]
33 | }
34 | })
35 | }
36 |
37 | const getOverview = (query: IOverviewQuery) => {
38 | return rest.request({
39 | method: 'get',
40 | url: `/host/${query.id}/overview`,
41 | params: {
42 | from: query.from,
43 | to: query.to
44 | }
45 | })
46 | }
47 |
48 | export default {
49 | getHost,
50 | getOverview,
51 | postHost,
52 | deleteHost,
53 | patchHost
54 | }
--------------------------------------------------------------------------------
/src/app/containers/View2.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { TimerStore } from '../stores';
4 | import { RouterStore } from 'mobx-react-router';
5 | import { RouteComponentProps } from 'react-router';
6 | import { Button } from 'antd';
7 | export interface ViewProps extends RouteComponentProps<{}> {
8 | timer: TimerStore;
9 | router: RouterStore;
10 | }
11 |
12 | @inject('timer', 'router')
13 | @observer
14 | class View extends React.Component {
15 | render () {
16 | const { goBack } = this.props.router;
17 | return (
18 |
19 |
Test2
20 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | onReset = () => {
30 | this.props.timer.resetTimer();
31 | }
32 | addTimer = () => {
33 | this.props.timer.addTimer();
34 | }
35 | subTimer = () => {
36 | this.props.timer.subTimer();
37 | }
38 |
39 | }
40 |
41 | export default View;
--------------------------------------------------------------------------------
/src/app/config/path.ts:
--------------------------------------------------------------------------------
1 | export const headerPath = [
2 | {
3 | title: '我的站点',
4 | path: '/website',
5 | },
6 | {
7 | title: '退出',
8 | path: '/signIn'
9 | }
10 | ];
11 |
12 | export const siderPath = [
13 | {
14 | title: '基本信息',
15 | icon: 'global',
16 | menuItems: [{
17 | title: '网站信息',
18 | path: '/base/webMsg'
19 | }]
20 | },
21 | {
22 | title: '实时数据',
23 | icon: 'dashboard',
24 | menuItems: [{
25 | title: '实时预览',
26 | path: '/realtime/webMsg'
27 | }]
28 | },
29 | {
30 | title: '性能分析',
31 | icon: 'laptop',
32 | menuItems: [{
33 | title: '网站性能',
34 | path: '/performance/webMsg'
35 | }, {
36 | title: '资源信息',
37 | path: '/performance/assetsMsg'
38 | }]
39 | },
40 | {
41 | title: '数据分析',
42 | icon: 'bar-chart',
43 | menuItems: [{
44 | title: '来源地址',
45 | path: '/common/referrer'
46 | }, {
47 | title: '环境系统',
48 | path: '/common/system'
49 | }, {
50 | title: '用户分析',
51 | path: '/common/user'
52 | }]
53 | },
54 | {
55 | title: '错误上报',
56 | icon: 'notification',
57 | menuItems: [{
58 | title: '错误概览',
59 | path: '/error'
60 | }]
61 | }
62 | ];
--------------------------------------------------------------------------------
/src/app/containers/Modules/User/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { autobind } from 'core-decorators';
4 | import Detail from '../../../components/Detail';
5 | import Title from '../../../components/Title';
6 | import ChinaMap from '../../../components/Echart/ChinaMap';
7 | import { ICommonDataPage } from '../../../interfaces';
8 |
9 | import './index.css';
10 |
11 | interface UserProps extends ICommonDataPage {
12 | }
13 |
14 | @inject('host', 'router', 'commmon')
15 | @autobind
16 | @observer
17 | class User extends React.Component {
18 | async componentWillMount () {
19 | await this.props.host.getHost();
20 | await this.props.commmon.getCountry({
21 | id: this.props.host.id,
22 | to: new Date().toISOString(),
23 | from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
24 | });
25 | }
26 | render () {
27 | const title = '用户数据展示';
28 | const messages = [
29 | { name: '用户量:', value: 200 },
30 | { name: '', value: '' }
31 | ]
32 | return (
33 |
34 |
35 |
36 |
37 | ({ value: item.count, name: item.value}))}/>
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | export default User;
45 |
--------------------------------------------------------------------------------
/src/app/layout/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Layout, Menu } from 'antd';
3 | import { headerPath } from '../../config/path';
4 | import { autobind } from 'core-decorators';
5 | import { IAuth } from '../../interfaces/index';
6 | import { Link } from 'react-router-dom';
7 | import './style.css';
8 |
9 | const { Header } = Layout;
10 |
11 | interface HeaderLayoutProps extends IAuth {
12 | }
13 |
14 | @autobind
15 | class HeaderLayout extends React.Component {
16 | handleMenuItemClick ({ item, key, keyPath }: any) {
17 | if (headerPath[+key].path === '/signIn') {
18 | this.props.auth.clearStorage();
19 | }
20 | this.props.history.push(headerPath[+key].path);
21 | }
22 | render () {
23 | return (
24 |
25 | Tiny Log
26 |
43 |
44 | );
45 | }
46 | }
47 |
48 | export default HeaderLayout;
--------------------------------------------------------------------------------
/src/app/components/Echart/SmoothedLine/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import echarts from 'echarts';
3 |
4 | export interface ISommthedLine {
5 | width: number;
6 | height: number;
7 | id: string;
8 | title: string;
9 | xValues?: (string|number)[];
10 | yValues?: (string|number)[];
11 | }
12 |
13 | class SommthedLine extends React.Component {
14 | constructor (props: ISommthedLine, state: ISommthedLine) {
15 | super(props, state)
16 | this.state = Object.assign({}, this.props)
17 | }
18 | componentWillReceiveProps (nextProps: ISommthedLine) {
19 | this.setState(nextProps, () => {
20 | this.initChart()
21 | })
22 | }
23 | componentDidMount () {
24 | this.initChart()
25 | }
26 | initChart () {
27 | let chart = echarts.init(document.getElementById(this.state.id), 'macarons');
28 | chart.setOption({
29 | title: {
30 | left: 'center',
31 | top: 'top',
32 | text: this.state.title,
33 | textStyle: {
34 | fontSize: 14
35 | }
36 | },
37 | xAxis: {
38 | type: 'category',
39 | data: this.state.xValues
40 | },
41 | yAxis: {
42 | type: 'value'
43 | },
44 | series: [{
45 | data: this.state.yValues,
46 | type: 'line',
47 | smooth: true
48 | }]
49 | });
50 | }
51 | render () {
52 | return (
53 |
54 | );
55 | }
56 | }
57 |
58 | export default SommthedLine;
--------------------------------------------------------------------------------
/src/app/containers/Modules/Referrer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { autobind } from 'core-decorators';
4 | import Detail from '../../../components/Detail';
5 | import Title from '../../../components/Title';
6 | import { ICommonDataPage } from '../../../interfaces';
7 | import Bar from '../../../components/Echart/Bar';
8 | import './index.css';
9 |
10 | interface ReferrerProps extends ICommonDataPage {
11 | }
12 |
13 | @inject('host', 'router', 'commmon')
14 | @autobind
15 | @observer
16 | class Referrer extends React.Component {
17 | async componentWillMount () {
18 | await this.props.host.getHost();
19 | await this.props.commmon.getReffer({
20 | id: this.props.host.id,
21 | to: new Date().toISOString(),
22 | from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
23 | })
24 | }
25 | render () {
26 | const title = '网站来源地址';
27 | const messages = [
28 | { name: '来源地址总数:', value: this.props.commmon.referrer.slice().length + '个' },
29 | { name: '来源次数最多地址:', value: 'www.baidu.com' }
30 | ]
31 | return (
32 |
33 |
34 |
35 |
36 | item.value)}
42 | yValues={this.props.commmon.referrer.map(item => item.count)}
43 | />
44 |
45 |
46 | )
47 | }
48 | }
49 |
50 | export default Referrer;
51 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | TinyLog Ui
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/app/stores/AuthStore.ts:
--------------------------------------------------------------------------------
1 | import { ISignIn, ISignUp } from './../interfaces/index';
2 | import { observable, action } from 'mobx';
3 | import api from '../api/auth';
4 | import { IUser } from '../models';
5 |
6 | // 登录注册状态
7 | class AuthStore {
8 | @observable token;
9 | @observable id;
10 | @observable email;
11 | constructor () {
12 | this.id = '';
13 | this.token = '';
14 | this.email = '';
15 | }
16 | setLocalStorage ({ id, token, email }: IUser) {
17 | localStorage.setItem('id', id);
18 | localStorage.setItem('token', token);
19 | localStorage.setItem('email', email);
20 | }
21 | clearStorage () {
22 | localStorage.clear();
23 | }
24 | @action async signIn (data: ISignIn) {
25 | try {
26 | const { data: res } = await api.signIn(data);
27 | this.id = res.data.id;
28 | this.token = res.data.token;
29 | this.email = res.data.email;
30 | this.setLocalStorage({
31 | id: this.id,
32 | token: this.token,
33 | email: this.email
34 | });
35 | return res;
36 | } catch (error) {
37 | return error;
38 | }
39 | }
40 |
41 | @action async signUp (data: ISignUp) {
42 | try {
43 | const { data: res } = await api.signUp(data);
44 | this.id = res.data.id;
45 | this.token = res.data.token;
46 | this.email = res.data.email;
47 | this.setLocalStorage({
48 | id: this.id,
49 | token: this.token,
50 | email: this.email
51 | });
52 | return res;
53 | } catch (error) {
54 | return error;
55 | }
56 | }
57 |
58 | @action signOut () {
59 | this.id = '';
60 | this.token = '';
61 | this.email = '';
62 | this.clearStorage()
63 | }
64 | }
65 |
66 | export default AuthStore;
--------------------------------------------------------------------------------
/src/app/containers/View.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { TimerStore } from '../stores';
4 | import { RouterStore } from 'mobx-react-router';
5 | import { RouteComponentProps } from 'react-router';
6 | import { Button } from 'antd';
7 | import echarts from 'echarts';
8 |
9 | export interface ViewProps extends RouteComponentProps<{}> {
10 | timer: TimerStore;
11 | router: RouterStore;
12 | }
13 |
14 | @inject('timer', 'router')
15 | @observer
16 | class View extends React.Component {
17 | componentDidMount () {
18 | let chart = echarts.init(document.getElementById('main'));
19 | chart.setOption({
20 | title: { text: 'ECharts 入门示例' },
21 | tooltip: {},
22 | xAxis: {
23 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
24 | },
25 | yAxis: {},
26 | series: [{
27 | name: '销量',
28 | type: 'bar',
29 | data: [5, 20, 36, 10, 10, 20]
30 | }]
31 | });
32 | }
33 | render () {
34 | const { push } = this.props.router;
35 | return (
36 |
37 |
38 |
Test1
39 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 | onReset = () => {
49 | this.props.timer.resetTimer();
50 | }
51 | addTimer = () => {
52 | this.props.timer.addTimer();
53 | }
54 | subTimer = () => {
55 | this.props.timer.subTimer();
56 | }
57 |
58 | }
59 |
60 | export default View;
--------------------------------------------------------------------------------
/src/app/layout/Sider/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Layout, Menu, Icon } from 'antd';
3 | import { siderPath } from '../../config/path';
4 | import { Link } from 'react-router-dom';
5 | import { autobind } from 'core-decorators';
6 |
7 | const { SubMenu } = Menu;
8 | const { Sider } = Layout;
9 |
10 | interface TitleState {
11 | item: object;
12 | key: string;
13 | }
14 |
15 | interface ISiderLayout {
16 | onMenuItemClick: any;
17 | }
18 |
19 | @autobind
20 | class SiderLayout extends React.Component {
21 | handleMenuItemClick (state: TitleState) {
22 | let arr = state.key.split('-').map((item) => {
23 | return parseInt(item, 10);
24 | });
25 | this.props.onMenuItemClick(siderPath[arr[0]].menuItems[arr[1]].title);
26 | }
27 | render () {
28 | return (
29 |
30 |
55 |
56 | );
57 | }
58 | }
59 |
60 | export default SiderLayout;
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:stretch
2 | MAINTAINER Ruiming Zhuang
3 |
4 | ENV NGINX_VERSION 1.12.1-1~stretch
5 | ENV NJS_VERSION 1.12.1.0.1.10-1~stretch
6 |
7 | RUN apt-get update \
8 | && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 \
9 | && \
10 | NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
11 | found=''; \
12 | for server in \
13 | ha.pool.sks-keyservers.net \
14 | hkp://keyserver.ubuntu.com:80 \
15 | hkp://p80.pool.sks-keyservers.net:80 \
16 | pgp.mit.edu \
17 | ; do \
18 | echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
19 | apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
20 | done; \
21 | test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
22 | apt-get remove --purge -y gnupg1 && apt-get -y --purge autoremove && rm -rf /var/lib/apt/lists/* \
23 | && echo "deb http://nginx.org/packages/debian/ stretch nginx" >> /etc/apt/sources.list \
24 | && apt-get update \
25 | && apt-get install --no-install-recommends --no-install-suggests -y \
26 | nginx=${NGINX_VERSION} \
27 | nginx-module-xslt=${NGINX_VERSION} \
28 | nginx-module-geoip=${NGINX_VERSION} \
29 | nginx-module-image-filter=${NGINX_VERSION} \
30 | nginx-module-njs=${NJS_VERSION} \
31 | gettext-base \
32 | && rm -rf /var/lib/apt/lists/*
33 |
34 | # forward request and error logs to docker log collector
35 | RUN ln -sf /dev/stdout /var/log/nginx/access.log \
36 | && ln -sf /dev/stderr /var/log/nginx/error.log
37 |
38 | COPY package.json /tmp/package.json
39 | COPY package-lock.json /tmp/package-lock.json
40 | RUN cd /tmp && NPM_CONFIG_LOGLEVEL=warn yarn install
41 | COPY . /tmp
42 | RUN cd /tmp && yarn run build
43 | RUN rm -rf /usr/share/nginx/html && mv /tmp/build /usr/share/nginx/html
44 | COPY nginx-site.conf /etc/nginx/conf.d/default.conf
45 |
46 | EXPOSE 80
47 | CMD nginx -g 'daemon off;'
48 |
--------------------------------------------------------------------------------
/src/app/components/Echart/PvUvLine/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import echarts from 'echarts';
3 |
4 | export interface IPvUvLine {
5 | width: number;
6 | height: number;
7 | id: string;
8 | title: string;
9 | xValues?: (string|number)[];
10 | pvValues?: (string|number)[];
11 | uvValues?: (string|number)[];
12 | }
13 |
14 | class PvUvLine extends React.Component {
15 | constructor (props: IPvUvLine, state: IPvUvLine) {
16 | super(props, state)
17 | this.state = Object.assign({}, this.props)
18 | }
19 | componentWillReceiveProps (nextProps: IPvUvLine) {
20 | this.setState(nextProps, () => {
21 | this.initChart()
22 | })
23 | }
24 | componentDidMount () {
25 | this.initChart()
26 | }
27 | initChart () {
28 | let chart = echarts.init(document.getElementById(this.state.id), 'macarons');
29 | chart.setOption({
30 | title: {
31 | left: 'center',
32 | top: 'bottom',
33 | text: this.state.title,
34 | textStyle: {
35 | fontSize: 14
36 | }
37 | },
38 | tooltip: {
39 | trigger: 'none',
40 | axisPointer: {
41 | type: 'cross'
42 | }
43 | },
44 | legend: {
45 | data: ['pv 访问数据', 'uv 访问数据']
46 | },
47 | xAxis: {
48 | type: 'category',
49 | data: this.state.xValues
50 | },
51 | yAxis: {
52 | type: 'value'
53 | },
54 | series: [{
55 | name: 'pv 访问数据',
56 | data: this.state.pvValues,
57 | type: 'line',
58 | smooth: true
59 | }, {
60 | name: 'uv 访问数据',
61 | data: this.state.uvValues,
62 | type: 'line',
63 | smooth: true
64 | }]
65 | });
66 | }
67 | render () {
68 | return (
69 |
70 | );
71 | }
72 | }
73 |
74 | export default PvUvLine;
--------------------------------------------------------------------------------
/src/app/components/Echart/Bar/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import echarts from 'echarts';
3 |
4 | interface IBar {
5 | width: number;
6 | height: number;
7 | title: string;
8 | id: string;
9 | xValues: (string|number)[];
10 | yValues: (string|number)[];
11 | }
12 |
13 | class Bar extends React.Component {
14 | constructor (props: IBar, state: IBar) {
15 | super(props, state)
16 | this.state = Object.assign({}, this.props)
17 | }
18 | componentWillReceiveProps (nextProps: IBar) {
19 | this.setState(nextProps, () => {
20 | this.initChart();
21 | })
22 | }
23 | componentDidMount() {
24 | this.initChart();
25 | }
26 | initChart () {
27 | let chart = echarts.init(document.getElementById(this.state.id), 'macarons');
28 | chart.setOption({
29 | title: {
30 | left: 'center',
31 | top: 'top',
32 | text: this.props.title,
33 | textStyle: {
34 | fontSize: 14
35 | }
36 | },
37 | tooltip: {
38 | trigger: 'axis',
39 | axisPointer: {
40 | type: 'shadow'
41 | }
42 | },
43 | grid: {
44 | left: '3%',
45 | right: '4%',
46 | bottom: '3%',
47 | containLabel: true
48 | },
49 | xAxis: [
50 | {
51 | type: 'category',
52 | data: this.state.xValues,
53 | axisTick: {
54 | alignWithLabel: true
55 | }
56 | }
57 | ],
58 | yAxis: [
59 | {
60 | type: 'value'
61 | }
62 | ],
63 | series: [
64 | {
65 | name: this.state.title,
66 | type: 'bar',
67 | barWidth: '60%',
68 | data: this.state.yValues
69 | }
70 | ]
71 | });
72 | }
73 | render() {
74 | return (
75 |
76 | );
77 | }
78 | }
79 |
80 | export default Bar;
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Router, Route, Switch } from 'react-router';
4 | import { createBrowserHistory } from 'history';
5 | import { useStrict } from 'mobx';
6 | import { Provider } from 'mobx-react';
7 | import { RouterStore, syncHistoryWithStore } from 'mobx-react-router';
8 | import {
9 | TokenStore,
10 | AuthStore,
11 | HostStore,
12 | OverViewStore,
13 | AssetsStore,
14 | CommonDataStore,
15 | PageStore,
16 | RealTimeStore
17 | } from './stores';
18 | import registerServiceWorker from './registerServiceWorker';
19 | import { Root } from './containers/Root';
20 | import './index.css';
21 | import Container from './containers/Container';
22 | import SignIn from './containers/Auth/signIn';
23 | import SignUp from './containers/Auth/signUp';
24 | import './macarons';
25 | import 'echarts/map/js/world';
26 |
27 | useStrict(true);
28 |
29 | const browserHistory = createBrowserHistory();
30 | const routerStore = new RouterStore();
31 | const history = syncHistoryWithStore(browserHistory, routerStore);
32 | const rootStore = {
33 | token: new TokenStore(),
34 | auth: new AuthStore(),
35 | host: new HostStore(),
36 | overview: new OverViewStore(),
37 | assets: new AssetsStore(),
38 | commmon: new CommonDataStore(),
39 | page: new PageStore(),
40 | realtime: new RealTimeStore(),
41 | router: routerStore
42 | };
43 |
44 | ReactDOM.render(
45 |
46 |
47 |
48 |
49 |
53 |
57 |
61 |
62 |
63 |
64 | ,
65 | document.getElementById('root') as HTMLElement
66 | );
67 | registerServiceWorker();
68 |
--------------------------------------------------------------------------------
/src/app/containers/Auth/signIn.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { IAuth } from '../../interfaces/index';
4 | import { Form, Icon, Input, Button } from 'antd';
5 | import { Link } from 'react-router-dom';
6 | import { autobind } from 'core-decorators';
7 | import { ISignIn } from '../../interfaces';
8 | const FormItem = Form.Item;
9 | import './style.css';
10 |
11 | interface SignInState extends ISignIn {
12 | }
13 |
14 | @inject('auth', 'router')
15 | @autobind
16 | @observer
17 | class SignIn extends React.Component {
18 | componentWillMount () {
19 | if (localStorage.getItem('token')) {
20 | this.props.history.push('/');
21 | return;
22 | }
23 | }
24 | async handleSubmit (event: any) {
25 | event.preventDefault();
26 | const res = await this.props.auth.signIn({
27 | email: this.state.email,
28 | password: this.state.password
29 | })
30 | if (res.code === 200) {
31 | this.props.history.push('/');
32 | }
33 | }
34 | handleInputEmail (event: any) {
35 | this.setState({
36 | email: event.target.value
37 | });
38 | }
39 | handleInputPass (event: any) {
40 | this.setState({
41 | password: event.target.value
42 | });
43 | }
44 | render () {
45 | return (
46 |
63 | );
64 | }
65 | }
66 |
67 | export default SignIn;
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right