pull/41/head
sunface 5 years ago
parent a74fbe8581
commit 2701a556b0

@ -56,6 +56,7 @@ const (
// connect to cassandra/scyllaDB cluster // connect to cassandra/scyllaDB cluster
func connectDatabase() error { func connectDatabase() error {
c := gocql.NewCluster(misc.Conf.CQL.Cluster...) c := gocql.NewCluster(misc.Conf.CQL.Cluster...)
c.Consistency = gocql.One
c.Timeout = ConnectTimeout * time.Second c.Timeout = ConnectTimeout * time.Second
c.ReconnectInterval = ReconnectInterval * time.Millisecond c.ReconnectInterval = ReconnectInterval * time.Millisecond

@ -0,0 +1,8 @@
# create-react-app does a check on several packages to make sure the
# versions required by CRA are the package versions available:
# https://github.com/facebook/create-react-app/blob/dea19fdb30c2e896ed8ac75b68a612b0b92b2406/packages/react-scripts/scripts/utils/verifyPackageTree.js#L23-L29
# The repo is set up to use yarn workspaces and keeps all
# packages/jaeger-ui packages local to packages/jaeger-ui. But, the
# check CRA does not detect this. So, the following env-var skips the
# check.
SKIP_PREFLIGHT_CHECK=true

@ -1,4 +0,0 @@
ENV = 'development'
# base api
REACT_APP_ADMIN_BASE_API = '/dev-api'

@ -1,4 +0,0 @@
ENV = 'production'
# base api
REACT_APP_ADMIN_BASE_API = '/pro-api'

@ -0,0 +1,2 @@
build
coverage

@ -1,70 +1,98 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
module.exports = { module.exports = {
"env": { env: {
"browser": true, browser: true,
"es6": true jest: true,
jasmine: true,
}, },
"extends": [ settings: {
"eslint:recommended", 'import/resolver': {
"plugin:react/recommended" node: {
], extensions: ['.js', 'json', '.tsx','.jsx','.less'],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"require": true,
"module": true,
"__dirname": true,
"process": true
},
// 解析器用于解析代码
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}, },
"ecmaVersion": 2018, },
"sourceType": "module"
}, },
"plugins": [ extends: ['react-app', 'airbnb', 'prettier', 'prettier/react'],
"react", overrides: [
"react-hooks" {
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: '.',
},
plugins: ['@typescript-eslint'],
rules: {
'no-unused-vars': 0,
'@typescript-eslint/interface-name-prefix': ['error', 'always'],
'@typescript-eslint/no-unused-vars': 1,
},
},
], ],
"rules": { rules: {
// no-var /* general */
'no-var': 'error', 'arrow-parens': [1, 'as-needed'],
// 要求或禁止 var 声明中的初始化 'comma-dangle': 0,
'init-declarations': 2, 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
// 强制使用单引号 'no-continue': 0,
'quotes': ['error', 'single'], 'no-plusplus': 0,
// 要求或禁止使用分号而不是 ASI 'no-self-compare': 0,
'semi': ['error', 'never'], 'no-underscore-dangle': 0,
// 禁止不必要的分号 'prefer-destructuring': 0,
'no-extra-semi': 'error',
// 强制使用一致的换行风格 /* jsx */
'linebreak-style': ['error', 'unix'], 'jsx-a11y/anchor-is-valid': 0,
// 空格4个 'jsx-a11y/click-events-have-key-events': 0,
'indent': ['error', 4, {'SwitchCase': 1}], 'jsx-a11y/href-no-hash': 0,
// 指定数组的元素之间要以空格隔开(,后面) never参数[ 之前和 ] 之后不能带空格always参数[ 之前和 ] 之后必须带空格 'jsx-a11y/interactive-supports-focus': 0,
'array-bracket-spacing': [2, 'never'], 'jsx-a11y/label-has-associated-control': 0,
// 在块级作用域外访问块内定义的变量是否报错提示 'jsx-a11y/label-has-for': 0,
'block-scoped-var': 0, 'jsx-a11y/mouse-events-have-key-events': 0,
// if while function 后面的{必须与if在同一行java风格。 'jsx-a11y/no-static-element-interactions': 1,
'brace-style': [2, '1tbs', {'allowSingleLine': true}],
// 双峰驼命名格式 /* react */
'camelcase': 2, 'react/destructuring-assignment': 0,
// 数组和对象键值对最后一个逗号, never参数不能带末尾的逗号, always参数必须带末尾的逗号 'react/jsx-curly-brace-presence': ['error', 'never'],
'comma-dangle': [2, 'never'], 'react/jsx-filename-extension': 0,
// 控制逗号前后的空格 'react/forbid-prop-types': 1,
'comma-spacing': [2, {'before': false, 'after': true}], 'react/require-default-props': 1,
// 控制逗号在行尾出现还是在行首出现 'react/no-array-index-key': 1,
'comma-style': [2, 'last'], 'react/sort-comp': [
// 圈复杂度 2,
'complexity': [2, 9], {
// 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always order: [
'computed-property-spacing': [2, 'never'], 'type-annotations',
// TODO 关闭 强制方法必须返回值TypeScript强类型不配置 'statics',
// 'consistent-return': 0 'state',
// react-hooks 'propTypes',
"react-hooks/rules-of-hooks": "error", 'static-methods',
"react-hooks/exhaustive-deps": "warn" 'instance-variables',
} 'constructor',
}; 'lifecycle',
'everything-else',
'/^on.+$/',
'render',
],
},
],
/* import */
'import/prefer-default-export': 0,
'import/no-named-default': 0,
'import/extensions': 0,
},
};

30
ui/.gitignore vendored

@ -1,23 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies # dependencies
/node_modules node_modules
/.pnp
.pnp.js
# testing
/coverage
# production # production
/build build
# reports
coverage
.nyc_output
# misc # misc
.DS_Store .DS_Store
.env.local npm-debug.log
.env.development.local .vscode
.env.test.local .idea
.env.production.local yarn-error.log
lerna-debug.log
.eslintcache
npm-debug.log* src/styles/light.json
yarn-debug.log* src/styles/dark.json
yarn-error.log*

@ -0,0 +1 @@
10

@ -1,15 +0,0 @@
## 可用的脚本
在项目目录下,你可以运行:
### `yarn run sm`
在开发模式下运行app.
打开[http://localhost:3000](http://localhost:3000)在浏览器中查看。
在开发模式下运行JSON Server服务.
打开[http://localhost:3023](http://localhost:3023)在浏览器中查看。

@ -1,60 +1,57 @@
const { // Copyright (c) 2017 Uber Technologies, Inc.
override, //
addDecoratorsLegacy, // Licensed under the Apache License, Version 2.0 (the "License");
fixBabelImports, // you may not use this file except in compliance with the License.
addLessLoader, // You may obtain a copy of the License at
addWebpackAlias //
// addWebpackPlugin // http://www.apache.org/licenses/LICENSE-2.0
} = require('customize-cra') //
const themeVariables = require('./src/styles/theme/themeVariables') // Unless required by applicable law or agreed to in writing, software
const AntDesignThemePlugin = require('antd-theme-webpack-plugin') // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* eslint-disable import/no-extraneous-dependencies */
const fs = require('fs');
const { fixBabelImports, addLessLoader, override ,addWebpackPlugin} = require('customize-cra');
const path = require('path') const path = require('path')
const { getLessVars } = require('antd-theme-generator')
const AntDesignThemePlugin = require('antd-theme-webpack-plugin')
const themeVariables = getLessVars(path.join(__dirname, './src/styles/vars.less'))
const defaultVars = getLessVars('./node_modules/antd/lib/style/themes/default.less')
const darkVars = { ...getLessVars('./node_modules/antd/lib/style/themes/dark.less'), '@primary-color': defaultVars['@primary-color'] };
const lightVars = { ...getLessVars('./node_modules/antd/lib/style/themes/compact.less'), '@primary-color': defaultVars['@primary-color'] };
const newDarkVars = {...darkVars, ...themeVariables}
const newLightVars = {...lightVars, ...themeVariables}
fs.writeFileSync('./src/styles/dark.json', JSON.stringify(newDarkVars));
fs.writeFileSync('./src/styles/light.json', JSON.stringify(newLightVars));
const options = { const options = {
antDir: path.join(__dirname, './node_modules/antd'), // antd包位置 antDir: path.join(__dirname, './node_modules/antd'), // antd包位置
stylesDir: path.join(__dirname, './src/styles'), //主题文件所在文件夹 stylesDir: path.join(__dirname, './src'), //主题文件所在文件夹
varFile: path.join(__dirname, './src/styles/vars.less'), // 自定义默认的主题色 varFile: path.join(__dirname, './src/styles/vars.less'), // 自定义默认的主题色
mainLessFile: path.join(__dirname, './src/styles/main.less'), // 项目中其他自定义的样式(如果不需要动态修改其他样式,该文件可以为空) themeVariables: Array.from(new Set([
outputFilePath: path.join(__dirname, './public/color.less'), //提取的less文件输出到什么地方 ...Object.keys(darkVars),
themeVariables: themeVariables, //要改变的主题变量 ...Object.keys(lightVars),
indexFileName: './public/index.html', // index.html所在位置 ...Object.keys(themeVariables),
generateOnce: false // 是否只生成一次 ])),
} generateOnce: false // 是否只生成一次,
const addTheme = () => (config) => {
config.plugins.push(new AntDesignThemePlugin(options))
return config
} }
module.exports = override( module.exports = override(
// 使用 Day.js 替换 momentjs 优化打包大小 fixBabelImports('import', {
// addWebpackPlugin( libraryName: 'antd',
libraryDirectory: 'es',
// ), style: true
// 装饰器语法 }),
addDecoratorsLegacy(), // 主题
// 自动加载antd addWebpackPlugin(new AntDesignThemePlugin(options)),
fixBabelImports('import', { addLessLoader({
libraryName: 'antd', javascriptEnabled: true,
libraryDirectory: 'es', })
style: true
}),
// less
addLessLoader({
javascriptEnabled: true,
localIdentName: '[local]--[hash:base64:5]'
}),
// 路径别名
addWebpackAlias({
'@': path.resolve(__dirname, 'src'),
'@library': path.resolve(__dirname, 'src/library'),
'@layouts': path.resolve(__dirname, 'src/layouts'),
'@utils': path.resolve(__dirname, 'src/library/utils'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@store': path.resolve(__dirname, 'src/store'),
'@styles': path.resolve(__dirname, 'src/styles'),
'@components': path.resolve(__dirname, 'src/components'),
'@http': path.resolve(__dirname, 'src/library/utils/http')
}),
// 主题
addTheme()
) )

@ -1,31 +0,0 @@
[react 文档](https://zh-hans.reactjs.org/)
[Eslint 参考资料](https://blog.csdn.net/walid1992/article/details/54633760)
[mock 文档](http://mockjs.com/)
[json-server 参考资料](https://blog.csdn.net/div_ma/article/details/80579971)
[mockjs 参考资料](https://blog.csdn.net/div_ma/article/details/80592996)
[mock json-server 虚拟数据服务搭建](https://blog.csdn.net/qq_41629150/article/details/99645632)
## 安装依赖
```javascript
npm install json-server -g
yarn global add json-server
```
## 使用 concurrently 并行地运行多个命令
```javascript
npm i concurrently --save
yarn add concurrently
```

@ -1,6 +0,0 @@
module.exports = {
HOST: 'localhost', // 定义ip地址
PORT: '3023', // 定义端口号
DB_FILE: './db.js', // 定义批量模拟数据文件
API: '/api'//创建根api名 这里的 /mock 如同 后端真实/api
}

@ -1,25 +0,0 @@
let Mock = require('mockjs')
let Random = Mock.Random
module.exports = function() {
let data = {
news: []
}
let images = [1, 2, 3].map( x =>Random.image('200x100', Random.color(), Random.word(2, 6)))
for (let i = 0; i < 100; i++) {
let content = Random.cparagraph(0, 10)
data.news.push({
id: i,
title: Random.cword(8, 20),
desc: content.substr(0, 40),
tag: Random.cword(2, 6),
views: Random.integer(100, 5000),
images: images.slice(0, Random.integer(1, 3))
})
}
return data
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

@ -1,85 +0,0 @@
<html>
<head>
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay"
crossorigin="anonymous"
/>
<title>JSON Server</title>
<link rel="shortcut icon" href="favicon.ico"><link href="main.css" rel="stylesheet"></head>
<body>
<header>
<div class="container">
<nav>
<ul>
<li class="title">
JSON Server
</li>
<li>
<a href="https://github.com/users/typicode/sponsorship">
<i class="fas fa-heart"></i>GitHub赞助商
</a>
</li>
<li>
<a href="https://my-json-server.typicode.com">
<i class="fas fa-burn"></i>远程
</a>
</li>
<li>
<a href="https://thanks.typicode.com">
<i class="far fa-laugh"></i>支持者
</a>
</li>
</ul>
</nav>
</div>
</header>
<main>
<div class="container">
<h1>恭喜!</h1>
<p>
您成功运行了JSON服务器
<br />
✧*。٩(ˊᗜˋ*)و✧*。
</p>
<div id="resources"></div>
<p>
要访问和修改资源您可以使用HTTP方法:
</p>
<p>
<code>GET[获取资源]</code>
<code>POST[新建资源]</code>
<code>PUT[全量更新资源]</code>
<code>PATCH[局部更新资源]</code>
<code>DELETE[删除资源]</code>
<code>OPTIONS</code>
</p>
<p>
<a href="http://www.ruanyifeng.com/blog/2014/05/restful_api.html" target="_blank">RESTful API 设计指南</a>
</p>
<div id="custom-routes"></div>
<h1>文档</h1>
<p>
<a href="https://github.com/typicode/json-server">
README
</a>
</p>
</div>
</main>
<footer>
<div class="container">
<p>
若要替换此页,请创建
<code>./public/index.html</code> 文件.
</p>
<p>此页面为已创建覆盖页面</p>
</div>
</footer>
<script type="text/javascript" src="main.js"></script></body>
</html>

@ -1,114 +0,0 @@
body {
display: flex;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
flex-direction: column;
padding: 0;
margin: 0;
color: #3b4252;
letter-spacing: 0;
}
.container {
max-width: 960px;
margin: auto;
padding: 1rem;
}
header {
border-bottom: 1px solid #eee;
}
header a {
color: inherit;
text-decoration: none;
}
header a:hover {
text-decoration: underline;
}
nav ul {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
}
nav li.title {
flex-grow: 5;
text-align: left;
font-weight: bold;
font-size: 1.4rem;
color: #3b4252;
}
nav li {
flex-grow: 1;
align-self: center;
text-align: right;
color: #4c566a;
}
.fa-heart {
color: deeppink;
}
main {
flex: 1;
}
footer {
margin-top: 4rem;
border-top: 1px solid #eee;
}
h1 {
margin-top: 4rem;
font-weight: normal;
}
i {
margin-right: 0.5rem;
}
a {
color: #5e81ac;
}
a:hover {
color: #81a1c1;
text-decoration: underline;
}
table {
margin-left: 0;
}
td {
border: 0;
padding: 0 1em 0.5em 0;
}
td:first-child {
width: 1%;
white-space: nowrap;
}
ul {
list-style-position: inside;
padding-left: 0;
}
li {
list-style-type: none;
margin-bottom: 0.2rem;
}
code {
padding: 0.2rem;
margin: 0rem 0.2rem;
border-radius: 0.2rem;
background: #e5e9f0;
}

File diff suppressed because one or more lines are too long

@ -1,4 +0,0 @@
const API = '/api'
module.exports = {
[`${API}` + '/*'] : '/$1' // 路由请求由/mock/*接管
}

@ -1,86 +0,0 @@
const jsonServer = require('json-server')
let Mock = require('mockjs')
let Random = Mock.Random
const server = jsonServer.create()
const middlewares = jsonServer.defaults()
const rules = require('./routes')
const {HOST, PORT, DB_FILE, API} = require('./config.js')
const DB = require(DB_FILE)
const router = jsonServer.router(DB()) // 将所创建的数据对象传入,以之生成相应的路由
server.use(jsonServer.bodyParser)
server.use(middlewares)
server.post(`${API}/reg`, ({body:{username='', password=''}}, res) => {
(username !== 'admin' && password) ?
res.jsonp({
'err': 0,
'msg': '注册成功',
'data': {
username,
password
}
}) :
res.jsonp({
'err': 1,
'msg': '注册失败'
})
})
// 响应/mock/login,进行登录验证操作
server.post(`${API}/login`, ({body:{username='', password=''}}, res) => {
(username === 'admin' && password === '123456') ?
setTimeout(() => { // 由于本地请求速度较快不方便loading动效显示利用延时器模拟真实服务器请求速度
res.jsonp({
'err': 0,
'msg': '登录成功',
'data': {
'token': '123',
'address': '打破',
'email': 'louis.lyr@outlook.com',
'tel': '15185724613',
'avatar': '' // Random.image('200x200', Random.color(), Random.word(2, 6))
}
})
}, 2000) :
setTimeout(() => { // 由于本地请求速度较快不方便loading动效显示利用延时器模拟真实服务器请求速度
res.jsonp({
'err': 1,
'msg': '登录失败'
})
}, 2000)
})
// 自定义返回内容
router.render = (req, res) => {
let status = ''
let len = Object.keys(res.locals.data).length // 判断是否获取到mockJS模拟的数据
if (res.req.originalMethod === 'DELETE') {
status = len === 0
} else {
status = !!len
}
setTimeout(() => { // 由于本地请求速度较快不方便loading动效显示利用延时器模拟真实服务器请求速度
res.jsonp({ // 使用res.jsonp()方法将mockJS模拟生成的数据进行自定义包装后输出
err: status ? 0 : 1,
msg: '操作' + (status ? '成功' : '失败'),
data: res.locals.data
})
}, 2000)
}
// router.render = (req, res) => {
// console.log(req)
// res.status(500).jsonp({
// error: 'error message here'
// })
// }
server.use(jsonServer.rewriter(rules)) // 根据需要重写路由匹配规则
server.use(router) // 安装路由
server.listen({
host: HOST,
port: PORT
}, function() {
console.log(`JSON Server is running in http://${HOST}:${PORT}`)
})

@ -1,74 +1,162 @@
{ {
"name": "imdev-ui",
"version": "0.1.0",
"private": true, "private": true,
"name": "apm-ui",
"version": "1.9.0",
"main": "src/index.js",
"license": "Apache-2.0",
"homepage": ".",
"workspaces": {
"nohoist": [
"customize-cra",
"customize-cra/**",
"react-scripts",
"react-scripts/**",
"react-app-rewired",
"react-app-rewired/**"
]
},
"devDependencies": {
"@types/match-sorter": "^2.3.0",
"@types/react-window": "^1.8.0",
"@typescript-eslint/eslint-plugin": "1.12.0",
"@typescript-eslint/parser": "1.12.0",
"@typescript-eslint/typescript-estree": "1.12.0",
"babel-eslint": "10.0.1",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.2.0",
"enzyme-to-json": "^3.3.0",
"eslint": "5.14.1",
"eslint-config-airbnb": "17.1.0",
"eslint-config-prettier": "4.0.0",
"eslint-config-react-app": "3.0.7",
"eslint-plugin-flowtype": "^3.4.2",
"eslint-plugin-import": "2.16.0",
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-react": "7.12.4",
"http-proxy-middleware": "^0.19.1",
"husky": "1.3.1",
"jsdom": "13.2.0",
"npm-run-all": "4.1.5",
"prettier": "1.18.2",
"react-test-renderer": "^15.6.1",
"rxjs-compat": "6.4.0",
"sinon": "7.3.2",
"source-map-explorer": "^1.6.0"
},
"dependencies": { "dependencies": {
"@ant-design/dark-theme": "^0.2.2", "@ant-design/icons": "^4.1.0",
"@antv/data-set": "^0.10.2", "@types/classnames": "^2.2.7",
"@antv/g2plot": "^0.11.5", "@types/deep-freeze": "^0.1.1",
"@types/jest": "^24.0.25", "@types/history": "^4.7.2",
"@types/js-cookie": "^2.2.4", "@types/js-cookie": "^2.2.6",
"@types/node": "^13.1.7", "@types/lodash": "^4.14.123",
"@types/qs": "^6.9.0", "@types/memoize-one": "4.1.1",
"@types/react": "^16.9.17", "@types/moment": "^2.13.0",
"@types/react-color": "^3.0.1", "@types/object-hash": "^1.3.0",
"@types/react-dom": "^16.9.4", "@types/react-copy-to-clipboard": "^4.2.6",
"@types/react-responsive": "^8.0.2", "@types/react-grid-layout": "^0.17.1",
"@types/react-router-dom": "^5.1.2", "@types/react-icons": "2.2.7",
"antd": "3.26.6", "@types/react-redux": "^5.0.6",
"antd-dayjs-webpack-plugin": "^0.0.7", "@types/react-router-dom": "^5.1.4",
"axios": "^0.19.0", "@types/react-virtualized-select": "^3.0.7",
"babel-plugin-import": "^1.12.2", "@types/recompose": "^0.30.5",
"bizcharts": "^3.5.6", "@types/redux-actions": "2.2.1",
"antd": "^4.3.0",
"antd-theme-generator": "^1.2.3",
"antd-theme-webpack-plugin": "^1.3.4",
"apm-plexus": "^0.2.0",
"axios": "^0.19.2",
"babel-plugin-import": "1.13.0",
"classnames": "^2.2.5",
"combokeys": "^3.0.0",
"copy-to-clipboard": "^3.1.0",
"customize-cra": "^0.9.1", "customize-cra": "^0.9.1",
"deep-freeze": "^0.0.1",
"drange": "^2.0.0",
"fuzzy": "^0.1.3",
"global": "^4.3.2",
"history": "^4.6.3",
"is-promise": "^2.1.0",
"isomorphic-fetch": "^2.2.1",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"less": "^3.10.3", "json-markup": "^1.1.0",
"less": "^3.11.1",
"less-loader": "^5.0.0", "less-loader": "^5.0.0",
"mobx": "^5.15.0", "less-vars-to-js": "^1.2.1",
"mobx-react": "^6.1.4", "lodash": "^4.17.15",
"react": "^16.12.0", "lru-memoize": "^1.1.0",
"match-sorter": "^3.1.1",
"memoize-one": "^5.0.0",
"mobx": "^5.15.4",
"mobx-react": "^6.2.2",
"moment": "^2.18.1",
"prop-types": "^15.5.10",
"query-string": "^6.3.0",
"raven-js": "^3.22.1",
"react": "^16.3.2",
"react-app-rewired": "^2.1.5", "react-app-rewired": "^2.1.5",
"react-color": "^2.17.3", "react-circular-progressbar": "^2.0.3",
"react-dom": "^16.12.0", "react-color": "^2.14.1",
"react-dimensions": "^1.3.0",
"react-dom": "^16.13.1",
"react-ga": "^2.4.1",
"react-grid-layout": "^0.18.3",
"react-helmet": "^5.1.3",
"react-icons": "2.2.7",
"react-intl": "^3.9.3", "react-intl": "^3.9.3",
"react-loadable": "^5.5.0", "react-redux": "^5.0.6",
"react-responsive": "^8.0.1",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.2.0", "react-router-redux": "5.0.0-alpha.6",
"typescript": "^3.7.4" "react-scripts": "3.4.1",
"react-virtualized-select": "^3.1.0",
"react-vis": "^1.7.2",
"react-vis-force": "^0.3.1",
"react-window": "^1.8.3",
"recompose": "^0.25.0",
"redux": "^3.7.2",
"redux-actions": "^2.2.1",
"redux-form": "^7.0.3",
"redux-promise-middleware": "^4.3.0",
"reselect": "^3.0.1",
"store": "^2.0.12",
"ts-key-enum": "^2.0.0",
"tween-functions": "^1.2.0",
"typescript": "3.5.3",
"u-basscss": "2.0.0"
}, },
"scripts": { "scripts": {
"analyze": "source-map-explorer build/static/js/main.*",
"build": "REACT_APP_VSN_STATE=$(../../scripts/get-tracking-version.js) react-app-rewired build",
"coverage": "yarn run test --coverage",
"start:ga-debug": "REACT_APP_GA_DEBUG=1 REACT_APP_VSN_STATE=$(../../scripts/get-tracking-version.js) react-app-rewired start",
"start": "react-app-rewired start", "start": "react-app-rewired start",
"build": "react-app-rewired build", "test-dev": "react-app-rewired test --env=jsdom",
"test": "react-app-rewired test", "test": "CI=1 react-app-rewired test --env=jsdom --color",
"sm": "concurrently \"yarn start\" \"yarn run mock\" ", "tsc-lint": "tsc --build",
"mock": "cd mocks && node ./server.js" "tsc-lint-debug": "tsc --listFiles",
"prettier": "prettier --write '{.,scripts}/*.{js,json,md,ts,tsx}' 'packages/*/{src,demo/src}/**/!(layout.worker.bundled|react-vis).{css,js,json,md,ts,tsx}' 'packages/*/*.{css,js,json,md,ts,tsx}'",
"prettier-lint": "prettier --list-different '{.,scripts}/*.{js,json,md,ts,tsx}' 'packages/*/{src,demo/src}/**/!(layout.worker.bundled|react-vis).{css,js,json,md,ts,tsx}' 'packages/*/*.{css,js,json,md,ts,tsx}'",
"eslint": "eslint --cache 'scripts/*.{js,ts,tsx}' 'packages/*/src/**/*.{js,ts,tsx}' 'packages/*/*.{js,ts,tsx}'",
"lint": "npm-run-all -ln --parallel prettier-lint tsc-lint eslint check-license"
}, },
"eslintConfig": { "prettier": {
"extends": "react-app" "printWidth": 110,
"proseWrap": "never",
"singleQuote": true,
"trailingComma": "es5"
}, },
"browserslist": { "jest": {
"production": [ "collectCoverageFrom": [
">0.2%", "!src/setup*.js",
"not dead", "!src/utils/DraggableManager/demo/*.tsx",
"not op_mini all" "!src/utils/test/**/*.js",
], "!src/demo/**/*.js"
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
] ]
}, },
"devDependencies": { "browserslist": [
"@babel/plugin-proposal-decorators": "^7.7.4", ">0.5%",
"antd-theme-webpack-plugin": "^1.3.0", "not dead",
"babel-eslint": "^10.0.3", "not ie <= 11",
"concurrently": "^5.0.0", "not op_mini all"
"eslint": "^6.7.2", ]
"eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-hooks": "^2.0.1",
"json-server": "^0.15.1",
"mockjs": "^1.0.1-beta3"
},
"proxy": "http://localhost:3023"
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -1,51 +1,43 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app"/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="apple-touch-icon" href="logo192.png" />
<title>React App</title> <!-- prevent caching of this HTML by any server, Go or otherwise -->
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<!-- NOTE: The document MUST have a <base> element. package.json#homepage is set to "." as part of resolving https://github.com/jaegertracing/jaeger-ui/issues/42 and therefore static assets are linked via relative URLs. This will break on many document URLs, e.g. /trace/abc, unless a valid base URL is provided. The base href defaults to "/" but the query-service can inject an override. -->
<base href="/" data-inject-target="BASE_URL" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>APM UI</title>
</head>
<body>
<link rel="stylesheet/less" type="text/css" href="/color.less" />
<script> <script>
window.less = { window.less = {
async: false, async: false,
env: 'development'//production development env: 'production'
}; };
</script> </script>
</head> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
<body>
<link rel="stylesheet/less" type="text/css" href="/color.less" />
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="apm-ui-root"></div>
<script type="text/javascript" src="/less.min.js"></script>
<!-- <script> <!--
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 This HTML file is a template.
var isOpera = userAgent.indexOf("Opera") > -1; If you open it directly in the browser, you will see an empty page.
console.log(userAgent)
//判断是否Opera浏览器 You can add webfonts, meta tags, or analytics to this file.
if (isOpera) { The build step will place the bundled scripts into the <body> tag.
console.log("Opera");
}; To begin the development, run `npm start` in this folder.
//判断是否Firefox浏览器 To create a production bundle, use `npm run build`.
if (userAgent.indexOf("Firefox") > -1) { -->
console.log("FF");
}
//判断是否Chrome浏览器
if (userAgent.indexOf("Chrome") > -1){
console.log("Chrome");
}
//判断是否Safari浏览器
if (userAgent.indexOf("Safari") > -1) {
console.log("Safari");
}
//判断是否IE浏览器
if (!!window.ActiveXObject || "ActiveXObject" in window) {
console.log("IE");
};
</script> -->
</body> </body>
</html> </html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -1,2 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

@ -1,33 +0,0 @@
@import '../../styles/main.less';
.languages{
display: flex;
&>div{
position: relative;
cursor: pointer;
box-shadow: 0px 0px 4px 1px rgba(220, 223, 225, 0.5);
width: 28px;
height: 28px;
margin-right: 14px;
border-radius: 50%;
transition: all .2s;
}
&>div:hover{
transform: scale(1.3);
}
&>div:last-child{
margin-right: 0;
}
.selected{
position: absolute;
top: 6%;
left: 6%;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #fff;
}
}
.icon{
font-size: 28px;
}

@ -1,32 +0,0 @@
import React from 'react'
import { Icon } from 'antd'
import { inject, observer } from 'mobx-react'
import style from './index.module.less'
import {ISystem} from '../../store/system'
const Languages = inject('system')(observer((props:{system:ISystem} & any) =>{
let {system}:{system:ISystem} = props
const IconFont = Icon.createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1585712_tvew52du1cn.js'
})
function setLocale(locale:string){
system.setLocale(locale)
}
return (
<>
<div className={style.languages}>
<div onClick={()=>{setLocale('en_GB')}}>
<IconFont type="icon-yingguo" className={`${style.icon}` } />
<div className={`${system.locale === 'en_GB'?style.selected:''}`}></div>
</div>
<div onClick={()=>{setLocale('zh_CN')}}>
<IconFont type="icon-china" className={`${style.icon}`} />
<div className={`${system.locale === 'zh_CN'?style.selected:''}`}></div>
</div>
</div>
</>
)
}))
export default Languages

@ -0,0 +1,14 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,63 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// site-prefix.js must be the first import of the main webpack entrypoint
// becaue it configures the webpack publicPath.
/* eslint-disable import/first */
import './site-prefix';
import React from 'react';
import ReactDOM from 'react-dom';
import { document } from 'global';
import { Provider } from 'mobx-react'
// eslint-disable-next-line import/order, import/no-unresolved
import UIApp from './pages/App';
import './styles/main.less'
import 'react-grid-layout/css/styles.css'
// these need to go after the App import
/* eslint-disable import/first */
import 'u-basscss/css/flexbox.css';
import 'u-basscss/css/layout.css';
import 'u-basscss/css/margin.css';
import 'u-basscss/css/padding.css';
import 'u-basscss/css/position.css';
import 'u-basscss/css/typography.css';
const UI_ROOT_ID = 'apm-ui-root';
import './index.css';
import stores from './store'
import ConfigProvider from './components/ConfigProvider'
import Intl from './components/Intl'
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<Provider {...stores}>
<Intl>
<ConfigProvider>
<UIApp />
</ConfigProvider>
</Intl>
</Provider>
, document.getElementById(UI_ROOT_ID));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

@ -1,17 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import * as serviceWorker from './serviceWorker'
import App from './pages/App'
import { BrowserRouter as Router} from 'react-router-dom'
import { Provider } from 'mobx-react'
import stores from './store'
ReactDOM.render(
<Router>
<Provider {...stores}>
<App />
</Provider>
</Router>
, document.getElementById('root'))
serviceWorker.unregister()

@ -1,7 +0,0 @@
@import '../../styles/main.less';
.breadcrumb{
display: flex;
justify-content: space-between;
padding: 6px 20px;
}

@ -1,17 +1,14 @@
import React from 'react' import React from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Breadcrumb } from 'antd' import { Breadcrumb } from 'antd'
import style from './index.module.less'
import { inject, observer } from 'mobx-react'
import { FormattedMessage as Message } from 'react-intl' import { FormattedMessage as Message } from 'react-intl'
import {ISystem} from '../../store/system'
const BreadcrumbWrapper = inject('system')(observer((props:{system:ISystem}) =>{ const BreadcrumbWrapper = () =>{
let location = useLocation() let location = useLocation()
let pathname = location.pathname.split('/') let pathname = location.pathname.split('/')
return ( return (
<> <>
<div className={`${style.breadcrumb}`}> <div>
<div>{<Message id={pathname[pathname.length-1]}/>}</div>
<Breadcrumb separator=">"> <Breadcrumb separator=">">
{ {
pathname.map((item, key)=>{ pathname.map((item, key)=>{
@ -27,6 +24,6 @@ const BreadcrumbWrapper = inject('system')(observer((props:{system:ISystem}) =>{
</div> </div>
</> </>
) )
})) }
export default BreadcrumbWrapper export default BreadcrumbWrapper

@ -1,22 +1,19 @@
import React, { Suspense, useState, useEffect } from 'react' import React, { Suspense, useState, useEffect } from 'react'
import { Layout, BackTop } from 'antd' import { Layout, BackTop } from 'antd'
import BreadcrumbWrapper from '../Breadcrumb'
import { Route } from 'react-router-dom' import { Route } from 'react-router-dom'
import { isEmpty } from '../../library/utils/validate' import { isEmpty } from '../../library/utils/validate'
const { Content } = Layout const { Content } = Layout
// import {
// TransitionGroup,
// CSSTransition
// } from "react-transition-group";
function ContentWrapper(porps){ function ContentWrapper(porps){
let { routers } = porps const { routers } = porps
let [routeItem, setRouteItem] = useState([]) const [routeItem, setRouteItem] = useState([])
useEffect( () => { useEffect( () => {
let item = [] const item = []
routers.map((route) => { routers.map(route => {
if(!isEmpty(route.children)){ if(!isEmpty(route.children)){
route.children.map((r) => { route.children.map(r => {
item.push(r) item.push(r)
return '' return ''
}) })
@ -32,17 +29,17 @@ function ContentWrapper(porps){
}, [routers]) }, [routers])
return( return(
<> <>
<Content> <Content style={{ padding: '6px 20px' ,marginTop: 44 }}>
<Suspense fallback={<div></div>}> <Suspense fallback={<div />}>
<BreadcrumbWrapper />
{ {
routeItem.map((route, key) => { routeItem.map((route, i) => {
return( return(
<Route key={`${key}`} path={route.path} component={route.component}/> <Route key={i.toString()} path={route.path} component={route.component}/>
) )
}) })
} }
{/* <Route path="/home/*" component={Error404} /> */} {/* other url path redirect to home */}
{/* <Redirect to="/dashboard" /> */}
</Suspense> </Suspense>
<BackTop /> <BackTop />
</Content> </Content>

@ -1,28 +0,0 @@
@import '../../styles/main.less';
.drawer{
:global {
.ant-col{
margin-bottom: 20px;
}
}
.row {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
}
.column {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.title {
font-size: 16px;
color: #3b4859;
margin-bottom: 20px;
}
.sub_title {
font-size: 14px;
color: #8998ac;
}
}

@ -1,57 +0,0 @@
import React from 'react'
import { Drawer, Row, Switch } from 'antd'
import { inject, observer } from 'mobx-react'
import style from './index.module.less'
import { CirclePicker,ColorResult } from 'react-color'
import { modifyVars } from '../../library/utils/modifyVars'
import Languages from '../../components/Languages'
import { FormattedMessage as Message } from 'react-intl'
import {ISystem} from '../../store/system'
const DrawerWrapper = inject('system')(observer((props:{system:ISystem} & any) =>{
let {system}:{system:ISystem} = props
let primary = (color:ColorResult)=>{
system.setPrimary(color.hex)
modifyVars(system.dark, color.hex)
// modifyVars({'@primary-color': color.hex})
}
let dark = ()=>{
system.setDark()
modifyVars(system.dark, system.primary)
}
return (
<>
<div>
<Drawer
title={<Message id='setting'/>}
width={380}
onClose={system.setDrawer}
visible={system.drawer}
bodyStyle={{ paddingBottom: 80 }}
className={`${style.drawer} ${system.dark?style.dark:''}`}
>
<Row>
<div className={style.row}>
<div><Message id='model'/></div>
<div><Switch defaultChecked onClick={ dark } /></div>
</div>
<div className={style.row}>
<div><Message id='themes'/></div>
<div>
<CirclePicker color={system.primary} onChangeComplete={ primary }/>
</div>
</div>
<div className={style.row}>
<div><Message id='languages'/></div>
<div>
<Languages />
</div>
</div>
</Row>
</Drawer>
</div>
</>
)
}))
export default DrawerWrapper

@ -0,0 +1,15 @@
.header{
height:100%;
display: flex;
justify-content: space-between;
box-sizing: border-box;
&>div{
display: flex;
align-items: center;
&>div{
display: flex;
align-items: center;
justify-content: center;
}
}
}

@ -1,56 +0,0 @@
@import '../../styles/main.less';
.header{
height: 100%;
display: flex;
justify-content: space-between;
box-sizing: border-box;
&>div{
display: flex;
align-items: center;
&>div{
display: flex;
align-items: center;
justify-content: center;
height: 64px;
width: 50px;
}
}
.menu_icon{
display: block;
font-size: 18px;
}
.icon{
display: block;
font-size: 18px;
}
.plummer{
position: relative;
}
}
.pointer{
cursor: pointer;
}
.messagePopover{
width: 400px;
}
.userPopover{
width: 200px;
display: flex;
flex-direction: column;
&>div{
width: 100%;
height: 48px;
line-height: 48px;
cursor: pointer;
& > i{
margin-right: 6px;
}
&:hover{
&>span, &>i{
color: @dropdown-selected-color;
}
}
}
}

@ -1,86 +1,53 @@
import React from 'react' import React from 'react'
import { Layout, Icon, Badge, Avatar, Popover } from 'antd'
import { useHistory } from 'react-router-dom'
import { inject, observer } from 'mobx-react' import { inject, observer } from 'mobx-react'
import { removeToken } from '../../library/utils/auth' import { ISystem } from '../../store/system'
import style from './index.module.less' import { Layout, DatePicker } from 'antd'
import {IUser} from '../../store/user' import { MenuFoldOutlined } from '@ant-design/icons';
import {ISystem} from '../../store/system' import './index.less'
import {isMobile} from '../../pages/Responsive' import moment from 'moment';
import BreadcrumbWrapper from '../Breadcrumb'
const { Header } = Layout const { Header } = Layout
const HeaderWrapper = inject('system', 'user')(observer((props:{user:IUser,system:ISystem} & any) =>{ function Index(props: { system: ISystem }) {
let history = useHistory() let { system } = props
let {system, user}:{system:ISystem,user:IUser} = props const { RangePicker } = DatePicker;
function changeDate(_: any, dateString: any) {
const onClickLogout = ()=>{ system.setStartDate(dateString[0])
removeToken() system.setEndDate(dateString[1])
history.push('/login')
} }
const userPopover = (
<div className={style.userPopover}>
<div onClick={onClickLogout}><Icon type="logout" /><span>退</span></div>
</div>
)
const messagePopover = (
<div className={style.messagePopover}>
<div>ss</div>
</div>
)
return (
<>
<Header>
{
isMobile() ?
<>dsa</>
:
<div className={style.header}>
<div>
<Icon type={system.collapsed?'menu-unfold':'menu-fold'} className={style.menu_icon} onClick={()=>{system.setCollapsed()}} />
</div>
<div>
<div>
{/* <AutoComplete
className="certain-category-search"
dropdownClassName="certain-category-search-dropdown"
dropdownMatchSelectWidth={false}
dropdownStyle={{ width: 300 }}
size="large"
style={{ width: '100%' }}
placeholder="input here"
optionLabelProp="value"
>
<Input suffix={<Icon type="search" className={`${style.icon}`} />} />
</AutoComplete> */}
</div>
<div>
<Badge dot>
<Popover placement="bottomRight" content={messagePopover}>
<Icon type="bell" className={style.icon} />
</Popover>
</Badge>
</div>
<div> return (
<Popover className={`${style.pointer}`} placement="bottomRight" content={userPopover}> <Header className="site-layout-background" style={{ height: '46px', lineHeight: '46px', padding: '0 20px' ,position: 'fixed', zIndex: 1, width: 'calc(100% - 83px)'}} >
<Badge dot> <div className='header'>
<Avatar icon="user" src={user.avatar}/> <div>
</Badge> <BreadcrumbWrapper />
</Popover> </div>
</div> <div>
<div>
<div> <RangePicker
<Icon type="align-left" className={style.icon} onClick={()=>{system.setDrawer()}} /> className="date-picker"
</div> showTime={{ format: 'HH:mm' }}
format="YYYY-MM-DD HH:mm"
</div> onChange={changeDate}
</div> value={[moment(system.startDate), moment(system.endDate)]}
} ranges={{
</Header> '5m': [moment().subtract(5, 'm'), moment()],
</> '30m': [moment().subtract(30, 'm'), moment()],
'1h': [moment().subtract(1, 'h'), moment()],
'6h': [moment().subtract(6, 'h'), moment()],
'1d': [moment().subtract(1, 'd'), moment()],
'3d': [moment().subtract(3, 'd'), moment()],
'7d': [moment().subtract(7, 'd'), moment()],
}}
/>
</div>
</div>
</div>
</Header>
) )
})) }
let HeaderWrapper = inject('system')(observer(Index))
export default HeaderWrapper export default HeaderWrapper as any

@ -1,33 +0,0 @@
.logo{
width: 100%;
display: flex;
font-size: 18px;
&>div{
display: flex;
align-items: center;
}
.title{
flex: 1;
font-weight:900;
color: #3B4859;
box-sizing: border-box;
padding-left: 19px;
&>span:first-of-type{
color: #3B7CFF;
}
}
.title_hidden{
display: none;
}
.icon{
height: 64px;
width:64px;
justify-content: center;
transform: rotate(180deg);
cursor: pointer;
}
.icon_transition{
transform: rotate(0);
}
}

@ -1,22 +0,0 @@
import React from 'react'
import { Icon } from 'antd'
import style from './index.module.less'
import { inject, observer } from 'mobx-react'
import {ISystem} from '../../store/system'
let Logo = inject('system')(observer((props:{system:ISystem}) => {
let {system} = props
return(
<div className={style.logo}>
{/* ${display?style.title:style.title_hidden} */}
<div>
dsa
</div>
<div onClick={()=>{system.setCollapsed()}} className={`${style.icon} ${system.collapsed?style.icon_transition:''}`}>
<Icon type="menu-unfold" />
</div>
</div>
)
}))
export default Logo

@ -1,70 +0,0 @@
import React from 'react'
import { useLocation } from 'react-router-dom'
import { Menu, Icon } from 'antd'
import { Link } from 'react-router-dom'
import { inject, observer } from 'mobx-react'
import { isEmpty } from '../../library/utils/validate'
import style from './index.module.less'
import { FormattedMessage as Message } from 'react-intl'
const { SubMenu } = Menu
const MenuWrapper = inject('system')(observer((props) =>{
let {system, routers} = props
let location = useLocation()
return (
<>
<div className={style.menu}>
<div className={style.logo}>
</div>
<Menu
mode={`${system.collapsed ? 'vertical' : 'inline'}`}
theme={`${system.theme}`}
selectedKeys={[location.pathname]}
forceSubMenuRender={true}
>
{
routers.map((route) => {
if(isEmpty(route.children)){
return (
<Menu.Item key={`${route.path}`}>
<Link to={route.path}>
<Icon type={route.icon}/>
<span><Message id={route.title}/></span>
</Link>
</Menu.Item>
)
}else{
let items = []
route.children.map((r) => {
items.push(
<Menu.Item key={r.path}>
<Link to={r.path}>
{
isEmpty(r.icon)?'':<Icon type={r.icon}/>
}
<span><Message id={r.title}/></span>
</Link>
</Menu.Item>
)
return ''
})
return (
<SubMenu key={`${route.path}`} title={
<span>
<Icon type={route.icon} />
<span><Message id={route.title}/></span>
</span>
}>
{items}
</SubMenu>
)
}
})
}
</Menu>
</div>
</>
)
}))
export default MenuWrapper

@ -1,7 +0,0 @@
.menu{
.logo{
height: 32px;
background: #aaa;
margin: 16px;
}
}

@ -0,0 +1,116 @@
import React, { useEffect } from 'react'
import { useLocation, Link } from 'react-router-dom'
import { Menu, Layout } from 'antd'
import { inject, observer } from 'mobx-react'
import './index.less'
import { FormattedMessage as Message } from 'react-intl'
import { GlobalOutlined, UserOutlined } from '@ant-design/icons';
import { isEmpty } from '../../library/utils/validate'
import { logout } from '../../library/utils/account';
import { createFromIconfontCN } from '@ant-design/icons';
import darkVars from '../../styles/dark.json';
import lightVars from '../../styles/light.json';
const { Sider } = Layout
const { SubMenu } = Menu
const SiderWrapper = inject('system')(observer(props => {
const { system, routers } = props
useEffect(() => {
let vars = system.theme === "light" ? lightVars : darkVars;
vars = { ...vars, '@white': '#fff', '@black': '#000' };
window.less.modifyVars(vars)
},[system.theme])
const location = useLocation()
const MyIcon = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1402269_m6v7u5uwb2.js',
});
return (
<Sider collapsed={system.collapsed} className="sider" style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
}}>
<div>
<div className="logo" />
<Menu
mode={`${system.collapsed ? 'vertical' : 'inline'}`}
selectedKeys={[location.pathname]}
forceSubMenuRender="true"
className="sider-menu"
theme="dark"
>
{
routers.map(route => {
if (isEmpty(route.children)) {
return (
<Menu.Item key={`${route.path}`}>
<Link to={route.path}>
<route.icon />
<span><Message id={route.title} /></span>
</Link>
</Menu.Item>
)
}
const items = []
route.children.map(r => {
if (r.inMenu !== false) {
items.push(
<Menu.Item key={r.path}>
<Link to={r.path}>
{
// isEmpty(r.icon) ? '' : <r.icon />
}
<span><Message id={r.title} /></span>
</Link>
</Menu.Item>
)
}
return ''
})
return (
<SubMenu key={`${route.path}`} title={
<span>
<route.icon />
<span><Message id={route.title} /></span>
</span>
}>
{items}
</SubMenu>
)
})
}
<Menu.Item key="set-locale1" style={{ position: 'absolute', bottom: '95px', right: '32px' }}>
{
system.theme === 'light' ?
<MyIcon type="icon-moon" onClick={() => {
system.setTheme('dark')
}} /> :
<MyIcon type="icon-sun" onClick={() => {
system.setTheme('light')
}} style={{ fontSize: '18px' }} />
}
<span><Message id='changeTheme' /></span>
</Menu.Item>
<Menu.Item key="set-locale" style={{ position: 'absolute', bottom: '55px', right: '32px', fontSize: '16px' }}>
<GlobalOutlined onClick={() => { system.setLocale() }} />
<span><Message id='languages' /></span>
</Menu.Item>
<SubMenu key="user-setting" icon={<UserOutlined />} style={{ position: 'absolute', bottom: '20px', right: '0px', fontSize: '16px' }}>
<Menu.Item key="logout" onClick={logout}>Logout</Menu.Item>
</SubMenu>
</Menu>
</div>
</Sider>
)
}))
export default SiderWrapper

@ -0,0 +1,11 @@
.sider{
box-shadow: 0 0 5px #404040;
.ant-layout-sider-children {
ul {
border:none;
}
}
.ant-menu-item.ant-menu-item-selected {
background: transparent !important;
}
}

@ -1,4 +0,0 @@
import Layout from '@/layouts/Layout'
export {
Layout
}

@ -0,0 +1,33 @@
import React, { useEffect } from 'react'
import { withRouter, useHistory } from 'react-router-dom'
import { Layout} from 'antd'
import './index.less'
import { getToken } from '../library/utils/auth'
import { isEmpty } from '../library/utils/validate'
import Routers from '../routes'
import SiderWrapper from './Sider'
import HeaderWrapper from './Header'
import ContentWrapper from './Content'
const Index = () => {
const history = useHistory()
useEffect(() => {
if(isEmpty(getToken())){
history.push('/login')
}
return () => { }
})
return (
<Layout className="layouts" style={{ minHeight: '100vh' }}>
<SiderWrapper routers={Routers} />
<Layout style={{ marginLeft: 83 }}>
<HeaderWrapper />
<ContentWrapper routers={Routers} />
</Layout>
</Layout>
)
}
export default withRouter(Index)

@ -1,6 +1,4 @@
@import '../../styles/main.less'; .layouts{
.app{
:global { :global {
.ant-menu-inline{ .ant-menu-inline{
border-right: none; border-right: none;
@ -26,4 +24,9 @@
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) ~"100%/100%" no-repeat; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) ~"100%/100%" no-repeat;
} }
} }
} }
.ant-layout-header.site-layout-background {
background: transparent;
}

@ -12,6 +12,13 @@ const enUS:localeData = {
setting: 'Setting', setting: 'Setting',
themes: 'Themes', themes: 'Themes',
model: 'Model', model: 'Model',
languages: 'Languages' languages: 'Change Language',
ui: 'Home',
trace: 'Distributed Tracing',
traceSearch: 'Trace Search',
traceDetail: 'Trace Detail',
search: 'Search',
detail: 'Detail',
changeTheme: 'Change Theme'
} }
export default enUS export default enUS

@ -12,6 +12,13 @@ const zhCN: localeData = {
setting: '设置', setting: '设置',
themes: '主题', themes: '主题',
model: '模式', model: '模式',
languages: '语言' languages: '切换语言',
ui: '主页',
trace: '分布式链路',
traceSearch: '链路查询',
traceDetail: '链路详情',
search: '查询',
detail: '详情',
changeTheme: '更换主题'
} }
export default zhCN export default zhCN

@ -1,35 +0,0 @@
import React from 'react'
import Element from '../routes/modules/Element'
const Dashboard = React.lazy(() => import('../../pages/Index/Dashboard'))
const Charts = React.lazy(() => import('../../pages/Index/Charts'))
export type Router = {
path: string,
title: string,
icon: string,
component?: any,
children?:any
}
const Routers:Router[] = [
{
path: '/home/dashboard',
title: 'dashboard',
icon: 'dashboard',
component: Dashboard
},
{
path: '/home/charts',
title: 'chart',
icon: 'pie-chart',
component: Charts
},
{
path: '/home/element',
title: 'element',
icon: 'build',
children: Element
}
]
export default Routers

@ -1,19 +0,0 @@
import React from 'react'
const A = React.lazy(() => import('../../../pages/Index/Demo/A'))
const B = React.lazy(() => import('../../../pages/Index/Demo/B'))
const Demo = [
{
path: '/a',
title: 'A',
icon: 'bar-chart',
component: A
},
{
path: '/b',
title: 'B',
icon: 'bar-chart',
component: B
}
]
export default Demo

@ -1,34 +0,0 @@
import React from 'react'
const Accordion = React.lazy(() => import('../../../pages/Index/Elements/Accordion'))
const Paginations = React.lazy(() => import('../../../pages/Index/Elements/Paginations'))
const DatePickers = React.lazy(() => import('../../../pages/Index/Elements/DatePickers'))
const Icons = React.lazy(() => import('../../../pages/Index/Elements/Icons'))
const Demo = [
{
path: '/home/element/accordion',
title: 'accordion',
icon: 'pic-center',
component: Accordion
},
{
path: '/home/element/paginations',
title: 'paginations',
icon: 'pic-center',
component: Paginations
},
{
path: '/home/element/datePickers',
title: 'datePickers',
icon: 'calendar',
component: DatePickers
},
{
path: '/home/element/icons',
title: 'icons',
icon: 'smile',
component: Icons
}
]
export default Demo

@ -0,0 +1,8 @@
import {removeToken } from './auth';
import storage from './localStorage'
export function logout() {
storage.set("lastPath", window.location.pathname+window.location.search)
removeToken()
window.location.href="/login"
}

@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'X-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

@ -1,15 +0,0 @@
import Cookies from 'js-cookie'
const TokenKey = 'admin-token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token:string) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

@ -0,0 +1,99 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const COLORS_HEX = [
'#17B8BE',
'#F8DCA1',
'#B7885E',
'#FFCB99',
'#F89570',
'#829AE3',
'#E79FD5',
'#1E96BE',
'#89DAC1',
'#B3AD9E',
'#12939A',
'#DDB27C',
'#88572C',
'#FF9833',
'#EF5D28',
'#162A65',
'#DA70BF',
'#125C77',
'#4DC19C',
'#776E57',
];
// TS needs the precise return type
function strToRgb(s: string): [number, number, number] {
if (s.length !== 7) {
return [0, 0, 0];
}
const r = s.slice(1, 3);
const g = s.slice(3, 5);
const b = s.slice(5);
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
}
export class ColorGenerator {
colorsHex: string[];
colorsRgb: [number, number, number][];
cache: Map<string, number>;
currentIdx: number;
constructor(colorsHex: string[] = COLORS_HEX) {
this.colorsHex = colorsHex;
this.colorsRgb = colorsHex.map(strToRgb);
this.cache = new Map();
this.currentIdx = 0;
}
_getColorIndex(key: string): number {
let i = this.cache.get(key);
if (i == null) {
i = this.currentIdx;
this.cache.set(key, this.currentIdx);
this.currentIdx = ++this.currentIdx % this.colorsHex.length;
}
return i;
}
/**
* Will assign a color to an arbitrary key.
* If the key has been used already, it will
* use the same color.
*/
getColorByKey(key: string) {
const i = this._getColorIndex(key);
return this.colorsHex[i];
}
/**
* Retrieve the RGB values associated with a key. Adds the key and associates
* it with a color if the key is not recognized.
* @return {number[]} An array of three ints [0, 255] representing a color.
*/
getRgbColorByKey(key: string): [number, number, number] {
const i = this._getColorIndex(key);
return this.colorsRgb[i];
}
clear() {
this.cache.clear();
this.currentIdx = 0;
}
}
export default new ColorGenerator();

@ -0,0 +1,67 @@
import _round from 'lodash/round';
import moment from 'moment';
export const ONE_MILLISECOND = 1000;
const TODAY = 'Today';
const YESTERDAY = 'Yesterday';
export const STANDARD_DATE_FORMAT = 'YYYY-MM-DD';
export const STANDARD_DATETIME_FORMAT = 'MMMM D YYYY, HH:mm:ss.SSS';
/**
* Humanizes the duration based on the inputUnit
*
* Example:
* 5000ms => 5s
* 1000μs => 1ms
*/
export function formatDuration(duration: number, inputUnit: string = 'microseconds'): string {
let d = duration;
if (inputUnit === 'microseconds') {
d = duration / 1000;
}
let units = 'ms';
if (d >= 1000) {
units = 's';
d /= 1000;
}
return _round(d, 2) + units;
}
export function formatRelativeDate(value: any, fullMonthName: boolean = false) {
const m = moment.isMoment(value) ? value : moment(value);
const monthFormat = fullMonthName ? 'MMMM' : 'MMM';
const dt = new Date();
if (dt.getFullYear() !== m.year()) {
return m.format(`${monthFormat} D, YYYY`);
}
const mMonth = m.month();
const mDate = m.date();
const date = dt.getDate();
if (mMonth === dt.getMonth() && mDate === date) {
return TODAY;
}
dt.setDate(date - 1);
if (mMonth === dt.getMonth() && mDate === dt.getDate()) {
return YESTERDAY;
}
return m.format(`${monthFormat} D`);
}
/**
* @param {number} timestamp
* @param {number} initialTimestamp
* @param {number} totalDuration
* @return {number} 0-100 percentage
*/
export function getPercentageOfDuration(duration: number, totalDuration: number) {
return (duration / totalDuration) * 100;
}
export function formatDate(duration: number) {
return moment(duration / ONE_MILLISECOND).format(STANDARD_DATE_FORMAT);
}
export function formatDatetime(duration: number) {
return moment(duration / ONE_MILLISECOND).format(STANDARD_DATETIME_FORMAT);
}

@ -0,0 +1,53 @@
/* eslint-disable */
// 该模块用来请求API网关
import axios from 'axios'
import { message } from 'antd';
import { getToken } from './auth'
import storage from '../../library/utils/localStorage'
import { logout } from '../../library/utils/account';
// create an axios instance
const service = axios.create({
baseURL: "http://localhost:9085", // api的base_url
timeout: 120000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// 设置token
config.headers['X-Token'] = getToken()
return config
},
error => {
// Do something with request error
Promise.reject(error)
})
// respone interceptor
service.interceptors.response.use(
response => {
// 1054 == need re-login
if (response.data.err_code == 1054) {
message.error(response.data.message)
setTimeout(function() {
logout()
},500)
return response
}
// 错误码不为0代表发生了错误1
if (response.data.err_code != 0) {
if (response.data.message != '') {
message.error(response.data.message+' : '+ response.data.err_code)
}
return Promise.reject(response.data.message+' : '+ response.data.err_code)
}
return response
},
error => {
message.error(error.message)
return Promise.reject(error)
})
export default service

@ -1,29 +0,0 @@
import axios from 'axios'
if (process.env.NODE_ENV === 'production') {
axios.defaults.baseURL = 'https://www.baidu.com'
}else{
axios.defaults.baseURL = 'https://www.production.com'
}
// let loadingInstance = null //这里是loading
//使用create方法创建axios实例
export const Service = axios.create({
timeout: 7000, // 请求超时时间
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
// 添加请求拦截器
Service.interceptors.request.use(config => {
return config
})
// 添加响应拦截器
Service.interceptors.response.use(response => {
// loadingInstance.close()
// console.log(response)
return response.data
}, error => {
return Promise.reject(error)
})

@ -1,156 +0,0 @@
import axios from 'axios'
import {message} from 'antd'
import QS from 'qs'
import {AxiosResponse} from 'axios'
type tCodeMessage = {
[key:number]:string
}
const codeMessage:tCodeMessage = {
200:'服务器成功返回请求的数据',
202:'一个请求已经进入后台排队(异步任务)。',
204:'删除数据成功。',
400:'发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401:'用户没有权限(令牌、用户名、密码错误)。',
403:'用户得到授权,但是访问是被禁止的。',
404:'发出的请求针对的是不存在的记录,服务器没有进行操作。',
406:'请求的格式不可得。',
410:'请求的资源被永久删除,且不会再得到的。',
422:'当创建一个对象时,发生一个验证错误。',
500:'服务器发生错误,请检查服务器。',
502:'网关错误。',
503:'服务不可用,服务器暂时过载或维护。',
504:'网关超时。'
}
// 全局的默认值
if (process.env.NODE_ENV === 'production') {
axios.defaults.baseURL = 'https://www.baidu.com'
}else{
axios.defaults.baseURL = 'http://localhost:3023'
//axios.defaults.baseURL = 'http://rap2api.taobao.org/app/mock/242047'
}
// 请求超时时间
axios.defaults.timeout = 10000
// post请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
// 请求拦截器
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断是否存在token如果存在则统一在http请求的header都加上token不用每次请求都手动添加了
// 即使本地存在token也有可能token是过期的所以在响应拦截器中要对返回状态进行判断
const token = '1'
token && (config.headers.Authorization = token)
return config
},
error => {
return error
}
)
// 响应拦截器
axios.interceptors.response.use(
response => {
if (response.status === 200) {
return response
} else {
return response
}
},
// 服务器状态码不是200的情况
error => {
if (error.response.status) {
switch (error.response.status) {
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
message.error(`${codeMessage[error.response.status]}`)
// router.replace({
// path: '/login',
// query: { redirect: router.currentRoute.fullPath }
// });
break
// 403 token过期
// 登录过期对用户进行提示
// 清除本地token和清空vuex中token对象
// 跳转登录页面
case 403:
message.error(`${codeMessage[error.response.status]}`)
// 清除token
// localStorage.removeItem('token');
// store.commit('loginSuccess', null);
// // 跳转登录页面并将要浏览的页面fullPath传过去登录成功后跳转需要访问的页面
// setTimeout(() => {
// router.replace({
// path: '/login',
// query: {
// redirect: router.currentRoute.fullPath
// }
// });
// }, 1000);
break
// 404请求不存在
case 404:
message.error(`${codeMessage[error.response.status]}`)
// Toast({
// message: '网络请求不存在',
// duration: 1500,
// forbidClick: true
// });
break
// 其他错误,直接抛出错误提示
default:
message.warning(`${codeMessage[error.response.status]}`)
// Toast({
// message: error.response.data.message,
// duration: 1500,
// forbidClick: true
// });
}
return error.response
}
}
)
/**
* getget
* @param {String} url [url]
* @param {Object} params []
* @param {Function} successBack []
* @param {Function} errorBack []
* @param {Function} finallyBack []
*/
export async function get(url:string, params:any, successBack = function(res:AxiosResponse){}, errorBack = function(err:any){}, finallyBack = function(){}){
try {
const res = await axios.get(url, params)
successBack(res.data)
} catch(err) {
errorBack(err)
} finally {
finallyBack()
}
}
/**
* postpost
* @param {String} url [url]
* @param {Object} params []
* @param {Function} successBack []
* @param {Function} errorBack []
* @param {Function} finallyBack []
*/
export async function post(url:string, params:any, successBack = function(res:AxiosResponse){}, errorBack = function(err:any){}, finallyBack = function(){}) {
try {
const res = await axios.post(url, QS.stringify(params))
successBack(res.data)
} catch(err) {
errorBack(err)
} finally {
finallyBack()
}
}

@ -1,9 +0,0 @@
import {Light, Dark} from '../../styles/theme'
export function modifyVars(model:boolean, primary:string){
if(model){
window.less.modifyVars(Dark(primary))
}else{
window.less.modifyVars(Light(primary))
}
}

@ -0,0 +1,48 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import sitePrefix from './site-prefix';
const origin = process.env.NODE_ENV === 'test' ? global.location.origin : window.location.origin;
/**
* Generate the URL prefix from `sitePrefix` and use it for all subsequent calls
* to `prefixUrl()`. `sitePrefix` should be an absolute URL, e.g. with an origin.
* `pathPrefix` is just the path portion and should not have a trailing slash:
*
* - `"http://localhost:3000/"` to `""`
* - `"http://localhost:3000/abc/"` to `"/abc"`
* - `"http://localhost:3000/abc/def/"` to `"/abc/def"`
*/
// exported for tests
export function getPathPrefix(orig, sitePref) {
const o = orig == null ? '' : orig.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
const s = sitePref == null ? '' : sitePref;
const rx = new RegExp(`^${o}|/$`, 'ig');
return s.replace(rx, '');
}
const pathPrefix = getPathPrefix(origin, sitePrefix);
/**
* Add the path prefix to the URL. See [site-prefix.js](../site-prefix.js) and
* the `<base>` tag in [index.html](../../public/index.html) for details.
*
* @param {string} value The URL to have the prefix added to.
* @return {string} The resultant URL.
*/
export default function prefixUrl(value) {
const s = value == null ? '' : String(value);
return `${pathPrefix}${s}`;
}

@ -0,0 +1,30 @@
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Per the resolution of https://github.com/jaegertracing/jaeger-ui/issues/42,
// package.json#homepage is set to "." and the document MUST have a <base>
// element to define a usable base URL.
const baseNode = document.querySelector('base');
if (!baseNode && process.env.NODE_ENV !== 'test') {
throw new Error('<base> element not found');
}
const sitePrefix = baseNode ? baseNode.href : `${global.location.origin}/`;
// Configure the webpack publicPath to match the <base>:
// https://webpack.js.org/guides/public-path/#on-the-fly
// eslint-disable-next-line camelcase
window.__webpack_public_path__ = sitePrefix;
export default sitePrefix;

@ -3,14 +3,10 @@
* @param {*} value * @param {*} value
* @returns {Boolean} * @returns {Boolean}
*/ */
export function isEmpty(value:any){ export function isEmpty(value){
if(value === null || value === '' || value === 'undefined' || value === undefined || value === 'null' || value.length === 0){ if(value === null || value === '' || value === 'undefined' || value === undefined || value === 'null' || value.length === 0){
return true return true
} else{
// value = value.replace(/\s/g, '')
// if(value === ''){
// return true
// }
return false
} }
return false
} }

@ -0,0 +1,33 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
// import { Link } from 'react-router-dom';
import ErrorMessage from '../Trace/components/common/ErrorMessage';
// import prefixUrl from '../../utils/prefix-url';
type NotFoundProps = {
error: any;
};
export default function NotFound({ error }: NotFoundProps) {
return (
<section className="ub-m3">
<h1>Error</h1>
{error && <ErrorMessage error={error} />}
{/* <Link to={prefixUrl('/')}>Back home</Link> */}
</section>
);
}

@ -0,0 +1,29 @@
/*
Copyright (c) 2017 Uber Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* Ant's default font-family, Helvetica Neue For Number, is inconcistent in thickness for a given weight */
.font-reset,
html > body {
font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Roboto, Arial, sans-serif;
}
a {
color: #11939a;
}
a:hover {
color: #00474e;
}

@ -0,0 +1,62 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Route, Switch,BrowserRouter as Router } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import NotFound from './NotFound';
// eslint-disable-next-line import/order, import/no-unresolved
import TracePage from '../Trace/components/TracePage';
// eslint-disable-next-line import/order, import/no-unresolved
import Login from '../Login'
import { ROUTE_PATH as tracePath } from '../Trace/components/TracePage/url';
import configureStore from '../Trace/utils/configure-store';
import processScripts from '../Trace/utils/config/process-scripts';
import './index.css';
import Layouts from '../../layouts/index.jsx'
const history = require("history").createBrowserHistory()
export default class UIApp extends Component {
constructor(props) {
super(props);
this.store = configureStore(history);
processScripts();
}
render() {
return (
<Provider store={this.store}>
<ConnectedRouter history={history}>
<Router>
<Switch>
<Route path={tracePath} component={TracePage} />
<Route path="/ui" component={Layouts} />
<Route path="/login" exact component={Login} />
<Route component={NotFound} />
</Switch>
</Router>
</ConnectedRouter>
</Provider>
);
}
}

@ -1,35 +0,0 @@
import React, { useEffect } from 'react'
import {Route, Switch, Redirect} from 'react-router-dom'
import '@/styles/main.less'
import Home from '../../pages/Index'
import Login from '../../pages/Login'
import ConfigProvider from '../../components/ConfigProvider'
import Intl from '../../components/Intl'
let App = () => {
//npm install --save rc-form-hooks
//
useEffect(() => {
// console.log("modify vars")
// alert(1)
// modifyVars(system.dark, system.primary)
return () => {}
})
return (
<>
<Intl>
<ConfigProvider>
<Switch>
<Route path="/home" component={Home} />
<Route path="/login" exact component={Login} />
<Redirect to="/home"/>
</Switch>
</ConfigProvider>
</Intl>
</>
)
}
export default App

@ -1,424 +0,0 @@
import React, { useState, useEffect} from 'react'
import { inject, observer } from 'mobx-react'
import { Spin, Row, Col, Card } from 'antd'
import {
Chart,
Geom,
Axis,
Tooltip,
Coord,
Legend
} from 'bizcharts'
import DataSet from '@antv/data-set'
//
const Groupedcolumn = ()=>{
const data = [
{
name: 'London',
'1月': 18.9,
'2月': 28.8,
'3月': 39.3,
'4月': 81.4,
'5月': 47,
'6月': 20.3,
'7月': 24,
'8月': 35.6,
'9月': 5.6,
'10月': 1.6,
'11月': 31.6,
'12月': 13.6
},
{
name: 'Berlin',
'1月': 12.9,
'2月': 2.8,
'3月': 43.3,
'4月': 1.4,
'5月': 90,
'6月': 50.3,
'7月': 21,
'8月': 15.6,
'9月': 12.6,
'10月': 23.6,
'11月': 21.6,
'12月': 54.6
}
]
const ds = new DataSet()
const dv = ds.createView().source(data)
dv.transform({
type: 'fold',
fields: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
//
key: '月份',
// key
value: '月均降雨量' // value
})
return (
<>
<Chart height={200} padding='auto' data={dv} forceFit>
<Tooltip
crosshairs={{
type: 'y'
}}
/>
<Geom
type='interval'
position='月份*月均降雨量'
color={'name'}
adjust={[
{
type: 'dodge',
marginRatio: 1 / 32
}
]}
/>
</Chart>
</>
)
}
//
const Basiccolumn = ()=>{
const data = [
{
year: '一月',
sales: 38
},
{
year: '二月',
sales: 52
},
{
year: '三月',
sales: 61
},
{
year: '四月',
sales: 145
},
{
year: '五月',
sales: 48
},
{
year: '六月',
sales: 38
},
{
year: '七月',
sales: 38
},
{
year: '八月',
sales: 38
}
]
const cols = {
sales: {
tickInterval: 20
}
}
return (
<>
<Chart height={200} padding='auto' data={data} scale={cols} forceFit>
<Axis name='sales' />
<Tooltip
crosshairs={{
type: 'y'
}}
/>
<Geom type='interval' position='year*sales' />
</Chart>
</>
)
}
//
const Range = () => {
const data = [
{
profession: '两年制副学士学位',
highest: 110000,
minimum: 23000,
mean: 56636
},
{
profession: '执法与救火',
highest: 120000,
minimum: 18000,
mean: 66625
},
{
profession: '教育学',
highest: 125000,
minimum: 24000,
mean: 72536
},
{
profession: '心理学',
highest: 130000,
minimum: 22500,
mean: 75256
},
{
profession: '计算机科学',
highest: 131000,
minimum: 23000,
mean: 77031
}
]
const ds = new DataSet()
const dv = ds.createView().source(data)
dv.transform({
type: 'map',
callback(row) {
//
row.range = [row.minimum, row.highest]
return row
}
})
return (
<>
<Chart height={200} padding='auto' data={dv} forceFit>
<Coord transpose />
<Axis
name='profession'
label={{
offset: 12
}}
/>
<Axis name='range' />
<Tooltip />
<Geom type='interval' position='profession*range' />
</Chart>
</>
)
}
//
const Stackedpercentagecolumn = ()=>{
const data = [
{
country: 'Europe',
year: '1750',
value: 163
},
{
country: 'Europe',
year: '1800',
value: 203
},
{
country: 'Europe',
year: '1850',
value: 276
},
{
country: 'Europe',
year: '1900',
value: 408
},
{
country: 'Europe',
year: '1950',
value: 547
},
{
country: 'Europe',
year: '1999',
value: 729
},
{
country: 'Europe',
year: '2050',
value: 628
},
{
country: 'Europe',
year: '2100',
value: 828
},
{
country: 'Asia',
year: '1750',
value: 502
},
{
country: 'Asia',
year: '1800',
value: 635
},
{
country: 'Asia',
year: '1850',
value: 809
},
{
country: 'Asia',
year: '1900',
value: 947
},
{
country: 'Asia',
year: '1950',
value: 1402
},
{
country: 'Asia',
year: '1999',
value: 3634
},
{
country: 'Asia',
year: '2050',
value: 5268
},
{
country: 'Asia',
year: '2100',
value: 7268
}
]
const ds = new DataSet()
const dv = ds
.createView()
.source(data)
.transform({
type: 'percent',
field: 'value',
//
dimension: 'country',
//
groupBy: ['year'],
//
as: 'percent'
})
const cols = {
percent: {
min: 0,
formatter(val) {
return (val * 100).toFixed(2) + '%'
}
}
}
return (
<>
<Chart height={200} padding='auto' data={dv} scale={cols} forceFit>
<Legend />
<Axis name='year' />
<Axis name='percent' />
<Tooltip />
<Geom
type='intervalStack'
position='year*percent'
color={'country'}
/>
</Chart>
</>
)
}
//
const Stackedcolumn = ()=>{
const data = [
{
name: 'London',
'Jan.': 18.9,
'Feb.': 28.8,
'Mar.': 39.3,
'Apr.': 81.4,
May: 47,
'Jun.': 20.3,
'Jul.': 24,
'Aug.': 35.6
},
{
name: 'Berlin',
'Jan.': 12.4,
'Feb.': 23.2,
'Mar.': 34.5,
'Apr.': 99.7,
May: 52.6,
'Jun.': 35.5,
'Jul.': 37.4,
'Aug.': 42.4
}]
const ds = new DataSet()
const dv = ds.createView().source(data)
dv.transform({
type: 'fold',
fields: ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.'],
//
key: '月份',
// key
value: '月均降雨量' // value
})
return (
<>
<Chart height={200} padding="auto" data={dv} forceFit>
<Legend />
<Axis name='月份' />
<Axis name='月均降雨量' />
<Tooltip />
<Geom
type='intervalStack'
position='月份*月均降雨量'
color={'name'}
style={{
stroke: '#fff',
lineWidth: 1
}}
/>
</Chart>
</>
)
}
const Charts = inject('system')(observer((props) =>{
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [loading])
return (
<Spin spinning={loading}>
<div className='container'>
<Row gutter={24}>
<Col span={24}>
<Card title='条形图' style={{borderRadius:'10px'}}>
<Col xs={24} sm={24} md={8} lg={8} xl={8} className='gutter-row'>
<Groupedcolumn />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8} className='gutter-row' span={8}>
<Basiccolumn />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8} className='gutter-row' span={8}>
<Range />
</Col>
</Card>
</Col>
<Col span={24} style={{marginTop: '20px'}}>
<Card title='堆叠柱形图' style={{borderRadius:'10px'}}>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className='gutter-row' span={8}>
<Stackedpercentagecolumn />
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className='gutter-row' span={8}>
<Stackedcolumn />
</Col>
</Card>
</Col>
<Col span={24} style={{marginTop: '20px'}}>
<Card title='横图表' style={{borderRadius:'10px'}}>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className='gutter-row' span={8}>
<Stackedpercentagecolumn />
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className='gutter-row' span={8}>
<Stackedcolumn />
</Col>
</Card>
</Col>
</Row>
</div>
</Spin>
)
}))
export default Charts

@ -1,97 +0,0 @@
import React, { useState, useEffect} from 'react'
import { inject, observer } from 'mobx-react'
import { Spin, Row, Col, Card } from 'antd'
import {
Chart,
Geom,
Axis,
Tooltip
} from 'bizcharts'
const Dashboard = inject('system')(observer(() =>{
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
const data = [
{
year: '1991',
value: 3
},
{
year: '1992',
value: 4
},
{
year: '1993',
value: 3.5
},
{
year: '1994',
value: 5
},
{
year: '1995',
value: 4.9
},
{
year: '1996',
value: 6
},
{
year: '1997',
value: 7
},
{
year: '1998',
value: 9
},
{
year: '1999',
value: 13
}
]
const cols = {
value: {
min: 0
},
year: {
range: [0, 1]
}
}
return (
<Spin spinning={loading} tip='Loading...'>
<div className='container'>
<Row gutter={24}>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className='gutter-row'>
<Card style={{borderRadius:'10px'}}>
<Chart padding="auto" height={278} data={data} scale={cols} forceFit>
<Axis name='year' />
<Axis name='value' />
<Tooltip
crosshairs={{
type: 'y'
}}
/>
<Geom type='line' position='year*value' size={2} />
<Geom
type='point'
position='year*value'
size={4}
shape={'circle'}
style={{
stroke: '#fff',
lineWidth: 1
}}
/>
</Chart>
</Card>
</Col>
</Row>
</div>
</Spin>
)
}))
export default Dashboard

@ -1,20 +0,0 @@
import React, { useState, useEffect} from 'react'
import { inject, observer } from 'mobx-react'
import { Spin } from 'antd'
const A = inject('system')(observer(() =>{
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
return (
<Spin spinning={loading} tip="Loading...">
<div className='container'>
s
</div>
</Spin>
)
}))
export default A

@ -1,94 +0,0 @@
import React, { useState, useEffect } from 'react'
import { Spin } from 'antd'
function B() {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
return (
<Spin spinning={loading} tip="Loading...">
<div>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
<p>ds</p>
</div>
</Spin>
)
}
export default B

@ -1,45 +0,0 @@
import React, { useEffect, useState } from 'react'
import { Spin, Row, Col, Card, Collapse } from 'antd'
const { Panel } = Collapse
function Accordion() {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
const text = `
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
`
return (
<Spin spinning={loading} tip="Loading...">
<div className='container'>
<Row gutter={[8, 8]}>
<Col xs={24} sm={24} md={24} lg={12} xl={12}>
<Card title='折叠面板' style={{borderRadius:'10px'}}>
<Collapse defaultActiveKey={['1']} onChange={ ()=>{} }>
<Panel header="This is panel header 1" key="1">
<p>{text}</p>
</Panel>
<Panel header="This is panel header 2" key="2">
<p>{text}</p>
</Panel>
<Panel header="This is panel header 3" key="3" disabled>
<p>{text}</p>
</Panel>
</Collapse>
</Card>
</Col>
<Col xs={24} sm={24} md={24} lg={12} xl={12}>
<Card title='更多分页' style={{borderRadius:'10px'}}>
</Card>
</Col>
</Row>
</div>
</Spin>
)
}
export default Accordion

@ -1,19 +0,0 @@
import React, { useEffect, useState } from 'react'
import { Spin } from 'antd'
function DatePickers() {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
return (
<Spin spinning={loading} tip="Loading...">
<div className='container'>
DatePicker
</div>
</Spin>
)
}
export default DatePickers

@ -1,19 +0,0 @@
import React, { useEffect, useState } from 'react'
import { Spin } from 'antd'
function Icons() {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
return (
<Spin spinning={loading} tip="Loading...">
<div className='container'>
Icons
</div>
</Spin>
)
}
export default Icons

@ -1,53 +0,0 @@
import React, { useEffect, useState } from 'react'
import { Spin, Row, Col, Card, Pagination } from 'antd'
function Paginations() {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
return () => {}
}, [])
function onShowSizeChange(current:number, pageSize:number) {
console.log(current, pageSize)
}
return (
<Spin spinning={loading} tip="Loading...">
<div className='container'>
<Row gutter={[8, 8]}>
<Col xs={24} sm={24} md={24} lg={12} xl={12}>
<Card title='基本分页' style={{borderRadius:'10px'}}>
<Pagination defaultCurrent={1} total={50} />
</Card>
</Col>
<Col xs={24} sm={24} md={24} lg={12} xl={12}>
<Card title='更多分页' style={{borderRadius:'10px'}}>
<Pagination defaultCurrent={6} total={500} />
</Card>
</Col>
</Row>
<Row gutter={[8, 8]}>
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Card title='改变每页显示条目数' style={{borderRadius:'10px'}}>
<Pagination
showSizeChanger
onShowSizeChange={onShowSizeChange}
defaultCurrent={3}
total={500}
/>
<br />
<Pagination
showSizeChanger
onShowSizeChange={onShowSizeChange}
defaultCurrent={3}
total={500}
disabled
/>
</Card>
</Col>
</Row>
</div>
</Spin>
)
}
export default Paginations

@ -1,45 +0,0 @@
import React, { useEffect } from 'react'
import { withRouter, useHistory } from 'react-router-dom'
import { Layout } from 'antd'
import style from './index.module.less'
import { inject, observer } from 'mobx-react'
import MenuWrapper from '../../layouts/Menu'
import DrawerWrapper from '../../layouts/Drawer'
import ContentWrapper from '../../layouts/Content'
import HeaderWrapper from '../../layouts/Header'
import { getToken } from '../../library/utils/auth'
import { isEmpty } from '../../library/utils/validate'
import Routers from '../../library/routes'
import {isDesktop} from '../../pages/Responsive'
import { ISystem } from '../../store/system'
const { Sider } = Layout
let Index = inject('system')(observer((props:{system:ISystem}) => {
let {system} = props
let history = useHistory()
useEffect(() => {
if(isEmpty(getToken())){
history.push('/login')
}
return () => {}
})
return (
<Layout className={`${style.app} ${system.dark?style.dark:''}`}>
{isDesktop() &&
<Sider collapsed={system.collapsed}>
<MenuWrapper routers={Routers}/>
</Sider>
}
<Layout>
<HeaderWrapper />
<ContentWrapper routers={Routers}/>
<DrawerWrapper />
</Layout>
</Layout>
)
}))
export default withRouter(Index as any)

@ -0,0 +1,16 @@
.login{
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login .rectangle{
width: 360px;
box-shadow: 1px 2px 4px 0px;
border-radius: 10px;
padding: 20px;
}

@ -0,0 +1,97 @@
import React from 'react'
import { Form, Input, Button } from 'antd';
import { useHistory } from 'react-router-dom';
import './index.css'
import { inject, observer } from 'mobx-react'
// eslint-disable-next-line import/order, import/no-unresolved
import storage from '../../library/utils/localStorage'
import request from '../../library/utils/http'
import { isEmpty } from '../../library/utils/validate'
import { setToken } from '../../library/utils/auth';
function FormBox(props) {
const {account:acc} = props
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
const history = useHistory()
const onFinish = values => {
request({
url: '/web/login',
method: 'POST',
params: {
username:values.username,
password: values.password
}
}).then(res => {
setToken(res.data.data.token)
acc.setInfo(res.data.data.account)
setTimeout(()=> {
const oldPath = storage.get('lastPath')
if (!isEmpty(oldPath)) {
storage.remove('lastPath')
history.push(oldPath)
} else {
history.push('/ui/dashboard')
}
},200)
})
};
const onFinishFailed = () => {
// console.log('Failed:', errorInfo);
};
return (
<div className="login">
<div className="rectangle">
<Form
{...layout}
name="basic"
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</div>
</div>
);
}
const Login = inject('account')(observer(FormBox))
export default Login

@ -1,22 +0,0 @@
.app{
width: 100vw;
height: 100vh;
background: #f3f6f9;
display: flex;
justify-content: center;
align-items: center;
.rectangle{
width: 360px;
background-color: #ffffff;
box-shadow: 0px 2px 4px 0px
#d0d0d0;
border-radius: 10px;
padding: 20px;
}
:global {
.ant-form-item{
margin-bottom: 0;
}
}
}

@ -1,106 +0,0 @@
import React, { useState } from 'react'
import { Button, Form, Input, Icon, message, Spin } from 'antd'
import {WrappedFormUtils} from 'antd/lib/form/Form'
import { useHistory } from 'react-router-dom'
import style from './index.module.less'
import { post } from '../../library/utils/http/index'
import { setToken } from '../../library/utils/auth'
import storage from '../../library/utils/localStorage'
import { inject, observer } from 'mobx-react'
import { IUser } from '../../store/user'
function FormBox(props: { user: IUser, form: WrappedFormUtils }) {
const [loading, setloading] = useState(false)
let { user } = props
let history = useHistory()
const [btnLoading, setBtnLoading] = useState(false)
const { getFieldDecorator } = props.form
function handleSubmit(e: React.FormEvent<HTMLElement>) {
e.preventDefault()
props.form.validateFields((err: any, values: any) => {
setBtnLoading(true)
post('/api/login',
{
username: 'admin',
password: '123456'
},
function (res: any) {
if (res.err === 1) {
message.error(res.msg)
} else {
setToken(res.data.token)
delete res.data.token
storage.set('info', res.data)
setloading(true)
user.setInfo(res.data)
setTimeout(() => {
setloading(false)
history.push('/home/dashboard')
}, 2000)
}
},
function (err: any) {
console.log(err)
message.error('ds')
},
function () {
setBtnLoading(false)
}
)
// if (!err) {
// history.push("/index");
// }
})
}
return (
<div>
<Spin spinning={loading}>
<Form className="login-form">
<Form.Item>
{getFieldDecorator('username', {
initialValue: 'dsa',
rules: [{ required: true, message: '用户名不能为空' }]
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
initialValue: 'dsa',
rules: [{ required: true, message: '密码不能为空' }]
})(
<Input.Password
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>
)}
</Form.Item>
<Form.Item labelAlign='right' labelCol={{ span: 3, offset: 12 }}>
<Button onClick={handleSubmit} type="primary" shape="round" loading={btnLoading ? true : false}>
</Button>
</Form.Item>
</Form>
</Spin>
</div>
)
}
const FormBoxWrapper = Form.create({ name: 'normal_login' })(inject('user')(observer(FormBox)))
let Login = inject('user')(observer(() => {
//npm install --save rc-form-hooks
// https://www.jianshu.com/p/fc59cb61f7cc
return (
<div className={style.app}>
<div className={style.rectangle}>
<FormBoxWrapper />
</div>
</div>
)
}))
export default Login

@ -1,19 +0,0 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React from 'react'
import {useMediaQuery,MediaQueryProps} from 'react-responsive'
import MediaQuery from 'react-responsive'
const Desktop = (props:MediaQueryProps) => <MediaQuery {...props} minWidth={768} />
const Mobile = (props:MediaQueryProps) => <MediaQuery {...props} maxWidth={767} />
const isMobile = () => useMediaQuery({
query: '(max-device-width: 991px)'
})
const isDesktop = () => useMediaQuery({
query: '(min-device-width: 992px)'
})
export {
Desktop,
Mobile,
isMobile,
isDesktop
}

@ -0,0 +1,59 @@
import React,{useEffect} from 'react';
import './index.less';
import { Card } from 'antd';
import { ISystem } from '../../store/system'
import { inject, observer } from 'mobx-react'
import GridLayout from 'react-grid-layout';
const { Meta } = Card;
function Index(props: {system:ISystem}) {
let {system} = props
useEffect(() => {
},[system.startDate,system.endDate])
const layout = [
{i: 'a', x: 0, y: 0, w: 3, h: 3},
{i: 'b', x: 3, y: 0, w: 3, h: 3},
{i: 'c', x: 6, y: 0, w: 3, h: 3}
];
return (
<div className="test">
<GridLayout className="layout" layout={layout} cols={12} width={1200}>
<div key="a">
<Card
hoverable
style={{ width: '100%',height:'100%' }}
// cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
>
<Meta title="Europe Street beat" description="www.instagram.com" />
</Card>
</div>
<div key="b">
<Card
hoverable
style={{ width: '100%',height:'100%' }}
// cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
>
<Meta title="Europe Street beat" description="www.instagram.com" />
</Card>
</div>
<div key="c">
<Card
hoverable
style={{ width: '100%',height:'100%' }}
// cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
>
<Meta title="Europe Street beat" description="www.instagram.com" />
</Card>
</div>
</GridLayout>
</div>
);
}
let Test = inject('system')(observer(Index))
export default Test;

@ -0,0 +1,30 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as fileReaderActions from './file-reader-api';
import readJsonFile from '../utils/readJsonFile';
jest.mock('../utils/readJsonFile');
describe('actions/file-reader-api', () => {
beforeEach(() => {
readJsonFile.mockReset();
});
it('loadJsonTraces calls readJsonFile', () => {
const arg = 'example-arg';
fileReaderActions.loadJsonTraces(arg);
expect(readJsonFile.mock.calls).toEqual([[arg]]);
});
});

@ -0,0 +1,24 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { createAction } from 'redux-actions';
import readJsonFile from '../utils/readJsonFile';
// eslint-disable-next-line import/prefer-default-export
export const loadJsonTraces = createAction(
'@FILE_READER_API/LOAD_JSON',
fileList => readJsonFile(fileList),
fileList => ({ fileList })
);

@ -0,0 +1,64 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { createAction } from 'redux-actions';
import JaegerAPI from '../api/jaeger';
export const fetchTrace = createAction(
'@JAEGER_API/FETCH_TRACE',
id => JaegerAPI.fetchTrace(id),
id => ({ id })
);
export const fetchMultipleTraces = createAction(
'@JAEGER_API/FETCH_MULTIPLE_TRACES',
ids => JaegerAPI.searchTraces({ traceID: ids }),
ids => ({ ids })
);
export const archiveTrace = createAction(
'@JAEGER_API/ARCHIVE_TRACE',
id => JaegerAPI.archiveTrace(id),
id => ({ id })
);
export const searchTraces = createAction(
'@JAEGER_API/SEARCH_TRACES',
query => JaegerAPI.searchTraces(query),
query => ({ query })
);
export const fetchServices = createAction('@JAEGER_API/FETCH_SERVICES', () => JaegerAPI.fetchServices());
export const fetchServiceOperations = createAction(
'@JAEGER_API/FETCH_SERVICE_OPERATIONS',
serviceName => JaegerAPI.fetchServiceOperations(serviceName),
serviceName => ({ serviceName })
);
export const fetchServiceServerOps = createAction(
'@JAEGER_API/FETCH_SERVICE_SERVER_OP',
serviceName => JaegerAPI.fetchServiceServerOps(serviceName),
serviceName => ({ serviceName })
);
export const fetchDeepDependencyGraph = createAction(
'@JAEGER_API/FETCH_DEEP_DEPENDENCY_GRAPH',
query => JaegerAPI.fetchDeepDependencyGraph(query),
query => ({ query })
);
export const fetchDependencies = createAction('@JAEGER_API/FETCH_DEPENDENCIES', () =>
JaegerAPI.fetchDependencies()
);

@ -0,0 +1,152 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* eslint-disable import/first */
jest.mock('node-fetch', () => () =>
Promise.resolve({
status: 200,
data: () => Promise.resolve({ data: null }),
json: () => Promise.resolve({ data: null }),
})
);
import sinon from 'sinon';
import isPromise from 'is-promise';
import * as jaegerApiActions from './jaeger-api';
import JaegerAPI from '../api/jaeger';
describe('actions/jaeger-api', () => {
const query = { param: 'value' };
const id = 'my-trace-id';
const ids = [id, id];
let mock;
beforeEach(() => {
mock = sinon.mock(JaegerAPI);
});
afterEach(() => {
mock.restore();
});
it('@JAEGER_API/FETCH_TRACE should fetch the trace by id', () => {
mock.expects('fetchTrace').withExactArgs(id);
jaegerApiActions.fetchTrace(id);
expect(() => mock.verify()).not.toThrow();
});
it('@JAEGER_API/FETCH_TRACE should return the promise', () => {
const { payload } = jaegerApiActions.fetchTrace(id);
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/FETCH_TRACE should attach the id as meta', () => {
const { meta } = jaegerApiActions.fetchTrace(id);
expect(meta.id).toBe(id);
});
it('@JAEGER_API/FETCH_MULTIPLE_TRACES should fetch traces by ids', () => {
mock.expects('searchTraces').withExactArgs(sinon.match.has('traceID', ids));
jaegerApiActions.fetchMultipleTraces(ids);
expect(() => mock.verify()).not.toThrow();
});
it('@JAEGER_API/FETCH_MULTIPLE_TRACES should return the promise', () => {
const { payload } = jaegerApiActions.fetchMultipleTraces(ids);
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/FETCH_MULTIPLE_TRACES should attach the ids as meta', () => {
const { meta } = jaegerApiActions.fetchMultipleTraces(ids);
expect(meta.ids).toBe(ids);
});
it('@JAEGER_API/ARCHIVE_TRACE should archive the trace by id', () => {
mock.expects('archiveTrace').withExactArgs(id);
jaegerApiActions.archiveTrace(id);
expect(() => mock.verify()).not.toThrow();
});
it('@JAEGER_API/ARCHIVE_TRACE should return the promise', () => {
const { payload } = jaegerApiActions.archiveTrace(id);
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/ARCHIVE_TRACE should attach the id as meta', () => {
const { meta } = jaegerApiActions.archiveTrace(id);
expect(meta.id).toBe(id);
});
it('@JAEGER_API/SEARCH_TRACES should fetch the trace by id', () => {
mock.expects('searchTraces').withExactArgs(query);
jaegerApiActions.searchTraces(query);
expect(() => mock.verify()).not.toThrow();
});
it('@JAEGER_API/SEARCH_TRACES should return the promise', () => {
const { payload } = jaegerApiActions.searchTraces(query);
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/SEARCH_TRACES should attach the query as meta', () => {
const { meta } = jaegerApiActions.searchTraces(query);
expect(meta.query).toEqual(query);
});
it('@JAEGER_API/FETCH_SERVICES should return a promise', () => {
const { payload } = jaegerApiActions.fetchServices();
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/FETCH_SERVICE_OPERATIONS should call the JaegerAPI', () => {
const called = mock
.expects('fetchServiceOperations')
.once()
.withExactArgs('service');
jaegerApiActions.fetchServiceOperations('service');
expect(called.verify()).toBeTruthy();
});
it('@JAEGER_API/FETCH_SERVICE_SERVER_OP should call the JaegerAPI', () => {
const called = mock
.expects('fetchServiceServerOps')
.once()
.withExactArgs('service');
jaegerApiActions.fetchServiceServerOps('service');
expect(called.verify()).toBeTruthy();
});
it('@JAEGER_API/FETCH_DEEP_DEPENDENCY_GRAPH should fetch the graph by params', () => {
mock.expects('fetchDeepDependencyGraph').withExactArgs(query);
jaegerApiActions.fetchDeepDependencyGraph(query);
expect(() => mock.verify()).not.toThrow();
});
it('@JAEGER_API/FETCH_DEEP_DEPENDENCY_GRAPH should return the promise', () => {
const { payload } = jaegerApiActions.fetchDeepDependencyGraph(query);
expect(isPromise(payload)).toBeTruthy();
});
it('@JAEGER_API/FETCH_DEEP_DEPENDENCY_GRAPH should attach the query as meta', () => {
const { meta } = jaegerApiActions.fetchDeepDependencyGraph(query);
expect(meta.query).toEqual(query);
});
it('@JAEGER_API/FETCH_DEPENDENCIES should call the JaegerAPI', () => {
const called = mock.expects('fetchDependencies').once();
jaegerApiActions.fetchDependencies();
expect(called.verify()).toBeTruthy();
});
});

@ -0,0 +1,111 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import fetch from 'isomorphic-fetch';
import moment from 'moment';
import queryString from 'query-string';
import prefixUrl from '../utils/prefix-url';
// export for tests
export function getMessageFromError(errData, status) {
if (errData.code != null && errData.msg != null) {
if (errData.code === status) {
return errData.msg;
}
return `${errData.code} - ${errData.msg}`;
}
try {
return JSON.stringify(errData);
} catch (_) {
return String(errData);
}
}
function getJSON(url, options = {}) {
const { query = null, ...init } = options;
init.credentials = 'same-origin';
const queryStr = query ? `?${queryString.stringify(query)}` : '';
return fetch(`${url}${queryStr}`, init).then(response => {
if (response.status < 400) {
return response.json();
}
return response.text().then(bodyText => {
let data;
let bodyTextFmt;
let errorMessage;
try {
data = JSON.parse(bodyText);
bodyTextFmt = JSON.stringify(data, null, 2);
} catch (_) {
data = null;
bodyTextFmt = null;
}
if (data && Array.isArray(data.errors) && data.errors.length) {
errorMessage = data.errors.map(err => getMessageFromError(err, response.status)).join('; ');
} else {
errorMessage = bodyText || `${response.status} - ${response.statusText}`;
}
if (typeof errorMessage === 'string') {
errorMessage = errorMessage.trim();
}
const error = new Error(`HTTP Error: ${errorMessage}`);
error.httpStatus = response.status;
error.httpStatusText = response.statusText;
error.httpBody = bodyTextFmt || bodyText;
error.httpUrl = url;
error.httpQuery = typeof query === 'string' ? query : queryString.stringify(query);
throw error;
});
});
}
export const DEFAULT_API_ROOT = prefixUrl('/api/');
export const ANALYTICS_ROOT = prefixUrl('/analytics/');
export const DEFAULT_DEPENDENCY_LOOKBACK = moment.duration(1, 'weeks').asMilliseconds();
const JaegerAPI = {
apiRoot: DEFAULT_API_ROOT,
archiveTrace(id) {
return getJSON(`${this.apiRoot}archive/${id}`, { method: 'POST' });
},
fetchQualityMetrics(service, lookback) {
return getJSON(`/qualitymetrics-v2`, { query: { service, lookback } });
},
fetchDecoration(url) {
return getJSON(url);
},
fetchDeepDependencyGraph(query) {
return getJSON(`${ANALYTICS_ROOT}v1/dependencies`, { query });
},
fetchDependencies(endTs = new Date().getTime(), lookback = DEFAULT_DEPENDENCY_LOOKBACK) {
return getJSON(`${this.apiRoot}dependencies`, { query: { endTs, lookback } });
},
fetchServiceOperations(serviceName) {
return getJSON(`${this.apiRoot}services/${encodeURIComponent(serviceName)}/operations`);
},
fetchServiceServerOps(service) {
return getJSON(`${this.apiRoot}operations`, {
query: {
service,
spanKind: 'server',
},
});
},
fetchTrace(id) {
return getJSON(`${this.apiRoot}traces/${id}`);
},
};
export default JaegerAPI;

@ -0,0 +1,183 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* eslint-disable import/first */
jest.mock('isomorphic-fetch', () =>
jest.fn(() =>
Promise.resolve({
status: 200,
data: () => Promise.resolve({ data: null }),
json: () => Promise.resolve({ data: null }),
})
)
);
import fetchMock from 'isomorphic-fetch';
import queryString from 'query-string';
import traceGenerator from '../demo/trace-generators';
import JaegerAPI, {
getMessageFromError,
DEFAULT_API_ROOT,
DEFAULT_DEPENDENCY_LOOKBACK,
ANALYTICS_ROOT,
} from './jaeger';
const defaultOptions = {
credentials: 'same-origin',
};
describe('archiveTrace', () => {
it('POSTs the specified id', () => {
JaegerAPI.archiveTrace('trace-id');
expect(fetchMock).toHaveBeenLastCalledWith(`${DEFAULT_API_ROOT}archive/trace-id`, {
...defaultOptions,
method: 'POST',
});
});
});
describe('fetchDeepDependencyGraph', () => {
it('GETs the specified query', () => {
const query = { service: 'serviceName', start: 400, end: 800 };
JaegerAPI.fetchDeepDependencyGraph(query);
expect(fetchMock).toHaveBeenLastCalledWith(
`${ANALYTICS_ROOT}v1/dependencies?${queryString.stringify(query)}`,
defaultOptions
);
});
});
describe('fetchDependencies', () => {
it('GETs the specified query', () => {
const endTs = 'end time stamp';
const lookback = 'test lookback';
JaegerAPI.fetchDependencies(endTs, lookback);
expect(fetchMock).toHaveBeenLastCalledWith(
`${DEFAULT_API_ROOT}dependencies?${queryString.stringify({ endTs, lookback })}`,
defaultOptions
);
});
it('has default query values', () => {
JaegerAPI.fetchDependencies();
expect(fetchMock).toHaveBeenLastCalledWith(
expect.stringMatching(
new RegExp(`${DEFAULT_API_ROOT}dependencies\\?endTs=\\d+&lookback=${DEFAULT_DEPENDENCY_LOOKBACK}`)
),
defaultOptions
);
});
});
describe('fetchServiceServerOps', () => {
it('GETs the specified query', () => {
const service = 'serviceName';
const query = { service, spanKind: 'server' };
JaegerAPI.fetchServiceServerOps(service);
expect(fetchMock).toHaveBeenLastCalledWith(
`${DEFAULT_API_ROOT}operations?${queryString.stringify(query)}`,
defaultOptions
);
});
});
describe('fetchTrace', () => {
const generatedTraces = traceGenerator.traces({ numberOfTraces: 5 });
it('fetchTrace() should fetch with the id', () => {
JaegerAPI.fetchTrace('trace-id');
expect(fetchMock).toHaveBeenLastCalledWith(`${DEFAULT_API_ROOT}traces/trace-id`, defaultOptions);
});
it('fetchTrace() should resolve the whole response', async () => {
fetchMock.mockReturnValue(
Promise.resolve({
status: 200,
json: () => Promise.resolve({ data: generatedTraces }),
})
);
const resp = await JaegerAPI.fetchTrace('trace-id');
expect(resp.data).toBe(generatedTraces);
});
it('fetchTrace() throws an error on a >= 400 status code', done => {
const status = 400;
const statusText = 'some-status';
const msg = 'some-message';
const errorData = { errors: [{ msg, code: status }] };
fetchMock.mockReturnValue(
Promise.resolve({
status,
statusText,
text: () => Promise.resolve(JSON.stringify(errorData)),
})
);
JaegerAPI.fetchTrace('trace-id').catch(err => {
expect(err.message).toMatch(msg);
expect(err.httpStatus).toBe(status);
expect(err.httpStatusText).toBe(statusText);
done();
});
});
it('fetchTrace() throws an useful error derived from a text payload', done => {
const status = 400;
const statusText = 'some-status';
const errorData = 'this is some error message';
fetchMock.mockReturnValue(
Promise.resolve({
status,
statusText,
text: () => Promise.resolve(errorData),
})
);
JaegerAPI.fetchTrace('trace-id').catch(err => {
expect(err.message).toMatch(errorData);
expect(err.httpStatus).toBe(status);
expect(err.httpStatusText).toBe(statusText);
done();
});
});
});
describe('getMessageFromError()', () => {
describe('{ code, msg } error data', () => {
const data = { code: 1, msg: 'some-message' };
it('ignores code if it is the same as `status` arg', () => {
expect(getMessageFromError(data, 1)).toBe(data.msg);
});
it('returns`$code - $msg` when code is novel', () => {
const rv = getMessageFromError(data, -1);
expect(rv).toBe(`${data.code} - ${data.msg}`);
});
});
describe('other data formats', () => {
it('stringifies the value, when possible', () => {
const data = ['abc'];
expect(getMessageFromError(data)).toBe(JSON.stringify(data));
});
it('returns the string, otherwise', () => {
const data = {};
data.data = data;
expect(getMessageFromError(data)).toBe(String(data));
});
});
});

@ -0,0 +1,82 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { createActions, handleActions, ActionFunctionAny } from 'redux-actions';
import { archiveTrace } from '../../../actions/jaeger-api';
import { ApiError } from '../../../types/api-error';
import { TracesArchive } from '../../../types/archive';
import generateActionTypes from '../../../utils/generate-action-types';
type ArchiveAction = {
meta: {
id: string;
};
payload?: ApiError | string;
};
const initialState: TracesArchive = {};
const actionTypes = generateActionTypes('@jaeger-ui/archive-trace', ['ACKNOWLEDGE']);
const fullActions = createActions({
[actionTypes.ACKNOWLEDGE]: traceID => traceID,
});
export const actions: { [actionType: string]: ActionFunctionAny<any> } = {
...(fullActions as any).jaegerUi.archiveTrace,
archiveTrace,
};
function acknowledge(state: TracesArchive, { payload }: ArchiveAction) {
const traceID = typeof payload === 'string' ? payload : null;
if (!traceID) {
// make flow happy
throw new Error('Invalid state, missing traceID for archive acknowledge');
}
const traceArchive = state[traceID];
if (traceArchive && traceArchive.isLoading) {
// acknowledgement during loading is invalid (should not happen)
return state;
}
const next = { ...traceArchive, isAcknowledged: true };
return { ...state, [traceID]: next };
}
function archiveStarted(state: TracesArchive, { meta }: ArchiveAction) {
return { ...state, [meta.id]: { isLoading: true } };
}
function archiveDone(state: TracesArchive, { meta }: ArchiveAction) {
return { ...state, [meta.id]: { isArchived: true, isAcknowledged: false } };
}
function archiveErred(state: TracesArchive, { meta, payload }: ArchiveAction) {
if (!payload) {
// make flow happy
throw new Error('Invalid state, missing API error details');
}
const traceArchive = { error: payload, isArchived: false, isError: true, isAcknowledged: false };
return { ...state, [meta.id]: traceArchive };
}
export default handleActions(
{
[actionTypes.ACKNOWLEDGE]: acknowledge,
[`${archiveTrace}_PENDING`]: archiveStarted,
[`${archiveTrace}_FULFILLED`]: archiveDone,
[`${archiveTrace}_REJECTED`]: archiveErred,
},
initialState
);

@ -0,0 +1,34 @@
/*
Copyright (c) 2017 Uber Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.ArchiveNotifier--errorNotification {
position: absolute;
right: 0;
width: 650px;
}
.ArchiveNotifier--errorIcon,
.ArchiveNotifier--doneIcon {
font-size: 30px;
}
.ArchiveNotifier--errorIcon {
color: #c00;
}
.ArchiveNotifier--doneIcon {
color: teal;
}

@ -0,0 +1,117 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as React from 'react';
import { notification } from 'antd';
import { LoadingOutlined,ClockCircleOutlined} from '@ant-design/icons';
import ErrorMessage from '../../common/ErrorMessage';
import './index.css';
const ENotifiedState = {
Progress : 'ENotifiedState.Progress',
Outcome : 'ENotifiedState.Outcome',
}
function getNextNotifiedState(props) {
const { archivedState } = props;
if (!archivedState) {
return null;
}
if (archivedState.isLoading) {
return ENotifiedState.Progress;
}
return archivedState.isAcknowledged ? null : ENotifiedState.Outcome;
}
function updateNotification(oldState,nextState, props) {
if (oldState === nextState) {
return;
}
if (oldState) {
notification.close(oldState);
}
if (nextState === ENotifiedState.Progress) {
notification.info({
key: ENotifiedState.Progress,
description: null,
duration: 0,
icon: <LoadingOutlined />,
message: 'Archiving trace...',
});
return;
}
const { acknowledge, archivedState } = props;
if (nextState === ENotifiedState.Outcome) {
if (archivedState && archivedState.error) {
const error = typeof archivedState.error === 'string' ? archivedState.error : archivedState.error;
notification.warn({
key: ENotifiedState.Outcome,
className: 'ArchiveNotifier--errorNotification',
message: <ErrorMessage.Message error={error} wrap />,
description: <ErrorMessage.Details error={error} wrap />,
duration: null,
icon: <ClockCircleOutlined className="ArchiveNotifier--errorIcon" />,
onClose: acknowledge,
});
} else if (archivedState && archivedState.isArchived) {
notification.success({
key: ENotifiedState.Outcome,
description: null,
duration: null,
icon: <ClockCircleOutlined className="ArchiveNotifier--doneIcon" />,
message: 'This trace has been archived.',
onClose: acknowledge,
});
} else {
throw new Error('Unexpected condition');
}
}
}
function processProps(notifiedState, props) {
const nxNotifiedState = getNextNotifiedState(props);
updateNotification(notifiedState, nxNotifiedState, props);
return nxNotifiedState;
}
export default class ArchiveNotifier extends React.PureComponent {
constructor(props) {
super(props);
const notifiedState = processProps(null, props);
this.state = { notifiedState };
}
componentWillReceiveProps(nextProps) {
const notifiedState = processProps(this.state.notifiedState, nextProps);
if (this.state.notifiedState !== notifiedState) {
this.setState({ notifiedState });
}
}
componentWillUnmount() {
const { notifiedState } = this.state;
if (notifiedState) {
notification.close(notifiedState);
}
}
render() {
return null;
}
}

@ -0,0 +1,286 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* eslint-disable import/first */
jest.mock('./scroll-page');
import { scrollBy, scrollTo } from './scroll-page';
import ScrollManager from './ScrollManager';
const SPAN_HEIGHT = 2;
function getTrace() {
const spans = [];
const trace = {
spans,
duration: 2000,
startTime: 1000,
};
for (let i = 0; i < 10; i++) {
spans.push({ duration: 1, startTime: 1000, spanID: i + 1 });
}
return trace;
}
function getAccessors() {
return {
getViewRange: jest.fn(() => [0, 1]),
getSearchedSpanIDs: jest.fn(),
getCollapsedChildren: jest.fn(),
getViewHeight: jest.fn(() => SPAN_HEIGHT * 2),
getBottomRowIndexVisible: jest.fn(),
getTopRowIndexVisible: jest.fn(),
getRowPosition: jest.fn(),
mapRowIndexToSpanIndex: jest.fn(n => n),
mapSpanIndexToRowIndex: jest.fn(n => n),
};
}
describe('ScrollManager', () => {
let trace;
let accessors;
let manager;
beforeEach(() => {
scrollBy.mockReset();
scrollTo.mockReset();
trace = getTrace();
accessors = getAccessors();
manager = new ScrollManager(trace, { scrollBy, scrollTo });
manager.setAccessors(accessors);
});
it('saves the accessors', () => {
const n = Math.random();
manager.setAccessors(n);
expect(manager._accessors).toBe(n);
});
describe('_scrollPast()', () => {
it('throws if accessors is not set', () => {
manager.setAccessors(null);
expect(manager._scrollPast).toThrow();
});
it('is a noop if an invalid rowPosition is returned by the accessors', () => {
// eslint-disable-next-line no-console
const oldWarn = console.warn;
// eslint-disable-next-line no-console
console.warn = () => {};
manager._scrollPast(null, null);
expect(accessors.getRowPosition.mock.calls.length).toBe(1);
expect(accessors.getViewHeight.mock.calls.length).toBe(0);
expect(scrollTo.mock.calls.length).toBe(0);
// eslint-disable-next-line no-console
console.warn = oldWarn;
});
it('scrolls up with direction is `-1`', () => {
const y = 10;
const expectTo = y - 0.5 * accessors.getViewHeight();
accessors.getRowPosition.mockReturnValue({ y, height: SPAN_HEIGHT });
manager._scrollPast(NaN, -1);
expect(scrollTo.mock.calls).toEqual([[expectTo]]);
});
it('scrolls down with direction `1`', () => {
const y = 10;
const vh = accessors.getViewHeight();
const expectTo = y + SPAN_HEIGHT - 0.5 * vh;
accessors.getRowPosition.mockReturnValue({ y, height: SPAN_HEIGHT });
manager._scrollPast(NaN, 1);
expect(scrollTo.mock.calls).toEqual([[expectTo]]);
});
});
describe('_scrollToVisibleSpan()', () => {
function getRefs(spanID) {
return [{ refType: 'CHILD_OF', spanID }];
}
let scrollPastMock;
beforeEach(() => {
scrollPastMock = jest.fn();
manager._scrollPast = scrollPastMock;
});
it('throws if accessors is not set', () => {
manager.setAccessors(null);
expect(manager._scrollToVisibleSpan).toThrow();
});
it('exits if the trace is not set', () => {
manager.setTrace(null);
manager._scrollToVisibleSpan();
expect(scrollPastMock.mock.calls.length).toBe(0);
});
it('does nothing if already at the boundary', () => {
accessors.getTopRowIndexVisible.mockReturnValue(0);
accessors.getBottomRowIndexVisible.mockReturnValue(trace.spans.length - 1);
manager._scrollToVisibleSpan(-1);
expect(scrollPastMock.mock.calls.length).toBe(0);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock.mock.calls.length).toBe(0);
});
it('centers the current top or bottom span', () => {
accessors.getTopRowIndexVisible.mockReturnValue(5);
accessors.getBottomRowIndexVisible.mockReturnValue(5);
manager._scrollToVisibleSpan(-1);
expect(scrollPastMock).lastCalledWith(5, -1);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock).lastCalledWith(5, 1);
});
it('skips spans that are out of view', () => {
trace.spans[4].startTime = trace.startTime + trace.duration * 0.5;
accessors.getViewRange = () => [0.4, 0.6];
accessors.getTopRowIndexVisible.mockReturnValue(trace.spans.length - 1);
accessors.getBottomRowIndexVisible.mockReturnValue(0);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock).lastCalledWith(4, 1);
manager._scrollToVisibleSpan(-1);
expect(scrollPastMock).lastCalledWith(4, -1);
});
it('skips spans that do not match the text search', () => {
accessors.getTopRowIndexVisible.mockReturnValue(trace.spans.length - 1);
accessors.getBottomRowIndexVisible.mockReturnValue(0);
accessors.getSearchedSpanIDs = () => new Set([trace.spans[4].spanID]);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock).lastCalledWith(4, 1);
manager._scrollToVisibleSpan(-1);
expect(scrollPastMock).lastCalledWith(4, -1);
});
it('scrolls to boundary when scrolling away from closest spanID in findMatches', () => {
const closetFindMatchesSpanID = 4;
accessors.getTopRowIndexVisible.mockReturnValue(closetFindMatchesSpanID - 1);
accessors.getBottomRowIndexVisible.mockReturnValue(closetFindMatchesSpanID + 1);
accessors.getSearchedSpanIDs = () => new Set([trace.spans[closetFindMatchesSpanID].spanID]);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock).lastCalledWith(trace.spans.length - 1, 1);
manager._scrollToVisibleSpan(-1);
expect(scrollPastMock).lastCalledWith(0, -1);
});
it('scrolls to last visible row when boundary is hidden', () => {
const parentOfLastRowWithHiddenChildrenIndex = trace.spans.length - 2;
accessors.getBottomRowIndexVisible.mockReturnValue(0);
accessors.getCollapsedChildren = () =>
new Set([trace.spans[parentOfLastRowWithHiddenChildrenIndex].spanID]);
accessors.getSearchedSpanIDs = () => new Set([trace.spans[0].spanID]);
trace.spans[trace.spans.length - 1].references = getRefs(
trace.spans[parentOfLastRowWithHiddenChildrenIndex].spanID
);
manager._scrollToVisibleSpan(1);
expect(scrollPastMock).lastCalledWith(parentOfLastRowWithHiddenChildrenIndex, 1);
});
describe('scrollToNextVisibleSpan() and scrollToPrevVisibleSpan()', () => {
beforeEach(() => {
// change spans so 0 and 4 are top-level and their children are collapsed
const spans = trace.spans;
let parentID;
for (let i = 0; i < spans.length; i++) {
switch (i) {
case 0:
case 4:
parentID = spans[i].spanID;
break;
default:
spans[i].references = getRefs(parentID);
}
}
// set which spans are "in-view" and which have collapsed children
accessors.getTopRowIndexVisible.mockReturnValue(trace.spans.length - 1);
accessors.getBottomRowIndexVisible.mockReturnValue(0);
accessors.getCollapsedChildren.mockReturnValue(new Set([spans[0].spanID, spans[4].spanID]));
});
it('skips spans that are hidden because their parent is collapsed', () => {
manager.scrollToNextVisibleSpan();
expect(scrollPastMock).lastCalledWith(4, 1);
manager.scrollToPrevVisibleSpan();
expect(scrollPastMock).lastCalledWith(4, -1);
});
it('ignores references with unknown types', () => {
// modify spans[2] so that it has an unknown refType
const spans = trace.spans;
spans[2].references = [{ refType: 'OTHER' }];
manager.scrollToNextVisibleSpan();
expect(scrollPastMock).lastCalledWith(2, 1);
manager.scrollToPrevVisibleSpan();
expect(scrollPastMock).lastCalledWith(4, -1);
});
it('handles more than one level of ancestry', () => {
// modify spans[2] so that it has an unknown refType
const spans = trace.spans;
spans[2].references = getRefs(spans[1].spanID);
manager.scrollToNextVisibleSpan();
expect(scrollPastMock).lastCalledWith(4, 1);
manager.scrollToPrevVisibleSpan();
expect(scrollPastMock).lastCalledWith(4, -1);
});
});
describe('scrollToFirstVisibleSpan', () => {
beforeEach(() => {
jest.spyOn(manager, '_scrollToVisibleSpan').mockImplementationOnce();
});
it('calls _scrollToVisibleSpan searching downwards from first span', () => {
manager.scrollToFirstVisibleSpan();
expect(manager._scrollToVisibleSpan).toHaveBeenCalledWith(1, 0);
});
});
});
describe('scrollPageDown() and scrollPageUp()', () => {
it('scrolls by +/~ viewHeight when invoked', () => {
manager.scrollPageDown();
expect(scrollBy).lastCalledWith(0.95 * accessors.getViewHeight(), true);
manager.scrollPageUp();
expect(scrollBy).lastCalledWith(-0.95 * accessors.getViewHeight(), true);
});
it('is a no-op if _accessors or _scroller is not defined', () => {
manager._accessors = null;
manager.scrollPageDown();
manager.scrollPageUp();
expect(scrollBy.mock.calls.length).toBe(0);
manager._accessors = accessors;
manager._scroller = null;
manager.scrollPageDown();
manager.scrollPageUp();
expect(scrollBy.mock.calls.length).toBe(0);
});
});
describe('destroy()', () => {
it('disposes', () => {
expect(manager._trace).toBeDefined();
expect(manager._accessors).toBeDefined();
expect(manager._scroller).toBeDefined();
manager.destroy();
expect(manager._trace).not.toBeDefined();
expect(manager._accessors).not.toBeDefined();
expect(manager._scroller).not.toBeDefined();
});
});
});

@ -0,0 +1,274 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { TNil } from '../../types';
import { Span, SpanReference, Trace } from '../../types/trace';
/**
* `Accessors` is necessary because `ScrollManager` needs to be created by
* `TracePage` so it can be passed into the keyboard shortcut manager. But,
* `ScrollManager` needs to know about the state of `ListView` and `Positions`,
* which are very low-level. And, storing their state info in redux or
* `TracePage#state` would be inefficient because the state info only rarely
* needs to be accessed (when a keyboard shortcut is triggered). `Accessors`
* allows that state info to be accessed in a loosely coupled fashion on an
* as-needed basis.
*/
export type Accessors = {
getViewRange: () => [number, number];
getSearchedSpanIDs: () => Set<string> | TNil;
getCollapsedChildren: () => Set<string> | TNil;
getViewHeight: () => number;
getBottomRowIndexVisible: () => number;
getTopRowIndexVisible: () => number;
getRowPosition: (rowIndex: number) => { height: number; y: number };
mapRowIndexToSpanIndex: (rowIndex: number) => number;
mapSpanIndexToRowIndex: (spanIndex: number) => number;
};
interface IScroller {
scrollTo: (rowIndex: number) => void;
// TODO arg names throughout
scrollBy: (rowIndex: number, opt?: boolean) => void;
}
/**
* Returns `{ isHidden: true, ... }` if one of the parents of `span` is
* collapsed, e.g. has children hidden.
*
* @param {Span} span The Span to check for.
* @param {Set<string>} childrenAreHidden The set of Spans known to have hidden
* children, either because it is
* collapsed or has a collapsed parent.
* @param {Map<string, Span | TNil} spansMap Mapping from spanID to Span.
* @returns {{ isHidden: boolean, parentIds: Set<string> }}
*/
function isSpanHidden(span: Span, childrenAreHidden: Set<string>, spansMap: Map<string, Span | TNil>) {
const parentIDs = new Set<string>();
let { references }: { references: SpanReference[] | TNil } = span;
let parentID: undefined | string;
const checkRef = (ref: SpanReference) => {
if (ref.refType === 'CHILD_OF' || ref.refType === 'FOLLOWS_FROM') {
parentID = ref.spanID;
parentIDs.add(parentID);
return childrenAreHidden.has(parentID);
}
return false;
};
while (Array.isArray(references) && references.length) {
const isHidden = references.some(checkRef);
if (isHidden) {
return { isHidden, parentIDs };
}
if (!parentID) {
break;
}
const parent = spansMap.get(parentID);
parentID = undefined;
references = parent && parent.references;
}
return { parentIDs, isHidden: false };
}
/**
* ScrollManager is intended for scrolling the TracePage. Has two modes, paging
* and scrolling to the previous or next visible span.
*/
export default class ScrollManager {
_trace: Trace | TNil;
_scroller: IScroller;
_accessors: Accessors | TNil;
constructor(trace: Trace | TNil, scroller: IScroller) {
this._trace = trace;
this._scroller = scroller;
this._accessors = undefined;
}
_scrollPast(rowIndex: number, direction: 1 | -1) {
const xrs = this._accessors;
/* istanbul ignore next */
if (!xrs) {
throw new Error('Accessors not set');
}
const isUp = direction < 0;
const position = xrs.getRowPosition(rowIndex);
if (!position) {
// eslint-disable-next-line no-console
console.warn('Invalid row index');
return;
}
let { y } = position;
const vh = xrs.getViewHeight();
if (!isUp) {
y += position.height;
// scrollTop is based on the top of the window
y -= vh;
}
y += direction * 0.5 * vh;
this._scroller.scrollTo(y);
}
_scrollToVisibleSpan(direction: 1 | -1, startRow?: number) {
const xrs = this._accessors;
/* istanbul ignore next */
if (!xrs) {
throw new Error('Accessors not set');
}
if (!this._trace) {
return;
}
const { duration, spans, startTime: traceStartTime } = this._trace;
const isUp = direction < 0;
let boundaryRow: number;
if (startRow != null) {
boundaryRow = startRow;
} else if (isUp) {
boundaryRow = xrs.getTopRowIndexVisible();
} else {
boundaryRow = xrs.getBottomRowIndexVisible();
}
const spanIndex = xrs.mapRowIndexToSpanIndex(boundaryRow);
if ((spanIndex === 0 && isUp) || (spanIndex === spans.length - 1 && !isUp)) {
return;
}
// fullViewSpanIndex is one row inside the view window unless already at the top or bottom
let fullViewSpanIndex = spanIndex;
if (spanIndex !== 0 && spanIndex !== spans.length - 1) {
fullViewSpanIndex -= direction;
}
const [viewStart, viewEnd] = xrs.getViewRange();
const checkVisibility = viewStart !== 0 || viewEnd !== 1;
// use NaN as fallback to make flow happy
const startTime = checkVisibility ? traceStartTime + duration * viewStart : NaN;
const endTime = checkVisibility ? traceStartTime + duration * viewEnd : NaN;
const findMatches = xrs.getSearchedSpanIDs();
const _collapsed = xrs.getCollapsedChildren();
const childrenAreHidden = _collapsed ? new Set(_collapsed) : null;
// use empty Map as fallback to make flow happy
const spansMap: Map<string, Span> = childrenAreHidden
? new Map(spans.map(s => [s.spanID, s] as [string, Span]))
: new Map();
const boundary = direction < 0 ? -1 : spans.length;
let nextSpanIndex: number | undefined;
for (let i = fullViewSpanIndex + direction; i !== boundary; i += direction) {
const span = spans[i];
const { duration: spanDuration, spanID, startTime: spanStartTime } = span;
const spanEndTime = spanStartTime + spanDuration;
if (checkVisibility && (spanStartTime > endTime || spanEndTime < startTime)) {
// span is not visible within the view range
continue;
}
if (findMatches && !findMatches.has(spanID)) {
// skip to search matches (when searching)
continue;
}
if (childrenAreHidden) {
// make sure the span is not collapsed
const { isHidden, parentIDs } = isSpanHidden(span, childrenAreHidden, spansMap);
if (isHidden) {
parentIDs.forEach(id => childrenAreHidden.add(id));
continue;
}
}
nextSpanIndex = i;
break;
}
if (!nextSpanIndex || nextSpanIndex === boundary) {
// might as well scroll to the top or bottom
nextSpanIndex = boundary - direction;
// If there are hidden children, scroll to the last visible span
if (childrenAreHidden) {
let isFallbackHidden: boolean;
do {
const { isHidden, parentIDs } = isSpanHidden(spans[nextSpanIndex], childrenAreHidden, spansMap);
if (isHidden) {
parentIDs.forEach(id => childrenAreHidden.add(id));
nextSpanIndex--;
}
isFallbackHidden = isHidden;
} while (isFallbackHidden);
}
}
const nextRow = xrs.mapSpanIndexToRowIndex(nextSpanIndex);
this._scrollPast(nextRow, direction);
}
/**
* Sometimes the ScrollManager is created before the trace is loaded. This
* setter allows the trace to be set asynchronously.
*/
setTrace(trace: Trace | TNil) {
this._trace = trace;
}
/**
* `setAccessors` is bound in the ctor, so it can be passed as a prop to
* children components.
*/
setAccessors = (accessors: Accessors) => {
this._accessors = accessors;
};
/**
* Scrolls around one page down (0.95x). It is bounds in the ctor, so it can
* be used as a keyboard shortcut handler.
*/
scrollPageDown = () => {
if (!this._scroller || !this._accessors) {
return;
}
this._scroller.scrollBy(0.95 * this._accessors.getViewHeight(), true);
};
/**
* Scrolls around one page up (0.95x). It is bounds in the ctor, so it can
* be used as a keyboard shortcut handler.
*/
scrollPageUp = () => {
if (!this._scroller || !this._accessors) {
return;
}
this._scroller.scrollBy(-0.95 * this._accessors.getViewHeight(), true);
};
/**
* Scrolls to the next visible span, ignoring spans that do not match the
* text filter, if there is one. It is bounds in the ctor, so it can
* be used as a keyboard shortcut handler.
*/
scrollToNextVisibleSpan = () => {
this._scrollToVisibleSpan(1);
};
/**
* Scrolls to the previous visible span, ignoring spans that do not match the
* text filter, if there is one. It is bounds in the ctor, so it can
* be used as a keyboard shortcut handler.
*/
scrollToPrevVisibleSpan = () => {
this._scrollToVisibleSpan(-1);
};
scrollToFirstVisibleSpan = () => {
this._scrollToVisibleSpan(1, 0);
};
destroy() {
this._trace = undefined;
this._scroller = undefined as any;
this._accessors = undefined;
}
}

@ -0,0 +1,83 @@
/*
Copyright (c) 2018 The Jaeger Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.OpNode {
width: 100%;
background: #fff;
cursor: pointer;
white-space: nowrap;
border-collapse: separate;
}
.OpNode--popoverContent {
border: 1px solid #bbb;
}
.OpNode--vectorBorder {
box-sizing: content-box;
stroke: rgba(0, 0, 0, 0.4);
stroke-width: 2;
}
.OpNode td,
.OpNode th {
border: none;
}
.OpNode.is-ui-find-match {
outline: inherit;
outline-color: #fff3d7;
}
.OpNode--popover .OpNode.is-ui-find-match {
outline: #fff3d7 solid 3px;
}
.OpNode--legendNode {
background: #096dd9;
}
.OpNode--mode-time {
background: #eee;
}
.OpNode--metricCell {
text-align: right;
padding: 0.3rem 0.5rem;
background: rgba(255, 255, 255, 0.3);
}
.OpNode--labelCell {
padding: 0.3rem 0.5rem 0.3rem 0.75rem;
}
.OpNode--popover .OpNode--copyIcon,
.OpNode:not(:hover) .OpNode--copyIcon {
color: transparent;
pointer-events: none;
}
.OpNode--service {
display: flex;
justify-content: space-between;
}
/* Tweak the popover aesthetics - unfortunate but necessary */
.OpNode--popover .ant-popover-inner-content {
padding: 0;
position: relative;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save