From d0581cd0c904a8f2548345a37d03a6197102e38b Mon Sep 17 00:00:00 2001 From: Breezewish Date: Sat, 9 May 2020 17:39:52 +0800 Subject: [PATCH 01/14] Prevent animation bug when PD ip is invalid --- ui/dashboardApp/layout/main/Sider/index.js | 93 +++++++++------------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/ui/dashboardApp/layout/main/Sider/index.js b/ui/dashboardApp/layout/main/Sider/index.js index 02dc3f36a6..a6cd0909d5 100644 --- a/ui/dashboardApp/layout/main/Sider/index.js +++ b/ui/dashboardApp/layout/main/Sider/index.js @@ -4,44 +4,26 @@ import { Layout, Menu } from 'antd' import { Link } from 'react-router-dom' import { useEventListener } from '@umijs/hooks' import { useTranslation } from 'react-i18next' -import { useTrail, useSpring, animated } from 'react-spring' +import { useSpring, animated } from 'react-spring' import client from '@lib/client' import Banner from './Banner' import styles from './index.module.less' -const AnimatedMenuItem = animated(Menu.Item) -const AnimatedSubMenu = animated(Menu.SubMenu) - -function TrailMenu({ items, delay, ...props }) { - const trail = useTrail(items.length, { - opacity: 1, - transform: 'translate3d(0, 0, 0)', - from: { opacity: 0, transform: 'translate3d(0, 60px, 0)' }, - delay, - config: { mass: 1, tension: 5000, friction: 200 }, - }) - return ( - {trail.map((style, idx) => items[idx]({ style }))} - ) -} - -function useAnimatedAppMenuItem(registry, appId, title) { +function useAppMenuItem(registry, appId, title) { const { t } = useTranslation() - return (animationProps) => { - const app = registry.apps[appId] - if (!app) { - return null - } - return ( - - - {app.icon ? : null} - {title ? title : t(`${appId}.nav_title`, appId)} - - - ) + const app = registry.apps[appId] + if (!app) { + return null } + return ( + + + {app.icon ? : null} + {title ? title : t(`${appId}.nav_title`, appId)} + + + ) } function useActiveAppId(registry) { @@ -69,7 +51,7 @@ function useCurrentLogin() { return login } -export default function Sider({ +function Sider({ registry, fullWidth, defaultCollapsed, @@ -82,11 +64,9 @@ export default function Sider({ const activeAppId = useActiveAppId(registry) const currentLogin = useCurrentLogin() - const debugSubMenuItems = [ - useAnimatedAppMenuItem(registry, 'instance_profiling'), - ] - const debugSubMenu = (animationProps) => ( - @@ -94,26 +74,25 @@ export default function Sider({ {t('nav.sider.debug')} } - {...animationProps} > - {debugSubMenuItems.map((r) => r())} - + {debugSubMenuItems} + ) const menuItems = [ - useAnimatedAppMenuItem(registry, 'overview'), - useAnimatedAppMenuItem(registry, 'cluster_info'), - useAnimatedAppMenuItem(registry, 'keyviz'), - useAnimatedAppMenuItem(registry, 'statement'), - useAnimatedAppMenuItem(registry, 'slow_query'), - useAnimatedAppMenuItem(registry, 'diagnose'), - useAnimatedAppMenuItem(registry, 'search_logs'), + useAppMenuItem(registry, 'overview'), + useAppMenuItem(registry, 'cluster_info'), + useAppMenuItem(registry, 'keyviz'), + useAppMenuItem(registry, 'statement'), + useAppMenuItem(registry, 'slow_query'), + useAppMenuItem(registry, 'diagnose'), + useAppMenuItem(registry, 'search_logs'), debugSubMenu, ] const extraMenuItems = [ - useAnimatedAppMenuItem(registry, 'dashboard_settings'), - useAnimatedAppMenuItem( + useAppMenuItem(registry, 'dashboard_settings'), + useAppMenuItem( registry, 'user_profile', currentLogin ? currentLogin.username : '...' @@ -142,20 +121,24 @@ export default function Sider({ fullWidth={fullWidth} collapsedWidth={collapsedWidth} /> - - + {menuItems} + + + > + {extraMenuItems} + ) } + +export default Sider From 89a3313441cdb0f9b6b0e8ce3b4fad2df415deac Mon Sep 17 00:00:00 2001 From: Breezewish Date: Sat, 9 May 2020 19:59:02 +0800 Subject: [PATCH 02/14] Small improvements --- etc/manualTestEnv/.gitignore | 1 + etc/manualTestEnv/basic/README.md | 19 +++++++++++++++++++ etc/manualTestEnv/basic/Vagrantfile | 15 +++++++++++++++ .../AnimatedSkeleton/index.module.less | 5 +++++ ui/lib/components/AnimatedSkeleton/index.tsx | 7 +------ 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 etc/manualTestEnv/.gitignore create mode 100644 etc/manualTestEnv/basic/README.md create mode 100644 etc/manualTestEnv/basic/Vagrantfile diff --git a/etc/manualTestEnv/.gitignore b/etc/manualTestEnv/.gitignore new file mode 100644 index 0000000000..a977916f65 --- /dev/null +++ b/etc/manualTestEnv/.gitignore @@ -0,0 +1 @@ +.vagrant/ diff --git a/etc/manualTestEnv/basic/README.md b/etc/manualTestEnv/basic/README.md new file mode 100644 index 0000000000..ce300a4ad7 --- /dev/null +++ b/etc/manualTestEnv/basic/README.md @@ -0,0 +1,19 @@ +# basic + +An environment with TiDB, PD, TiKV, TiFlash. + +## Usage + +Start the box and cluster: + +```ssh +vagrant up +vagrant ssh +tiup playground nightly --monitor --host 10.0.1.2 +``` + +Connect the cluster in the box: + +```ssh +bin/tidb-dashboard --pd http://10.0.1.2:2379 +``` diff --git a/etc/manualTestEnv/basic/Vagrantfile b/etc/manualTestEnv/basic/Vagrantfile new file mode 100644 index 0000000000..1dfffd2ee7 --- /dev/null +++ b/etc/manualTestEnv/basic/Vagrantfile @@ -0,0 +1,15 @@ +Vagrant.configure("2") do |config| + config.vm.box = "hashicorp/bionic64" + config.vm.network "private_network", ip: "10.0.1.2" + config.vm.provision "shell", privileged: false, inline: <<-SHELL + sudo apt install -y zsh + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + sudo chsh -s /usr/bin/zsh vagrant + curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh + source /home/vagrant/.zshrc + SHELL + config.vm.provider "virtualbox" do |v| + v.memory = 4096 + v.cpus = 2 + end +end diff --git a/ui/lib/components/AnimatedSkeleton/index.module.less b/ui/lib/components/AnimatedSkeleton/index.module.less index 8db2e714ae..dc5bf0e153 100644 --- a/ui/lib/components/AnimatedSkeleton/index.module.less +++ b/ui/lib/components/AnimatedSkeleton/index.module.less @@ -8,4 +8,9 @@ width: 100%; height: 100%; } + + .skeleton, + .content { + will-change: opacity; + } } diff --git a/ui/lib/components/AnimatedSkeleton/index.tsx b/ui/lib/components/AnimatedSkeleton/index.tsx index 6c6f7b7510..572f059403 100644 --- a/ui/lib/components/AnimatedSkeleton/index.tsx +++ b/ui/lib/components/AnimatedSkeleton/index.tsx @@ -67,12 +67,7 @@ function AnimatedSkeleton({ }} >
- +
Date: Sun, 10 May 2020 12:04:26 +0800 Subject: [PATCH 03/14] Add instructions for env presets --- etc/manualTestEnv/.gitignore | 2 + etc/manualTestEnv/basic/README.md | 20 +++--- etc/manualTestEnv/basicMultiHost/README.md | 35 +++++++++++ etc/manualTestEnv/basicMultiHost/Vagrantfile | 23 +++++++ .../basicMultiHost/topology.yaml | 29 +++++++++ etc/manualTestEnv/multiReplica/README.md | 35 +++++++++++ etc/manualTestEnv/multiReplica/Vagrantfile | 19 ++++++ etc/manualTestEnv/multiReplica/topology.yaml | 61 +++++++++++++++++++ 8 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 etc/manualTestEnv/basicMultiHost/README.md create mode 100644 etc/manualTestEnv/basicMultiHost/Vagrantfile create mode 100644 etc/manualTestEnv/basicMultiHost/topology.yaml create mode 100644 etc/manualTestEnv/multiReplica/README.md create mode 100644 etc/manualTestEnv/multiReplica/Vagrantfile create mode 100644 etc/manualTestEnv/multiReplica/topology.yaml diff --git a/etc/manualTestEnv/.gitignore b/etc/manualTestEnv/.gitignore index a977916f65..dd2a4be7ca 100644 --- a/etc/manualTestEnv/.gitignore +++ b/etc/manualTestEnv/.gitignore @@ -1 +1,3 @@ .vagrant/ +shared_key +shared_key.pub diff --git a/etc/manualTestEnv/basic/README.md b/etc/manualTestEnv/basic/README.md index ce300a4ad7..0e8040f179 100644 --- a/etc/manualTestEnv/basic/README.md +++ b/etc/manualTestEnv/basic/README.md @@ -4,16 +4,16 @@ An environment with TiDB, PD, TiKV, TiFlash. ## Usage -Start the box and cluster: +1. Start the box and cluster: -```ssh -vagrant up -vagrant ssh -tiup playground nightly --monitor --host 10.0.1.2 -``` + ```bash + vagrant up + vagrant ssh + tiup playground nightly --monitor --host 10.0.1.2 + ``` -Connect the cluster in the box: +2. Outside the box, start TiDB Dashboard server: -```ssh -bin/tidb-dashboard --pd http://10.0.1.2:2379 -``` + ```bash + bin/tidb-dashboard --pd http://10.0.1.2:2379 + ``` diff --git a/etc/manualTestEnv/basicMultiHost/README.md b/etc/manualTestEnv/basicMultiHost/README.md new file mode 100644 index 0000000000..0af0747a52 --- /dev/null +++ b/etc/manualTestEnv/basicMultiHost/README.md @@ -0,0 +1,35 @@ +# basicMultiHost + +An environment with TiDB, PD, TiKV, TiFlash, each in different host. + +## Usage + +1. Generate shared SSH key (only need to do it once): + + ```bash + ssh-keygen -t rsa -b 2048 -f ./shared_key -q -N "" + ``` + +1. Start the box: + + ```bash + vagrant up + ``` + +1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): + + ```bash + tiup cluster deploy multiHost nightly topology.yaml -i shared_key -y --user vagrant + ``` + +1. Start the cluster in the box: + + ```bash + tiup cluster start multiHost + ``` + +1. Start TiDB Dashboard server: + + ```bash + bin/tidb-dashboard --pd http://10.0.1.11:2379 + ``` diff --git a/etc/manualTestEnv/basicMultiHost/Vagrantfile b/etc/manualTestEnv/basicMultiHost/Vagrantfile new file mode 100644 index 0000000000..87bb599f69 --- /dev/null +++ b/etc/manualTestEnv/basicMultiHost/Vagrantfile @@ -0,0 +1,23 @@ +Vagrant.configure("2") do |config| + ssh_pub_key = File.readlines("#{File.dirname(__FILE__)}/shared_key.pub").first.strip + + config.vm.box = "hashicorp/bionic64" + config.vm.provision "shell", privileged: false, inline: <<-SHELL + sudo apt install -y zsh + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + sudo chsh -s /usr/bin/zsh vagrant + + echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys + SHELL + + config.vm.provider "virtualbox" do |v| + v.memory = 1024 + v.cpus = 1 + end + + (1..4).each do |i| + config.vm.define "node #{i}" do |node| + node.vm.network "private_network", ip: "10.0.1.#{i+10}" + end + end +end diff --git a/etc/manualTestEnv/basicMultiHost/topology.yaml b/etc/manualTestEnv/basicMultiHost/topology.yaml new file mode 100644 index 0000000000..15fe4aa74a --- /dev/null +++ b/etc/manualTestEnv/basicMultiHost/topology.yaml @@ -0,0 +1,29 @@ +global: + user: tidb + deploy_dir: "tidb-deploy" + data_dir: "tidb-data" + +server_configs: + pd: + replication.enable-placement-rules: true + +pd_servers: + - host: 10.0.1.11 + +tikv_servers: + - host: 10.0.1.12 + +tidb_servers: + - host: 10.0.1.13 + +tiflash_servers: + - host: 10.0.1.14 + +grafana_servers: + - host: 10.0.1.11 + +monitoring_servers: + - host: 10.0.1.11 + +alertmanager_servers: + - host: 10.0.1.11 diff --git a/etc/manualTestEnv/multiReplica/README.md b/etc/manualTestEnv/multiReplica/README.md new file mode 100644 index 0000000000..2c139afff3 --- /dev/null +++ b/etc/manualTestEnv/multiReplica/README.md @@ -0,0 +1,35 @@ +# multiReplica + +An environment with multiple TiKV nodes in different labels. + +## Usage + +1. Generate shared SSH key (only need to do it once): + + ```bash + ssh-keygen -t rsa -b 2048 -f ./shared_key -q -N "" + ``` + +1. Start the box: + + ```bash + vagrant up + ``` + +1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): + + ```bash + tiup cluster deploy multiReplica nightly topology.yaml -i shared_key -y --user vagrant + ``` + +1. Start the cluster in the box: + + ```bash + tiup cluster start multiReplica + ``` + +1. Start TiDB Dashboard server: + + ```bash + bin/tidb-dashboard --pd http://10.0.1.20:2379 + ``` diff --git a/etc/manualTestEnv/multiReplica/Vagrantfile b/etc/manualTestEnv/multiReplica/Vagrantfile new file mode 100644 index 0000000000..5a0e123cff --- /dev/null +++ b/etc/manualTestEnv/multiReplica/Vagrantfile @@ -0,0 +1,19 @@ +Vagrant.configure("2") do |config| + ssh_pub_key = File.readlines("#{File.dirname(__FILE__)}/shared_key.pub").first.strip + + config.vm.box = "hashicorp/bionic64" + config.vm.provision "shell", privileged: false, inline: <<-SHELL + sudo apt install -y zsh + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + sudo chsh -s /usr/bin/zsh vagrant + + echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys + SHELL + + config.vm.provider "virtualbox" do |v| + v.memory = 4096 + v.cpus = 2 + end + + config.vm.network "private_network", ip: "10.0.1.20" +end diff --git a/etc/manualTestEnv/multiReplica/topology.yaml b/etc/manualTestEnv/multiReplica/topology.yaml new file mode 100644 index 0000000000..78a03385a1 --- /dev/null +++ b/etc/manualTestEnv/multiReplica/topology.yaml @@ -0,0 +1,61 @@ +global: + user: tidb + deploy_dir: "tidb-deploy" + data_dir: "tidb-data" + +server_configs: + tikv: + server.grpc-concurrency: 1 + raftstore.apply-pool-size: 1 + raftstore.store-pool-size: 1 + readpool.unified.max-thread-count: 1 + readpool.storage.use-unified-pool: false + readpool.coprocessor.use-unified-pool: true + storage.block-cache.capacity: 256MB + raftstore.capacity: 10GB + pd: + replication.location-labels: ["zone", "rack", "host"] + +pd_servers: + - host: 10.0.1.20 + +tikv_servers: + - host: 10.0.1.20 + port: 20160 + status_port: 20180 + config: + server.labels: { host: "tikv1", rack: "rack1" } + - host: 10.0.1.20 + port: 20161 + status_port: 20181 + config: + server.labels: { host: "tikv1", rack: "rack1" } + - host: 10.0.1.20 + port: 20162 + status_port: 20182 + config: + server.labels: { host: "tikv2", rack: "rack1" } + - host: 10.0.1.20 + port: 20163 + status_port: 20183 + config: + server.labels: { host: "tikv2", rack: "rack1" } + - host: 10.0.1.20 + port: 20164 + status_port: 20184 + config: + server.labels: { host: "tikv3", rack: "rack2" } + - host: 10.0.1.20 + port: 20165 + status_port: 20185 + config: + server.labels: { host: "tikv3", rack: "rack2" } + +tidb_servers: + - host: 10.0.1.20 + +grafana_servers: + - host: 10.0.1.20 + +monitoring_servers: + - host: 10.0.1.20 From fa50922bd8eade6e5faa25530d41d23745ac0a1b Mon Sep 17 00:00:00 2001 From: Breezewish Date: Mon, 11 May 2020 11:59:51 +0800 Subject: [PATCH 04/14] Remove some unnecessary quotes Signed-off-by: Breezewish --- .../basicMultiHost/topology.yaml | 4 ++-- etc/manualTestEnv/multiReplica/topology.yaml | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/etc/manualTestEnv/basicMultiHost/topology.yaml b/etc/manualTestEnv/basicMultiHost/topology.yaml index 15fe4aa74a..f2039e4ca3 100644 --- a/etc/manualTestEnv/basicMultiHost/topology.yaml +++ b/etc/manualTestEnv/basicMultiHost/topology.yaml @@ -1,7 +1,7 @@ global: user: tidb - deploy_dir: "tidb-deploy" - data_dir: "tidb-data" + deploy_dir: tidb-deploy + data_dir: tidb-data server_configs: pd: diff --git a/etc/manualTestEnv/multiReplica/topology.yaml b/etc/manualTestEnv/multiReplica/topology.yaml index 78a03385a1..cad40a20ea 100644 --- a/etc/manualTestEnv/multiReplica/topology.yaml +++ b/etc/manualTestEnv/multiReplica/topology.yaml @@ -1,7 +1,7 @@ global: user: tidb - deploy_dir: "tidb-deploy" - data_dir: "tidb-data" + deploy_dir: tidb-deploy + data_dir: tidb-data server_configs: tikv: @@ -14,7 +14,10 @@ server_configs: storage.block-cache.capacity: 256MB raftstore.capacity: 10GB pd: - replication.location-labels: ["zone", "rack", "host"] + replication.location-labels: + - zone + - rack + - host pd_servers: - host: 10.0.1.20 @@ -24,32 +27,32 @@ tikv_servers: port: 20160 status_port: 20180 config: - server.labels: { host: "tikv1", rack: "rack1" } + server.labels: { host: tikv1, rack: rack1 } - host: 10.0.1.20 port: 20161 status_port: 20181 config: - server.labels: { host: "tikv1", rack: "rack1" } + server.labels: { host: tikv1, rack: rack1 } - host: 10.0.1.20 port: 20162 status_port: 20182 config: - server.labels: { host: "tikv2", rack: "rack1" } + server.labels: { host: tikv2, rack: rack1 } - host: 10.0.1.20 port: 20163 status_port: 20183 config: - server.labels: { host: "tikv2", rack: "rack1" } + server.labels: { host: tikv2, rack: rack1 } - host: 10.0.1.20 port: 20164 status_port: 20184 config: - server.labels: { host: "tikv3", rack: "rack2" } + server.labels: { host: tikv3, rack: rack2 } - host: 10.0.1.20 port: 20165 status_port: 20185 config: - server.labels: { host: "tikv3", rack: "rack2" } + server.labels: { host: tikv3, rack: rack2 } tidb_servers: - host: 10.0.1.20 From eb0f902716e794c3a4ab37798d169ec7bdd79a35 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Thu, 14 May 2020 11:25:37 +0800 Subject: [PATCH 05/14] wip Signed-off-by: Breezewish --- pkg/apiserver/clusterinfo/fetch.go | 97 ---- pkg/apiserver/clusterinfo/service.go | 241 +++++--- pkg/apiserver/clusterinfo/topology.go | 32 ++ pkg/apiserver/metrics/metrics.go | 31 +- pkg/utils/clusterinfo/fetcher.go | 518 ------------------ .../clusterinfo.go => topology/models.go} | 36 +- pkg/utils/topology/monitor.go | 51 ++ pkg/utils/topology/pd.go | 146 +++++ pkg/utils/topology/store.go | 155 ++++++ pkg/utils/topology/tidb.go | 139 +++++ pkg/utils/topology/topology.go | 87 +++ ui/dashboardApp/index.js | 6 +- ui/dashboardApp/layout/main/Sider/index.js | 1 + .../apps/ClusterInfo/components/HostTable.tsx | 2 +- .../ClusterInfo/components/InstanceTable.tsx | 273 ++++----- ui/lib/apps/ClusterInfo/translations/en.yaml | 7 - .../apps/ClusterInfo/translations/zh-CN.yaml | 7 - ui/lib/apps/DebugPlayground/index.meta.ts | 8 + ui/lib/apps/DebugPlayground/index.tsx | 58 ++ ui/lib/apps/InstanceProfiling/pages/List.tsx | 83 +-- .../KeyViz/components/KeyVizSettingForm.tsx | 15 +- .../components/MonitorAlertBar.module.less | 8 - .../Overview/components/MonitorAlertBar.tsx | 119 ++-- ui/lib/apps/Overview/components/Nodes.tsx | 150 ++--- ui/lib/apps/Overview/index.tsx | 18 +- ui/lib/apps/Overview/translations/en.yaml | 9 +- ui/lib/apps/Overview/translations/zh-CN.yaml | 11 +- .../SearchLogs/components/SearchHeader.tsx | 8 +- ui/lib/apps/SearchLogs/components/utils.ts | 119 ++-- .../components/BaseSelect/index.module.less | 89 +++ ui/lib/components/BaseSelect/index.tsx | 183 +++++++ ui/lib/components/CardTableV2/GroupHeader.tsx | 114 ++++ ui/lib/components/CardTableV2/index.tsx | 53 +- .../InstanceSelect/DropOverlay.module.less | 6 + .../components/InstanceSelect/DropOverlay.tsx | 56 ++ .../InstanceSelect/SelectDisplay.tsx | 68 +++ ui/lib/components/InstanceSelect/index.tsx | 165 ++++++ .../components/InstanceStatusBadge/index.tsx | 83 +++ ui/lib/components/index.ts | 6 + ui/lib/utils/form.ts | 11 - ui/lib/utils/instanceTable.ts | 92 ++++ ui/lib/utils/useClientRequest.ts | 10 +- 42 files changed, 2157 insertions(+), 1214 deletions(-) delete mode 100644 pkg/apiserver/clusterinfo/fetch.go create mode 100644 pkg/apiserver/clusterinfo/topology.go delete mode 100644 pkg/utils/clusterinfo/fetcher.go rename pkg/utils/{clusterinfo/clusterinfo.go => topology/models.go} (67%) create mode 100644 pkg/utils/topology/monitor.go create mode 100644 pkg/utils/topology/pd.go create mode 100644 pkg/utils/topology/store.go create mode 100644 pkg/utils/topology/tidb.go create mode 100644 pkg/utils/topology/topology.go create mode 100644 ui/lib/apps/DebugPlayground/index.meta.ts create mode 100644 ui/lib/apps/DebugPlayground/index.tsx delete mode 100644 ui/lib/apps/Overview/components/MonitorAlertBar.module.less create mode 100644 ui/lib/components/BaseSelect/index.module.less create mode 100644 ui/lib/components/BaseSelect/index.tsx create mode 100644 ui/lib/components/CardTableV2/GroupHeader.tsx create mode 100644 ui/lib/components/InstanceSelect/DropOverlay.module.less create mode 100644 ui/lib/components/InstanceSelect/DropOverlay.tsx create mode 100644 ui/lib/components/InstanceSelect/SelectDisplay.tsx create mode 100644 ui/lib/components/InstanceSelect/index.tsx create mode 100644 ui/lib/components/InstanceStatusBadge/index.tsx delete mode 100644 ui/lib/utils/form.ts create mode 100644 ui/lib/utils/instanceTable.ts diff --git a/pkg/apiserver/clusterinfo/fetch.go b/pkg/apiserver/clusterinfo/fetch.go deleted file mode 100644 index 182660fc52..0000000000 --- a/pkg/apiserver/clusterinfo/fetch.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020 PingCAP, 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, -// See the License for the specific language governing permissions and -// limitations under the License. - -package clusterinfo - -import ( - "context" - - "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/clusterinfo" -) - -type ClusterInfo struct { - TiDB struct { - Nodes []clusterinfo.TiDBInfo `json:"nodes"` - Err *string `json:"err"` - } `json:"tidb"` - - TiKV struct { - Nodes []clusterinfo.TiKVInfo `json:"nodes"` - Err *string `json:"err"` - } `json:"tikv"` - - PD struct { - Nodes []clusterinfo.PDInfo `json:"nodes"` - Err *string `json:"err"` - } `json:"pd"` - - TiFlash struct { - Nodes []clusterinfo.TiFlashInfo `json:"nodes"` - Err *string `json:"err"` - } `json:"tiflash"` - - Grafana *clusterinfo.GrafanaInfo `json:"grafana"` - AlertManager *clusterinfo.AlertManagerInfo `json:"alert_manager"` -} - -// fetches etcd, and parses the ns below: -// * /topology/grafana -// * /topology/alertmanager -// * /topology/tidb -func fillTopologyUnderEtcd(ctx context.Context, service *Service, fillTargetInfo *ClusterInfo) { - tidb, grafana, alertManager, err := clusterinfo.GetTopologyUnderEtcd(ctx, service.etcdClient) - if err != nil { - // Note: GetTopology return error only when fetch etcd failed. - // So it's ok to fill all of them err - errString := err.Error() - fillTargetInfo.TiDB.Err = &errString - return - } - if grafana != nil { - fillTargetInfo.Grafana = grafana - } - if alertManager != nil { - fillTargetInfo.AlertManager = alertManager - } - //if len(tidb) == 0 { - // tidb, err = clusterinfo.GetTiDBTopologyFromOld(ctx, service.etcdClient) - // if err != nil { - // errString := err.Error() - // fillTargetInfo.TiDB.Err = &errString - // return - // } - //} - fillTargetInfo.TiDB.Nodes = tidb -} - -func fillPDTopology(ctx context.Context, service *Service, fillTargetInfo *ClusterInfo) { - pdPeers, err := clusterinfo.GetPDTopology(service.config.PDEndPoint, service.httpClient) - if err != nil { - errString := err.Error() - fillTargetInfo.PD.Err = &errString - return - } - fillTargetInfo.PD.Nodes = pdPeers -} - -func fillStoreTopology(ctx context.Context, service *Service, fillTargetInfo *ClusterInfo) { - kv, tiflashes, err := clusterinfo.GetStoreTopology(service.config.PDEndPoint, service.httpClient) - if err != nil { - errString := err.Error() - fillTargetInfo.TiKV.Err = &errString - fillTargetInfo.TiFlash.Err = &errString - return - } - fillTargetInfo.TiKV.Nodes = kv - fillTargetInfo.TiFlash.Nodes = tiflashes -} diff --git a/pkg/apiserver/clusterinfo/service.go b/pkg/apiserver/clusterinfo/service.go index e306dae32d..463f1ad64e 100644 --- a/pkg/apiserver/clusterinfo/service.go +++ b/pkg/apiserver/clusterinfo/service.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "sort" "sync" "time" @@ -30,7 +31,7 @@ import ( "github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/utils" "github.com/pingcap-incubator/tidb-dashboard/pkg/config" "github.com/pingcap-incubator/tidb-dashboard/pkg/tidb" - "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/clusterinfo" + "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/topology" ) type Service struct { @@ -52,14 +53,18 @@ func NewService(config *config.Config, etcdClient *clientv3.Client, httpClient * func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) { endpoint := r.Group("/topology") endpoint.Use(auth.MWAuthRequired()) - endpoint.GET("/all", s.topologyHandler) - endpoint.DELETE("/tidb/:address", s.deleteTiDBTopologyHandler) - endpoint.GET("/alertmanager/:address/count", s.topologyGetAlertCount) + endpoint.GET("/tidb", s.getTiDBTopology) + endpoint.DELETE("/tidb/:address", s.deleteTiDBTopology) + endpoint.GET("/store", s.getStoreTopology) + endpoint.GET("/pd", s.getPDTopology) + endpoint.GET("/alertmanager", s.getAlertManagerTopology) + endpoint.GET("/alertmanager/:address/count", s.getAlertManagerCounts) + endpoint.GET("/grafana", s.getGrafanaTopology) endpoint = r.Group("/host") endpoint.Use(auth.MWAuthRequired()) endpoint.Use(utils.MWConnectTiDB(s.tidbForwarder)) - endpoint.GET("/all", s.hostHandler) + endpoint.GET("/all", s.getHostsInfo) } // @Summary Delete etcd's tidb key. @@ -70,7 +75,7 @@ func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) { // @Failure 401 {object} utils.APIError "Unauthorized failure" // @Security JwtAuth // @Router /topology/tidb/{address} [delete] -func (s *Service) deleteTiDBTopologyHandler(c *gin.Context) { +func (s *Service) deleteTiDBTopology(c *gin.Context) { address := c.Param("address") errorChannel := make(chan error, 2) ttlKey := fmt.Sprintf("/topology/tidb/%v/ttl", address) @@ -103,50 +108,111 @@ func (s *Service) deleteTiDBTopologyHandler(c *gin.Context) { c.JSON(http.StatusOK, nil) } -// @Summary Get all Dashboard topology and liveness. -// @Description Get information about the dashboard topology. +// @ID getTiDBTopology +// @Summary Get TiDB instances +// @Description Get TiDB instances topology // @Produce json -// @Success 200 {object} clusterinfo.ClusterInfo -// @Router /topology/all [get] +// @Success 200 {array} topology.TiDBInfo +// @Router /topology/tidb [get] // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" -func (s *Service) topologyHandler(c *gin.Context) { - var returnObject ClusterInfo +func (s *Service) getTiDBTopology(c *gin.Context) { + instances, err := topology.FetchTiDBTopology(s.etcdClient) + if err != nil { + _ = c.Error(err) + return + } + c.JSON(http.StatusOK, instances) +} - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() +type StoreTopologyResponse struct { + TiKV []topology.StoreInfo `json:"tikv"` + TiFlash []topology.StoreInfo `json:"tiflash"` +} - fetchers := []func(ctx context.Context, service *Service, info *ClusterInfo){ - fillTopologyUnderEtcd, - fillStoreTopology, - fillPDTopology, +// @ID getStoreTopology +// @Summary Get TiKV / TiFlash instances +// @Description Get TiKV / TiFlash instances topology +// @Produce json +// @Success 200 {object} StoreTopologyResponse +// @Router /topology/store [get] +// @Security JwtAuth +// @Failure 401 {object} utils.APIError "Unauthorized failure" +func (s *Service) getStoreTopology(c *gin.Context) { + tikvInstances, tiFlashInstances, err := topology.FetchStoreTopology(s.config.PDEndPoint, s.httpClient) + if err != nil { + _ = c.Error(err) + return } + c.JSON(http.StatusOK, StoreTopologyResponse{ + TiKV: tikvInstances, + TiFlash: tiFlashInstances, + }) +} - var wg sync.WaitGroup - for _, fetcher := range fetchers { - wg.Add(1) - currentFetcher := fetcher - go func() { - defer wg.Done() - currentFetcher(ctx, s, &returnObject) - }() +// @ID getPDTopology +// @Summary Get PD instances +// @Description Get PD instances topology +// @Produce json +// @Success 200 {array} topology.PDInfo +// @Router /topology/pd [get] +// @Security JwtAuth +// @Failure 401 {object} utils.APIError "Unauthorized failure" +func (s *Service) getPDTopology(c *gin.Context) { + instances, err := topology.FetchPDTopology(s.config.PDEndPoint, s.httpClient) + if err != nil { + _ = c.Error(err) + return } - wg.Wait() + c.JSON(http.StatusOK, instances) +} - c.JSON(http.StatusOK, returnObject) +// @ID getAlertManagerTopology +// @Summary Get AlertManager instance +// @Description Get AlertManager instance topology +// @Produce json +// @Success 200 {object} topology.AlertManagerInfo +// @Router /topology/alertmanager [get] +// @Security JwtAuth +// @Failure 401 {object} utils.APIError "Unauthorized failure" +func (s *Service) getAlertManagerTopology(c *gin.Context) { + instance, err := topology.FetchAlertManagerTopology(s.etcdClient) + if err != nil { + _ = c.Error(err) + return + } + c.JSON(http.StatusOK, instance) +} + +// @ID getGrafanaTopology +// @Summary Get Grafana instance +// @Description Get Grafana instance topology +// @Produce json +// @Success 200 {object} topology.GrafanaInfo +// @Router /topology/grafana [get] +// @Security JwtAuth +// @Failure 401 {object} utils.APIError "Unauthorized failure" +func (s *Service) getGrafanaTopology(c *gin.Context) { + instance, err := topology.FetchGrafanaTopology(s.etcdClient) + if err != nil { + _ = c.Error(err) + return + } + c.JSON(http.StatusOK, instance) } -// @Summary Get the count of alert -// @Description Get alert number of the alert manager. +// @ID getAlertManagerCounts +// @Summary Get alert count +// @Description Get alert count from alert manager // @Produce json // @Success 200 {object} int // @Param address path string true "ip:port" // @Router /topology/alertmanager/{address}/count [get] // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" -func (s *Service) topologyGetAlertCount(c *gin.Context) { +func (s *Service) getAlertManagerCounts(c *gin.Context) { address := c.Param("address") - cnt, err := clusterinfo.GetAlertCountByAddress(address, s.httpClient) + cnt, err := fetchAlertManagerCounts(address, s.httpClient) if err != nil { _ = c.Error(err) return @@ -154,6 +220,7 @@ func (s *Service) topologyGetAlertCount(c *gin.Context) { c.JSON(http.StatusOK, cnt) } +// @ID getHostsInfo // @Summary Get all host information in the cluster // @Description Get information about host in the cluster // @Produce json @@ -161,80 +228,72 @@ func (s *Service) topologyGetAlertCount(c *gin.Context) { // @Router /host/all [get] // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" -func (s *Service) hostHandler(c *gin.Context) { +func (s *Service) getHostsInfo(c *gin.Context) { db := utils.GetTiDBConnection(c) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var wg sync.WaitGroup - var clusterInfo ClusterInfo - fetchers := []func(ctx context.Context, service *Service, info *ClusterInfo){ - fillTopologyUnderEtcd, - fillStoreTopology, - fillPDTopology, - } - for _, fetcher := range fetchers { - wg.Add(1) - currentFetcher := fetcher - go func() { - defer wg.Done() - currentFetcher(ctx, s, &clusterInfo) - }() + allHostsMap, err := s.fetchAllInstanceHostsMap() + if err != nil { + _ = c.Error(err) + return } - - var infos []HostInfo - var err error - wg.Add(1) - go func() { - defer wg.Done() - infos, err = GetAllHostInfo(db) - }() - wg.Wait() - + hostsInfo, err := GetAllHostInfo(db) if err != nil { _ = c.Error(err) return } - allHosts := loadUnavailableHosts(clusterInfo) + hostsInfoMap := make(map[string]HostInfo) + for _, hi := range hostsInfo { + hostsInfoMap[hi.IP] = hi + } -OuterLoop: - for _, host := range allHosts { - for _, info := range infos { - if host == info.IP { - continue OuterLoop - } + hiList := make([]HostInfo, 0, len(hostsInfo)) + for hostIP, _ := range allHostsMap { + if hi, ok := hostsInfoMap[hostIP]; ok { + hiList = append(hiList, hi) + } else { + hiList = append(hiList, HostInfo{ + IP: hostIP, + Unavailable: true, + }) } - infos = append(infos, HostInfo{ - IP: host, - Unavailable: true, - }) } - c.JSON(http.StatusOK, infos) + sort.Slice(hiList, func(i, j int) bool { + return hiList[i].IP > hiList[j].IP + }) + + c.JSON(http.StatusOK, hiList) } -func loadUnavailableHosts(info ClusterInfo) []string { - var allNodes []string - for _, node := range info.TiDB.Nodes { - if node.Status != clusterinfo.ComponentStatusUp { - allNodes = append(allNodes, node.IP) - } +func (s *Service) fetchAllInstanceHostsMap() (map[string]struct{}, error) { + allHosts := make(map[string]struct{}) + pdInfo, err := topology.FetchPDTopology(s.config.PDEndPoint, s.httpClient) + if err != nil { + return nil, err } - for _, node := range info.TiKV.Nodes { - switch node.Status { - case clusterinfo.ComponentStatusUp, - clusterinfo.ComponentStatusOffline, - clusterinfo.ComponentStatusTombstone: - default: - allNodes = append(allNodes, node.IP) - } + for _, i := range pdInfo { + allHosts[i.IP] = struct{}{} } - for _, node := range info.PD.Nodes { - if node.Status != clusterinfo.ComponentStatusUp { - allNodes = append(allNodes, node.IP) - } + + tikvInfo, tiFlashInfo, err := topology.FetchStoreTopology(s.config.PDEndPoint, s.httpClient) + if err != nil { + return nil, err + } + for _, i := range tikvInfo { + allHosts[i.IP] = struct{}{} + } + for _, i := range tiFlashInfo { + allHosts[i.IP] = struct{}{} + } + + tidbInfo, err := topology.FetchTiDBTopology(s.etcdClient) + if err != nil { + return nil, err } - return allNodes + for _, i := range tidbInfo { + allHosts[i.IP] = struct{}{} + } + + return allHosts, nil } diff --git a/pkg/apiserver/clusterinfo/topology.go b/pkg/apiserver/clusterinfo/topology.go new file mode 100644 index 0000000000..aa8ba69f39 --- /dev/null +++ b/pkg/apiserver/clusterinfo/topology.go @@ -0,0 +1,32 @@ +package clusterinfo + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +func fetchAlertManagerCounts(alertManagerAddr string, httpClient *http.Client) (int, error) { + apiAddress := fmt.Sprintf("http://%s/api/v2/alerts", alertManagerAddr) + resp, err := httpClient.Get(apiAddress) + if err != nil { + return 0, err + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return 0, err + } + + var alerts []struct{} + + err = json.Unmarshal(data, &alerts) + if err != nil { + return 0, err + } + + return len(alerts), nil +} diff --git a/pkg/apiserver/metrics/metrics.go b/pkg/apiserver/metrics/metrics.go index 50096e0a6e..dd4d1b781c 100644 --- a/pkg/apiserver/metrics/metrics.go +++ b/pkg/apiserver/metrics/metrics.go @@ -1,8 +1,6 @@ package metrics import ( - "context" - "encoding/json" "fmt" "io/ioutil" "net/http" @@ -15,15 +13,13 @@ import ( "go.etcd.io/etcd/clientv3" "github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/user" - "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/clusterinfo" + "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/topology" ) var ( - ErrNS = errorx.NewNamespace("error.api.metrics") - ErrEtcdAccessFailed = ErrNS.NewType("etcd_access_failed") - ErrPrometheusNotFound = ErrNS.NewType("prometheus_not_found") - ErrPrometheusRegistryInvalid = ErrNS.NewType("prometheus_registry_invalid") - ErrPrometheusQueryFailed = ErrNS.NewType("prometheus_query_failed") + ErrNS = errorx.NewNamespace("error.api.metrics") + ErrPrometheusNotFound = ErrNS.NewType("prometheus_not_found") + ErrPrometheusQueryFailed = ErrNS.NewType("prometheus_query_failed") ) type Service struct { @@ -69,22 +65,15 @@ func (s *Service) queryHandler(c *gin.Context) { return } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - resp, err := s.etcdClient.Get(ctx, "/topology/prometheus", clientv3.WithPrefix()) + pi, err := topology.FetchPrometheusTopology(s.etcdClient) if err != nil { - _ = c.Error(ErrEtcdAccessFailed.NewWithNoMessage()) + _ = c.Error(err) return } - if resp.Count == 0 { + if pi == nil { _ = c.Error(ErrPrometheusNotFound.NewWithNoMessage()) return } - info := clusterinfo.PrometheusInfo{} - if err = json.Unmarshal(resp.Kvs[0].Value, &info); err != nil { - _ = c.Error(ErrPrometheusRegistryInvalid.NewWithNoMessage()) - return - } params := url.Values{} params.Add("query", req.Query) @@ -95,15 +84,15 @@ func (s *Service) queryHandler(c *gin.Context) { client := http.Client{ Timeout: 10 * time.Second, } - promResp, err := client.Get(fmt.Sprintf("http://%s:%d/api/v1/query_range?%s", info.IP, info.Port, params.Encode())) + promResp, err := client.Get(fmt.Sprintf("http://%s:%d/api/v1/query_range?%s", pi.IP, pi.Port, params.Encode())) if err != nil { - _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to query Prometheus")) + _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to send requests to Prometheus")) return } defer promResp.Body.Close() body, err := ioutil.ReadAll(promResp.Body) if err != nil { - _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to query Prometheus")) + _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to read Prometheus query result")) return } c.Data(promResp.StatusCode, promResp.Header.Get("content-type"), body) diff --git a/pkg/utils/clusterinfo/fetcher.go b/pkg/utils/clusterinfo/fetcher.go deleted file mode 100644 index dfa1d4dd22..0000000000 --- a/pkg/utils/clusterinfo/fetcher.go +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright 2020 PingCAP, 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, -// See the License for the specific language governing permissions and -// limitations under the License. - -package clusterinfo - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/pingcap/log" - "go.etcd.io/etcd/clientv3" - "go.uber.org/zap" -) - -// GetTopology return error only when fetch etcd failed. -func GetTopologyUnderEtcd(ctx context.Context, etcdClient *clientv3.Client) (tidbNodes []TiDBInfo, grafanaNode *GrafanaInfo, alertManagerNode *AlertManagerInfo, e error) { - resp, err := etcdClient.Get(ctx, "/topology", clientv3.WithPrefix()) - if err != nil { - return nil, nil, nil, err - } - tidbTTLMap := map[string][]byte{} - tidbEntryMap := map[string]*TiDBInfo{} - for _, kv := range resp.Kvs { - keyParts := strings.Split(string(kv.Key), "/")[1:] - if len(keyParts) < 2 { - continue - } - // There can be four kinds of keys: - // * /topology/grafana: stores grafana topology info. - // * /topology/alertmanager: stores alertmanager topology info. - // * /topology/tidb/ip:port/info: stores tidb topology info. - // * /topology/tidb/ip:port/ttl : stores tidb last update ttl time. - switch keyParts[1] { - case "grafana": - r := GrafanaInfo{} - if err = json.Unmarshal(kv.Value, &r); err != nil { - continue - } - grafanaNode = &r - case "alertmanager": - r := AlertManagerInfo{} - if err = json.Unmarshal(kv.Value, &r); err != nil { - continue - } - alertManagerNode = &r - case "tidb": - // the key should be like /topology/tidb/ip:port/info or /ttl - if len(keyParts) != 4 { - continue - } - address, fieldType := keyParts[2], keyParts[3] - fillDBMap(address, fieldType, kv.Value, tidbEntryMap, tidbTTLMap) - } - } - - tidbNodes = genDBList(tidbEntryMap, tidbTTLMap) - - return tidbNodes, grafanaNode, alertManagerNode, nil -} - -// address should be like "ip:port" -// fieldType should be "ttl" or "info" -// value is field value. -func fillDBMap(address, fieldType string, value []byte, infoMap map[string]*TiDBInfo, ttlMap map[string][]byte) { - if fieldType == "ttl" { - ttlMap[address] = value - } else if fieldType == "info" { - ds := struct { - Version string `json:"version"` - GitHash string `json:"git_hash"` - StatusPort uint `json:"status_port"` - DeployPath string `json:"deploy_path"` - StartTimestamp int64 `json:"start_timestamp"` - }{} - - //var currentInfo TiDB - err := json.Unmarshal(value, &ds) - if err != nil { - return - } - host, port, err := parseHostAndPortFromAddress(address) - if err != nil { - return - } - - infoMap[address] = &TiDBInfo{ - GitHash: ds.GitHash, - Version: ds.Version, - IP: host, - Port: port, - DeployPath: ds.DeployPath, - Status: ComponentStatusUnreachable, - StatusPort: ds.StatusPort, - StartTimestamp: ds.StartTimestamp, - } - } -} - -func genDBList(infoMap map[string]*TiDBInfo, ttlMap map[string][]byte) []TiDBInfo { - nodes := make([]TiDBInfo, 0) - - // Note: it means this TiDB has non-ttl key, but ttl-key not exists. - for address, info := range infoMap { - if ttlFreshUnixNanoSec, ok := ttlMap[address]; ok { - unixNano, err := strconv.ParseInt(string(ttlFreshUnixNanoSec), 10, 64) - if err != nil { - info.Status = ComponentStatusUnreachable - } else { - ttlFreshTime := time.Unix(0, unixNano) - if time.Since(ttlFreshTime) > time.Second*45 { - info.Status = ComponentStatusUnreachable - } else { - info.Status = ComponentStatusUp - } - } - } else { - info.Status = ComponentStatusUnreachable - } - nodes = append(nodes, *info) - } - - return nodes -} - -type store struct { - Address string `json:"address"` - ID int `json:"id"` - Labels []struct { - Key string `json:"key"` - Value string `json:"value"` - } - StateName string `json:"state_name"` - Version string `json:"version"` - StatusAddress string `json:"status_address"` - GitHash string `json:"git_hash"` - DeployPath string `json:"deploy_path"` - StartTimestamp int64 `json:"start_timestamp"` -} - -func getAllStoreNodes(endpoint string, httpClient *http.Client) ([]store, error) { - resp, err := httpClient.Get(endpoint + "/pd/api/v1/stores") - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("fetch stores got wrong status code") - } - defer resp.Body.Close() - storeResp := struct { - Count int `json:"count"` - Stores []struct { - Store store - } `json:"stores"` - }{} - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - err = json.Unmarshal(data, &storeResp) - if err != nil { - return nil, err - } - ret := make([]store, storeResp.Count) - for i, s := range storeResp.Stores { - ret[i] = s.Store - } - return ret, nil -} - -type tikvStore struct { - store -} - -func getAllTiKVNodes(stores []store) []tikvStore { - tikvs := make([]tikvStore, len(stores)) - for i := range stores { - isTiFlash := false - for _, label := range stores[i].Labels { - if label.Key == "engine" && label.Value == "tiflash" { - isTiFlash = true - } - } - if !isTiFlash { - tikvs = append(tikvs, tikvStore{stores[i]}) - } - } - return tikvs -} - -func getTiKVTopology(stores []tikvStore) ([]TiKVInfo, error) { - nodes := make([]TiKVInfo, 0) - for _, v := range stores { - // parse ip and port - host, port, err := parseHostAndPortFromAddress(v.Address) - if err != nil { - continue - } - _, statusPort, err := parseHostAndPortFromAddress(v.StatusAddress) - if err != nil { - continue - } - // In current TiKV, it's version may not start with 'v', - // so we may need to add a prefix 'v' for it. - version := strings.Trim(v.Version, "\n ") - if !strings.HasPrefix(version, "v") { - version = "v" + version - } - node := TiKVInfo{ - Version: version, - IP: host, - Port: port, - GitHash: v.GitHash, - DeployPath: v.DeployPath, - Status: storeStateToStatus(v.StateName), - StatusPort: statusPort, - Labels: map[string]string{}, - StartTimestamp: v.StartTimestamp, - } - for _, v := range v.Labels { - node.Labels[v.Key] = node.Labels[v.Value] - } - nodes = append(nodes, node) - } - - return nodes, nil -} - -type tiflashStore struct { - store -} - -func getAllTiFlashNodes(stores []store) []tiflashStore { - tiflashes := make([]tiflashStore, len(stores)) - for i := range stores { - for _, label := range stores[i].Labels { - if label.Key == "engine" && label.Value == "tiflash" { - tiflashes = append(tiflashes, tiflashStore{stores[i]}) - } - } - } - - return tiflashes -} - -func getTiFlashTopology(stores []tiflashStore) ([]TiFlashInfo, error) { - nodes := make([]TiFlashInfo, 0) - for _, v := range stores { - // parse ip and port - host, port, err := parseHostAndPortFromAddress(v.Address) - if err != nil { - continue - } - _, statusPort, err := parseHostAndPortFromAddress(v.StatusAddress) - if err != nil { - continue - } - version := strings.Trim(v.Version, "\n ") - node := TiFlashInfo{ - Version: version, - IP: host, - Port: port, - DeployPath: v.DeployPath, // TiFlash hasn't BinaryPath for now, so it would be empty - Status: storeStateToStatus(v.StateName), - StatusPort: statusPort, - Labels: map[string]string{}, - StartTimestamp: v.StartTimestamp, - } - for _, v := range v.Labels { - node.Labels[v.Key] = node.Labels[v.Value] - } - nodes = append(nodes, node) - } - - return nodes, nil -} - -func GetStoreTopology(endpoint string, httpClient *http.Client) ([]TiKVInfo, []TiFlashInfo, error) { - stores, err := getAllStoreNodes(endpoint, httpClient) - if err != nil { - return nil, nil, err - } - - tikvStores := getAllTiKVNodes(stores) - tikvInfos, err := getTiKVTopology(tikvStores) - if err != nil { - return nil, nil, err - } - - tiflashStores := getAllTiFlashNodes(stores) - tiflashInfos, err := getTiFlashTopology(tiflashStores) - if err != nil { - return nil, nil, err - } - - return tikvInfos, tiflashInfos, nil -} - -func GetPDTopology(pdEndPoint string, httpClient *http.Client) ([]PDInfo, error) { - nodes := make([]PDInfo, 0) - healthMapChan := make(chan map[string]struct{}) - go func() { - var err error - healthMap, err := getPDNodesHealth(pdEndPoint, httpClient) - if err != nil { - healthMap = map[string]struct{}{} - } - healthMapChan <- healthMap - }() - - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/members") - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, fmt.Errorf("fetch PD members got wrong status code") - } - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - ds := struct { - Count int `json:"count"` - Members []struct { - GitHash string `json:"git_hash"` - ClientUrls []string `json:"client_urls"` - DeployPath string `json:"deploy_path"` - BinaryVersion string `json:"binary_version"` - MemberID json.Number `json:"member_id"` - } `json:"members"` - }{} - - err = json.Unmarshal(data, &ds) - if err != nil { - return nil, err - } - - healthMap := <-healthMapChan - close(healthMapChan) - for _, ds := range ds.Members { - u := ds.ClientUrls[0] - ts, err := getPDStartTimestamp(u, httpClient) - if err != nil { - log.Warn("failed to get PD node status", zap.Error(err)) - continue - } - host, port, err := parseHostAndPortFromAddressURL(u) - if err != nil { - continue - } - var storeStatus ComponentStatus - if _, ok := healthMap[ds.MemberID.String()]; ok { - storeStatus = ComponentStatusUp - } else { - storeStatus = ComponentStatusUnreachable - } - - nodes = append(nodes, PDInfo{ - GitHash: ds.GitHash, - Version: ds.BinaryVersion, - IP: host, - Port: port, - DeployPath: ds.DeployPath, - Status: storeStatus, - StartTimestamp: ts, - }) - } - return nodes, nil -} - -func getPDStartTimestamp(pdEndPoint string, httpClient *http.Client) (int64, error) { - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/status") - if err != nil { - return 0, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return 0, fmt.Errorf("fetch PD %s status got wrong status code", pdEndPoint) - } - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return 0, err - } - - ds := struct { - StartTimestamp int64 `json:"start_timestamp"` - }{} - err = json.Unmarshal(data, &ds) - if err != nil { - return 0, err - } - - return ds.StartTimestamp, nil -} - -// address should be like "ip:port" as "127.0.0.1:2379". -// return error if string is not like "ip:port". -func parseHostAndPortFromAddress(address string) (string, uint, error) { - addresses := strings.Split(address, ":") - if len(addresses) != 2 { - return "", 0, fmt.Errorf("invalid address %s", address) - } - port, err := strconv.Atoi(addresses[1]) - if err != nil { - return "", 0, err - } - return addresses[0], uint(port), nil -} - -// address should be like "protocol://ip:port" as "http://127.0.0.1:2379". -func parseHostAndPortFromAddressURL(urlString string) (string, uint, error) { - u, err := url.Parse(urlString) - if err != nil { - return "", 0, err - } - port, err := strconv.Atoi(u.Port()) - if err != nil { - return "", 0, err - } - return u.Hostname(), uint(port), nil -} - -func storeStateToStatus(state string) ComponentStatus { - state = strings.Trim(strings.ToLower(state), "\n ") - switch state { - case "up": - return ComponentStatusUp - case "tombstone": - return ComponentStatusTombstone - case "offline": - return ComponentStatusOffline - case "down": - return ComponentStatusDown - case "disconnected": - return ComponentStatusUnreachable - default: - return ComponentStatusUnreachable - } -} - -func getPDNodesHealth(pdEndPoint string, httpClient *http.Client) (map[string]struct{}, error) { - // health member set - memberHealth := map[string]struct{}{} - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/health") - if err != nil { - return nil, err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - var healths []struct { - MemberID json.Number `json:"member_id"` - } - - err = json.Unmarshal(data, &healths) - if err != nil { - return nil, err - } - - for _, v := range healths { - memberHealth[v.MemberID.String()] = struct{}{} - } - return memberHealth, nil -} - -// GetAlertCountByAddress receives alert manager's address like "ip:port", and it returns the -// alert number of the alert manager. -func GetAlertCountByAddress(address string, httpClient *http.Client) (int, error) { - ip, port, err := parseHostAndPortFromAddress(address) - if err != nil { - return 0, err - } - - apiAddress := fmt.Sprintf("http://%s:%d/api/v2/alerts", ip, port) - resp, err := httpClient.Get(apiAddress) - if err != nil { - return 0, err - } - - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return 0, err - } - - var alerts []struct{} - - err = json.Unmarshal(data, &alerts) - if err != nil { - return 0, err - } - - return len(alerts), nil -} diff --git a/pkg/utils/clusterinfo/clusterinfo.go b/pkg/utils/topology/models.go similarity index 67% rename from pkg/utils/clusterinfo/clusterinfo.go rename to pkg/utils/topology/models.go index 8dfea81240..8a9104c88c 100644 --- a/pkg/utils/clusterinfo/clusterinfo.go +++ b/pkg/utils/topology/models.go @@ -11,19 +11,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clusterinfo +package topology type ComponentStatus uint const ( - // ComponentStatusUnreachable means unreachable or disconnected ComponentStatusUnreachable ComponentStatus = 0 ComponentStatusUp ComponentStatus = 1 ComponentStatusTombstone ComponentStatus = 2 ComponentStatusOffline ComponentStatus = 3 - - // PD's Store may have state name down. - ComponentStatusDown ComponentStatus = 4 + ComponentStatusDown ComponentStatus = 4 ) type PDInfo struct { @@ -33,7 +30,7 @@ type PDInfo struct { Port uint `json:"port"` DeployPath string `json:"deploy_path"` Status ComponentStatus `json:"status"` - StartTimestamp int64 `json:"start_timestamp"` + StartTimestamp int64 `json:"start_timestamp"` // Ts = 0 means unknown } type TiDBInfo struct { @@ -47,7 +44,8 @@ type TiDBInfo struct { StartTimestamp int64 `json:"start_timestamp"` } -type TiKVInfo struct { +// Store may be a TiKV store or TiFlash store +type StoreInfo struct { GitHash string `json:"git_hash"` Version string `json:"version"` IP string `json:"ip"` @@ -59,31 +57,19 @@ type TiKVInfo struct { StartTimestamp int64 `json:"start_timestamp"` } -type TiFlashInfo struct { - Version string `json:"version"` - IP string `json:"ip"` - Port uint `json:"port"` - DeployPath string `json:"deploy_path"` - Status ComponentStatus `json:"status"` - StatusPort uint `json:"status_port"` - Labels map[string]string `json:"labels"` - StartTimestamp int64 `json:"start_timestamp"` +type StandardComponentInfo struct { + IP string `json:"ip"` + Port uint `json:"port"` } type AlertManagerInfo struct { - IP string `json:"ip"` - Port uint `json:"port"` - DeployPath string `json:"deploy_path"` + StandardComponentInfo } type GrafanaInfo struct { - IP string `json:"ip"` - Port uint `json:"port"` - DeployPath string `json:"deploy_path"` + StandardComponentInfo } type PrometheusInfo struct { - IP string `json:"ip"` - Port uint `json:"port"` - BinaryPath string `json:"binary_path"` + StandardComponentInfo } diff --git a/pkg/utils/topology/monitor.go b/pkg/utils/topology/monitor.go new file mode 100644 index 0000000000..79f82619e9 --- /dev/null +++ b/pkg/utils/topology/monitor.go @@ -0,0 +1,51 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package topology + +import ( + "go.etcd.io/etcd/clientv3" +) + +func FetchAlertManagerTopology(etcdClient *clientv3.Client) (*AlertManagerInfo, error) { + i, err := fetchStandardComponentTopology("alertmanager", etcdClient) + if err != nil { + return nil, err + } + if i == nil { + return nil, nil + } + return &AlertManagerInfo{StandardComponentInfo: *i}, nil +} + +func FetchGrafanaTopology(etcdClient *clientv3.Client) (*GrafanaInfo, error) { + i, err := fetchStandardComponentTopology("grafana", etcdClient) + if err != nil { + return nil, err + } + if i == nil { + return nil, nil + } + return &GrafanaInfo{StandardComponentInfo: *i}, nil +} + +func FetchPrometheusTopology(etcdClient *clientv3.Client) (*PrometheusInfo, error) { + i, err := fetchStandardComponentTopology("prometheus", etcdClient) + if err != nil { + return nil, err + } + if i == nil { + return nil, nil + } + return &PrometheusInfo{StandardComponentInfo: *i}, nil +} diff --git a/pkg/utils/topology/pd.go b/pkg/utils/topology/pd.go new file mode 100644 index 0000000000..964b45f82d --- /dev/null +++ b/pkg/utils/topology/pd.go @@ -0,0 +1,146 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package topology + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "sort" + + "github.com/pingcap/log" + "go.uber.org/zap" +) + +func FetchPDTopology(pdEndPoint string, httpClient *http.Client) ([]PDInfo, error) { + nodes := make([]PDInfo, 0) + healthMap, err := fetchPDHealth(pdEndPoint, httpClient) + if err != nil { + return nil, err + } + + resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/members") + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD members API HTTP get failed") + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD members API read failed") + } + + ds := struct { + Count int `json:"count"` + Members []struct { + GitHash string `json:"git_hash"` + ClientUrls []string `json:"client_urls"` + DeployPath string `json:"deploy_path"` + BinaryVersion string `json:"binary_version"` + MemberID json.Number `json:"member_id"` + } `json:"members"` + }{} + + err = json.Unmarshal(data, &ds) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "PD members API unmarshal failed") + } + + for _, ds := range ds.Members { + u := ds.ClientUrls[0] + host, port, err := parseHostAndPortFromAddressURL(u) + if err != nil { + continue + } + + ts, err := fetchPDStartTimestamp(u, httpClient) + if err != nil { + log.Warn("Failed to fetch PD start timestamp", zap.String("targetPdNode", u), zap.Error(err)) + ts = 0 + } + + var storeStatus ComponentStatus + if _, ok := healthMap[ds.MemberID.String()]; ok { + storeStatus = ComponentStatusUp + } else { + storeStatus = ComponentStatusUnreachable + } + + nodes = append(nodes, PDInfo{ + GitHash: ds.GitHash, + Version: ds.BinaryVersion, + IP: host, + Port: port, + DeployPath: ds.DeployPath, + Status: storeStatus, + StartTimestamp: ts, + }) + } + + sort.Slice(nodes, func(i, j int) bool { + return nodes[i].IP > nodes[j].IP && nodes[i].Port > nodes[j].Port + }) + + return nodes, nil +} + +func fetchPDStartTimestamp(pdEndPoint string, httpClient *http.Client) (int64, error) { + resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/status") + if err != nil { + return 0, ErrPDAccessFailed.Wrap(err, "PD status API HTTP get failed") + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return 0, ErrPDAccessFailed.Wrap(err, "PD status API read failed") + } + + ds := struct { + StartTimestamp int64 `json:"start_timestamp"` + }{} + err = json.Unmarshal(data, &ds) + if err != nil { + return 0, ErrInvalidTopologyData.Wrap(err, "PD status API unmarshal failed") + } + + return ds.StartTimestamp, nil +} + +func fetchPDHealth(pdEndPoint string, httpClient *http.Client) (map[string]struct{}, error) { + // health member set + memberHealth := map[string]struct{}{} + resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/health") + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD health API HTTP get failed") + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD health API read failed") + } + + var healths []struct { + MemberID json.Number `json:"member_id"` + } + + err = json.Unmarshal(data, &healths) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "PD health API unmarshal failed") + } + + for _, v := range healths { + memberHealth[v.MemberID.String()] = struct{}{} + } + return memberHealth, nil +} diff --git a/pkg/utils/topology/store.go b/pkg/utils/topology/store.go new file mode 100644 index 0000000000..fd0e99a91d --- /dev/null +++ b/pkg/utils/topology/store.go @@ -0,0 +1,155 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package topology + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "sort" + "strings" + + "github.com/pingcap/log" + "go.uber.org/zap" +) + +// FetchStoreTopology returns TiKV info and TiFlash info. +func FetchStoreTopology(pdEndpoint string, httpClient *http.Client) ([]StoreInfo, []StoreInfo, error) { + stores, err := fetchStores(pdEndpoint, httpClient) + if err != nil { + return nil, nil, err + } + + tiKVStores := make([]store, 0, len(stores)) + tiFlashStores := make([]store, 0, len(stores)) + for _, store := range stores { + isTiFlash := false + for _, label := range store.Labels { + if label.Key == "engine" && label.Value == "tiflash" { + isTiFlash = true + } + } + if isTiFlash { + tiFlashStores = append(tiFlashStores, store) + } else { + tiKVStores = append(tiKVStores, store) + } + } + + return buildStoreTopology(tiKVStores), buildStoreTopology(tiFlashStores), nil +} + +func buildStoreTopology(stores []store) []StoreInfo { + nodes := make([]StoreInfo, 0, len(stores)) + for _, v := range stores { + host, port, err := parseHostAndPortFromAddress(v.Address) + if err != nil { + log.Warn("Failed to parse store address", zap.Any("store", v)) + continue + } + _, statusPort, err := parseHostAndPortFromAddress(v.StatusAddress) + if err != nil { + log.Warn("Failed to parse store status address", zap.Any("store", v)) + continue + } + // In current TiKV, it's version may not start with 'v', + // so we may need to add a prefix 'v' for it. + version := strings.Trim(v.Version, "\n ") + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + node := StoreInfo{ + Version: version, + IP: host, + Port: port, + GitHash: v.GitHash, + DeployPath: v.DeployPath, + Status: parseStoreState(v.StateName), + StatusPort: statusPort, + Labels: map[string]string{}, + StartTimestamp: v.StartTimestamp, + } + for _, v := range v.Labels { + node.Labels[v.Key] = node.Labels[v.Value] + } + nodes = append(nodes, node) + } + + return nodes +} + +type store struct { + Address string `json:"address"` + ID int `json:"id"` + Labels []struct { + Key string `json:"key"` + Value string `json:"value"` + } + StateName string `json:"state_name"` + Version string `json:"version"` + StatusAddress string `json:"status_address"` + GitHash string `json:"git_hash"` + DeployPath string `json:"deploy_path"` + StartTimestamp int64 `json:"start_timestamp"` +} + +func fetchStores(endpoint string, httpClient *http.Client) ([]store, error) { + resp, err := httpClient.Get(endpoint + "/pd/api/v1/stores") + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD stores API HTTP get failed") + } + defer resp.Body.Close() + storeResp := struct { + Count int `json:"count"` + Stores []struct { + Store store + } `json:"stores"` + }{} + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD stores API read failed") + } + err = json.Unmarshal(data, &storeResp) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "PD stores API unmarshal failed") + } + ret := make([]store, 0, storeResp.Count) + for _, s := range storeResp.Stores { + ret = append(ret, s.Store) + } + + sort.Slice(ret, func(i, j int) bool { + return ret[i].Address > ret[j].Address + }) + + return ret, nil +} + +func parseStoreState(state string) ComponentStatus { + state = strings.Trim(strings.ToLower(state), "\n ") + switch state { + case "up": + return ComponentStatusUp + case "tombstone": + return ComponentStatusTombstone + case "offline": + return ComponentStatusOffline + case "down": + return ComponentStatusDown + case "disconnected": + return ComponentStatusUnreachable + default: + return ComponentStatusUnreachable + } +} diff --git a/pkg/utils/topology/tidb.go b/pkg/utils/topology/tidb.go new file mode 100644 index 0000000000..f144246098 --- /dev/null +++ b/pkg/utils/topology/tidb.go @@ -0,0 +1,139 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package topology + +import ( + "context" + "encoding/json" + "sort" + "strconv" + "strings" + "time" + + "github.com/pingcap/log" + "go.etcd.io/etcd/clientv3" + "go.uber.org/zap" +) + +const tidbTopologyKeyPrefix = "/topology/tidb/" + +func FetchTiDBTopology(etcdClient *clientv3.Client) ([]TiDBInfo, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultFetchTimeout) + defer cancel() + + resp, err := etcdClient.Get(ctx, tidbTopologyKeyPrefix, clientv3.WithPrefix()) + if err != nil { + return nil, ErrPDAccessFailed.Wrap(err, "PD etcd get key %s failed", tidbTopologyKeyPrefix) + } + + nodesAlive := map[string]bool{} + nodesInfo := map[string]*TiDBInfo{} + + for _, kv := range resp.Kvs { + key := string(kv.Key) + if !strings.HasPrefix(key, tidbTopologyKeyPrefix) { + continue + } + // remainingKey looks like `ip:port/info` or `ip:port/ttl`. + remainingKey := key[len(tidbTopologyKeyPrefix):] + keyParts := strings.Split(remainingKey, "/") + if len(keyParts) != 2 { + log.Warn("Ignored invalid TiDB topology key", zap.String("key", key)) + continue + } + + switch keyParts[1] { + case "info": + node, err := parseTiDBInfo(keyParts[0], kv.Value) + if err == nil { + nodesInfo[keyParts[0]] = node + } else { + log.Warn("Ignored invalid TiDB topology info entry", + zap.String("key", key), + zap.String("value", string(kv.Value)), + zap.Error(err)) + } + case "ttl": + alive, err := parseTiDBAliveness(kv.Value) + if err == nil { + nodesAlive[keyParts[0]] = alive + } else { + log.Warn("Ignored invalid TiDB topology TTL entry", + zap.String("key", key), + zap.String("value", string(kv.Value)), + zap.Error(err)) + } + } + } + + nodes := make([]TiDBInfo, 0) + + for addr, info := range nodesInfo { + if alive, ok := nodesAlive[addr]; ok { + if alive { + info.Status = ComponentStatusUp + } + } + nodes = append(nodes, *info) + } + + sort.Slice(nodes, func(i, j int) bool { + return nodes[i].IP > nodes[j].IP && nodes[i].Port > nodes[j].Port + }) + + return nodes, nil +} + +func parseTiDBInfo(address string, value []byte) (*TiDBInfo, error) { + ds := struct { + Version string `json:"version"` + GitHash string `json:"git_hash"` + StatusPort uint `json:"status_port"` + DeployPath string `json:"deploy_path"` + StartTimestamp int64 `json:"start_timestamp"` + }{} + + err := json.Unmarshal(value, &ds) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "TiDB info unmarshal failed") + } + host, port, err := parseHostAndPortFromAddress(address) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "TiDB info address parse failed") + } + + return &TiDBInfo{ + GitHash: ds.GitHash, + Version: ds.Version, + IP: host, + Port: port, + DeployPath: ds.DeployPath, + Status: ComponentStatusUnreachable, + StatusPort: ds.StatusPort, + StartTimestamp: ds.StartTimestamp, + }, nil +} + +func parseTiDBAliveness(value []byte) (bool, error) { + unixTimestampNano, err := strconv.ParseUint(string(value), 10, 64) + if err != nil { + return false, ErrInvalidTopologyData.Wrap(err, "TiDB TTL info parse failed") + } + t := time.Unix(0, int64(unixTimestampNano)) + if time.Since(t) > time.Second*45 { + return false, nil + } else { + return true, nil + } +} diff --git a/pkg/utils/topology/topology.go b/pkg/utils/topology/topology.go new file mode 100644 index 0000000000..a020860328 --- /dev/null +++ b/pkg/utils/topology/topology.go @@ -0,0 +1,87 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package topology + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "github.com/joomcode/errorx" + "github.com/pingcap/log" + "go.etcd.io/etcd/clientv3" + "go.uber.org/zap" +) + +var ( + ErrNS = errorx.NewNamespace("error.topology") + ErrPDAccessFailed = ErrNS.NewType("pd_access_failed") + ErrInvalidTopologyData = ErrNS.NewType("invalid_topology_data") +) + +const defaultFetchTimeout = 2 * time.Second + +// address should be like "ip:port" as "127.0.0.1:2379". +// return error if string is not like "ip:port". +func parseHostAndPortFromAddress(address string) (string, uint, error) { + addresses := strings.Split(address, ":") + if len(addresses) != 2 { + return "", 0, fmt.Errorf("invalid address %s", address) + } + port, err := strconv.Atoi(addresses[1]) + if err != nil { + return "", 0, err + } + return addresses[0], uint(port), nil +} + +// address should be like "protocol://ip:port" as "http://127.0.0.1:2379". +func parseHostAndPortFromAddressURL(urlString string) (string, uint, error) { + u, err := url.Parse(urlString) + if err != nil { + return "", 0, err + } + port, err := strconv.Atoi(u.Port()) + if err != nil { + return "", 0, err + } + return u.Hostname(), uint(port), nil +} + +func fetchStandardComponentTopology(componentName string, etcdClient *clientv3.Client) (*StandardComponentInfo, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultFetchTimeout) + defer cancel() + + key := "/topology/" + componentName + resp, err := etcdClient.Get(ctx, key, clientv3.WithPrefix()) + if err != nil { + return nil, ErrPDAccessFailed.New("PD etcd get key %s failed", key) + } + if resp.Count == 0 { + return nil, nil + } + info := StandardComponentInfo{} + kv := resp.Kvs[0] + if err = json.Unmarshal(kv.Value, &info); err != nil { + log.Warn("Failed to unmarshal topology value", + zap.String("key", string(kv.Key)), + zap.String("value", string(kv.Value))) + return nil, nil + } + return &info, nil +} diff --git a/ui/dashboardApp/index.js b/ui/dashboardApp/index.js index f97471f348..05f8d8e515 100644 --- a/ui/dashboardApp/index.js +++ b/ui/dashboardApp/index.js @@ -11,6 +11,7 @@ import * as client from '@dashboard/client' import LayoutMain from '@dashboard/layout/main' import LayoutSignIn from '@dashboard/layout/signin' +import AppDebugPlayground from '@lib/apps/DebugPlayground/index.meta' import AppDashboardSettings from '@lib/apps/DashboardSettings/index.meta' import AppUserProfile from '@lib/apps/UserProfile/index.meta' import AppOverview from '@lib/apps/Overview/index.meta' @@ -18,7 +19,7 @@ import AppKeyViz from '@lib/apps/KeyViz/index.meta' import AppStatement from '@lib/apps/Statement/index.meta' import AppDiagnose from '@lib/apps/Diagnose/index.meta' import AppSearchLogs from '@lib/apps/SearchLogs/index.meta' -import AppInstanceProfiling from '@lib/apps/InstanceProfiling/index.meta' +// import AppInstanceProfiling from '@lib/apps/InstanceProfiling/index.meta' import AppClusterInfo from '@lib/apps/ClusterInfo/index.meta' import AppSlowQuery from '@lib/apps/SlowQuery/index.meta' @@ -50,6 +51,7 @@ async function main() { ) registry + .register(AppDebugPlayground) .register(AppDashboardSettings) .register(AppUserProfile) .register(AppOverview) @@ -58,7 +60,7 @@ async function main() { .register(AppClusterInfo) .register(AppDiagnose) .register(AppSearchLogs) - .register(AppInstanceProfiling) + // .register(AppInstanceProfiling) .register(AppSlowQuery) if (routing.isLocationMatch('/')) { diff --git a/ui/dashboardApp/layout/main/Sider/index.js b/ui/dashboardApp/layout/main/Sider/index.js index a6cd0909d5..ab3ee416a7 100644 --- a/ui/dashboardApp/layout/main/Sider/index.js +++ b/ui/dashboardApp/layout/main/Sider/index.js @@ -80,6 +80,7 @@ function Sider({ ) const menuItems = [ + useAppMenuItem(registry, 'debug_playground'), useAppMenuItem(registry, 'overview'), useAppMenuItem(registry, 'cluster_info'), useAppMenuItem(registry, 'keyviz'), diff --git a/ui/lib/apps/ClusterInfo/components/HostTable.tsx b/ui/lib/apps/ClusterInfo/components/HostTable.tsx index 0347e100f5..0e73da8cc9 100644 --- a/ui/lib/apps/ClusterInfo/components/HostTable.tsx +++ b/ui/lib/apps/ClusterInfo/components/HostTable.tsx @@ -23,7 +23,7 @@ export default function HostTable() { const { t } = useTranslation() const { data: tableData, isLoading } = useClientRequest((cancelToken) => - client.getInstance().hostAllGet({ cancelToken }) + client.getInstance().getHostsInfo({ cancelToken }) ) const columns = [ diff --git a/ui/lib/apps/ClusterInfo/components/InstanceTable.tsx b/ui/lib/apps/ClusterInfo/components/InstanceTable.tsx index 54bda880aa..0d8e53cd65 100644 --- a/ui/lib/apps/ClusterInfo/components/InstanceTable.tsx +++ b/ui/lib/apps/ClusterInfo/components/InstanceTable.tsx @@ -1,152 +1,96 @@ -import { Badge, Divider, Popconfirm, Tooltip } from 'antd' +import { Divider, Popconfirm, Tooltip, Alert } from 'antd' import { ColumnActionsMode } from 'office-ui-fabric-react/lib/DetailsList' -import React, { ReactNode } from 'react' +import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { DeleteOutlined } from '@ant-design/icons' - -import { - STATUS_DOWN, - STATUS_OFFLINE, - STATUS_TOMBSTONE, - STATUS_UP, -} from '@lib/apps/ClusterInfo/status/status' -import client from '@lib/client' -import { CardTableV2 } from '@lib/components' +import { CardTableV2, InstanceStatusBadge } from '@lib/components' import DateTime from '@lib/components/DateTime' import { useClientRequest } from '@lib/utils/useClientRequest' +import { usePersistFn } from '@umijs/hooks' +import { + buildInstanceTable, + IInstanceTableItem, + InstanceStatus, +} from '@lib/utils/instanceTable' +import client from '@lib/client' -function useStatusColumnRender(handleHideTiDB) { +function StatusColumn({ + node, + onHideTiDB, +}: { + node: IInstanceTableItem + onHideTiDB: (node) => void +}) { const { t } = useTranslation() - return (node) => { - if (node.status == null) { - // Tree node - return - } - let statusNode: ReactNode = null - switch (node.status) { - case STATUS_DOWN: - statusNode = ( - - ) - break - case STATUS_UP: - statusNode = ( - - ) - break - case STATUS_TOMBSTONE: - statusNode = ( - - ) - break - case STATUS_OFFLINE: - statusNode = ( - - ) - break - default: - statusNode = ( - - ) - break - } - return ( - - {statusNode} - {node.nodeKind === 'tidb' && node.status !== STATUS_UP && ( - <> - - { + onHideTiDB && onHideTiDB(node) + }) + + return ( + + + {node.instanceKind === 'tidb' && node.status !== InstanceStatus.Up && ( + <> + + + handleHideTiDB(node)} > - - - - - - - - )} - - ) - } -} - -function useHideTiDBHandler(updateData) { - return async (node) => { - await client - .getInstance() - .topologyTidbAddressDelete(`${node.ip}:${node.port}`) - updateData() - } -} - -function buildData(data) { - if (data === undefined) { - return {} - } - const tableData: any[] = [] // FIXME - const groupData: any[] = [] // FIXME - let startIndex = 0 - const kinds = ['tidb', 'tikv', 'pd', 'tiflash'] - kinds.forEach((nodeKind) => { - const nodes = data[nodeKind] - if (nodes.err) { - return - } - const count = nodes.nodes.length - groupData.push({ - key: nodeKind, - name: nodeKind, - startIndex: startIndex, - count: count, - level: 0, - }) - startIndex += count - const children = nodes.nodes.map((node) => { - return { - key: `${node.ip}:${node.port}`, - ...node, - nodeKind, - } - }) - tableData.push(...children) - }) - return { tableData, groupData } + + + + + + + )} + + ) } export default function ListPage() { const { t } = useTranslation() - - const { data, isLoading, sendRequest } = useClientRequest((cancelToken) => - client.getInstance().topologyAllGet({ cancelToken }) + const { + data: dataTiDB, + isLoading: loadingTiDB, + error: errTiDB, + sendRequest, + } = useClientRequest((cancelToken) => + client.getInstance().getTiDBTopology({ cancelToken }) + ) + const { + data: dataStores, + isLoading: loadingStores, + error: errStores, + } = useClientRequest((cancelToken) => + client.getInstance().getStoreTopology({ cancelToken }) + ) + const { + data: dataPD, + isLoading: loadingPD, + error: errPD, + } = useClientRequest((cancelToken) => + client.getInstance().getPDTopology({ cancelToken }) ) - const { tableData, groupData } = buildData(data) - const handleHideTiDB = useHideTiDBHandler(sendRequest) - const renderStatusColumn = useStatusColumnRender(handleHideTiDB) + const [tableData, groupData] = useMemo( + () => + buildInstanceTable({ + dataPD, + dataTiDB, + dataTiKV: dataStores?.tikv, + dataTiFlash: dataStores?.tiflash, + includeTiFlash: true, + }), + [dataTiDB, dataStores, dataPD] + ) const columns = [ { @@ -171,13 +115,23 @@ export default function ListPage() { maxWidth: 100, isResizable: true, columnActionsMode: ColumnActionsMode.disabled, - onRender: renderStatusColumn, + onRender: (node) => ( + { + await client + .getInstance() + .topologyTidbAddressDelete(`${node.ip}:${node.port}`) + sendRequest() + }} + /> + ), }, { name: t('cluster_info.list.instance_table.columns.up_time'), key: 'start_timestamp', minWidth: 100, - maxWidth: 150, + maxWidth: 200, isResizable: true, columnActionsMode: ColumnActionsMode.disabled, onRender: ({ start_timestamp: ts }) => { @@ -190,24 +144,24 @@ export default function ListPage() { name: t('cluster_info.list.instance_table.columns.version'), fieldName: 'version', key: 'version', - minWidth: 150, - maxWidth: 300, + minWidth: 100, + maxWidth: 150, isResizable: true, columnActionsMode: ColumnActionsMode.disabled, }, { - name: t('cluster_info.list.instance_table.columns.deploy_path'), - fieldName: 'deploy_path', - key: 'deploy_path', - minWidth: 150, - maxWidth: 300, + name: t('cluster_info.list.instance_table.columns.git_hash'), + fieldName: 'git_hash', + key: 'git_hash', + minWidth: 100, + maxWidth: 200, isResizable: true, columnActionsMode: ColumnActionsMode.disabled, }, { - name: t('cluster_info.list.instance_table.columns.git_hash'), - fieldName: 'git_hash', - key: 'git_hash', + name: t('cluster_info.list.instance_table.columns.deploy_path'), + fieldName: 'deploy_path', + key: 'deploy_path', minWidth: 150, maxWidth: 300, isResizable: true, @@ -216,12 +170,27 @@ export default function ListPage() { ] return ( - + <> + {errTiDB && ( + + )} + {errStores && ( + + )} + {errPD && ( + + )} + + ) } diff --git a/ui/lib/apps/ClusterInfo/translations/en.yaml b/ui/lib/apps/ClusterInfo/translations/en.yaml index d7f68b01ef..9052b8462d 100644 --- a/ui/lib/apps/ClusterInfo/translations/en.yaml +++ b/ui/lib/apps/ClusterInfo/translations/en.yaml @@ -14,13 +14,6 @@ cluster_info: hide_db: tooltip: Hide confirm: Do you want to hide this TiDB node? - status: - up: Up - down: Down - tombstone: Tombstone - offline: Offline - unknown: Unknown - unreachable: Unreachable host_table: title: Hosts columns: diff --git a/ui/lib/apps/ClusterInfo/translations/zh-CN.yaml b/ui/lib/apps/ClusterInfo/translations/zh-CN.yaml index 5bc14bab7a..32e7bfb2c6 100644 --- a/ui/lib/apps/ClusterInfo/translations/zh-CN.yaml +++ b/ui/lib/apps/ClusterInfo/translations/zh-CN.yaml @@ -14,13 +14,6 @@ cluster_info: hide_db: tooltip: 隐藏 confirm: 您确认要隐藏该 TiDB 节点吗? - status: - up: 在线 - down: 离线 - tombstone: 已缩容下线 - offline: 下线中 - unknown: 未知 - unreachable: 无法访问 host_table: title: 主机 columns: diff --git a/ui/lib/apps/DebugPlayground/index.meta.ts b/ui/lib/apps/DebugPlayground/index.meta.ts new file mode 100644 index 0000000000..ece87f1f28 --- /dev/null +++ b/ui/lib/apps/DebugPlayground/index.meta.ts @@ -0,0 +1,8 @@ +import { BugOutlined } from '@ant-design/icons' + +export default { + id: 'debug_playground', + routerPrefix: '/debug_playground', + icon: BugOutlined, + reactRoot: () => import(/* webpackChunkName: "debug_playground" */ '.'), +} diff --git a/ui/lib/apps/DebugPlayground/index.tsx b/ui/lib/apps/DebugPlayground/index.tsx new file mode 100644 index 0000000000..ab02b3c038 --- /dev/null +++ b/ui/lib/apps/DebugPlayground/index.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react' +import { Root, BaseSelect, InstanceSelect } from '@lib/components' +import { Select, Button } from 'antd' + +const App = () => { + const [instanceSelectValue, setInstanceSelectValue] = useState([]) + return ( + +

Debug Playground

+

Base Select

+
Content
} + valueRender={() => Short} + /> +
Content
} + valueRender={() => Very Lonnnnnnnnng Value} + /> +
Content
} + valueRender={() => Disabled} + /> + +

Antd Select

+ + +

Instance Select

+ +
Instance select value = {JSON.stringify(instanceSelectValue)}
+

Misc

+
{ + e.preventDefault() + }} + > + Prevent Default Area +
+
+ ) +} + +export default App diff --git a/ui/lib/apps/InstanceProfiling/pages/List.tsx b/ui/lib/apps/InstanceProfiling/pages/List.tsx index fac509b6da..d1f485db46 100644 --- a/ui/lib/apps/InstanceProfiling/pages/List.tsx +++ b/ui/lib/apps/InstanceProfiling/pages/List.tsx @@ -50,47 +50,48 @@ function filterTreeNode(inputValue, treeNode) { } function useTargetsMap() { - const { data } = useClientRequest((cancelToken) => - client.getInstance().topologyAllGet({ cancelToken }) - ) - return useMemo(() => { - const map = {} - if (!data) { - return map - } - // FIXME, declare type - data.tidb?.nodes?.forEach((node) => { - const display = `${node.ip}:${node.port}` - const target = { - kind: 'tidb', - display_name: display, - ip: node.ip, - port: node.status_port, - } - map[display] = target - }) - data.tikv?.nodes?.forEach((node) => { - const display = `${node.ip}:${node.port}` - const target = { - kind: 'tikv', - display_name: display, - ip: node.ip, - port: node.status_port, - } - map[display] = target - }) - data.pd?.nodes?.forEach((node) => { - const display = `${node.ip}:${node.port}` - const target = { - kind: 'pd', - display_name: display, - ip: node.ip, - port: node.port, - } - map[display] = target - }) - return map - }, [data]) + return {} + // const { data } = useClientRequest((cancelToken) => + // client.getInstance().topologyAllGet({ cancelToken }) + // ) + // return useMemo(() => { + // const map = {} + // if (!data) { + // return map + // } + // // FIXME, declare type + // data.tidb?.nodes?.forEach((node) => { + // const display = `${node.ip}:${node.port}` + // const target = { + // kind: 'tidb', + // display_name: display, + // ip: node.ip, + // port: node.status_port, + // } + // map[display] = target + // }) + // data.tikv?.nodes?.forEach((node) => { + // const display = `${node.ip}:${node.port}` + // const target = { + // kind: 'tikv', + // display_name: display, + // ip: node.ip, + // port: node.status_port, + // } + // map[display] = target + // }) + // data.pd?.nodes?.forEach((node) => { + // const display = `${node.ip}:${node.port}` + // const target = { + // kind: 'pd', + // display_name: display, + // ip: node.ip, + // port: node.port, + // } + // map[display] = target + // }) + // return map + // }, [data]) } const profilingDurationsSec = [10, 30, 60, 120] diff --git a/ui/lib/apps/KeyViz/components/KeyVizSettingForm.tsx b/ui/lib/apps/KeyViz/components/KeyVizSettingForm.tsx index 5add68218a..4cfc5963c2 100644 --- a/ui/lib/apps/KeyViz/components/KeyVizSettingForm.tsx +++ b/ui/lib/apps/KeyViz/components/KeyVizSettingForm.tsx @@ -12,7 +12,6 @@ import { import { ExclamationCircleOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import client, { ConfigKeyVisualConfig } from '@lib/client' -import { setHidden } from '@lib/utils/form' const policyConfigurable = process.env.NODE_ENV === 'development' @@ -167,17 +166,23 @@ function KeyVizSettingForm({ onClose, onConfigUpdated }: Props) { {policyOptions} + client.getInstance().getAlertManagerTopology({ cancelToken }) + ) + const { + data: grafanaData, + isLoading: grafanaIsLoading, + } = useClientRequest((cancelToken) => + client.getInstance().getGrafanaTopology({ cancelToken }) + ) + useEffect(() => { - const fetchNum = async () => { - if (!cluster || !cluster.alert_manager) { - return - } + if (!amData) { + return + } + async function fetch() { let resp = await client .getInstance() - .topologyAlertmanagerAddressCountGet( - `${cluster.alert_manager.ip}:${cluster.alert_manager.port}` - ) + .getAlertManagerCounts(`${amData!.ip}:${amData!.port}`) setAlertCounter(resp.data) } - fetchNum() - }, [cluster]) + fetch() + }, [amData]) return ( - -

- {!cluster || !cluster.grafana ? ( - t('overview.monitor_alert.view_monitor_warn') - ) : ( - - {t('overview.monitor_alert.view_monitor')} - + + + {!grafanaData && ( + + + + {t('overview.monitor_alert.view_monitor_warn')} + + + )} + {grafanaData && ( + + + {t('overview.monitor_alert.view_monitor')} + + )} -

-

- {!cluster || !cluster.alert_manager ? ( - t('overview.monitor_alert.view_alerts_warn') - ) : ( - - {alertCounter === 0 - ? t('overview.monitor_alert.view_zero_alerts') - : t('overview.monitor_alert.view_alerts', { - alertCount: alertCounter, - })} - + + + {!amData && ( + + + + {t('overview.monitor_alert.view_alerts_warn')} + + + )} + {amData && ( + + + 0 ? 'danger' : undefined}> + {alertCounter === 0 + ? t('overview.monitor_alert.view_zero_alerts') + : t('overview.monitor_alert.view_alerts', { + alertCount: alertCounter, + })} + + + )} -

-
-

- - {t('overview.monitor_alert.run_diagnose')} - - -

+ +
+ + + {t('overview.monitor_alert.run_diagnose')} + + + +
+
) } diff --git a/ui/lib/apps/Overview/components/Nodes.tsx b/ui/lib/apps/Overview/components/Nodes.tsx index b8e1721e13..639ac12d72 100644 --- a/ui/lib/apps/Overview/components/Nodes.tsx +++ b/ui/lib/apps/Overview/components/Nodes.tsx @@ -1,93 +1,113 @@ import { Link } from 'react-router-dom' import React, { useMemo } from 'react' -import { Card, AnimatedSkeleton } from '@lib/components' +import { Card, AnimatedSkeleton, Descriptions } from '@lib/components' import { useTranslation } from 'react-i18next' import { useClientRequest } from '@lib/utils/useClientRequest' import client from '@lib/client' -import { Alert, Typography } from 'antd' +import { Typography, Row, Col, Space } from 'antd' import { STATUS_UP, STATUS_TOMBSTONE, STATUS_OFFLINE, } from '@lib/apps/ClusterInfo/status/status' -import { RightOutlined } from '@ant-design/icons' +import { RightOutlined, WarningOutlined } from '@ant-design/icons' +import { Stack } from 'office-ui-fabric-react/lib/Stack' -export default function Nodes() { - const { t } = useTranslation() - const { data, isLoading, error } = useClientRequest((cancelToken) => - client.getInstance().topologyAllGet({ cancelToken }) - ) - - const statusMap = useMemo(() => { - if (!data) { - return [] +function ComponentItem(props: { + name: string + resp: { data?: { status?: number }[]; isLoading: boolean; error?: any } +}) { + const { name, resp } = props + const [upNums, allNums] = useMemo(() => { + if (!resp.data) { + return [0, 0] } - const r: any[] = [] - const components = ['tidb', 'tikv', 'tiflash', 'pd'] - components.forEach((componentName) => { - if (!data[componentName]) { - return - } - if (data[componentName].err) { - r.push({ name: componentName, error: true }) - return + let up = 0 + let all = 0 + for (const instance of resp.data) { + all++ + if ( + instance.status === STATUS_UP || + instance.status === STATUS_TOMBSTONE || + instance.status === STATUS_OFFLINE + ) { + up++ } + } + return [up, all] + }, [resp]) - let normals = 0, - abnormals = 0 - data[componentName].nodes.forEach((n) => { - if ( - n.status === STATUS_UP || - n.status === STATUS_TOMBSTONE || - n.status === STATUS_OFFLINE - ) { - normals++ - } else { - abnormals++ - } - }) + return ( + + {!resp.error && ( + + + + {upNums} + / {allNums} + + + + )} + {resp.error && ( + + + Error + + + )} + + ) +} - if (normals > 0 || abnormals > 0) { - r.push({ name: componentName, normals, abnormals }) - } - }) - return r - }, [data]) +export default function Nodes() { + const { t } = useTranslation() + const tidbResp = useClientRequest((cancelToken) => + client.getInstance().getTiDBTopology({ cancelToken }) + ) + const storeResp = useClientRequest((cancelToken) => + client.getInstance().getStoreTopology({ cancelToken }) + ) + const tiKVResp = { + ...storeResp, + data: storeResp.data?.tikv, + } + const tiFlashResp = { + ...storeResp, + data: storeResp.data?.tiflash, + } + const pdResp = useClientRequest((cancelToken) => + client.getInstance().getPDTopology({ cancelToken }) + ) return ( - {t('overview.nodes.title')} + {t('overview.status.title')} } noMarginLeft > - - {error && } - {data && - statusMap.map((s) => { - return ( -

- {t(`overview.nodes.component.${s.name}`)}: - {s.error && ( - Error - )} - {!s.error && ( - - {s.normals} Up /{' '} - 0 ? 'danger' : undefined} - > - {s.abnormals} Down - - - )} -

- ) - })} -
+ + + + + + + + + + + + + + + + + +
) } diff --git a/ui/lib/apps/Overview/index.tsx b/ui/lib/apps/Overview/index.tsx index 7a485c7254..1010d06147 100644 --- a/ui/lib/apps/Overview/index.tsx +++ b/ui/lib/apps/Overview/index.tsx @@ -1,11 +1,10 @@ import { Col, Row } from 'antd' -import React, { useEffect, useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { HashRouter as Router, Link } from 'react-router-dom' import { RightOutlined } from '@ant-design/icons' import { StatementsTable, useStatement } from '@lib/apps/Statement' -import client, { ClusterinfoClusterInfo } from '@lib/client' import { DateTime, MetricChart, Root } from '@lib/components' import SlowQueriesTable from '../SlowQuery/components/SlowQueriesTable' @@ -18,7 +17,6 @@ import Nodes from './components/Nodes' export default function App() { const { t } = useTranslation() - const [cluster, setCluster] = useState(null) const { orderOptions: stmtOrderOptions, changeOrder: changeStmtOrder, @@ -37,18 +35,6 @@ export default function App() { queryTimeRange, } = useSlowQuery({ ...DEF_SLOW_QUERY_OPTIONS, limit: 10 }, false) - useEffect(() => { - const fetchLoad = async () => { - try { - let res = await client.getInstance().topologyAllGet() - setCluster(res.data) - } catch (error) { - setCluster(null) - } - } - fetchLoad() - }, []) - return ( @@ -149,7 +135,7 @@ export default function App() { - + diff --git a/ui/lib/apps/Overview/translations/en.yaml b/ui/lib/apps/Overview/translations/en.yaml index b6c7734533..0b15c4f5c1 100644 --- a/ui/lib/apps/Overview/translations/en.yaml +++ b/ui/lib/apps/Overview/translations/en.yaml @@ -4,6 +4,8 @@ overview: title: Top SQL Statements recent_slow_query: title: Recent Slow Queries + status: + title: Alive Instances monitor_alert: title: Monitor view_monitor: View Metrics @@ -15,10 +17,3 @@ overview: metrics: total_requests: QPS latency: Latency - nodes: - title: Nodes - component: - tidb: TiDB - tikv: TiKV - pd: PD - tiflash: TiFlash diff --git a/ui/lib/apps/Overview/translations/zh-CN.yaml b/ui/lib/apps/Overview/translations/zh-CN.yaml index 622ae01ae6..9abf5b6198 100644 --- a/ui/lib/apps/Overview/translations/zh-CN.yaml +++ b/ui/lib/apps/Overview/translations/zh-CN.yaml @@ -5,9 +5,7 @@ overview: recent_slow_query: title: 最近的慢查询 status: - up: 正常 - abnormal: 异常 - nodes: '{{nodeType}} 节点' + title: 在线实例 monitor_alert: title: 监控告警 view_monitor: 查看监控 @@ -19,10 +17,3 @@ overview: metrics: total_requests: QPS latency: 延迟 - nodes: - title: 节点 - component: - tidb: TiDB - tikv: TiKV - pd: PD - tiflash: TiFlash diff --git a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx index 5edf9dc1f7..cb5a69fedf 100644 --- a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx @@ -14,7 +14,7 @@ import { namingMap, NodeKind, NodeKindList, - parseClusterInfo, + // parseClusterInfo, parseSearchingParams, } from './utils' import { @@ -77,8 +77,10 @@ export default function SearchHeader({ taskGroupID }: Props) { useMount(() => { async function fetchData() { - const res = await client.getInstance().topologyAllGet() - const targets = parseClusterInfo(res.data) + const res = {} as any + // const res = await client.getInstance().topologyAllGet() + // const targets = parseClusterInfo(res.data) + const targets = [] as any setAllTargets(targets) setComponents(targets.map((item) => item.display_name!)) if (!taskGroupID) { diff --git a/ui/lib/apps/SearchLogs/components/utils.ts b/ui/lib/apps/SearchLogs/components/utils.ts index 47cc2819d9..a05b5832d4 100644 --- a/ui/lib/apps/SearchLogs/components/utils.ts +++ b/ui/lib/apps/SearchLogs/components/utils.ts @@ -1,5 +1,4 @@ import { - ClusterinfoClusterInfo, LogsearchTaskGroupResponse, LogsearchTaskModel, ModelRequestTargetNode, @@ -40,65 +39,65 @@ export const namingMap = { export const AllLogLevel = [1, 2, 3, 4, 5, 6] -export function parseClusterInfo( - info: ClusterinfoClusterInfo -): ModelRequestTargetNode[] { - const targets: ModelRequestTargetNode[] = [] - info?.tidb?.nodes?.forEach((item) => { - if ( - item.ip === undefined || - item.port === undefined || - item.status_port === undefined - ) { - return - } - // TiDB has a different behavior: it use "status_port" for grpc, "port" for display. - targets.push({ - kind: NodeKind.TiDB, - ip: item.ip, - port: item.status_port, - display_name: `${item.ip}:${item.port}`, - }) - }) - info?.tikv?.nodes?.forEach((item) => { - if ( - item.ip === undefined || - item.port === undefined || - item.status_port === undefined - ) { - return - } - targets.push({ - kind: NodeKind.TiKV, - ip: item.ip, - port: item.port, - display_name: `${item.ip}:${item.port}`, - }) - }) - info?.pd?.nodes?.forEach((item) => { - if (item.ip === undefined || item.port === undefined) { - return - } - targets.push({ - kind: NodeKind.PD, - ip: item.ip, - port: item.port, - display_name: `${item.ip}:${item.port}`, - }) - }) - info?.tiflash?.nodes?.forEach((item) => { - if (!(item.ip && item.port)) { - return - } - targets.push({ - kind: NodeKind.TiFlash, - ip: item.ip, - port: item.port, - display_name: `${item.ip}:${item.port}`, - }) - }) - return targets -} +// export function parseClusterInfo( +// info: ClusterinfoClusterInfo +// ): ModelRequestTargetNode[] { +// const targets: ModelRequestTargetNode[] = [] +// info?.tidb?.nodes?.forEach((item) => { +// if ( +// item.ip === undefined || +// item.port === undefined || +// item.status_port === undefined +// ) { +// return +// } +// // TiDB has a different behavior: it use "status_port" for grpc, "port" for display. +// targets.push({ +// kind: NodeKind.TiDB, +// ip: item.ip, +// port: item.status_port, +// display_name: `${item.ip}:${item.port}`, +// }) +// }) +// info?.tikv?.nodes?.forEach((item) => { +// if ( +// item.ip === undefined || +// item.port === undefined || +// item.status_port === undefined +// ) { +// return +// } +// targets.push({ +// kind: NodeKind.TiKV, +// ip: item.ip, +// port: item.port, +// display_name: `${item.ip}:${item.port}`, +// }) +// }) +// info?.pd?.nodes?.forEach((item) => { +// if (item.ip === undefined || item.port === undefined) { +// return +// } +// targets.push({ +// kind: NodeKind.PD, +// ip: item.ip, +// port: item.port, +// display_name: `${item.ip}:${item.port}`, +// }) +// }) +// info?.tiflash?.nodes?.forEach((item) => { +// if (!(item.ip && item.port)) { +// return +// } +// targets.push({ +// kind: NodeKind.TiFlash, +// ip: item.ip, +// port: item.port, +// display_name: `${item.ip}:${item.port}`, +// }) +// }) +// return targets +// } interface Params { timeRange: TimeRange diff --git a/ui/lib/components/BaseSelect/index.module.less b/ui/lib/components/BaseSelect/index.module.less new file mode 100644 index 0000000000..8952c4ab24 --- /dev/null +++ b/ui/lib/components/BaseSelect/index.module.less @@ -0,0 +1,89 @@ +@import '~antd/es/style/themes/default.less'; +@import '~antd/es/style/mixins/index'; +@import '~antd/es/input/style/mixin'; +@import '~antd/es/select/style/index'; + +.baseSelect { + .reset-component; + position: relative; + display: inline-block; +} + +.baseSelectInner { + position: relative; + background-color: @select-background; + border: @border-width-base @border-style-base @select-border-color; + border-radius: @border-radius-base; + transition: all 0.3s @ease-in-out; + display: flex; + width: 100%; + height: @input-height-base; + padding: 0 @input-padding-horizontal-base; + cursor: pointer; + color: @text-color; + + &.focused { + .active(); + } + + &.disabled { + color: @disabled-color; + background: @input-disabled-bg; + cursor: not-allowed; + + .baseSelectInput { + cursor: not-allowed; + } + } + + &:not(.disabled):hover { + .hover(); + } +} + +.baseSelectInput { + opacity: 0; + position: absolute; + top: 0; + left: 0; + background: transparent; + border: none; + outline: none; + cursor: pointer; + width: 100%; + height: @select-height-without-border; +} + +.baseSelectValueDisplay { + position: relative; + display: block; + padding-right: @selection-item-padding; + font-weight: normal; + font-size: @select-dropdown-font-size; + line-height: @select-height-without-border; + transition: all 0.3s; + pointer-events: none; + width: 100%; +} + +.baseSelectArrow { + position: absolute; + top: 53%; // The same as Ant-design's select + right: @input-padding-horizontal-base; + width: @font-size-sm; + height: @font-size-sm; + margin-top: -@font-size-sm / 2; + color: @disabled-color; + font-size: @font-size-sm; + line-height: 1; + text-align: center; + pointer-events: none; +} + +.baseSelectOverlay { + background-color: @select-dropdown-bg; + border-radius: @border-radius-base; + outline: none; + box-shadow: @box-shadow-base; + box-sizing: border-box; +} diff --git a/ui/lib/components/BaseSelect/index.tsx b/ui/lib/components/BaseSelect/index.tsx new file mode 100644 index 0000000000..a3ce881009 --- /dev/null +++ b/ui/lib/components/BaseSelect/index.tsx @@ -0,0 +1,183 @@ +import React, { useState, useCallback, useRef } from 'react' +import cx from 'classnames' +import { Dropdown } from 'antd' +import { useEventListener } from '@umijs/hooks' +import { DownOutlined } from '@ant-design/icons' +import KeyCode from 'rc-util/lib/KeyCode' +import { TextWrap } from '..' + +import styles from './index.module.less' + +export interface IBaseSelectDropdownRenderProps { + value?: T + triggerOnChange?: (value: T) => void +} + +export interface IBaseSelectProps + extends Omit, 'onChange'> { + dropdownRender: ( + renderProps: IBaseSelectDropdownRenderProps + ) => React.ReactElement + value?: T + valueRender: (value?: T) => React.ReactNode + onChange?: (value: T) => void + disabled?: boolean + tabIndex?: number + autoFocus?: boolean +} + +function BaseSelect({ + dropdownRender, + value, + valueRender, + onChange, + disabled, + tabIndex, + autoFocus, + className, + onFocus, + onBlur, + onKeyDown, + onMouseDown, + ...restProps +}: IBaseSelectProps) { + const [dropdownVisible, setDropdownVisible] = useState(false) + const toggleDropdownVisible = useCallback(() => { + if (disabled) { + return + } + setDropdownVisible((v) => !v) + }, [disabled]) + + const [isFocused, setFocused] = useState(false) + + const handleContainerFocus = useCallback( + (ev: React.FocusEvent) => { + setFocused(true) + onFocus && onFocus(ev) + }, + [onFocus] + ) + + const handleContainerBlur = useCallback( + (ev: React.FocusEvent) => { + setDropdownVisible(false) + setFocused(false) + onBlur && onBlur(ev) + }, + [onBlur] + ) + + const handleContainerKeyDown = useCallback( + (ev: React.KeyboardEvent) => { + if (ev.which === KeyCode.ENTER) { + toggleDropdownVisible() + } else if (ev.which === KeyCode.ESC) { + setDropdownVisible(false) + } + onKeyDown && onKeyDown(ev) + }, + [toggleDropdownVisible, onKeyDown] + ) + + const handleContainerMouseDown = useCallback( + (ev: React.MouseEvent) => { + toggleDropdownVisible() + onMouseDown && onMouseDown(ev) + }, + [toggleDropdownVisible, onMouseDown] + ) + + const handleOverlayMouseDown = useCallback( + (ev: React.MouseEvent) => { + // Prevent dropdown container blur event + ev.preventDefault() + }, + [] + ) + + const dropdownOverlayRef = useRef(null) + const containerRef = useRef(null) + + const overlay = ( +
+ {dropdownRender({ + value, + triggerOnChange: onChange, + })} +
+ ) + + useEventListener('mousedown', (ev: MouseEvent) => { + // Close the dropdown if click outside + if (!dropdownVisible) { + return + } + const hitElements = [dropdownOverlayRef.current, containerRef.current] + if ( + hitElements.every( + (e) => + !e || + !ev.target || + (!e.contains(ev.target as HTMLElement) && e !== ev.target) + ) + ) { + setDropdownVisible(false) + } + }) + + // Close dropdown when disabled change + React.useEffect(() => { + setDropdownVisible((v) => { + if (v && !disabled) { + return false + } + // Otherwise, unchanged + return v + }) + }, [disabled]) + + return ( + +
+
+ +
+ {valueRender(value)} +
+
+
+ +
+
+
+ ) +} + +BaseSelect.whyDidYouRender = true + +export default React.memo(BaseSelect) diff --git a/ui/lib/components/CardTableV2/GroupHeader.tsx b/ui/lib/components/CardTableV2/GroupHeader.tsx new file mode 100644 index 0000000000..5a2ac87aa9 --- /dev/null +++ b/ui/lib/components/CardTableV2/GroupHeader.tsx @@ -0,0 +1,114 @@ +// FIXME: This is mostly a clone from https://github.com/microsoft/fluentui/blob/master/packages/office-ui-fabric-react/src/components/GroupedList/GroupHeader.base.tsx, but replaced with Ant'd Checkbox +// Drop it after https://github.com/microsoft/fluentui/issues/13144 is resolved + +import React from 'react' +import { + classNamesFunction, + styled, +} from 'office-ui-fabric-react/lib/Utilities' +import { + IGroupHeaderStyleProps, + IGroupHeaderStyles, + IGroupHeaderProps, + GroupSpacer, +} from 'office-ui-fabric-react/lib/GroupedList' +import { + FocusZone, + FocusZoneDirection, +} from 'office-ui-fabric-react/lib/FocusZone' +import { getStyles } from 'office-ui-fabric-react/lib/components/GroupedList/GroupHeader.styles' + +import { Icon } from 'office-ui-fabric-react/lib/Icon' +import { Checkbox } from 'antd' +import { usePersistFn } from '@umijs/hooks' + +const getClassNames = classNamesFunction< + IGroupHeaderStyleProps, + IGroupHeaderStyles +>() + +function BaseAntCheckboxGroupHeader(props: IGroupHeaderProps) { + const _classNames = getClassNames(props.styles, { + theme: props.theme!, + className: props.className, + selected: props.selected, + isCollapsed: props.group?.isCollapsed, + compact: props.compact, + }) + + const _onHeaderClick = usePersistFn(() => { + if (props.onToggleSelectGroup) { + props.onToggleSelectGroup(props.group!) + } + }) + + const _onToggleSelectGroupClick = usePersistFn( + (ev: React.MouseEvent) => { + if (props.onToggleSelectGroup) { + props.onToggleSelectGroup(props.group!) + } + ev.preventDefault() + ev.stopPropagation() + } + ) + + const _onToggleCollapse = usePersistFn( + (ev: React.MouseEvent) => { + if (props.onToggleCollapse) { + props.onToggleCollapse(props.group!) + } + ev.stopPropagation() + ev.preventDefault() + } + ) + + return ( +
+ + + + + +
+ {props.group?.name} +
+
+
+ ) +} + +export const AntCheckboxGroupHeader: React.FunctionComponent = styled< + IGroupHeaderProps, + IGroupHeaderStyleProps, + IGroupHeaderStyles +>(BaseAntCheckboxGroupHeader, getStyles, undefined, { + scope: 'GroupHeader', +}) diff --git a/ui/lib/components/CardTableV2/index.tsx b/ui/lib/components/CardTableV2/index.tsx index 13bb57500f..02107b128d 100644 --- a/ui/lib/components/CardTableV2/index.tsx +++ b/ui/lib/components/CardTableV2/index.tsx @@ -6,20 +6,52 @@ import { IColumn, IDetailsListProps, SelectionMode, + // IDetailsGroupDividerProps, } from 'office-ui-fabric-react/lib/DetailsList' import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky' import React, { useCallback, useEffect, useMemo } from 'react' import { usePersistFn } from '@umijs/hooks' - import AnimatedSkeleton from '../AnimatedSkeleton' import Card from '../Card' + import styles from './index.module.less' +export { AntCheckboxGroupHeader } from './GroupHeader' +// import { GroupSpacer } from 'office-ui-fabric-react/lib/GroupedList' +// import { Icon } from 'office-ui-fabric-react/lib/Icon' + DetailsList.whyDidYouRender = { customName: 'DetailsList', } as any -const MemoDetailsList = React.memo(DetailsList) +function renderStickyHeader(props, defaultRender) { + if (!props) { + return null + } + return ( + +
{defaultRender!(props)}
+
+ ) +} + +function renderCheckbox(props) { + return +} + +export function ImprovedDetailsList(props: IDetailsListProps) { + return ( + + ) +} + +ImprovedDetailsList.whyDidYouRender = true + +export const MemoDetailsList = React.memo(ImprovedDetailsList) function copyAndSort( items: T[], @@ -62,17 +94,6 @@ export interface ICardTableV2Props extends IDetailsListProps { onGetColumns?: (columns: IColumn[]) => void } -function renderStickyHeader(props, defaultRender) { - if (!props) { - return null - } - return ( - -
{defaultRender!(props)}
-
- ) -} - function useRenderClickableRow(onRowClicked) { return useCallback( (props, defaultRender) => { @@ -163,10 +184,6 @@ function CardTableV2(props: ICardTableV2Props) { // eslint-disable-next-line }, [columns]) - const onRenderCheckbox = useCallback((props) => { - return - }, []) - return ( , +} + +export default function DropOverlay({ + selection, + columns, + items, + groups, +}: { + selection: Selection + columns: IColumn[] + items: IInstanceTableItem[] + groups: IGroup[] +}) { + const [containerState, containerRef] = useSize() + return ( +
+ +
+ + + +
+
+
+ ) +} diff --git a/ui/lib/components/InstanceSelect/SelectDisplay.tsx b/ui/lib/components/InstanceSelect/SelectDisplay.tsx new file mode 100644 index 0000000000..0337e966e2 --- /dev/null +++ b/ui/lib/components/InstanceSelect/SelectDisplay.tsx @@ -0,0 +1,68 @@ +import React, { useMemo } from 'react' +import { IInstanceTableItem, InstanceKind } from '@lib/utils/instanceTable' +import { Typography } from 'antd' + +interface InstanceStat { + all: number + selected: number +} + +function newInstanceStat(): InstanceStat { + return { + all: 0, + selected: 0, + } +} + +export default function SelectDisplay({ + items, + selectedKeys, +}: { + items: IInstanceTableItem[] + selectedKeys: string[] +}) { + const text = useMemo(() => { + const selectedKeysMap = {} + selectedKeys.forEach((key) => (selectedKeysMap[key] = true)) + const instanceStats: { [key in InstanceKind]: InstanceStat } = { + pd: newInstanceStat(), + tidb: newInstanceStat(), + tikv: newInstanceStat(), + tiflash: newInstanceStat(), + } + items.forEach((item) => { + instanceStats[item.instanceKind].all++ + if (selectedKeysMap[item.key]) { + instanceStats[item.instanceKind].selected++ + } + }) + + let hasUnselected = false + const p: string[] = [] + for (const ik in instanceStats) { + const stats = instanceStats[ik] as InstanceStat + if (stats.selected !== stats.all) { + hasUnselected = true + } + if (stats.selected > 0) { + if (stats.all === stats.selected) { + p.push('All ' + ik) + } else { + p.push(`${stats.selected} ${ik}`) + } + } + } + + if (!hasUnselected) { + return 'All Instances' + } + return p.join(', ') + }, [items, selectedKeys]) + + if (items.length === 0 || selectedKeys.length === 0) { + // Not yet loaded + return Select Instance + } else { + return {text} + } +} diff --git a/ui/lib/components/InstanceSelect/index.tsx b/ui/lib/components/InstanceSelect/index.tsx new file mode 100644 index 0000000000..9106b6c8ff --- /dev/null +++ b/ui/lib/components/InstanceSelect/index.tsx @@ -0,0 +1,165 @@ +import React, { useCallback, useRef, useMemo, useEffect, useState } from 'react' +import { Tooltip, Typography } from 'antd' +import { Selection } from 'office-ui-fabric-react/lib/Selection' +import { BaseSelect, InstanceStatusBadge, TextWrap } from '../' +import { useClientRequest } from '@lib/utils/useClientRequest' +import client from '@lib/client' +import { usePersistFn } from '@umijs/hooks' +import { IColumn } from 'office-ui-fabric-react/lib/DetailsList' +import { + buildInstanceTable, + IInstanceTableItem, +} from '@lib/utils/instanceTable' + +import DropOverlay from './DropOverlay' +import SelectDisplay from './SelectDisplay' + +export interface IInstanceSelectProps { + enableTiFlash?: boolean + defaultSelectAll?: boolean + onChange?: (values: string[]) => void + value?: string[] +} + +export interface IInstanceSelectRefProps { + mapValueToNode: (value: string) => void +} + +function InstanceSelect( + props: IInstanceSelectProps, + ref: React.Ref +) { + const { + data: dataTiDB, + isLoading: loadingTiDB, + } = useClientRequest((cancelToken) => + client.getInstance().getTiDBTopology({ cancelToken }) + ) + const { + data: dataStores, + isLoading: loadingStores, + } = useClientRequest((cancelToken) => + client.getInstance().getStoreTopology({ cancelToken }) + ) + const { + data: dataPD, + isLoading: loadingPD, + } = useClientRequest((cancelToken) => + client.getInstance().getPDTopology({ cancelToken }) + ) + + const columns = useMemo(() => { + const c: IColumn[] = [ + { + name: 'Instance', + key: 'key', + minWidth: 160, + maxWidth: 160, + onRender: (node: IInstanceTableItem) => { + return ( + + + {node.key} + + + ) + }, + }, + { + name: 'Status', + key: 'status', + minWidth: 100, + maxWidth: 100, + onRender: (node: IInstanceTableItem) => { + return ( + + + + ) + }, + }, + ] + return c + }, []) + + const [tableItems, tableGroups] = useMemo(() => { + if (loadingTiDB || loadingStores || loadingPD) { + return [[], []] + } + return buildInstanceTable({ + dataPD, + dataTiDB, + dataTiKV: dataStores?.tikv, + dataTiFlash: dataStores?.tiflash, + includeTiFlash: props.enableTiFlash, + }) + }, [ + props.enableTiFlash, + props.defaultSelectAll, + dataTiDB, + dataStores, + dataPD, + loadingTiDB, + loadingStores, + loadingPD, + ]) + + const [selectedKeys, setSelectedKeys] = useState(props.value ?? []) + + const selection = useRef( + new Selection({ + onSelectionChanged: () => { + const s = selection.current.getSelection() as IInstanceTableItem[] + setSelectedKeys(s.map((v) => v.key)) + }, + }) + ) + + const dataHasLoaded = useRef(false) + + useEffect(() => { + // Select all if `defaultSelectAll` is set. + if (dataHasLoaded.current) { + return + } + if (tableItems.length === 0) { + return + } + if (props.defaultSelectAll) { + selection.current.setItems(tableItems) + selection.current.setAllSelected(true) + } + dataHasLoaded.current = true + }, [tableItems]) + + const mapValueToNode = usePersistFn(() => {}) + + React.useImperativeHandle(ref, () => ({ + mapValueToNode, + })) + + const renderValue = useCallback(() => { + return + }, [tableItems, selectedKeys]) + + const renderDropdown = useCallback(() => { + return ( + + ) + }, [columns, tableItems, tableGroups]) + + return ( + + ) +} + +export default React.forwardRef(InstanceSelect) diff --git a/ui/lib/components/InstanceStatusBadge/index.tsx b/ui/lib/components/InstanceStatusBadge/index.tsx new file mode 100644 index 0000000000..3d3a835572 --- /dev/null +++ b/ui/lib/components/InstanceStatusBadge/index.tsx @@ -0,0 +1,83 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { InstanceStatus } from '@lib/utils/instanceTable' +import { Badge } from 'antd' +import { addTranslationResource } from '@lib/utils/i18n' + +const translations = { + en: { + status: { + up: 'Up', + down: 'Down', + tombstone: 'Tombstone', + offline: 'Offline', + unknown: 'Unknown', + unreachable: 'Unreachable', + }, + }, + 'zh-CN': { + status: { + up: '在线', + down: '离线', + tombstone: '已缩容下线', + offline: '下线中', + unknown: '未知', + unreachable: '无法访问', + }, + }, +} + +for (const key in translations) { + addTranslationResource(key, { + component: { + instanceStatusBadge: translations[key], + }, + }) +} + +export interface IInstanceStatusBadgeProps { + status?: number +} + +function InstanceStatusBadge({ status }: IInstanceStatusBadgeProps) { + const { t } = useTranslation() + switch (status) { + case InstanceStatus.Down: + return ( + + ) + case InstanceStatus.Up: + return ( + + ) + case InstanceStatus.Tombstone: + return ( + + ) + case InstanceStatus.Offline: + return ( + + ) + default: + return ( + + ) + } +} + +export default React.memo(InstanceStatusBadge) diff --git a/ui/lib/components/index.ts b/ui/lib/components/index.ts index 33823aef9f..80637c04d1 100644 --- a/ui/lib/components/index.ts +++ b/ui/lib/components/index.ts @@ -38,5 +38,11 @@ export * from './TimeRangeSelector' export { default as TimeRangeSelector } from './TimeRangeSelector' export * from './AnimatedSkeleton' export { default as AnimatedSkeleton } from './AnimatedSkeleton' +export * from './InstanceStatusBadge' +export { default as InstanceStatusBadge } from './InstanceStatusBadge' +export * from './BaseSelect' +export { default as BaseSelect } from './BaseSelect' +export * from './InstanceSelect' +export { default as InstanceSelect } from './InstanceSelect' export { default as LanguageDropdown } from './LanguageDropdown' diff --git a/ui/lib/utils/form.ts b/ui/lib/utils/form.ts deleted file mode 100644 index 059704099c..0000000000 --- a/ui/lib/utils/form.ts +++ /dev/null @@ -1,11 +0,0 @@ -const hiddenProps = { - style: { - display: 'none', - }, -} - -const showProps = {} - -export function setHidden(hidden: boolean) { - return hidden ? hiddenProps : showProps -} diff --git a/ui/lib/utils/instanceTable.ts b/ui/lib/utils/instanceTable.ts new file mode 100644 index 0000000000..3d40bccaa6 --- /dev/null +++ b/ui/lib/utils/instanceTable.ts @@ -0,0 +1,92 @@ +import { + TopologyPDInfo, + TopologyTiDBInfo, + TopologyStoreInfo, +} from '@lib/client' +import { IGroup } from 'office-ui-fabric-react/lib/DetailsList' + +export type InstanceKind = 'pd' | 'tidb' | 'tikv' | 'tiflash' + +export const InstanceStatus = { + Unreachable: 0, + Up: 1, + Tombstone: 2, + Offline: 3, + Down: 4, +} + +export const InstanceKindName: { [key in InstanceKind]: string } = { + pd: 'PD', + tidb: 'TiDB', + tikv: 'TiKV', + tiflash: 'TiFlash', +} + +export interface IInstanceTableItem + extends TopologyPDInfo, + TopologyTiDBInfo, + TopologyStoreInfo { + key: string + instanceKind: InstanceKind +} + +export interface IBuildInstanceTableProps { + dataPD?: TopologyPDInfo[] + dataTiDB?: TopologyTiDBInfo[] + dataTiKV?: TopologyStoreInfo[] + dataTiFlash?: TopologyStoreInfo[] + includeTiFlash?: boolean + filterHost?: string +} + +export function buildInstanceTable({ + dataPD, + dataTiDB, + dataTiKV, + dataTiFlash, + includeTiFlash, + filterHost, +}: IBuildInstanceTableProps): [IInstanceTableItem[], IGroup[]] { + const tableData: IInstanceTableItem[] = [] + const groupData: IGroup[] = [] + let startIndex = 0 + const kinds: [ + InstanceKind, + TopologyPDInfo[] | TopologyTiDBInfo[] | TopologyStoreInfo[] | undefined + ][] = [ + ['pd', dataPD], + ['tidb', dataTiDB], + ['tikv', dataTiKV], + ] + if (includeTiFlash) { + kinds.push(['tiflash', dataTiFlash]) + } + for (const item of kinds) { + const [ik, instances] = item + if (!instances || instances.length === 0) { + continue + } + groupData.push({ + key: ik, + name: InstanceKindName[ik], + startIndex: startIndex, + count: instances.length, + level: 0, + }) + startIndex += instances.length + instances.forEach((instance) => { + const key = `${instance.ip}:${instance.port}` + if (filterHost != null && filterHost.length > 0) { + if (key.indexOf(filterHost) === -1) { + return + } + } + tableData.push({ + key: key, + instanceKind: ik, + ...instance, + }) + }) + } + return [tableData, groupData] +} diff --git a/ui/lib/utils/useClientRequest.ts b/ui/lib/utils/useClientRequest.ts index b625167d6d..26bc6aca7e 100644 --- a/ui/lib/utils/useClientRequest.ts +++ b/ui/lib/utils/useClientRequest.ts @@ -1,4 +1,4 @@ -import { useMount, useUnmount } from '@umijs/hooks' +import { useMount, useUnmount, usePersistFn } from '@umijs/hooks' import { useState, useRef, useEffect } from 'react' import { CancelToken, AxiosPromise, CancelTokenSource } from 'axios' import axios from 'axios' @@ -34,7 +34,7 @@ export function useClientRequest( const cancelTokenSource = useRef(null) const mounted = useRef(false) - const sendRequest = async () => { + const sendRequest = usePersistFn(async () => { if (!mounted.current) { return } @@ -72,7 +72,7 @@ export function useClientRequest( cancelTokenSource.current = null afterRequest && afterRequest() - } + }) useMount(() => { mounted.current = true @@ -138,7 +138,7 @@ export function useBatchClientRequest( } } - const sendRequest = async () => { + const sendRequest = usePersistFn(async () => { if (!mounted.current) { return } @@ -167,7 +167,7 @@ export function useBatchClientRequest( cancelTokenSource.current = null afterRequest && afterRequest() - } + }) useMount(() => { mounted.current = true From cc1ce633d51473462e13ed463d036f918f7b3f71 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Fri, 15 May 2020 10:42:48 +0800 Subject: [PATCH 06/14] Adjust vagrant files Signed-off-by: Breezewish --- etc/manualTestEnv/.gitignore | 3 +- .../Vagrantfile.partial.pubKey.ruby} | 11 ++---- etc/manualTestEnv/_shared/vagrant_key | 27 +++++++++++++ etc/manualTestEnv/_shared/vagrant_key.pub | 1 + etc/manualTestEnv/basic/README.md | 19 --------- etc/manualTestEnv/basicMultiHost/Vagrantfile | 23 ----------- .../{basicMultiHost => multiHost}/README.md | 19 ++++----- etc/manualTestEnv/multiHost/Vagrantfile | 14 +++++++ .../topology.yaml | 9 +++++ etc/manualTestEnv/multiReplica/README.md | 17 ++++---- etc/manualTestEnv/multiReplica/Vagrantfile | 15 ++----- etc/manualTestEnv/singleHost/README.md | 36 +++++++++++++++++ etc/manualTestEnv/singleHost/Vagrantfile | 10 +++++ etc/manualTestEnv/singleHost/topology.yaml | 37 ++++++++++++++++++ .../singleHostMultiDisk/.gitignore | 1 + .../singleHostMultiDisk/README.md | 36 +++++++++++++++++ .../singleHostMultiDisk/Vagrantfile | 10 +++++ .../singleHostMultiDisk/topology.yaml | 39 +++++++++++++++++++ .../apps/ClusterInfo/components/HostTable.tsx | 9 ++++- 19 files changed, 254 insertions(+), 82 deletions(-) rename etc/manualTestEnv/{basic/Vagrantfile => _shared/Vagrantfile.partial.pubKey.ruby} (55%) create mode 100644 etc/manualTestEnv/_shared/vagrant_key create mode 100644 etc/manualTestEnv/_shared/vagrant_key.pub delete mode 100644 etc/manualTestEnv/basic/README.md delete mode 100644 etc/manualTestEnv/basicMultiHost/Vagrantfile rename etc/manualTestEnv/{basicMultiHost => multiHost}/README.md (54%) create mode 100644 etc/manualTestEnv/multiHost/Vagrantfile rename etc/manualTestEnv/{basicMultiHost => multiHost}/topology.yaml (56%) create mode 100644 etc/manualTestEnv/singleHost/README.md create mode 100644 etc/manualTestEnv/singleHost/Vagrantfile create mode 100644 etc/manualTestEnv/singleHost/topology.yaml create mode 100644 etc/manualTestEnv/singleHostMultiDisk/.gitignore create mode 100644 etc/manualTestEnv/singleHostMultiDisk/README.md create mode 100644 etc/manualTestEnv/singleHostMultiDisk/Vagrantfile create mode 100644 etc/manualTestEnv/singleHostMultiDisk/topology.yaml diff --git a/etc/manualTestEnv/.gitignore b/etc/manualTestEnv/.gitignore index dd2a4be7ca..2f8e58cd1e 100644 --- a/etc/manualTestEnv/.gitignore +++ b/etc/manualTestEnv/.gitignore @@ -1,3 +1,2 @@ .vagrant/ -shared_key -shared_key.pub +tiup-cluster-*.log diff --git a/etc/manualTestEnv/basic/Vagrantfile b/etc/manualTestEnv/_shared/Vagrantfile.partial.pubKey.ruby similarity index 55% rename from etc/manualTestEnv/basic/Vagrantfile rename to etc/manualTestEnv/_shared/Vagrantfile.partial.pubKey.ruby index 1dfffd2ee7..3af2b17570 100644 --- a/etc/manualTestEnv/basic/Vagrantfile +++ b/etc/manualTestEnv/_shared/Vagrantfile.partial.pubKey.ruby @@ -1,15 +1,12 @@ Vagrant.configure("2") do |config| + ssh_pub_key = File.readlines("#{File.dirname(__FILE__)}/vagrant_key.pub").first.strip + config.vm.box = "hashicorp/bionic64" - config.vm.network "private_network", ip: "10.0.1.2" config.vm.provision "shell", privileged: false, inline: <<-SHELL sudo apt install -y zsh sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" sudo chsh -s /usr/bin/zsh vagrant - curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh - source /home/vagrant/.zshrc + + echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys SHELL - config.vm.provider "virtualbox" do |v| - v.memory = 4096 - v.cpus = 2 - end end diff --git a/etc/manualTestEnv/_shared/vagrant_key b/etc/manualTestEnv/_shared/vagrant_key new file mode 100644 index 0000000000..7b55495744 --- /dev/null +++ b/etc/manualTestEnv/_shared/vagrant_key @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxboZzYumqNoVOQ/hKKhIZHxNhf5tmnkLZry8i6Xur4FPLDiRxos/ +xVVDx0ynTPOyQVVaXtNxZnAmbR4HuNBzRvNoklwSXazt5YgWeiKCHtPpKFt3PJeE2cn6FJ +p6F6qFChG0NSPbZxJWWxv4noX0U3PLKgHNIehYK2Fu0E6plhSZazzJEVWapwo9d7aGnAsz +bBCd5TNZ5ogrXn+3bSFcdCbAfWOwYg54a+PzTQlzgt6JmhlEjpFfPhhpBW92pQXxmQ2c17 +iPCbA8G++FiaEwA5teex8k1+HzmHf7YjyhPr+I67EzEiIueJg2+0PYbM1p06S8kVTNDXsf +0eJx4Dr8qQAAA9iFPcpVhT3KVQAAAAdzc2gtcnNhAAABAQDFuhnNi6ao2hU5D+EoqEhkfE +2F/m2aeQtmvLyLpe6vgU8sOJHGiz/FVUPHTKdM87JBVVpe03FmcCZtHge40HNG82iSXBJd +rO3liBZ6IoIe0+koW3c8l4TZyfoUmnoXqoUKEbQ1I9tnElZbG/iehfRTc8sqAc0h6FgrYW +7QTqmWFJlrPMkRVZqnCj13toacCzNsEJ3lM1nmiCtef7dtIVx0JsB9Y7BiDnhr4/NNCXOC +3omaGUSOkV8+GGkFb3alBfGZDZzXuI8JsDwb74WJoTADm157HyTX4fOYd/tiPKE+v4jrsT +MSIi54mDb7Q9hszWnTpLyRVM0Nex/R4nHgOvypAAAAAwEAAQAAAQBtk0+/YDgQ9SKzx8AQ +xwmvXk+cBT76T0BpRAj9HwziiDe3GvZ2YC8MDc+NAEbq11ae7E0zpdv/WAGDkRPYcPShij +0Wdx3aef4wqLVEJCGWMfvRWLcAhjuiclM73cvxl5c42EzU8jUhrsDapuql9zhKky4w7mSe ++OL7z3gYyq8isvcQMe+1eXJqiv27AJJfAir+rLJZO/gDW36hOowhnZxYRlVYPgZ8GwetxD +VdCrgwUgR/2HYmbXYdVxI0PwswGc6rEqs5XXOYRzwvPTvRKdD3J5MxmsvJljT7FMr4kCLT +X1+aWysk1cgAUIdzzwQL8DLE/N9PFFYdZyNBkZMgedl9AAAAgCtP3F8XYFR18gQLPGLDyQ +FFg8+JHN9b/yIg2pymC6SI8qEp+GnuEK9IKhqh/Uw14KEKcs/9sgbZo0K9uTBTDG5F6Qmp +hADVbWXJ/97Xeya6kH2Sa56UKLCQ/uQWBKwLQ0auU/qwxATIZowh31XUXjzVBg6wgUjT7Q ++3Fk1zGYxnAAAAgQD5USIRUNwkI+htv+f1g8QdmrFAGymcGEkXAixKvBTon9cWQb2iyiK+ +2IO8EwFwRdL5kw2foILCnlp/4FevfxHU7wTcoFEp3PItUlcxYqO8vY2VCZ913oNLKBIt9p +uFfG2BZM5szMRNMh0svelu61FePsfN5Z8J0ltPrS8UKB95ywAAAIEAywbyNbjz1AxEjWIX +2Vbk4/MjQyjui8Wi7H0F+LDWyMfPJHzhnbr79Z/lIZmDAo++3EYU9J9s0C+wJ6vXGK+gvC +7e5qGfT/0J0DwBfLbpeTdDELCa/LmfLWVPzZ9Q+9Fq0AjmW9YXFZ/+qT9xfY1v9XfztFRS +xR1iXJ42q6ff5NsAAAAeYnJlZXpld2lzaEBCcmVlemV3aXNoTUJQLmxvY2FsAQIDBAU= +-----END OPENSSH PRIVATE KEY----- diff --git a/etc/manualTestEnv/_shared/vagrant_key.pub b/etc/manualTestEnv/_shared/vagrant_key.pub new file mode 100644 index 0000000000..e9962c03b1 --- /dev/null +++ b/etc/manualTestEnv/_shared/vagrant_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFuhnNi6ao2hU5D+EoqEhkfE2F/m2aeQtmvLyLpe6vgU8sOJHGiz/FVUPHTKdM87JBVVpe03FmcCZtHge40HNG82iSXBJdrO3liBZ6IoIe0+koW3c8l4TZyfoUmnoXqoUKEbQ1I9tnElZbG/iehfRTc8sqAc0h6FgrYW7QTqmWFJlrPMkRVZqnCj13toacCzNsEJ3lM1nmiCtef7dtIVx0JsB9Y7BiDnhr4/NNCXOC3omaGUSOkV8+GGkFb3alBfGZDZzXuI8JsDwb74WJoTADm157HyTX4fOYd/tiPKE+v4jrsTMSIi54mDb7Q9hszWnTpLyRVM0Nex/R4nHgOvyp diff --git a/etc/manualTestEnv/basic/README.md b/etc/manualTestEnv/basic/README.md deleted file mode 100644 index 0e8040f179..0000000000 --- a/etc/manualTestEnv/basic/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# basic - -An environment with TiDB, PD, TiKV, TiFlash. - -## Usage - -1. Start the box and cluster: - - ```bash - vagrant up - vagrant ssh - tiup playground nightly --monitor --host 10.0.1.2 - ``` - -2. Outside the box, start TiDB Dashboard server: - - ```bash - bin/tidb-dashboard --pd http://10.0.1.2:2379 - ``` diff --git a/etc/manualTestEnv/basicMultiHost/Vagrantfile b/etc/manualTestEnv/basicMultiHost/Vagrantfile deleted file mode 100644 index 87bb599f69..0000000000 --- a/etc/manualTestEnv/basicMultiHost/Vagrantfile +++ /dev/null @@ -1,23 +0,0 @@ -Vagrant.configure("2") do |config| - ssh_pub_key = File.readlines("#{File.dirname(__FILE__)}/shared_key.pub").first.strip - - config.vm.box = "hashicorp/bionic64" - config.vm.provision "shell", privileged: false, inline: <<-SHELL - sudo apt install -y zsh - sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" - sudo chsh -s /usr/bin/zsh vagrant - - echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys - SHELL - - config.vm.provider "virtualbox" do |v| - v.memory = 1024 - v.cpus = 1 - end - - (1..4).each do |i| - config.vm.define "node #{i}" do |node| - node.vm.network "private_network", ip: "10.0.1.#{i+10}" - end - end -end diff --git a/etc/manualTestEnv/basicMultiHost/README.md b/etc/manualTestEnv/multiHost/README.md similarity index 54% rename from etc/manualTestEnv/basicMultiHost/README.md rename to etc/manualTestEnv/multiHost/README.md index 0af0747a52..d7ba3c2985 100644 --- a/etc/manualTestEnv/basicMultiHost/README.md +++ b/etc/manualTestEnv/multiHost/README.md @@ -1,15 +1,9 @@ -# basicMultiHost +# multiHost -An environment with TiDB, PD, TiKV, TiFlash, each in different host. +TiDB, PD, TiKV, TiFlash each in different hosts. ## Usage -1. Generate shared SSH key (only need to do it once): - - ```bash - ssh-keygen -t rsa -b 2048 -f ./shared_key -q -N "" - ``` - 1. Start the box: ```bash @@ -19,7 +13,7 @@ An environment with TiDB, PD, TiKV, TiFlash, each in different host. 1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): ```bash - tiup cluster deploy multiHost nightly topology.yaml -i shared_key -y --user vagrant + tiup cluster deploy multiHost nightly topology.yaml -i ../_shared/vagrant_key -y --user vagrant ``` 1. Start the cluster in the box: @@ -33,3 +27,10 @@ An environment with TiDB, PD, TiKV, TiFlash, each in different host. ```bash bin/tidb-dashboard --pd http://10.0.1.11:2379 ``` + +## Cleanup + +```bash +tiup cluster destroy multiHost -y +vagrant destroy --force +``` diff --git a/etc/manualTestEnv/multiHost/Vagrantfile b/etc/manualTestEnv/multiHost/Vagrantfile new file mode 100644 index 0000000000..9f4890fdd1 --- /dev/null +++ b/etc/manualTestEnv/multiHost/Vagrantfile @@ -0,0 +1,14 @@ +load "#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.ruby" + +Vagrant.configure("2") do |config| + config.vm.provider "virtualbox" do |v| + v.memory = 1024 + v.cpus = 1 + end + + (1..4).each do |i| + config.vm.define "node#{i}" do |node| + node.vm.network "private_network", ip: "10.0.1.#{i+10}" + end + end +end diff --git a/etc/manualTestEnv/basicMultiHost/topology.yaml b/etc/manualTestEnv/multiHost/topology.yaml similarity index 56% rename from etc/manualTestEnv/basicMultiHost/topology.yaml rename to etc/manualTestEnv/multiHost/topology.yaml index f2039e4ca3..0129db2ca5 100644 --- a/etc/manualTestEnv/basicMultiHost/topology.yaml +++ b/etc/manualTestEnv/multiHost/topology.yaml @@ -4,6 +4,15 @@ global: data_dir: tidb-data server_configs: + tikv: + server.grpc-concurrency: 1 + raftstore.apply-pool-size: 1 + raftstore.store-pool-size: 1 + readpool.unified.max-thread-count: 1 + readpool.storage.use-unified-pool: false + readpool.coprocessor.use-unified-pool: true + storage.block-cache.capacity: 256MB + raftstore.capacity: 10GB pd: replication.enable-placement-rules: true diff --git a/etc/manualTestEnv/multiReplica/README.md b/etc/manualTestEnv/multiReplica/README.md index 2c139afff3..6e1717809d 100644 --- a/etc/manualTestEnv/multiReplica/README.md +++ b/etc/manualTestEnv/multiReplica/README.md @@ -1,15 +1,9 @@ # multiReplica -An environment with multiple TiKV nodes in different labels. +Multiple TiKV nodes in different labels. ## Usage -1. Generate shared SSH key (only need to do it once): - - ```bash - ssh-keygen -t rsa -b 2048 -f ./shared_key -q -N "" - ``` - 1. Start the box: ```bash @@ -19,7 +13,7 @@ An environment with multiple TiKV nodes in different labels. 1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): ```bash - tiup cluster deploy multiReplica nightly topology.yaml -i shared_key -y --user vagrant + tiup cluster deploy multiReplica nightly topology.yaml -i ../_shared/vagrant_key -y --user vagrant ``` 1. Start the cluster in the box: @@ -33,3 +27,10 @@ An environment with multiple TiKV nodes in different labels. ```bash bin/tidb-dashboard --pd http://10.0.1.20:2379 ``` + +## Cleanup + +```bash +tiup cluster destroy multiReplica -y +vagrant destroy --force +``` diff --git a/etc/manualTestEnv/multiReplica/Vagrantfile b/etc/manualTestEnv/multiReplica/Vagrantfile index 5a0e123cff..82098283c1 100644 --- a/etc/manualTestEnv/multiReplica/Vagrantfile +++ b/etc/manualTestEnv/multiReplica/Vagrantfile @@ -1,17 +1,8 @@ -Vagrant.configure("2") do |config| - ssh_pub_key = File.readlines("#{File.dirname(__FILE__)}/shared_key.pub").first.strip - - config.vm.box = "hashicorp/bionic64" - config.vm.provision "shell", privileged: false, inline: <<-SHELL - sudo apt install -y zsh - sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" - sudo chsh -s /usr/bin/zsh vagrant - - echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys - SHELL +load "#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.ruby" +Vagrant.configure("2") do |config| config.vm.provider "virtualbox" do |v| - v.memory = 4096 + v.memory = 4 * 1024 v.cpus = 2 end diff --git a/etc/manualTestEnv/singleHost/README.md b/etc/manualTestEnv/singleHost/README.md new file mode 100644 index 0000000000..e7a52a5c14 --- /dev/null +++ b/etc/manualTestEnv/singleHost/README.md @@ -0,0 +1,36 @@ +# singleHost + +TiDB, PD, TiKV, TiFlash in the same host. + +## Usage + +1. Start the box: + + ```bash + vagrant up + ``` + +1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): + + ```bash + tiup cluster deploy singleHost nightly topology.yaml -i ../_shared/vagrant_key -y --user vagrant + ``` + +1. Start the cluster in the box: + + ```bash + tiup cluster start singleHost + ``` + +1. Start TiDB Dashboard server: + + ```bash + bin/tidb-dashboard --pd http://10.0.1.2:2379 + ``` + +## Cleanup + +```bash +tiup cluster destroy singleHost -y +vagrant destroy --force +``` diff --git a/etc/manualTestEnv/singleHost/Vagrantfile b/etc/manualTestEnv/singleHost/Vagrantfile new file mode 100644 index 0000000000..77d49ffdea --- /dev/null +++ b/etc/manualTestEnv/singleHost/Vagrantfile @@ -0,0 +1,10 @@ +load "#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.ruby" + +Vagrant.configure("2") do |config| + config.vm.provider "virtualbox" do |v| + v.memory = 3 * 1024 + v.cpus = 2 + end + + config.vm.network "private_network", ip: "10.0.1.2" +end diff --git a/etc/manualTestEnv/singleHost/topology.yaml b/etc/manualTestEnv/singleHost/topology.yaml new file mode 100644 index 0000000000..bf79c98bff --- /dev/null +++ b/etc/manualTestEnv/singleHost/topology.yaml @@ -0,0 +1,37 @@ +global: + user: tidb + deploy_dir: tidb-deploy + data_dir: tidb-data + +server_configs: + tikv: + server.grpc-concurrency: 1 + raftstore.apply-pool-size: 1 + raftstore.store-pool-size: 1 + readpool.unified.max-thread-count: 1 + readpool.storage.use-unified-pool: false + readpool.coprocessor.use-unified-pool: true + storage.block-cache.capacity: 256MB + pd: + replication.enable-placement-rules: true + +pd_servers: + - host: 10.0.1.2 + +tikv_servers: + - host: 10.0.1.2 + +tidb_servers: + - host: 10.0.1.2 + +tiflash_servers: + - host: 10.0.1.2 + +grafana_servers: + - host: 10.0.1.2 + +monitoring_servers: + - host: 10.0.1.2 + +alertmanager_servers: + - host: 10.0.1.2 diff --git a/etc/manualTestEnv/singleHostMultiDisk/.gitignore b/etc/manualTestEnv/singleHostMultiDisk/.gitignore new file mode 100644 index 0000000000..8fce603003 --- /dev/null +++ b/etc/manualTestEnv/singleHostMultiDisk/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/etc/manualTestEnv/singleHostMultiDisk/README.md b/etc/manualTestEnv/singleHostMultiDisk/README.md new file mode 100644 index 0000000000..f32e4a59f6 --- /dev/null +++ b/etc/manualTestEnv/singleHostMultiDisk/README.md @@ -0,0 +1,36 @@ +# singleHostMultiDisk + +All instances in a single host, but on different disks. + +## Usage + +1. Start the box: + + ```bash + vagrant up + ``` + +1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once): + + ```bash + tiup cluster deploy singleHostMultiDisk nightly topology.yaml -i ../_shared/vagrant_key -y --user vagrant + ``` + +1. Start the cluster in the box: + + ```bash + tiup cluster start singleHostMultiDisk + ``` + +1. Start TiDB Dashboard server: + + ```bash + bin/tidb-dashboard --pd http://10.0.1.3:2379 + ``` + +## Cleanup + +```bash +tiup cluster destroy singleHostMultiDisk -y +vagrant destroy --force +``` diff --git a/etc/manualTestEnv/singleHostMultiDisk/Vagrantfile b/etc/manualTestEnv/singleHostMultiDisk/Vagrantfile new file mode 100644 index 0000000000..971db113cc --- /dev/null +++ b/etc/manualTestEnv/singleHostMultiDisk/Vagrantfile @@ -0,0 +1,10 @@ +load "#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.ruby" + +Vagrant.configure("2") do |config| + config.vm.provider "virtualbox" do |v| + v.memory = 3 * 1024 + v.cpus = 2 + end + + config.vm.network "private_network", ip: "10.0.1.3" +end diff --git a/etc/manualTestEnv/singleHostMultiDisk/topology.yaml b/etc/manualTestEnv/singleHostMultiDisk/topology.yaml new file mode 100644 index 0000000000..516de7cb5c --- /dev/null +++ b/etc/manualTestEnv/singleHostMultiDisk/topology.yaml @@ -0,0 +1,39 @@ +global: + user: vagrant + deploy_dir: tidb-deploy + data_dir: tidb-data + +server_configs: + tikv: + server.grpc-concurrency: 1 + raftstore.apply-pool-size: 1 + raftstore.store-pool-size: 1 + readpool.unified.max-thread-count: 1 + readpool.storage.use-unified-pool: false + readpool.coprocessor.use-unified-pool: true + storage.block-cache.capacity: 256MB + pd: + replication.enable-placement-rules: true + +pd_servers: + - host: 10.0.1.3 + +tikv_servers: + - host: 10.0.1.3 + +tidb_servers: + - host: 10.0.1.3 + deploy_dir: /vagrant/data/tidb + log_dir: /vagrant/data/tidb/log + +tiflash_servers: + - host: 10.0.1.3 + +grafana_servers: + - host: 10.0.1.3 + +monitoring_servers: + - host: 10.0.1.3 + +alertmanager_servers: + - host: 10.0.1.3 diff --git a/ui/lib/apps/ClusterInfo/components/HostTable.tsx b/ui/lib/apps/ClusterInfo/components/HostTable.tsx index 0e73da8cc9..4f7d9cf595 100644 --- a/ui/lib/apps/ClusterInfo/components/HostTable.tsx +++ b/ui/lib/apps/ClusterInfo/components/HostTable.tsx @@ -138,7 +138,7 @@ export default function HostTable() { pd: 0, tiflash: 0, } - return filterUniquePartitions(partitions).map((partition) => { + return filterUniquePartitions(partitions).map((partition, i) => { const currentMountPoint = partition.partition.path partitions.forEach((item) => { if (item.partition.path !== currentMountPoint) { @@ -159,9 +159,14 @@ export default function HostTable() { if (serverTotal.tiflash > 0) { serverInfos.push(`${serverTotal.tiflash} TiFlash`) } - return `${serverInfos.join( + const content = `${serverInfos.join( ',' )}: ${partition.partition.fstype.toUpperCase()} ${currentMountPoint}` + return ( + +
{content}
+
+ ) }) }, }, From 2f138d87a263788e6d98fbc89227e53e42961a75 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Sun, 17 May 2020 19:15:42 +0800 Subject: [PATCH 07/14] Add placeholder Signed-off-by: Breezewish --- .../components/BaseSelect/index.module.less | 4 +++ ui/lib/components/BaseSelect/index.tsx | 20 ++++++++++-- .../{SelectDisplay.tsx => ValueDisplay.tsx} | 20 +++++------- ui/lib/components/InstanceSelect/index.tsx | 32 ++++++++++++++++--- 4 files changed, 57 insertions(+), 19 deletions(-) rename ui/lib/components/InstanceSelect/{SelectDisplay.tsx => ValueDisplay.tsx} (82%) diff --git a/ui/lib/components/BaseSelect/index.module.less b/ui/lib/components/BaseSelect/index.module.less index 8952c4ab24..5433f4e63c 100644 --- a/ui/lib/components/BaseSelect/index.module.less +++ b/ui/lib/components/BaseSelect/index.module.less @@ -64,6 +64,10 @@ transition: all 0.3s; pointer-events: none; width: 100%; + + &.isPlaceholder { + opacity: 0.4; + } } .baseSelectArrow { diff --git a/ui/lib/components/BaseSelect/index.tsx b/ui/lib/components/BaseSelect/index.tsx index a3ce881009..be0b9cde1e 100644 --- a/ui/lib/components/BaseSelect/index.tsx +++ b/ui/lib/components/BaseSelect/index.tsx @@ -14,13 +14,17 @@ export interface IBaseSelectDropdownRenderProps { } export interface IBaseSelectProps - extends Omit, 'onChange'> { + extends Omit< + React.HTMLAttributes, + 'onChange' | 'placeholder' + > { dropdownRender: ( renderProps: IBaseSelectDropdownRenderProps ) => React.ReactElement value?: T valueRender: (value?: T) => React.ReactNode onChange?: (value: T) => void + placeholder?: React.ReactNode disabled?: boolean tabIndex?: number autoFocus?: boolean @@ -31,6 +35,7 @@ function BaseSelect({ value, valueRender, onChange, + placeholder, disabled, tabIndex, autoFocus, @@ -141,6 +146,9 @@ function BaseSelect({ }) }, [disabled]) + const renderedValue = valueRender(value) + const displayAsPlaceholder = renderedValue == null + return (
({ autoFocus={autoFocus} readOnly /> -
- {valueRender(value)} +
+ + {displayAsPlaceholder ? placeholder : renderedValue} +
diff --git a/ui/lib/components/InstanceSelect/SelectDisplay.tsx b/ui/lib/components/InstanceSelect/ValueDisplay.tsx similarity index 82% rename from ui/lib/components/InstanceSelect/SelectDisplay.tsx rename to ui/lib/components/InstanceSelect/ValueDisplay.tsx index 0337e966e2..6bef518195 100644 --- a/ui/lib/components/InstanceSelect/SelectDisplay.tsx +++ b/ui/lib/components/InstanceSelect/ValueDisplay.tsx @@ -1,6 +1,5 @@ import React, { useMemo } from 'react' import { IInstanceTableItem, InstanceKind } from '@lib/utils/instanceTable' -import { Typography } from 'antd' interface InstanceStat { all: number @@ -14,13 +13,15 @@ function newInstanceStat(): InstanceStat { } } -export default function SelectDisplay({ - items, - selectedKeys, -}: { +export interface IValueDisplayProps { items: IInstanceTableItem[] selectedKeys: string[] -}) { +} + +export default function ValueDisplay({ + items, + selectedKeys, +}: IValueDisplayProps) { const text = useMemo(() => { const selectedKeysMap = {} selectedKeys.forEach((key) => (selectedKeysMap[key] = true)) @@ -59,10 +60,5 @@ export default function SelectDisplay({ return p.join(', ') }, [items, selectedKeys]) - if (items.length === 0 || selectedKeys.length === 0) { - // Not yet loaded - return Select Instance - } else { - return {text} - } + return <>{text} } diff --git a/ui/lib/components/InstanceSelect/index.tsx b/ui/lib/components/InstanceSelect/index.tsx index 9106b6c8ff..a51942ec0a 100644 --- a/ui/lib/components/InstanceSelect/index.tsx +++ b/ui/lib/components/InstanceSelect/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useRef, useMemo, useEffect, useState } from 'react' -import { Tooltip, Typography } from 'antd' +import { Tooltip } from 'antd' import { Selection } from 'office-ui-fabric-react/lib/Selection' import { BaseSelect, InstanceStatusBadge, TextWrap } from '../' import { useClientRequest } from '@lib/utils/useClientRequest' @@ -12,7 +12,8 @@ import { } from '@lib/utils/instanceTable' import DropOverlay from './DropOverlay' -import SelectDisplay from './SelectDisplay' +import ValueDisplay from './ValueDisplay' +import { useShallowCompareEffect } from 'react-use' export interface IInstanceSelectProps { enableTiFlash?: boolean @@ -106,15 +107,34 @@ function InstanceSelect( const [selectedKeys, setSelectedKeys] = useState(props.value ?? []) + const onChange = usePersistFn((v: string[]) => { + console.log('onChange', v) + props.onChange?.(v) + }) + const selection = useRef( new Selection({ onSelectionChanged: () => { + console.log('onSelectionChanged') const s = selection.current.getSelection() as IInstanceTableItem[] - setSelectedKeys(s.map((v) => v.key)) + const keys = s.map((v) => v.key) + setSelectedKeys(keys) + onChange([...keys]) }, }) ) + useShallowCompareEffect(() => { + console.log('props.value changed') + // Update selection when value is changed + selection.current.setAllSelected(false) + if (props.value) { + for (const key of props.value) { + selection.current.setKeySelected(key, true, false) + } + } + }, [props.value]) + const dataHasLoaded = useRef(false) useEffect(() => { @@ -139,7 +159,10 @@ function InstanceSelect( })) const renderValue = useCallback(() => { - return + if (tableItems.length === 0 || selectedKeys.length === 0) { + return null + } + return }, [tableItems, selectedKeys]) const renderDropdown = useCallback(() => { @@ -158,6 +181,7 @@ function InstanceSelect( dropdownRender={renderDropdown} valueRender={renderValue} disabled={loadingTiDB || loadingStores || loadingPD} + placeholder="Select Instances" /> ) } From 88be557b0b703dc2f60cf1aedd57073d67a02250 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Mon, 25 May 2020 02:22:20 +0800 Subject: [PATCH 08/14] Use InstanceSelect --- ui/dashboardApp/index.js | 4 +- ui/lib/apps/DebugPlayground/index.tsx | 43 ++- .../apps/InstanceProfiling/pages/Detail.tsx | 5 +- ui/lib/apps/InstanceProfiling/pages/List.tsx | 354 ++++++++---------- .../InstanceProfiling/translations/en.yaml | 3 +- .../InstanceProfiling/translations/zh-CN.yaml | 3 +- .../SearchLogs/components/SearchHeader.tsx | 278 +++++++------- .../SearchLogs/components/SearchHistory.tsx | 101 +++-- .../SearchLogs/components/SearchResult.tsx | 70 ++-- .../SearchLogs/components/Styles.module.css | 4 - ui/lib/apps/SearchLogs/components/utils.ts | 138 ++----- ui/lib/apps/SearchLogs/translations/en.yaml | 4 +- .../apps/SearchLogs/translations/zh-CN.yaml | 4 +- ui/lib/apps/SlowQuery/utils/useSlowQuery.ts | 6 +- .../pages/List/TimeRangeSelector.tsx | 2 +- ui/lib/apps/Statement/utils/useStatement.ts | 4 +- .../components/BaseSelect/index.module.less | 9 + ui/lib/components/BaseSelect/index.tsx | 68 ++-- ui/lib/components/CardTable/index.module.less | 22 -- ui/lib/components/CardTable/index.tsx | 50 --- ui/lib/components/CardTableV2/GroupHeader.tsx | 1 - .../components/InstanceSelect/DropOverlay.tsx | 4 +- .../InstanceSelect/ValueDisplay.tsx | 27 +- ui/lib/components/InstanceSelect/index.tsx | 191 +++++++--- ui/lib/components/TimeRangeSelector/index.tsx | 117 +++--- ui/lib/components/index.ts | 2 - 26 files changed, 720 insertions(+), 794 deletions(-) delete mode 100644 ui/lib/components/CardTable/index.module.less delete mode 100644 ui/lib/components/CardTable/index.tsx diff --git a/ui/dashboardApp/index.js b/ui/dashboardApp/index.js index 05f8d8e515..c3a26d08e8 100644 --- a/ui/dashboardApp/index.js +++ b/ui/dashboardApp/index.js @@ -19,7 +19,7 @@ import AppKeyViz from '@lib/apps/KeyViz/index.meta' import AppStatement from '@lib/apps/Statement/index.meta' import AppDiagnose from '@lib/apps/Diagnose/index.meta' import AppSearchLogs from '@lib/apps/SearchLogs/index.meta' -// import AppInstanceProfiling from '@lib/apps/InstanceProfiling/index.meta' +import AppInstanceProfiling from '@lib/apps/InstanceProfiling/index.meta' import AppClusterInfo from '@lib/apps/ClusterInfo/index.meta' import AppSlowQuery from '@lib/apps/SlowQuery/index.meta' @@ -60,7 +60,7 @@ async function main() { .register(AppClusterInfo) .register(AppDiagnose) .register(AppSearchLogs) - // .register(AppInstanceProfiling) + .register(AppInstanceProfiling) .register(AppSlowQuery) if (routing.isLocationMatch('/')) { diff --git a/ui/lib/apps/DebugPlayground/index.tsx b/ui/lib/apps/DebugPlayground/index.tsx index ab02b3c038..4677c98fc1 100644 --- a/ui/lib/apps/DebugPlayground/index.tsx +++ b/ui/lib/apps/DebugPlayground/index.tsx @@ -1,9 +1,38 @@ -import React, { useState } from 'react' -import { Root, BaseSelect, InstanceSelect } from '@lib/components' +import React, { useState, useRef } from 'react' +import { + Root, + BaseSelect, + InstanceSelect, + IInstanceSelectRefProps, + Pre, +} from '@lib/components' import { Select, Button } from 'antd' -const App = () => { +const InstanceSelectRegion = () => { const [instanceSelectValue, setInstanceSelectValue] = useState([]) + const s = useRef(null) + + return ( + <> +

Instance Select

+ +
Instance select value = {JSON.stringify(instanceSelectValue)}
+
+        Instance select value instances ={' '}
+        {JSON.stringify(
+          s.current && s.current.getInstanceByKeys(instanceSelectValue)
+        )}
+      
+ + ) +} + +const App = () => { return (

Debug Playground

@@ -35,13 +64,7 @@ const App = () => { -

Instance Select

- -
Instance select value = {JSON.stringify(instanceSelectValue)}
+

Misc

record.target.kind, + onRender: (record) => { + return InstanceKindName[record.target.kind] + }, }, { name: t('instance_profiling.detail.table.columns.status'), diff --git a/ui/lib/apps/InstanceProfiling/pages/List.tsx b/ui/lib/apps/InstanceProfiling/pages/List.tsx index d1f485db46..758b749eb0 100644 --- a/ui/lib/apps/InstanceProfiling/pages/List.tsx +++ b/ui/lib/apps/InstanceProfiling/pages/List.tsx @@ -1,139 +1,92 @@ -import { Badge, Button, Form, message, Select, TreeSelect } from 'antd' +import { Badge, Button, Form, message, Select, Modal } from 'antd' import { ColumnActionsMode } from 'office-ui-fabric-react/lib/DetailsList' import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' -import React, { useMemo, useState } from 'react' +import React, { useMemo, useState, useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { usePersistFn } from '@umijs/hooks' - -import client from '@lib/client' -import { Card, CardTableV2 } from '@lib/components' +import client, { + ProfilingStartRequest, + ModelRequestTargetNode, +} from '@lib/client' +import { + Card, + CardTableV2, + InstanceSelect, + IInstanceSelectRefProps, +} from '@lib/components' import DateTime from '@lib/components/DateTime' import openLink from '@lib/utils/openLink' import { useClientRequest } from '@lib/utils/useClientRequest' -// FIXME: The following logic should be extracted into a common component. -function getTreeData(topologyMap) { - const treeDataByKind = { - tidb: [], - tikv: [], - pd: [], - } - Object.values(topologyMap).forEach((target: any) => { - if (!(target.kind in treeDataByKind)) { - return - } - treeDataByKind[target.kind].push({ - title: target.display_name, - value: target.display_name, - key: target.display_name, - }) - }) - const kindTitleMap = { - tidb: 'TiDB', - tikv: 'TiKV', - pd: 'PD', - } - return Object.keys(treeDataByKind) - .filter((kind) => treeDataByKind[kind].length > 0) - .map((kind) => ({ - title: kindTitleMap[kind], - value: kind, - key: kind, - children: treeDataByKind[kind], - })) -} - -function filterTreeNode(inputValue, treeNode) { - const name = treeNode.key - return name.includes(inputValue) -} - -function useTargetsMap() { - return {} - // const { data } = useClientRequest((cancelToken) => - // client.getInstance().topologyAllGet({ cancelToken }) - // ) - // return useMemo(() => { - // const map = {} - // if (!data) { - // return map - // } - // // FIXME, declare type - // data.tidb?.nodes?.forEach((node) => { - // const display = `${node.ip}:${node.port}` - // const target = { - // kind: 'tidb', - // display_name: display, - // ip: node.ip, - // port: node.status_port, - // } - // map[display] = target - // }) - // data.tikv?.nodes?.forEach((node) => { - // const display = `${node.ip}:${node.port}` - // const target = { - // kind: 'tikv', - // display_name: display, - // ip: node.ip, - // port: node.status_port, - // } - // map[display] = target - // }) - // data.pd?.nodes?.forEach((node) => { - // const display = `${node.ip}:${node.port}` - // const target = { - // kind: 'pd', - // display_name: display, - // ip: node.ip, - // port: node.port, - // } - // map[display] = target - // }) - // return map - // }, [data]) -} - const profilingDurationsSec = [10, 30, 60, 120] const defaultProfilingDuration = 30 export default function Page() { - const targetsMap = useTargetsMap() - - // FIXME: Use Antd form - const [selectedTargets, setSelectedTargets] = useState([]) - const [duration, setDuration] = useState(defaultProfilingDuration) - - const [submitting, setSubmitting] = useState(false) const { data: historyTable, isLoading: listLoading, } = useClientRequest((cancelToken) => client.getInstance().getProfilingGroups({ cancelToken }) ) - const { t } = useTranslation() const navigate = useNavigate() + const instanceSelect = useRef(null) + const [submitting, setSubmitting] = useState(false) - async function handleStart() { - if (selectedTargets.length === 0) { - // TODO: Show notification - return - } - setSubmitting(true) - const req = { - targets: selectedTargets.map((k) => targetsMap[k]), - duration_secs: duration, - } - try { - const res = await client.getInstance().startProfiling(req) - navigate(`/instance_profiling/${res.data.id}`) - } catch (e) { - // FIXME - message.error(e.message) - } - setSubmitting(false) - } + const handleFinish = useCallback( + async (fieldsValue) => { + if (!fieldsValue.instances || fieldsValue.instances.length === 0) { + Modal.error({ + content: 'Some required fields are not filled', + }) + return + } + if (!instanceSelect.current) { + Modal.error({ + content: 'Internal error: Instance select is not ready', + }) + return + } + setSubmitting(true) + const targets: ModelRequestTargetNode[] = instanceSelect + .current!.getInstanceByKeys(fieldsValue.instances) + .map((instance) => { + let port + switch (instance.instanceKind) { + case 'pd': + port = instance.port + break + case 'tidb': + case 'tikv': + port = instance.status_port + break + } + return { + kind: instance.instanceKind, + display_name: instance.key, + ip: instance.ip, + port, + } + }) + .filter((i) => i.port != null) + const req: ProfilingStartRequest = { + targets, + duration_secs: fieldsValue.duration, + } + try { + const res = await client.getInstance().startProfiling(req) + navigate(`/instance_profiling/${res.data.id}`) + } catch (e) { + // FIXME + Modal.error({ + content: e.message, + }) + } + setSubmitting(false) + }, + [navigate] + ) const handleRowClick = usePersistFn( (rec, _idx, ev: React.MouseEvent) => { @@ -141,106 +94,103 @@ export default function Page() { } ) - const historyTableColumns = [ - { - name: t('instance_profiling.list.table.columns.targets'), - key: 'targets', - minWidth: 150, - maxWidth: 250, - isResizable: true, - columnActionsMode: ColumnActionsMode.disabled, - onRender: (rec) => { - // TODO: Extract to utility function - const r: string[] = [] - if (rec.target_stats.num_tidb_nodes) { - r.push(`${rec.target_stats.num_tidb_nodes} TiDB`) - } - if (rec.target_stats.num_tikv_nodes) { - r.push(`${rec.target_stats.num_tikv_nodes} TiKV`) - } - if (rec.target_stats.num_pd_nodes) { - r.push(`${rec.target_stats.num_pd_nodes} PD`) - } - return {r.join(', ')} + const historyTableColumns = useMemo( + () => [ + { + name: t('instance_profiling.list.table.columns.targets'), + key: 'targets', + minWidth: 150, + maxWidth: 250, + isResizable: true, + columnActionsMode: ColumnActionsMode.disabled, + onRender: (rec) => { + // TODO: Extract to utility function + const r: string[] = [] + if (rec.target_stats.num_tidb_nodes) { + r.push(`${rec.target_stats.num_tidb_nodes} TiDB`) + } + if (rec.target_stats.num_tikv_nodes) { + r.push(`${rec.target_stats.num_tikv_nodes} TiKV`) + } + if (rec.target_stats.num_pd_nodes) { + r.push(`${rec.target_stats.num_pd_nodes} PD`) + } + return {r.join(', ')} + }, }, - }, - { - name: t('instance_profiling.list.table.columns.status'), - key: 'status', - minWidth: 100, - maxWidth: 150, - isResizable: true, - columnActionsMode: ColumnActionsMode.disabled, - onRender: (rec) => { - if (rec.state === 1) { - return ( - - ) - } else if (rec.state === 2) { - return ( - - ) - } + { + name: t('instance_profiling.list.table.columns.status'), + key: 'status', + minWidth: 100, + maxWidth: 150, + isResizable: true, + columnActionsMode: ColumnActionsMode.disabled, + onRender: (rec) => { + if (rec.state === 1) { + return ( + + ) + } else if (rec.state === 2) { + return ( + + ) + } + }, }, - }, - { - name: t('instance_profiling.list.table.columns.start_at'), - key: 'started_at', - minWidth: 160, - maxWidth: 220, - isResizable: true, - columnActionsMode: ColumnActionsMode.disabled, - onRender: (rec) => { - return + { + name: t('instance_profiling.list.table.columns.start_at'), + key: 'started_at', + minWidth: 160, + maxWidth: 220, + isResizable: true, + columnActionsMode: ColumnActionsMode.disabled, + onRender: (rec) => { + return + }, }, - }, - { - name: t('instance_profiling.list.table.columns.duration'), - key: 'duration', - minWidth: 100, - maxWidth: 150, - fieldName: 'profile_duration_secs', - isResizable: true, - columnActionsMode: ColumnActionsMode.disabled, - }, - ] + { + name: t('instance_profiling.list.table.columns.duration'), + key: 'duration', + minWidth: 100, + maxWidth: 150, + fieldName: 'profile_duration_secs', + isResizable: true, + columnActionsMode: ColumnActionsMode.disabled, + }, + ], + [t] + ) return ( -
+ - + - {profilingDurationsSec.map((sec) => ( {sec}s @@ -249,7 +199,7 @@ export default function Page() { - diff --git a/ui/lib/apps/InstanceProfiling/translations/en.yaml b/ui/lib/apps/InstanceProfiling/translations/en.yaml index 4e186900d9..5cc654138c 100644 --- a/ui/lib/apps/InstanceProfiling/translations/en.yaml +++ b/ui/lib/apps/InstanceProfiling/translations/en.yaml @@ -3,9 +3,8 @@ instance_profiling: list: control_form: title: Start Profiling Instances - nodes: + instances: label: Select instances - placeholder: Please select the instance to profile duration: label: Duration submit: Start Profiling diff --git a/ui/lib/apps/InstanceProfiling/translations/zh-CN.yaml b/ui/lib/apps/InstanceProfiling/translations/zh-CN.yaml index 2fb7d98695..591c430286 100644 --- a/ui/lib/apps/InstanceProfiling/translations/zh-CN.yaml +++ b/ui/lib/apps/InstanceProfiling/translations/zh-CN.yaml @@ -3,9 +3,8 @@ instance_profiling: list: control_form: title: 开始性能分析 - nodes: + instances: label: 选择实例 - placeholder: 请选择需要进行性能分析的目标实例 duration: label: 分析时长 submit: 开始分析 diff --git a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx index cb5a69fedf..28a08541a2 100644 --- a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx @@ -3,211 +3,185 @@ import { LogsearchCreateTaskGroupRequest, ModelRequestTargetNode, } from '@lib/client' -import { Button, Form, Input, Select, TreeSelect } from 'antd' -import { LegacyDataNode } from 'rc-tree-select/lib/interface' -import React, { ChangeEvent, useState } from 'react' +import { Button, Form, Input, Select, Modal } from 'antd' +import React, { useState, useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { useMount } from '@umijs/hooks' -import styles from './Styles.module.css' -import { - namingMap, - NodeKind, - NodeKindList, - // parseClusterInfo, - parseSearchingParams, -} from './utils' import { TimeRangeSelector, TimeRange, - DEF_TIME_RANGE, calcTimeRange, + InstanceSelect, + IInstanceSelectRefProps, } from '@lib/components' -const { SHOW_CHILD } = TreeSelect -const { Option } = Select - -function buildTreeData(targets: ModelRequestTargetNode[]) { - const servers = { - [NodeKind.TiDB]: [], - [NodeKind.TiKV]: [], - [NodeKind.PD]: [], - [NodeKind.TiFlash]: [], - } - - targets.forEach((item) => { - if (item === undefined || item.kind === undefined) { - return - } - servers[item.kind].push(item) - }) - - return NodeKindList.filter((kind) => servers[kind].length > 0).map( - (kind) => ({ - title: namingMap[kind], - value: kind, - key: kind, - children: servers[kind].map((item: ModelRequestTargetNode) => { - const addr = item.display_name! - return { - title: addr, - value: addr, - key: addr, - } - }), - }) - ) -} +import { ValidLogLevels, LogLevelText } from './utils' interface Props { taskGroupID?: number } -const LOG_LEVELS = ['debug', 'info', 'warn', 'trace', 'critical', 'error'] +interface IFormProps { + timeRange?: TimeRange + logLevel?: number + instances?: string[] + keywords?: string +} export default function SearchHeader({ taskGroupID }: Props) { const { t } = useTranslation() const navigate = useNavigate() - - const [timeRange, setTimeRange] = useState(DEF_TIME_RANGE) - const [logLevel, setLogLevel] = useState(2) - const [selectedComponents, setComponents] = useState([]) - const [searchValue, setSearchValue] = useState('') - const [allTargets, setAllTargets] = useState([]) + const [form] = Form.useForm() + const [isSubmitting, setSubmitting] = useState(false) + const instanceSelect = useRef(null) useMount(() => { async function fetchData() { - const res = {} as any - // const res = await client.getInstance().topologyAllGet() - // const targets = parseClusterInfo(res.data) - const targets = [] as any - setAllTargets(targets) - setComponents(targets.map((item) => item.display_name!)) if (!taskGroupID) { return } - const res2 = await client + const res = await client .getInstance() - .logsTaskgroupsIdGet(taskGroupID + '') - const { - timeRange, - logLevel, - components, - searchValue, - } = parseSearchingParams(res2.data) - setTimeRange(timeRange) - setLogLevel(logLevel === 0 ? 2 : logLevel) - setComponents(components.map((item) => item.display_name ?? '')) - setSearchValue(searchValue) + .logsTaskgroupsIdGet(String(taskGroupID)) + const { task_group, tasks } = res.data + const { start_time, end_time, min_level, patterns } = + task_group?.search_request ?? {} + const fieldsValue: IFormProps = { + timeRange: { + type: 'absolute', + value: [start_time! / 1000, end_time! / 1000], + }, + logLevel: min_level || 2, + instances: (tasks ?? []) + .filter((t) => t.target && t.target!.display_name) + .map((t) => t.target!.display_name!), + keywords: (patterns ?? []).join(' '), + } + form.setFieldsValue(fieldsValue) } fetchData() }) - async function createTaskGroup() { - // TODO: check select at least one component - const targets: ModelRequestTargetNode[] = allTargets.filter((item) => - selectedComponents.some((addr) => addr === item.display_name ?? '') - ) + const handleSearch = useCallback(async (fieldsValue: IFormProps) => { + if ( + !fieldsValue.instances || + fieldsValue.instances.length === 0 || + !fieldsValue.logLevel || + !fieldsValue.timeRange + ) { + Modal.error({ + content: 'Some required fields are not filled', + }) + return + } + if (!instanceSelect.current) { + Modal.error({ + content: 'Internal error: Instance select is not ready', + }) + return + } + setSubmitting(true) + + const targets: ModelRequestTargetNode[] = instanceSelect + .current!.getInstanceByKeys(fieldsValue.instances) + .map((instance) => { + let port + switch (instance.instanceKind) { + case 'pd': + case 'tikv': + case 'tiflash': + port = instance.port + break + case 'tidb': + port = instance.status_port + break + } + return { + kind: instance.instanceKind, + display_name: instance.key, + ip: instance.ip, + port, + } + }) + .filter((i) => i.port != null) + + const [startTime, endTime] = calcTimeRange(fieldsValue.timeRange) - const [startTime, endTime] = calcTimeRange(timeRange) - const params: LogsearchCreateTaskGroupRequest = { - targets: targets, + const req: LogsearchCreateTaskGroupRequest = { + targets, request: { start_time: startTime * 1000, // unix millionsecond end_time: endTime * 1000, // unix millionsecond - min_level: logLevel, - patterns: searchValue.split(/\s+/), // 'foo boo' => ['foo', 'boo'] + min_level: fieldsValue.logLevel, + patterns: (fieldsValue.keywords ?? '').split(/\s+/), // 'foo boo' => ['foo', 'boo'] }, } - const result = await client.getInstance().logsTaskgroupPut(params) - const id = result.data.task_group?.id - if (!id) { - // promp error here - return - } - navigate('/search_logs/detail/' + id) - } - function handleTimeRangeChange(value: TimeRange) { - setTimeRange(value) - } - - function handleLogLevelChange(value: number) { - setLogLevel(value) - } - - function handleComponentChange(values: string[]) { - setComponents(values) - } - - function handleSearchPatternChange(e: ChangeEvent) { - setSearchValue(e.target.value) - } - - function handleSearch() { - createTaskGroup() - } - - function filterTreeNode( - inputValue: string, - legacyDataNode?: LegacyDataNode - ): boolean { - const name = legacyDataNode?.key as string - return name.includes(inputValue) - } + try { + const result = await client.getInstance().logsTaskgroupPut(req) + const id = result.data.task_group?.id + if (!id) { + throw new Error('Invalid server response') + } + navigate(`/search_logs/detail/${id}`) + } catch (e) { + // FIXME + Modal.error({ + content: e.message, + }) + } + setSubmitting(false) + }, []) return ( - - + + - - + {ValidLogLevels.map((val) => ( + +
{LogLevelText[val]}
+
))}
- - - 0 ? '' : 'error'} + rules={[{ required: true }]} > - + + + - diff --git a/ui/lib/apps/SearchLogs/components/SearchHistory.tsx b/ui/lib/apps/SearchLogs/components/SearchHistory.tsx index 8be84f8f05..43bff70a70 100644 --- a/ui/lib/apps/SearchLogs/components/SearchHistory.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchHistory.tsx @@ -1,20 +1,19 @@ import client from '@lib/client' import { LogsearchTaskGroupModel } from '@lib/client' -import { Head, CardTableV2 } from '@lib/components' +import { Head, CardTableV2, DateTime } from '@lib/components' import { ArrowLeftOutlined } from '@ant-design/icons' import { Badge, Button } from 'antd' -import { RangeValue } from 'rc-picker/lib/interface' -import moment, { Moment } from 'moment' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' -import { DATE_TIME_FORMAT, LogLevelMap } from './utils' +import { LogLevelText } from './utils' import { Selection, SelectionMode, } from 'office-ui-fabric-react/lib/DetailsList' function componentRender({ target_stats: stats }) { + // FIXME: Extract common util const r: Array = [] if (stats?.num_tidb_nodes) { r.push(`${stats.num_tidb_nodes} TiDB`) @@ -28,21 +27,26 @@ function componentRender({ target_stats: stats }) { return {r.join(', ')} } -function formatTime(time: Moment | null | undefined): string { - if (!time) { - return '' - } - return time.format(DATE_TIME_FORMAT) +function timeRender({ search_request }: LogsearchTaskGroupModel) { + return ( + + {search_request?.start_time && ( + + )} + {' ~ '} + {search_request?.end_time && ( + + )} + + ) } -function timeRender({ search_request: request }) { - const startTime = request.start_time ? moment(request.start_time) : null - const endTime = request.end_time ? moment(request.end_time) : null - const timeRange = [startTime, endTime] as RangeValue - if (!timeRange?.[0] || !timeRange?.[1]) { - return '' - } - return `${formatTime(timeRange[0])} ~ ${formatTime(timeRange[1])}` +function levelRender({ search_request: request }: LogsearchTaskGroupModel) { + return LogLevelText[request?.min_level!] +} + +function patternRender({ search_request: request }: LogsearchTaskGroupModel) { + return (request?.patterns ?? []).join(' ') } export default function SearchHistory() { @@ -60,20 +64,7 @@ export default function SearchHistory() { getData() }, []) - function levelRender({ search_request: request }) { - return LogLevelMap[request.min_level!] - } - - function patternRender({ search_request: request }) { - return request.patterns && request.patterns.length > 0 - ? request.patterns.join(' ') - : '' - } - - function stateRender({ state }) { - if (state === undefined || state < 1) { - return - } + function stateRender({ state }: LogsearchTaskGroupModel) { switch (state) { case 1: return ( @@ -102,9 +93,9 @@ export default function SearchHistory() { async function handleDeleteSelected() { for (const taskGroupID of selectedRowKeys) { await client.getInstance().logsTaskgroupsIdDelete(taskGroupID) - const res = await client.getInstance().logsTaskgroupsGet() - setTaskGroups(res.data) } + const res = await client.getInstance().logsTaskgroupsGet() + setTaskGroups(res.data) } async function handleDeleteAll() { @@ -113,7 +104,7 @@ export default function SearchHistory() { if (key === undefined) { continue } - await client.getInstance().logsTaskgroupsIdDelete(key + '') + await client.getInstance().logsTaskgroupsIdDelete(String(key)) } const res = await client.getInstance().logsTaskgroupsGet() setTaskGroups(res.data) @@ -131,42 +122,42 @@ export default function SearchHistory() { name: t('search_logs.common.time_range'), key: 'time', minWidth: 200, - maxWidth: 400, + maxWidth: 300, onRender: timeRender, }, { name: t('search_logs.preview.level'), key: 'level', - minWidth: 100, - maxWidth: 200, + minWidth: 70, + maxWidth: 120, onRender: levelRender, }, { - name: t('search_logs.preview.component'), + name: t('search_logs.history.instances'), key: 'target_stats', - minWidth: 150, - maxWidth: 230, + minWidth: 100, + maxWidth: 250, onRender: componentRender, }, { name: t('search_logs.common.keywords'), key: 'keywords', - minWidth: 150, - maxWidth: 230, + minWidth: 100, + maxWidth: 200, onRender: patternRender, }, { - name: t('search_logs.history.state'), + name: t('search_logs.history.status'), key: 'state', - minWidth: 150, - maxWidth: 230, + minWidth: 100, + maxWidth: 150, onRender: stateRender, }, { name: t('search_logs.history.action'), key: 'action', - minWidth: 150, - maxWidth: 230, + minWidth: 100, + maxWidth: 200, onRender: actionRender, }, ] @@ -196,15 +187,13 @@ export default function SearchHistory() { } /> -
- -
+
) } diff --git a/ui/lib/apps/SearchLogs/components/SearchResult.tsx b/ui/lib/apps/SearchLogs/components/SearchResult.tsx index 77d309de6b..228c02fba7 100644 --- a/ui/lib/apps/SearchLogs/components/SearchResult.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchResult.tsx @@ -3,9 +3,9 @@ import { ModelRequestTargetNode, LogsearchTaskModel } from '@lib/client' import { CardTableV2 } from '@lib/components' import { Alert } from 'antd' import moment from 'moment' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { DATE_TIME_FORMAT, LogLevelMap, namingMap } from './utils' +import { LogLevelText, namingMap } from './utils' import Log from './Log' type LogPreview = { @@ -57,8 +57,8 @@ export default function SearchResult({ taskGroupID, tasks }: Props) { (value, index): LogPreview => { return { key: index, - time: moment(value.time).format(DATE_TIME_FORMAT), - level: LogLevelMap[value.level ?? 0], + time: moment(value.time).format('YYYY-MM-DD HH:mm:ss'), + level: LogLevelText[value.level ?? 0], component: getComponent(value.task_id), log: value.message, } @@ -73,35 +73,39 @@ export default function SearchResult({ taskGroupID, tasks }: Props) { getLogPreview() }, [taskGroupID, tasks]) - const columns = [ - { - name: t('search_logs.preview.time'), - key: 'time', - fieldName: 'time', - minWidth: 160, - maxWidth: 300, - }, - { - name: t('search_logs.preview.level'), - key: 'level', - fieldName: 'level', - minWidth: 60, - maxWidth: 120, - }, - { - name: t('search_logs.preview.component'), - key: 'component', - minWidth: 120, - maxWidth: 200, - onRender: componentRender, - }, - { - name: t('search_logs.preview.log'), - key: 'log', - minWidth: 500, - onRender: ({ log }) => , - }, - ] + const columns = useMemo( + () => [ + { + name: t('search_logs.preview.time'), + key: 'time', + fieldName: 'time', + minWidth: 160, + maxWidth: 300, + }, + { + name: t('search_logs.preview.level'), + key: 'level', + fieldName: 'level', + minWidth: 60, + maxWidth: 120, + }, + { + name: t('search_logs.preview.component'), + key: 'component', + minWidth: 120, + maxWidth: 200, + onRender: componentRender, + }, + { + name: t('search_logs.preview.log'), + key: 'log', + minWidth: 500, + onRender: ({ log }) => , + }, + ], + [t] + ) + return ( <> {!loading && ( diff --git a/ui/lib/apps/SearchLogs/components/Styles.module.css b/ui/lib/apps/SearchLogs/components/Styles.module.css index 932db1a4c7..ccb887fc10 100644 --- a/ui/lib/apps/SearchLogs/components/Styles.module.css +++ b/ui/lib/apps/SearchLogs/components/Styles.module.css @@ -6,7 +6,3 @@ margin-right: 12px; margin-bottom: 12px; } - -.components > :global(div) { - width: 100%; -} diff --git a/ui/lib/apps/SearchLogs/components/utils.ts b/ui/lib/apps/SearchLogs/components/utils.ts index a05b5832d4..40a07d1a7c 100644 --- a/ui/lib/apps/SearchLogs/components/utils.ts +++ b/ui/lib/apps/SearchLogs/components/utils.ts @@ -1,22 +1,34 @@ -import { - LogsearchTaskGroupResponse, - LogsearchTaskModel, - ModelRequestTargetNode, -} from '@lib/client' -import { TimeRange } from '@lib/components' - export const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' -export const LogLevelMap = { - 0: 'UNKNOWN', - 1: 'DEBUG', - 2: 'INFO', - 3: 'WARN', - 4: 'TRACE', - 5: 'CRITICAL', - 6: 'ERROR', +export enum LogLevel { + Unknown = 0, + Debug, + Info, + Warn, + Trace, + Critical, + Error, +} + +export const LogLevelText = { + [LogLevel.Unknown]: 'UNKNOWN', + [LogLevel.Debug]: 'DEBUG', + [LogLevel.Info]: 'INFO', + [LogLevel.Warn]: 'WARN', + [LogLevel.Trace]: 'TRACE', + [LogLevel.Critical]: 'CRITICAL', + [LogLevel.Error]: 'ERROR', } +export const ValidLogLevels = [ + LogLevel.Debug, + LogLevel.Info, + LogLevel.Warn, + // LogLevel.Trace, + LogLevel.Critical, + LogLevel.Error, +] + export enum TaskState { Running = 1, Finished, @@ -37,102 +49,6 @@ export const namingMap = { [NodeKind.TiFlash]: 'TiFlash', } -export const AllLogLevel = [1, 2, 3, 4, 5, 6] - -// export function parseClusterInfo( -// info: ClusterinfoClusterInfo -// ): ModelRequestTargetNode[] { -// const targets: ModelRequestTargetNode[] = [] -// info?.tidb?.nodes?.forEach((item) => { -// if ( -// item.ip === undefined || -// item.port === undefined || -// item.status_port === undefined -// ) { -// return -// } -// // TiDB has a different behavior: it use "status_port" for grpc, "port" for display. -// targets.push({ -// kind: NodeKind.TiDB, -// ip: item.ip, -// port: item.status_port, -// display_name: `${item.ip}:${item.port}`, -// }) -// }) -// info?.tikv?.nodes?.forEach((item) => { -// if ( -// item.ip === undefined || -// item.port === undefined || -// item.status_port === undefined -// ) { -// return -// } -// targets.push({ -// kind: NodeKind.TiKV, -// ip: item.ip, -// port: item.port, -// display_name: `${item.ip}:${item.port}`, -// }) -// }) -// info?.pd?.nodes?.forEach((item) => { -// if (item.ip === undefined || item.port === undefined) { -// return -// } -// targets.push({ -// kind: NodeKind.PD, -// ip: item.ip, -// port: item.port, -// display_name: `${item.ip}:${item.port}`, -// }) -// }) -// info?.tiflash?.nodes?.forEach((item) => { -// if (!(item.ip && item.port)) { -// return -// } -// targets.push({ -// kind: NodeKind.TiFlash, -// ip: item.ip, -// port: item.port, -// display_name: `${item.ip}:${item.port}`, -// }) -// }) -// return targets -// } - -interface Params { - timeRange: TimeRange - logLevel: number - components: ModelRequestTargetNode[] - searchValue: string -} - -export function parseSearchingParams(resp: LogsearchTaskGroupResponse): Params { - const { task_group, tasks } = resp - const { start_time, end_time, min_level, patterns } = - task_group?.search_request || {} - let timeRange: TimeRange = { - type: 'absolute', - value: [start_time! / 1000, end_time! / 1000], - } - return { - timeRange: timeRange, - logLevel: min_level ?? 2, - searchValue: patterns && patterns.length > 0 ? patterns.join(' ') : '', - components: tasks && tasks.length > 0 ? getComponents(tasks) : [], - } -} - -function getComponents(tasks: LogsearchTaskModel[]): ModelRequestTargetNode[] { - const targets: ModelRequestTargetNode[] = [] - tasks.forEach((task) => { - if (task.target === undefined) { - return - } - targets.push(task.target) - }) - return targets -} - export const NodeKindList = [ NodeKind.TiDB, NodeKind.TiKV, diff --git a/ui/lib/apps/SearchLogs/translations/en.yaml b/ui/lib/apps/SearchLogs/translations/en.yaml index 8bfe3f1115..6251d0bdca 100644 --- a/ui/lib/apps/SearchLogs/translations/en.yaml +++ b/ui/lib/apps/SearchLogs/translations/en.yaml @@ -15,7 +15,6 @@ search_logs: end_time: End Time log_level: Log Level components: instances - components_placeholder: Select at least one instance keywords: Keywords keywords_placeholder: Keywords, Optional, separated by spaces search: Search @@ -36,11 +35,12 @@ search_logs: component: Component log: Log history: + instances: Instances running: Running finished: Finished delete_selected: Delete selected delete_all: Delete All - state: State + status: Status action: Action detail: Detail delete: Delete diff --git a/ui/lib/apps/SearchLogs/translations/zh-CN.yaml b/ui/lib/apps/SearchLogs/translations/zh-CN.yaml index 4ecd58efa1..c44f2deed4 100644 --- a/ui/lib/apps/SearchLogs/translations/zh-CN.yaml +++ b/ui/lib/apps/SearchLogs/translations/zh-CN.yaml @@ -15,7 +15,6 @@ search_logs: end_time: 结束时间 log_level: 日志等级 components: 选择实例 - components_placeholder: 至少选择一个实例 keywords: 关键字 keywords_placeholder: 搜索关键字,可选,以空格分割 search: 搜索 @@ -36,11 +35,12 @@ search_logs: component: 组件 log: 日志 history: + instances: 实例 running: 正在搜索 finished: 已完成 delete_selected: 删除选中的任务 delete_all: 删除全部任务 - state: 状态 + status: 状态 action: 操作 detail: 查看详情 delete: 删除 diff --git a/ui/lib/apps/SlowQuery/utils/useSlowQuery.ts b/ui/lib/apps/SlowQuery/utils/useSlowQuery.ts index d80f75f4f5..39ba03fbdf 100644 --- a/ui/lib/apps/SlowQuery/utils/useSlowQuery.ts +++ b/ui/lib/apps/SlowQuery/utils/useSlowQuery.ts @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react' import { useSessionStorageState } from '@umijs/hooks' import client, { SlowqueryBase } from '@lib/client' -import { calcTimeRange, DEF_TIME_RANGE, TimeRange } from '@lib/components' +import { calcTimeRange, TimeRange } from '@lib/components' import useOrderState, { IOrderOptions } from '@lib/utils/useOrderState' const QUERY_OPTIONS = 'slow_query.query_options' @@ -13,7 +13,7 @@ const DEF_ORDER_OPTIONS: IOrderOptions = { } export interface ISlowQueryOptions { - timeRange: TimeRange + timeRange?: TimeRange schemas: string[] searchText: string limit: number @@ -23,7 +23,7 @@ export interface ISlowQueryOptions { } export const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = { - timeRange: DEF_TIME_RANGE, + timeRange: undefined, schemas: [], searchText: '', limit: 100, diff --git a/ui/lib/apps/Statement/pages/List/TimeRangeSelector.tsx b/ui/lib/apps/Statement/pages/List/TimeRangeSelector.tsx index 0e18f49917..259144db70 100644 --- a/ui/lib/apps/Statement/pages/List/TimeRangeSelector.tsx +++ b/ui/lib/apps/Statement/pages/List/TimeRangeSelector.tsx @@ -34,7 +34,7 @@ interface RangeTime { export type TimeRange = RecentSecTime | RangeTime -export const DEF_TIME_RANGE: TimeRange = { +export const DEFAULT_TIME_RANGE: TimeRange = { type: 'recent', value: 30 * 60, } diff --git a/ui/lib/apps/Statement/utils/useStatement.ts b/ui/lib/apps/Statement/utils/useStatement.ts index c56b4a95b8..2c09ebb81c 100644 --- a/ui/lib/apps/Statement/utils/useStatement.ts +++ b/ui/lib/apps/Statement/utils/useStatement.ts @@ -6,7 +6,7 @@ import useOrderState, { IOrderOptions } from '@lib/utils/useOrderState' import { calcValidStatementTimeRange, - DEF_TIME_RANGE, + DEFAULT_TIME_RANGE, TimeRange, } from '../pages/List/TimeRangeSelector' @@ -24,7 +24,7 @@ export interface IStatementQueryOptions { } export const DEF_STMT_QUERY_OPTIONS: IStatementQueryOptions = { - timeRange: DEF_TIME_RANGE, + timeRange: DEFAULT_TIME_RANGE, schemas: [], stmtTypes: [], } diff --git a/ui/lib/components/BaseSelect/index.module.less b/ui/lib/components/BaseSelect/index.module.less index 5433f4e63c..b0572755bc 100644 --- a/ui/lib/components/BaseSelect/index.module.less +++ b/ui/lib/components/BaseSelect/index.module.less @@ -41,6 +41,15 @@ } } +:global(.ant-form-item-has-error) { + .baseSelectInner { + border-color: @error-color !important; + &.focused { + .active(@error-color); + } + } +} + .baseSelectInput { opacity: 0; position: absolute; diff --git a/ui/lib/components/BaseSelect/index.tsx b/ui/lib/components/BaseSelect/index.tsx index be0b9cde1e..183c9593e7 100644 --- a/ui/lib/components/BaseSelect/index.tsx +++ b/ui/lib/components/BaseSelect/index.tsx @@ -1,45 +1,50 @@ -import React, { useState, useCallback, useRef } from 'react' +import React, { useState, useCallback, useRef, useMemo } from 'react' import cx from 'classnames' -import { Dropdown } from 'antd' import { useEventListener } from '@umijs/hooks' import { DownOutlined } from '@ant-design/icons' +import Trigger from 'rc-trigger' import KeyCode from 'rc-util/lib/KeyCode' import { TextWrap } from '..' import styles from './index.module.less' -export interface IBaseSelectDropdownRenderProps { - value?: T - triggerOnChange?: (value: T) => void -} - export interface IBaseSelectProps extends Omit< React.HTMLAttributes, 'onChange' | 'placeholder' > { - dropdownRender: ( - renderProps: IBaseSelectDropdownRenderProps - ) => React.ReactElement + dropdownRender: () => React.ReactElement value?: T valueRender: (value?: T) => React.ReactNode - onChange?: (value: T) => void placeholder?: React.ReactNode + overlayClassName?: string disabled?: boolean tabIndex?: number autoFocus?: boolean } +const builtinPlacements = { + bottomLeft: { + ignoreShake: true, + points: ['tl', 'bl'], + offset: [0, 4], + overflow: { + adjustX: 0, + adjustY: 0, + }, + }, +} + function BaseSelect({ dropdownRender, value, valueRender, - onChange, placeholder, disabled, tabIndex, autoFocus, className, + overlayClassName, onFocus, onBlur, onKeyDown, @@ -104,18 +109,17 @@ function BaseSelect({ const dropdownOverlayRef = useRef(null) const containerRef = useRef(null) - const overlay = ( -
- {dropdownRender({ - value, - triggerOnChange: onChange, - })} -
- ) + const overlay = useMemo(() => { + return ( +
+ {dropdownRender()} +
+ ) + }, [dropdownRender, overlayClassName, handleOverlayMouseDown]) useEventListener('mousedown', (ev: MouseEvent) => { // Close the dropdown if click outside @@ -141,7 +145,6 @@ function BaseSelect({ if (v && !disabled) { return false } - // Otherwise, unchanged return v }) }, [disabled]) @@ -150,7 +153,16 @@ function BaseSelect({ const displayAsPlaceholder = renderedValue == null return ( - +
({
- + ) } -BaseSelect.whyDidYouRender = true - export default React.memo(BaseSelect) diff --git a/ui/lib/components/CardTable/index.module.less b/ui/lib/components/CardTable/index.module.less deleted file mode 100644 index 811e990e7b..0000000000 --- a/ui/lib/components/CardTable/index.module.less +++ /dev/null @@ -1,22 +0,0 @@ -@import '~antd/es/style/themes/default.less'; - -.cardTable { - :global { - .ant-table-content tr:first-child > th:first-child { - padding-left: @padding-page !important; - } - - .ant-table-row td:first-child { - padding-left: @padding-page !important; - } - - .ant-table-pagination { - margin-right: @padding-page !important; - } - } -} - -.cardTableContent { - margin-left: -@padding-page; - margin-right: -@padding-page; -} diff --git a/ui/lib/components/CardTable/index.tsx b/ui/lib/components/CardTable/index.tsx deleted file mode 100644 index 02e0220366..0000000000 --- a/ui/lib/components/CardTable/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { ReactNode } from 'react' -import { Table, Skeleton } from 'antd' -import { TableProps } from 'antd/es/table' -import cx from 'classnames' -import Card from '../Card' -import styles from './index.module.less' - -export interface ICardTableProps - extends TableProps { - title?: any - className?: string - style?: object - loading?: boolean - loadingSkeletonRows?: number - cardExtra?: ReactNode - children?: ReactNode -} - -function CardTable({ - title, - className, - style, - loading, - loadingSkeletonRows, - cardExtra, - ...rest -}: ICardTableProps) { - return ( - - {loading ? ( - - ) : ( -
- - - )} - - ) -} - -export default CardTable diff --git a/ui/lib/components/CardTableV2/GroupHeader.tsx b/ui/lib/components/CardTableV2/GroupHeader.tsx index 5a2ac87aa9..a4f086c12c 100644 --- a/ui/lib/components/CardTableV2/GroupHeader.tsx +++ b/ui/lib/components/CardTableV2/GroupHeader.tsx @@ -75,7 +75,6 @@ function BaseAntCheckboxGroupHeader(props: IGroupHeaderProps) { diff --git a/ui/lib/components/index.ts b/ui/lib/components/index.ts index 80637c04d1..f5b36488c5 100644 --- a/ui/lib/components/index.ts +++ b/ui/lib/components/index.ts @@ -6,8 +6,6 @@ export * from './Card' export { default as Card } from './Card' export * from './CardTabs' export { default as CardTabs } from './CardTabs' -export * from './CardTable' -export { default as CardTable } from './CardTable' export * from './CardTableV2' export { default as CardTableV2 } from './CardTableV2' export * from './Bar' From 989e47008a098bbfe2ded3e9edb6a54f75c538d6 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Mon, 25 May 2020 23:21:27 +0800 Subject: [PATCH 09/14] Fix --- ui/lib/antd.global.less | 2 ++ ui/lib/components/TimeRangeSelector/index.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ui/lib/antd.global.less b/ui/lib/antd.global.less index 292c0dfb39..5b81af33f7 100644 --- a/ui/lib/antd.global.less +++ b/ui/lib/antd.global.less @@ -1,3 +1,5 @@ +// Slightly modified from https://github.com/ant-design/ant-design/blob/master/components/style/core/base.less + /* stylelint-disable at-rule-no-unknown */ // Reboot diff --git a/ui/lib/components/TimeRangeSelector/index.tsx b/ui/lib/components/TimeRangeSelector/index.tsx index cd26baf08e..35f3a68aa1 100644 --- a/ui/lib/components/TimeRangeSelector/index.tsx +++ b/ui/lib/components/TimeRangeSelector/index.tsx @@ -69,6 +69,8 @@ function TimeRangeSelector({ value, onChange }: ITimeRangeSelectorProps) { if (!value) { onChange?.(DEFAULT_TIME_RANGE) } + // ignore [onChange] + // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]) const rangePickerValue = useMemo(() => { From 612f9693211bb6a69c2cbe7ad7843e86959fd657 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Tue, 2 Jun 2020 17:44:44 +0800 Subject: [PATCH 10/14] Update --- go.mod | 2 +- ui/lib/utils/formatSql.ts | 2 +- ui/package.json | 2 +- ui/yarn.lock | 1170 +++++++++++++++---------------------- 4 files changed, 483 insertions(+), 693 deletions(-) diff --git a/go.mod b/go.mod index 8faa533434..fb2ef09b51 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/spf13/pflag v1.0.1 github.com/stretchr/testify v1.4.0 github.com/swaggo/http-swagger v0.0.0-20200103000832-0e9263c4b516 - github.com/swaggo/swag v1.6.5 + github.com/swaggo/swag v1.6.7-0.20200529100950-7c765ddd0476 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 go.uber.org/atomic v1.5.0 go.uber.org/fx v1.10.0 diff --git a/ui/lib/utils/formatSql.ts b/ui/lib/utils/formatSql.ts index d0335d9b2b..62bdb9a8e2 100644 --- a/ui/lib/utils/formatSql.ts +++ b/ui/lib/utils/formatSql.ts @@ -1,4 +1,4 @@ -import sqlFormatter from 'sql-formatter-plus' +import sqlFormatter from 'sql-formatter-plus-plus' export default function formatSql(sql?: string): string { return sqlFormatter.format(sql || '', { uppercase: true }) diff --git a/ui/package.json b/ui/package.json index f348dc3d37..703986826e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -36,7 +36,7 @@ "react-use": "^14.2.0", "single-spa": "^5.3.4", "single-spa-react": "^2.14.0", - "sql-formatter-plus": "^1.3.6", + "sql-formatter-plus-plus": "^1.4.0", "string-template": "^1.0.0" }, "scripts": { diff --git a/ui/yarn.lock b/ui/yarn.lock index d0eab62154..cd859c3f86 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -53,21 +53,12 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/compat-data@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35" - integrity sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q== +"@babel/compat-data@^7.9.0", "@babel/compat-data@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" + integrity sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g== dependencies: - browserslist "^4.8.5" - invariant "^2.2.4" - semver "^5.5.0" - -"@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== - dependencies: - browserslist "^4.9.1" + browserslist "^4.11.1" invariant "^2.2.4" semver "^5.5.0" @@ -94,42 +85,33 @@ source-map "^0.5.0" "@babel/core@^7.1.0", "@babel/core@^7.4.5": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.7.tgz#b69017d221ccdeb203145ae9da269d72cf102f3b" - integrity sha512-rBlqF3Yko9cynC5CCFy6+K/w2N+Sq/ff2BPy+Krp7rHlABIr5epbA7OxVeKoMHB39LZOp1UY5SuLjy6uWi35yA== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" + integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.7" - "@babel/helpers" "^7.8.4" - "@babel/parser" "^7.8.7" + "@babel/generator" "^7.9.6" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.6" + "@babel/parser" "^7.9.6" "@babel/template" "^7.8.6" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.7" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" - json5 "^2.1.0" + json5 "^2.1.2" lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.8.6", "@babel/generator@^7.8.7": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.8.tgz#cdcd58caab730834cee9eeadb729e833b625da3e" - integrity sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg== +"@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" + integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== dependencies: - "@babel/types" "^7.8.7" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.9.0", "@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== - dependencies: - "@babel/types" "^7.9.5" + "@babel/types" "^7.9.6" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -158,14 +140,6 @@ "@babel/helper-module-imports" "^7.8.3" "@babel/types" "^7.9.5" -"@babel/helper-builder-react-jsx@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.8.3.tgz#dee98d7d79cc1f003d80b76fe01c7f8945665ff6" - integrity sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ== - dependencies: - "@babel/types" "^7.8.3" - esutils "^2.0.0" - "@babel/helper-builder-react-jsx@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" @@ -174,36 +148,27 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/types" "^7.9.0" -"@babel/helper-call-delegate@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz#28a279c2e6c622a6233da548127f980751324cab" - integrity sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ== +"@babel/helper-compilation-targets@^7.8.7", "@babel/helper-compilation-targets@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" + integrity sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.7" - -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" + "@babel/compat-data" "^7.9.6" + browserslist "^4.11.1" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" - integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg== +"@babel/helper-create-class-features-plugin@^7.8.3", "@babel/helper-create-class-features-plugin@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz#965c8b0a9f051801fd9d3b372ca0ccf200a90897" + integrity sha512-6N9IeuyHvMBRyjNYOMJHrhwtu4WJMrYf8hVbEHD3pbbbmNOk1kmXSQs7bA4dYDUaIx4ZEzdnvo6NwC3WHd/Qow== dependencies: - "@babel/helper-function-name" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-replace-supers" "^7.9.6" "@babel/helper-split-export-declaration" "^7.8.3" "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": @@ -232,16 +197,7 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.9.5": +"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== @@ -278,19 +234,6 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-module-transforms@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4" - integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.8.6" - lodash "^4.17.13" - "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -334,15 +277,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6", "@babel/helper-replace-supers@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" + integrity sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA== dependencies: "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/helper-simple-access@^7.8.3": version "7.8.3" @@ -359,7 +302,7 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-validator-identifier@^7.9.5": +"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== @@ -374,42 +317,28 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helpers@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" - integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== +"@babel/helpers@^7.9.0", "@babel/helpers@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" + integrity sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw== dependencies: "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.4" - "@babel/types" "^7.8.3" - -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== - dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/highlight@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== dependencies: + "@babel/helper-validator-identifier" "^7.9.0" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.8.6", "@babel/parser@^7.8.7": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.8.tgz#4c3b7ce36db37e0629be1f0d50a571d2f86f6cd4" - integrity sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA== - -"@babel/parser@^7.7.0", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0", "@babel/parser@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" + integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" @@ -469,18 +398,10 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.8.3" -"@babel/plugin-proposal-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" - integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - -"@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" - integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg== +"@babel/plugin-proposal-object-rest-spread@^7.9.0", "@babel/plugin-proposal-object-rest-spread@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz#7a093586fcb18b08266eb1a7177da671ac575b63" + integrity sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" @@ -502,14 +423,6 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" - integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" @@ -640,21 +553,7 @@ "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d" - integrity sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - globals "^11.1.0" - -"@babel/plugin-transform-classes@^7.9.0": +"@babel/plugin-transform-classes@^7.9.0", "@babel/plugin-transform-classes@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg== @@ -675,10 +574,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== +"@babel/plugin-transform-destructuring@^7.8.3", "@babel/plugin-transform-destructuring@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" + integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" @@ -713,13 +612,6 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-flow" "^7.8.3" -"@babel/plugin-transform-for-of@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085" - integrity sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-for-of@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" @@ -749,71 +641,34 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-modules-amd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" - integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== +"@babel/plugin-transform-modules-amd@^7.9.0", "@babel/plugin-transform-modules-amd@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" + integrity sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" - integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== +"@babel/plugin-transform-modules-commonjs@^7.9.0", "@babel/plugin-transform-modules-commonjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" + integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" - integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== +"@babel/plugin-transform-modules-systemjs@^7.9.0", "@babel/plugin-transform-modules-systemjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" + integrity sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg== dependencies: "@babel/helper-hoist-variables" "^7.8.3" "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-umd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" - integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" @@ -845,16 +700,7 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.3" -"@babel/plugin-transform-parameters@^7.8.7": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.8.tgz#0381de466c85d5404565243660c4496459525daf" - integrity sha512-hC4Ld/Ulpf1psQciWWwdnUspQoQco2bMzSrwU6TmzRlvoYQe4rQFy9vnCZDTlVeCQj0JPfL+1RX0V8hCJvkgBA== - dependencies: - "@babel/helper-call-delegate" "^7.8.7" - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.9.5": +"@babel/plugin-transform-parameters@^7.8.7", "@babel/plugin-transform-parameters@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA== @@ -870,11 +716,10 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-react-constant-elements@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.8.3.tgz#784c25294bddaad2323eb4ff0c9f4a3f6c87d6bc" - integrity sha512-glrzN2U+egwRfkNFtL34xIBYTxbbUF2qJTP8HD3qETBBqzAWSeNB821X0GjU06+dNpq/UyCIjI72FmGE5NNkQQ== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz#a75abc936a3819edec42d3386d9f1c93f28d9d9e" + integrity sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-react-display-name@7.8.3", "@babel/plugin-transform-react-display-name@^7.8.3": @@ -893,14 +738,6 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" -"@babel/plugin-transform-react-jsx-self@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.8.3.tgz#c4f178b2aa588ecfa8d077ea80d4194ee77ed702" - integrity sha512-01OT7s5oa0XTLf2I8XGsL8+KqV9lx3EZV+jxn/L2LQ97CGKila2YMroTkCEIE0HV/FF7CMSRsIAybopdN9NTdg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" - "@babel/plugin-transform-react-jsx-self@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz#f4f26a325820205239bb915bad8e06fcadabb49b" @@ -909,14 +746,6 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" -"@babel/plugin-transform-react-jsx-source@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.8.3.tgz#951e75a8af47f9f120db731be095d2b2c34920e0" - integrity sha512-PLMgdMGuVDtRS/SzjNEQYUT8f4z1xb2BAT54vM1X5efkVuYBf5WyGUMbpmARcfq3NaglIwz08UVQK4HHHbC6ag== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" - "@babel/plugin-transform-react-jsx-source@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz#89ef93025240dd5d17d3122294a093e5e0183de0" @@ -925,16 +754,7 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" -"@babel/plugin-transform-react-jsx@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.8.3.tgz#4220349c0390fdefa505365f68c103562ab2fc4a" - integrity sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g== - dependencies: - "@babel/helper-builder-react-jsx" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" - -"@babel/plugin-transform-react-jsx@^7.9.1": +"@babel/plugin-transform-react-jsx@^7.9.1", "@babel/plugin-transform-react-jsx@^7.9.4": version "7.9.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz#86f576c8540bd06d0e95e0b61ea76d55f6cbd03f" integrity sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw== @@ -1006,11 +826,11 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-typescript@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz#4bb4dde4f10bbf2d787fce9707fb09b483e33359" - integrity sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.6.tgz#2248971416a506fc78278fc0c0ea3179224af1e9" + integrity sha512-8OvsRdvpt3Iesf2qsAn+YdlwAJD7zJ+vhFZmDCa4b8dTp7MmHtKk5FF2mCsGxjZwuwsy/yIIay/nLmxST1ctVQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.9.6" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-typescript" "^7.8.3" @@ -1022,7 +842,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/polyfill@^7.6.0": +"@babel/polyfill@^7.7.0": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.7.tgz#151ec24c7135481336168c3bd8b8bf0cf91c032f" integrity sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w== @@ -1097,26 +917,28 @@ semver "^5.5.0" "@babel/preset-env@^7.4.5": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.7.tgz#1fc7d89c7f75d2d70c2b6768de6c2e049b3cb9db" - integrity sha512-BYftCVOdAYJk5ASsznKAUl53EMhfBbr8CJ1X+AJLfGPscQkwJFiaV/Wn9DPH/7fzm2v6iRYJKYHSqyynTGw0nw== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.6.tgz#df063b276c6455ec6fcfc6e53aacc38da9b0aea6" + integrity sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ== dependencies: - "@babel/compat-data" "^7.8.6" - "@babel/helper-compilation-targets" "^7.8.7" + "@babel/compat-data" "^7.9.6" + "@babel/helper-compilation-targets" "^7.9.6" "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-proposal-async-generator-functions" "^7.8.3" "@babel/plugin-proposal-dynamic-import" "^7.8.3" "@babel/plugin-proposal-json-strings" "^7.8.3" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.6" "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-json-strings" "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" @@ -1125,24 +947,24 @@ "@babel/plugin-transform-async-to-generator" "^7.8.3" "@babel/plugin-transform-block-scoped-functions" "^7.8.3" "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.8.6" + "@babel/plugin-transform-classes" "^7.9.5" "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.9.5" "@babel/plugin-transform-dotall-regex" "^7.8.3" "@babel/plugin-transform-duplicate-keys" "^7.8.3" "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.8.6" + "@babel/plugin-transform-for-of" "^7.9.0" "@babel/plugin-transform-function-name" "^7.8.3" "@babel/plugin-transform-literals" "^7.8.3" "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.8.3" - "@babel/plugin-transform-modules-commonjs" "^7.8.3" - "@babel/plugin-transform-modules-systemjs" "^7.8.3" - "@babel/plugin-transform-modules-umd" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.6" + "@babel/plugin-transform-modules-commonjs" "^7.9.6" + "@babel/plugin-transform-modules-systemjs" "^7.9.6" + "@babel/plugin-transform-modules-umd" "^7.9.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" "@babel/plugin-transform-new-target" "^7.8.3" "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-parameters" "^7.9.5" "@babel/plugin-transform-property-literals" "^7.8.3" "@babel/plugin-transform-regenerator" "^7.8.7" "@babel/plugin-transform-reserved-words" "^7.8.3" @@ -1152,8 +974,9 @@ "@babel/plugin-transform-template-literals" "^7.8.3" "@babel/plugin-transform-typeof-symbol" "^7.8.4" "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/types" "^7.8.7" - browserslist "^4.8.5" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.6" + browserslist "^4.11.1" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -1183,15 +1006,16 @@ "@babel/plugin-transform-react-jsx-source" "^7.9.0" "@babel/preset-react@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.8.3.tgz#23dc63f1b5b0751283e04252e78cf1d6589273d2" - integrity sha512-9hx0CwZg92jGb7iHYQVgi0tOEHP/kM60CtWJQnmbATSPIQQ2xYzfoCI3EdqAhFBeeJwYMdWQuDUHMsuDbH9hyQ== + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d" + integrity sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-react-display-name" "^7.8.3" - "@babel/plugin-transform-react-jsx" "^7.8.3" - "@babel/plugin-transform-react-jsx-self" "^7.8.3" - "@babel/plugin-transform-react-jsx-source" "^7.8.3" + "@babel/plugin-transform-react-jsx" "^7.9.4" + "@babel/plugin-transform-react-jsx-development" "^7.9.0" + "@babel/plugin-transform-react-jsx-self" "^7.9.0" + "@babel/plugin-transform-react-jsx-source" "^7.9.0" "@babel/preset-typescript@7.9.0": version "7.9.0" @@ -1202,9 +1026,9 @@ "@babel/plugin-transform-typescript" "^7.9.0" "@babel/runtime-corejs3@^7.8.3": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz#26fe4aa77e9f1ecef9b776559bbb8e84d34284b7" - integrity sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz#67aded13fffbbc2cb93247388cf84d77a4be9a71" + integrity sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA== dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" @@ -1216,10 +1040,10 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" - integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" @@ -1230,6 +1054,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" + integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1239,49 +1070,25 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4", "@babel/traverse@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" - integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.6" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.0", "@babel/traverse@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" + integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" + "@babel/generator" "^7.9.6" "@babel/helper-function-name" "^7.9.5" "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + "@babel/parser" "^7.9.6" + "@babel/types" "^7.9.6" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.7.tgz#1fc9729e1acbb2337d5b6977a63979b4819f5d1d" - integrity sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.7.0", "@babel/types@^7.9.0", "@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" + integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== dependencies: "@babel/helper-validator-identifier" "^7.9.5" lodash "^4.17.13" @@ -1671,9 +1478,9 @@ loader-utils "^1.2.3" "@types/babel__core@^7.1.0": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" - integrity sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg== + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" + integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -1697,9 +1504,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.9.tgz#be82fab304b141c3eee81a4ce3b034d0eba1590a" - integrity sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.11.tgz#1ae3010e8bf8851d324878b42acec71986486d18" + integrity sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q== dependencies: "@babel/types" "^7.3.0" @@ -2001,9 +1808,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*": - version "13.9.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72" - integrity sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ== + version "14.0.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.1.tgz#5d93e0a099cd0acd5ef3d5bde3c086e1f49ff68c" + integrity sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA== "@types/node@^13.11.1": version "13.11.1" @@ -2064,53 +1871,53 @@ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== "@types/yargs@^13.0.0": - version "13.0.8" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.8.tgz#a38c22def2f1c2068f8971acb3ea734eb3c64a99" - integrity sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA== + version "13.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1" + integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^2.10.0": - version "2.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz#4431bc6d3af41903e5255770703d4e55a0ccbdec" - integrity sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg== + version "2.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.33.0.tgz#d6c8319d5011b4783bb3d2dadf105d8bdd499bd5" + integrity sha512-QV6P32Btu1sCI/kTqjTNI/8OpCYyvlGjW5vD8MpTIg+HGE5S88HtT1G+880M4bXlvXj/NjsJJG0aGcVh0DdbeQ== dependencies: - "@typescript-eslint/experimental-utils" "2.28.0" + "@typescript-eslint/experimental-utils" "2.33.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.28.0": - version "2.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz#1fd0961cd8ef6522687b4c562647da6e71f8833d" - integrity sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w== +"@typescript-eslint/experimental-utils@2.33.0": + version "2.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.33.0.tgz#000f1e5f344fbea1323dc91cc174805d75f99a03" + integrity sha512-qzPM2AuxtMrRq78LwyZa8Qn6gcY8obkIrBs1ehqmQADwkYzTE1Pb4y2W+U3rE/iFkSWcWHG2LS6MJfj6SmHApg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.28.0" + "@typescript-eslint/typescript-estree" "2.33.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" "@typescript-eslint/parser@^2.10.0": - version "2.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.28.0.tgz#bb761286efd2b0714761cab9d0ee5847cf080385" - integrity sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw== + version "2.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.33.0.tgz#395c0ef229ebef883608f8632a34f0acf02b9bdd" + integrity sha512-AUtmwUUhJoH6yrtxZMHbRUEMsC2G6z5NSxg9KsROOGqNXasM71I8P2NihtumlWTUCRld70vqIZ6Pm4E5PAziEA== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.28.0" - "@typescript-eslint/typescript-estree" "2.28.0" + "@typescript-eslint/experimental-utils" "2.33.0" + "@typescript-eslint/typescript-estree" "2.33.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.28.0": - version "2.28.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz#d34949099ff81092c36dc275b6a1ea580729ba00" - integrity sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q== +"@typescript-eslint/typescript-estree@2.33.0": + version "2.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.33.0.tgz#33504c050ccafd38f397a645d4e9534d2eccbb5c" + integrity sha512-d8rY6/yUxb0+mEwTShCQF2zYQdLlqihukNfG9IUlLYz5y1CH6G/9XYbrxQLq3Z14RNvkCC6oe+OcFlyUpwUbkg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" - semver "^6.3.0" + semver "^7.3.2" tsutils "^3.17.1" "@uifabric/foundation@^7.5.23": @@ -2413,9 +2220,9 @@ acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1: integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== acorn@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" - integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" + integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== add-dom-event-listener@^1.1.0: version "1.1.0" @@ -2459,9 +2266,9 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -2905,17 +2712,17 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^9.6.1: - version "9.7.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" - integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + version "9.7.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4" + integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ== dependencies: - browserslist "^4.8.3" - caniuse-lite "^1.0.30001020" + browserslist "^4.11.1" + caniuse-lite "^1.0.30001039" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.26" - postcss-value-parser "^4.0.2" + postcss "^7.0.27" + postcss-value-parser "^4.0.3" aws-sign2@~0.7.0: version "0.7.0" @@ -2998,6 +2805,13 @@ babel-plugin-dynamic-import-node@^2.3.0: dependencies: object.assign "^4.1.0" +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + babel-plugin-import@^1.13.0: version "1.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-import/-/babel-plugin-import-1.13.0.tgz#c532fd533df9db53b47d4d4db3676090fc5c07a5" @@ -3189,11 +3003,16 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +bn.js@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5" + integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA== + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -3311,7 +3130,7 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0: +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= @@ -3320,17 +3139,18 @@ browserify-rsa@^4.0.0: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.1.0.tgz#4fe971b379a5aeb4925e06779f9fa1f41d249d70" + integrity sha512-VYxo7cDCeYUoBZ0ZCy4UyEUCP3smyBd4DRQM5nrFS1jJjPJjX7rP3oLRpPoWfkhQfyJ0I9ZbHbKafrFD/SGlrg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.2" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" browserify-zlib@^0.2.0: version "0.2.0" @@ -3349,22 +3169,13 @@ browserslist@4.10.0: node-releases "^1.1.52" pkg-up "^3.1.0" -browserslist@^4.0.0, browserslist@^4.6.4, browserslist@^4.8.3, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c" - integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw== +browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.2, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1: + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== dependencies: - caniuse-lite "^1.0.30001030" - electron-to-chromium "^1.3.363" - node-releases "^1.1.50" - -browserslist@^4.6.2: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== - dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" node-releases "^1.1.53" pkg-up "^2.0.0" @@ -3433,9 +3244,9 @@ bytes@3.1.0: integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: bluebird "^3.5.5" chownr "^1.1.1" @@ -3564,15 +3375,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001030: - version "1.0.30001035" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e" - integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ== - -caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001038: - version "1.0.30001042" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz#c91ec21ec2d270bd76dbc2ce261260c292b8c93c" - integrity sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043: + version "1.0.30001058" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001058.tgz#9f8a318389e28f060272274ac93a661d17f8bf0d" + integrity sha512-UiRZmBYd1HdVVdFKy7PuLVx9e2NS7SMyx7QpWvFjiklYrLJKpLd19cRnRNqlw4zYa7vVejS3c8JUVobX241zHQ== capture-exit@^2.0.0: version "2.0.0" @@ -3652,7 +3458,7 @@ check-types@^8.0.3: resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== -chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.1.8: +chokidar@^2.0.0, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -3672,9 +3478,9 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.1.8: fsevents "^1.2.7" chokidar@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" + integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -3682,7 +3488,7 @@ chokidar@^3.3.0: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.3.0" + readdirp "~3.4.0" optionalDependencies: fsevents "~2.1.2" @@ -3751,9 +3557,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== clipboard@^2.0.0: version "2.0.6" @@ -4119,11 +3925,11 @@ copy-to-clipboard@^3, copy-to-clipboard@^3.2.0: toggle-selection "^1.0.6" core-js-compat@^3.6.2: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== dependencies: - browserslist "^4.8.3" + browserslist "^4.8.5" semver "7.0.0" core-js-pure@^3.0.0: @@ -4142,9 +3948,9 @@ core-js@^2.4.0, core-js@^2.6.5: integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-js@^3.5.0: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -4180,7 +3986,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -4191,7 +3997,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -4356,7 +4162,7 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" -css-tree@^1.0.0-alpha.28: +css-tree@1.0.0-alpha.39, css-tree@^1.0.0-alpha.28: version "1.0.0-alpha.39" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== @@ -4468,11 +4274,11 @@ cssnano@^4.1.10: postcss "^7.0.0" csso@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" - integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== dependencies: - css-tree "1.0.0-alpha.37" + css-tree "1.0.0-alpha.39" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@^0.3.4: version "0.3.8" @@ -5189,17 +4995,12 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== -electron-to-chromium@^1.3.363: - version "1.3.378" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.378.tgz#18c572cbb54bf5b2769855597cdc7511c02b481f" - integrity sha512-nBp/AfhaVIOnfwgL1CZxt80IcqWcyYXiX6v5gflAksxy+SzBVz7A7UWR1Nos92c9ofXW74V9PoapzRb0jJfYXw== - -electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.390: - version "1.3.409" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.409.tgz#c9a4380ff0ad3e26cd1b4b4cef800d17c46aec7e" - integrity sha512-CB2HUXiMsaVYY5VvcpELhDShiTRhI2FfN7CuacEZ5mDmMFuSG/ZVm8HoSya0+S61RvUd3TjIjFSKywqHZpRPzQ== +electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413: + version "1.3.437" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.437.tgz#110f1cd407e5d09b43d5585e5f237b71063412cf" + integrity sha512-PBQn2q68ErqMyBUABh9Gh8R6DunGky8aB5y3N5lPM7OVpldwyUbAK5AX9WcwE/5F6ceqvQ+iQLYkJYRysAs6Bg== -elliptic@^6.0.0: +elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== @@ -5266,9 +5067,9 @@ entities@^1.1.1: integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436" + integrity sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw== errno@^0.1.1, errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -5291,10 +5092,10 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" @@ -5575,11 +5376,11 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.1.0.tgz#c5c0b66f383e7656404f86b31334d72524eddb48" - integrity sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q== + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^4.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -5588,12 +5389,17 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -esutils@^2.0.0, esutils@^2.0.2: +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + +esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -5604,9 +5410,9 @@ etag@~1.8.1: integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== events@^3.0.0: version "3.1.0" @@ -5898,9 +5704,9 @@ fbjs@^0.8.9: ua-parser-js "^0.7.18" figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^3.0.0: version "3.2.0" @@ -6079,9 +5885,9 @@ flat-cache@^2.0.1: write "1.0.3" flatted@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" - integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flatten@^1.0.2: version "1.0.3" @@ -6104,9 +5910,9 @@ follow-redirects@1.5.10: debug "=3.1.0" follow-redirects@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb" - integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ== + version "1.11.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" + integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA== dependencies: debug "^3.0.0" @@ -6273,19 +6079,24 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@2.1.2, fsevents@~2.1.2: +fsevents@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== fsevents@^1.2.7: - version "1.2.11" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" - integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== dependencies: bindings "^1.5.0" nan "^2.12.1" +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -6355,14 +6166,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== @@ -6500,11 +6304,16 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: +graceful-fs@^4.0.0: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -6577,9 +6386,9 @@ hammerjs@^2.0.8: integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== har-schema@^2.0.0: version "2.0.0" @@ -6665,12 +6474,13 @@ has@^1.0.0, has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" @@ -6796,14 +6606,14 @@ html-entities@^1.2.1: integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== html-escaper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" - integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== html-minifier-terser@^5.0.1: - version "5.0.5" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.0.5.tgz#8f12f639789f04faa9f5cf2ff9b9f65607f21f8b" - integrity sha512-cBSFFghQh/uHcfSiL42KxxIRMF7A144+3E44xdlctIjxEmkEfCvouxNyFH2wysXk1fCGBPwtcr3hDWlGTfkDew== + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== dependencies: camel-case "^4.1.1" clean-css "^4.2.3" @@ -7107,7 +6917,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7513,11 +7323,6 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-regex@^1.0.4, is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" @@ -7619,9 +7424,11 @@ is-wsl@^1.1.0: integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= is-wsl@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" - integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" @@ -8075,9 +7882,9 @@ jest-worker@^24.6.0, jest-worker@^24.9.0: supports-color "^6.1.0" jest-worker@^25.1.0: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.2.6.tgz#d1292625326794ce187c38f51109faced3846c58" - integrity sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA== + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" + integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== dependencies: merge-stream "^2.0.0" supports-color "^7.0.0" @@ -8248,13 +8055,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" - integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== - dependencies: - minimist "^1.2.5" - json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" @@ -8501,12 +8301,12 @@ load-json-file@^4.0.0: strip-bom "^3.0.0" loader-fs-cache@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz#54cedf6b727e1779fd8f01205f05f6e88706f086" - integrity sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9" + integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA== dependencies: find-cache-dir "^0.1.1" - mkdirp "0.5.1" + mkdirp "^0.5.1" loader-runner@^2.4.0: version "2.4.0" @@ -8684,9 +8484,9 @@ make-dir@^2.0.0, make-dir@^2.1.0: semver "^5.6.0" make-dir@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -8873,17 +8673,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -8891,9 +8691,9 @@ mime@1.6.0, mime@^1.4.1: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + version "2.4.5" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.5.tgz#d8de2ecb92982dedbb6541c9b6841d7f218ea009" + integrity sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w== mimic-fn@^2.0.0, mimic-fn@^2.1.0: version "2.1.0" @@ -8937,11 +8737,6 @@ minimatch@3.0.4, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -8962,16 +8757,16 @@ minipass-flush@^1.0.5: minipass "^3.0.0" minipass-pipeline@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz#3dcb6bb4a546e32969c7ad710f2c79a86abba93a" - integrity sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34" + integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ== dependencies: minipass "^3.0.0" minipass@^3.0.0, minipass@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" - integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== dependencies: yallist "^4.0.0" @@ -9007,21 +8802,14 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: +mkdirp@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== dependencies: minimist "^1.2.5" -mkdirp@^0.5.3: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -9109,9 +8897,9 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nano-css@^5.2.1: version "5.3.0" @@ -9250,17 +9038,10 @@ node-notifier@^5.4.2: shellwords "^0.1.1" which "^1.3.0" -node-releases@^1.1.50: - version "1.1.52" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" - integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== - dependencies: - semver "^6.3.0" - node-releases@^1.1.52, node-releases@^1.1.53: - version "1.1.53" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" - integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== + version "1.1.55" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.55.tgz#8af23b7c561d8e2e6e36a46637bab84633b07cee" + integrity sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w== normalize-package-data@^2.3.2: version "2.5.0" @@ -9377,9 +9158,12 @@ object-inspect@^1.7.0: integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== object-is@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" + integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -9653,14 +9437,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== - dependencies: - p-try "^2.0.0" - -p-limit@^2.2.2: +p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -9751,7 +9528,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: +parse-asn1@^5.0.0, parse-asn1@^5.1.5: version "5.1.5" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== @@ -9960,7 +9737,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -10083,9 +9860,9 @@ popmotion@9.0.0-beta-8: tslib "^1.10.0" portfinder@^1.0.25: - version "1.0.25" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" - integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg== + version "1.0.26" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" + integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== dependencies: async "^2.6.2" debug "^3.1.1" @@ -10725,10 +10502,10 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" - integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" @@ -10748,10 +10525,10 @@ postcss@7.0.21: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.23, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2" + integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -10871,9 +10648,9 @@ promise@^8.0.3: asap "~2.0.6" prompts@^2.0.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.1.tgz#b63a9ce2809f106fa9ae1277c275b167af46ea05" - integrity sha512-qIP2lQyCwYbdzcqHIUi2HAxiWixhoM9OdLCWf8txXsapC/X9YdsCoeyRIXE/GP+Q0J37Q7+XN/MFqbUa7IzXNA== + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== dependencies: kleur "^3.0.3" sisteransi "^1.0.4" @@ -10908,9 +10685,9 @@ prr@~1.0.1: integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= psl@^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -11507,12 +11284,12 @@ react-i18next@^11.3.5: "@babel/runtime" "^7.3.1" html-parse-stringify2 "2.0.1" -react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.12.0, react-is@^16.7.0: version "16.13.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== -react-is@^16.8.6: +react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -11734,7 +11511,7 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1: +"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -11752,12 +11529,12 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - picomatch "^2.0.7" + picomatch "^2.2.1" realpath-native@^1.1.0: version "1.1.0" @@ -11812,9 +11589,9 @@ regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== regenerator-transform@^0.14.2: - version "0.14.3" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.3.tgz#54aebff2ef58c0ae61e695ad1b9a9d65995fff78" - integrity sha512-zXHNKJspmONxBViAb3ZUmFoFPnTBs3zFhCEZJiwp/gkNzxVbTqNJVjYKx6Qk1tQ1P4XLf4TbH9+KBB7wGoAaUw== + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== dependencies: "@babel/runtime" "^7.8.4" private "^0.1.8" @@ -11846,9 +11623,9 @@ regexpp@^2.0.1: integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== regexpp@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" - integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g== + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== regexpu-core@^4.7.0: version "4.7.0" @@ -12089,17 +11866,17 @@ resolve@1.15.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.4.0: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== dependencies: path-parse "^1.0.6" -resolve@^1.15.1: - version "1.16.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.0.tgz#063dc704fa3413e13ac1d0d1756a7cbfe95dd1a7" - integrity sha512-LarL/PIKJvc09k1jaeT4kQb/8/7P+qV4qSnN2K80AES+OHdfZELAKVOBjxsvtToT/uLOfFbvYvKfZmV8cee7nA== +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -12187,11 +11964,9 @@ rtl-css-js@^1.9.0: "@babel/runtime" "^7.1.2" run-async@^2.2.0, run-async@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -12206,9 +11981,9 @@ rw@1: integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= rxjs@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== dependencies: tslib "^1.9.0" @@ -12217,10 +11992,10 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-regex@^1.1.0: version "1.1.0" @@ -12295,9 +12070,9 @@ schema-utils@^1.0.0: ajv-keywords "^3.1.0" schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ== + version "2.6.6" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c" + integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA== dependencies: ajv "^6.12.0" ajv-keywords "^3.4.1" @@ -12368,6 +12143,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -12523,9 +12303,9 @@ side-channel@^1.0.2: object-inspect "^1.7.0" signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== simple-swizzle@^0.2.2: version "0.2.2" @@ -12545,9 +12325,9 @@ single-spa@^5.3.4: integrity sha512-J0I+OfKn04QdaOp3lC8UL2xmD/BA/V0OisbCFbrR5LDIdsbcP9QG6ZFRNqIQQP8C9XXUtlGnA3ZWPLQBKGcOrw== sisteransi@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.4.tgz#386713f1ef688c7c0304dc4c0632898941cad2e3" - integrity sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig== + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== size-sensor@^0.2.0: version "0.2.6" @@ -12652,9 +12432,9 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: urix "^0.1.0" source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -12703,14 +12483,14 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" @@ -12755,13 +12535,14 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sql-formatter-plus@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/sql-formatter-plus/-/sql-formatter-plus-1.3.6.tgz#56e671181ff34c0dc403bac6f950486626f9d406" - integrity sha512-3pelcpLve9GgsXshWL/59zyDkhICkXDURKjXyUN+PJqVGAt+ZmNPB2JY+hgnaQG5X9TQI7Q+WiyLEprHQAWuaQ== +sql-formatter-plus-plus@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sql-formatter-plus-plus/-/sql-formatter-plus-plus-1.4.0.tgz#e7b329ed572a4b804fd7f8ceb9960811af0b8656" + integrity sha512-AEXMcq+jwTTS/Ol1cD83eSRMdJje6yb5vWOFtl0drF9sGl7yVjc7tmJnDWaLStAOcalv/9BIk/91KKcw++ZjOA== dependencies: - "@babel/polyfill" "^7.6.0" + "@babel/polyfill" "^7.7.0" lodash "^4.17.15" + regexpu-core "^4.7.0" sshpk@^1.7.0: version "1.16.1" @@ -12982,21 +12763,39 @@ string.prototype.matchall@^4.0.2: regexp.prototype.flags "^1.3.0" side-channel "^1.0.2" +string.prototype.trimend@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -13080,9 +12879,9 @@ strip-final-newline@^2.0.0: integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== style-loader@0.23.1: version "0.23.1" @@ -13259,19 +13058,10 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@^4.1.2: - version "4.6.7" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" - integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^4.4.3, terser@^4.6.3: - version "4.6.11" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f" - integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA== +terser@^4.1.2, terser@^4.4.3, terser@^4.6.3: + version "4.6.13" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.13.tgz#e879a7364a5e0db52ba4891ecde007422c56a916" + integrity sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -13492,9 +13282,9 @@ ts-pnp@^1.1.6: integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== tsutils@^3.17.1: version "3.17.1" @@ -14001,11 +13791,11 @@ warning@^4.0.1, warning@^4.0.3, warning@~4.0.3: loose-envify "^1.0.0" watchpack@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== + version "1.6.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" + integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA== dependencies: - chokidar "^2.0.2" + chokidar "^2.1.8" graceful-fs "^4.1.2" neo-async "^2.5.0" @@ -14543,11 +14333,11 @@ yaml-loader@^0.5.0: js-yaml "^3.5.2" yaml@^1.7.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.8.2.tgz#a29c03f578faafd57dcb27055f9a5d569cb0c3d9" - integrity sha512-omakb0d7FjMo3R1D2EbTKVIk6dAVLRxFXdLZMEUToeAvuqgG/YuHMuQOZ5fgk+vQ8cx+cnGKwyg+8g8PNT0xQg== + version "1.9.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" + integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== dependencies: - "@babel/runtime" "^7.8.7" + "@babel/runtime" "^7.9.2" yargs-parser@^11.1.1: version "11.1.1" From 7b132878a9e6963b5b4df351d47c8a426fabcfb3 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Tue, 2 Jun 2020 19:41:13 +0800 Subject: [PATCH 11/14] Fix warnings --- ui/lib/apps/InstanceProfiling/pages/List.tsx | 2 +- .../SearchLogs/components/SearchHeader.tsx | 133 +++++++++--------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/ui/lib/apps/InstanceProfiling/pages/List.tsx b/ui/lib/apps/InstanceProfiling/pages/List.tsx index 758b749eb0..8fc23a24a9 100644 --- a/ui/lib/apps/InstanceProfiling/pages/List.tsx +++ b/ui/lib/apps/InstanceProfiling/pages/List.tsx @@ -1,4 +1,4 @@ -import { Badge, Button, Form, message, Select, Modal } from 'antd' +import { Badge, Button, Form, Select, Modal } from 'antd' import { ColumnActionsMode } from 'office-ui-fabric-react/lib/DetailsList' import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' import React, { useMemo, useState, useCallback, useRef } from 'react' diff --git a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx index 28a08541a2..cf653d2438 100644 --- a/ui/lib/apps/SearchLogs/components/SearchHeader.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchHeader.tsx @@ -63,76 +63,79 @@ export default function SearchHeader({ taskGroupID }: Props) { fetchData() }) - const handleSearch = useCallback(async (fieldsValue: IFormProps) => { - if ( - !fieldsValue.instances || - fieldsValue.instances.length === 0 || - !fieldsValue.logLevel || - !fieldsValue.timeRange - ) { - Modal.error({ - content: 'Some required fields are not filled', - }) - return - } - if (!instanceSelect.current) { - Modal.error({ - content: 'Internal error: Instance select is not ready', - }) - return - } - setSubmitting(true) + const handleSearch = useCallback( + async (fieldsValue: IFormProps) => { + if ( + !fieldsValue.instances || + fieldsValue.instances.length === 0 || + !fieldsValue.logLevel || + !fieldsValue.timeRange + ) { + Modal.error({ + content: 'Some required fields are not filled', + }) + return + } + if (!instanceSelect.current) { + Modal.error({ + content: 'Internal error: Instance select is not ready', + }) + return + } + setSubmitting(true) - const targets: ModelRequestTargetNode[] = instanceSelect - .current!.getInstanceByKeys(fieldsValue.instances) - .map((instance) => { - let port - switch (instance.instanceKind) { - case 'pd': - case 'tikv': - case 'tiflash': - port = instance.port - break - case 'tidb': - port = instance.status_port - break - } - return { - kind: instance.instanceKind, - display_name: instance.key, - ip: instance.ip, - port, - } - }) - .filter((i) => i.port != null) + const targets: ModelRequestTargetNode[] = instanceSelect + .current!.getInstanceByKeys(fieldsValue.instances) + .map((instance) => { + let port + switch (instance.instanceKind) { + case 'pd': + case 'tikv': + case 'tiflash': + port = instance.port + break + case 'tidb': + port = instance.status_port + break + } + return { + kind: instance.instanceKind, + display_name: instance.key, + ip: instance.ip, + port, + } + }) + .filter((i) => i.port != null) - const [startTime, endTime] = calcTimeRange(fieldsValue.timeRange) + const [startTime, endTime] = calcTimeRange(fieldsValue.timeRange) - const req: LogsearchCreateTaskGroupRequest = { - targets, - request: { - start_time: startTime * 1000, // unix millionsecond - end_time: endTime * 1000, // unix millionsecond - min_level: fieldsValue.logLevel, - patterns: (fieldsValue.keywords ?? '').split(/\s+/), // 'foo boo' => ['foo', 'boo'] - }, - } + const req: LogsearchCreateTaskGroupRequest = { + targets, + request: { + start_time: startTime * 1000, // unix millionsecond + end_time: endTime * 1000, // unix millionsecond + min_level: fieldsValue.logLevel, + patterns: (fieldsValue.keywords ?? '').split(/\s+/), // 'foo boo' => ['foo', 'boo'] + }, + } - try { - const result = await client.getInstance().logsTaskgroupPut(req) - const id = result.data.task_group?.id - if (!id) { - throw new Error('Invalid server response') + try { + const result = await client.getInstance().logsTaskgroupPut(req) + const id = result.data.task_group?.id + if (!id) { + throw new Error('Invalid server response') + } + navigate(`/search_logs/detail/${id}`) + } catch (e) { + // FIXME + Modal.error({ + content: e.message, + }) } - navigate(`/search_logs/detail/${id}`) - } catch (e) { - // FIXME - Modal.error({ - content: e.message, - }) - } - setSubmitting(false) - }, []) + setSubmitting(false) + }, + [navigate] + ) return ( Date: Wed, 3 Jun 2020 00:55:53 +0800 Subject: [PATCH 12/14] Update --- pkg/apiserver/apiserver.go | 1 + pkg/apiserver/clusterinfo/service.go | 32 ++-- pkg/apiserver/clusterinfo/topology.go | 18 +- pkg/apiserver/metrics/metrics.go | 36 +++- pkg/apiserver/profiling/profile.go | 6 +- pkg/keyvisual/decorator/tidb_requests.go | 21 ++- pkg/pd/pd.go | 18 ++ pkg/pd/pd_client.go | 74 ++++++++ pkg/utils/topology/monitor.go | 14 +- pkg/utils/topology/pd.go | 46 ++--- pkg/utils/topology/store.go | 21 +-- pkg/utils/topology/tidb.go | 8 +- pkg/utils/topology/topology.go | 10 +- .../SearchLogs/components/SearchProgress.tsx | 171 ++++++++---------- .../SearchLogs/components/SearchResult.tsx | 5 +- .../SearchLogs/components/Styles.module.less | 5 +- .../SearchLogs/pages/LogSearchHistory.tsx | 1 + ui/lib/apps/SearchLogs/utils/index.ts | 23 --- .../pages/List/TimeRangeSelector.tsx | 11 +- ui/lib/apps/Statement/translations/en.yaml | 4 +- ui/lib/apps/Statement/translations/zh-CN.yaml | 4 +- ui/lib/components/AnimatedSkeleton/index.tsx | 7 +- ui/lib/components/CardTableV2/index.tsx | 4 +- ui/lib/utils/instanceTable.ts | 2 + 24 files changed, 313 insertions(+), 229 deletions(-) create mode 100644 pkg/pd/pd.go create mode 100644 pkg/pd/pd_client.go diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 4c41c9e5ad..3914bb2fe0 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -107,6 +107,7 @@ func (s *Service) Start(ctx context.Context) error { s.newPDDataProvider, dbstore.NewDBStore, pd.NewEtcdClient, + pd.NewPDClient, config.NewDynamicConfigManager, tidb.NewForwarderConfig, tidb.NewForwarder, diff --git a/pkg/apiserver/clusterinfo/service.go b/pkg/apiserver/clusterinfo/service.go index 8f5e95bb9d..74f4d50afe 100644 --- a/pkg/apiserver/clusterinfo/service.go +++ b/pkg/apiserver/clusterinfo/service.go @@ -30,30 +30,30 @@ import ( "github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/user" "github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/utils" - "github.com/pingcap-incubator/tidb-dashboard/pkg/config" + "github.com/pingcap-incubator/tidb-dashboard/pkg/pd" "github.com/pingcap-incubator/tidb-dashboard/pkg/tidb" "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/topology" ) type Service struct { - ctx context.Context + lifecycleCtx context.Context - config *config.Config + pdClient *pd.Client etcdClient *clientv3.Client httpClient *http.Client tidbForwarder *tidb.Forwarder } -func NewService(lc fx.Lifecycle, config *config.Config, etcdClient *clientv3.Client, httpClient *http.Client, tidbForwarder *tidb.Forwarder) *Service { +func NewService(lc fx.Lifecycle, pdClient *pd.Client, etcdClient *clientv3.Client, httpClient *http.Client, tidbForwarder *tidb.Forwarder) *Service { s := &Service{ - config: config, + pdClient: pdClient, etcdClient: etcdClient, httpClient: httpClient, tidbForwarder: tidbForwarder, } lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { - s.ctx = ctx + s.lifecycleCtx = ctx return nil }, }) @@ -90,7 +90,7 @@ func (s *Service) deleteTiDBTopology(c *gin.Context) { errorChannel := make(chan error, 2) ttlKey := fmt.Sprintf("/topology/tidb/%v/ttl", address) nonTTLKey := fmt.Sprintf("/topology/tidb/%v/info", address) - ctx, cancel := context.WithTimeout(s.ctx, time.Second*5) + ctx, cancel := context.WithTimeout(s.lifecycleCtx, time.Second*5) defer cancel() var wg sync.WaitGroup @@ -127,7 +127,7 @@ func (s *Service) deleteTiDBTopology(c *gin.Context) { // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getTiDBTopology(c *gin.Context) { - instances, err := topology.FetchTiDBTopology(s.etcdClient) + instances, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.etcdClient) if err != nil { _ = c.Error(err) return @@ -149,7 +149,7 @@ type StoreTopologyResponse struct { // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getStoreTopology(c *gin.Context) { - tikvInstances, tiFlashInstances, err := topology.FetchStoreTopology(s.config.PDEndPoint, s.httpClient) + tikvInstances, tiFlashInstances, err := topology.FetchStoreTopology(s.pdClient) if err != nil { _ = c.Error(err) return @@ -169,7 +169,7 @@ func (s *Service) getStoreTopology(c *gin.Context) { // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getPDTopology(c *gin.Context) { - instances, err := topology.FetchPDTopology(s.config.PDEndPoint, s.httpClient) + instances, err := topology.FetchPDTopology(s.pdClient) if err != nil { _ = c.Error(err) return @@ -186,7 +186,7 @@ func (s *Service) getPDTopology(c *gin.Context) { // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getAlertManagerTopology(c *gin.Context) { - instance, err := topology.FetchAlertManagerTopology(s.etcdClient) + instance, err := topology.FetchAlertManagerTopology(s.lifecycleCtx, s.etcdClient) if err != nil { _ = c.Error(err) return @@ -203,7 +203,7 @@ func (s *Service) getAlertManagerTopology(c *gin.Context) { // @Security JwtAuth // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getGrafanaTopology(c *gin.Context) { - instance, err := topology.FetchGrafanaTopology(s.etcdClient) + instance, err := topology.FetchGrafanaTopology(s.lifecycleCtx, s.etcdClient) if err != nil { _ = c.Error(err) return @@ -222,7 +222,7 @@ func (s *Service) getGrafanaTopology(c *gin.Context) { // @Failure 401 {object} utils.APIError "Unauthorized failure" func (s *Service) getAlertManagerCounts(c *gin.Context) { address := c.Param("address") - cnt, err := fetchAlertManagerCounts(address, s.httpClient) + cnt, err := fetchAlertManagerCounts(s.lifecycleCtx, address, s.httpClient) if err != nil { _ = c.Error(err) return @@ -278,7 +278,7 @@ func (s *Service) getHostsInfo(c *gin.Context) { func (s *Service) fetchAllInstanceHostsMap() (map[string]struct{}, error) { allHosts := make(map[string]struct{}) - pdInfo, err := topology.FetchPDTopology(s.config.PDEndPoint, s.httpClient) + pdInfo, err := topology.FetchPDTopology(s.pdClient) if err != nil { return nil, err } @@ -286,7 +286,7 @@ func (s *Service) fetchAllInstanceHostsMap() (map[string]struct{}, error) { allHosts[i.IP] = struct{}{} } - tikvInfo, tiFlashInfo, err := topology.FetchStoreTopology(s.config.PDEndPoint, s.httpClient) + tikvInfo, tiFlashInfo, err := topology.FetchStoreTopology(s.pdClient) if err != nil { return nil, err } @@ -297,7 +297,7 @@ func (s *Service) fetchAllInstanceHostsMap() (map[string]struct{}, error) { allHosts[i.IP] = struct{}{} } - tidbInfo, err := topology.FetchTiDBTopology(s.etcdClient) + tidbInfo, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.etcdClient) if err != nil { return nil, err } diff --git a/pkg/apiserver/clusterinfo/topology.go b/pkg/apiserver/clusterinfo/topology.go index aa8ba69f39..e4cbff313d 100644 --- a/pkg/apiserver/clusterinfo/topology.go +++ b/pkg/apiserver/clusterinfo/topology.go @@ -1,28 +1,36 @@ package clusterinfo import ( + "context" "encoding/json" "fmt" "io/ioutil" "net/http" ) -func fetchAlertManagerCounts(alertManagerAddr string, httpClient *http.Client) (int, error) { - apiAddress := fmt.Sprintf("http://%s/api/v2/alerts", alertManagerAddr) - resp, err := httpClient.Get(apiAddress) +func fetchAlertManagerCounts(ctx context.Context, alertManagerAddr string, httpClient *http.Client) (int, error) { + apiUrl := fmt.Sprintf("http://%s/api/v2/alerts", alertManagerAddr) + req, err := http.NewRequestWithContext(ctx, "GET", apiUrl, nil) + if err != nil { + return 0, err + } + + resp, err := httpClient.Do(req) if err != nil { return 0, err } defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("alert manager API returns non success status code") + } + data, err := ioutil.ReadAll(resp.Body) if err != nil { return 0, err } var alerts []struct{} - err = json.Unmarshal(data, &alerts) if err != nil { return 0, err diff --git a/pkg/apiserver/metrics/metrics.go b/pkg/apiserver/metrics/metrics.go index a39aa90472..8318b268ea 100644 --- a/pkg/apiserver/metrics/metrics.go +++ b/pkg/apiserver/metrics/metrics.go @@ -1,6 +1,7 @@ package metrics import ( + "context" "fmt" "io/ioutil" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/joomcode/errorx" "go.etcd.io/etcd/clientv3" + "go.uber.org/fx" "github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/user" "github.com/pingcap-incubator/tidb-dashboard/pkg/utils/topology" @@ -23,13 +25,26 @@ var ( ) type Service struct { + ctx context.Context + + httpClient *http.Client etcdClient *clientv3.Client } -func NewService(etcdClient *clientv3.Client) *Service { - return &Service{ +func NewService(lc fx.Lifecycle, httpClient *http.Client, etcdClient *clientv3.Client) *Service { + s := &Service{ + httpClient: httpClient, etcdClient: etcdClient, } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + s.ctx = ctx + return nil + }, + }) + + return s } func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) { @@ -65,7 +80,7 @@ func (s *Service) queryHandler(c *gin.Context) { return } - pi, err := topology.FetchPrometheusTopology(s.etcdClient) + pi, err := topology.FetchPrometheusTopology(s.ctx, s.etcdClient) if err != nil { _ = c.Error(err) return @@ -81,23 +96,32 @@ func (s *Service) queryHandler(c *gin.Context) { params.Add("end", strconv.Itoa(req.EndTimeSec)) params.Add("step", strconv.Itoa(req.StepSec)) - client := http.Client{ - Timeout: 10 * time.Second, + uri := fmt.Sprintf("http://%s:%d/api/v1/query_range?%s", pi.IP, pi.Port, params.Encode()) + promReq, err := http.NewRequestWithContext(s.ctx, http.MethodGet, uri, nil) + if err != nil { + _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to build Prometheus request")) + return } - promResp, err := client.Get(fmt.Sprintf("http://%s:%d/api/v1/query_range?%s", pi.IP, pi.Port, params.Encode())) + + newHttpClient := *s.httpClient + newHttpClient.Timeout = 10 * time.Second + promResp, err := newHttpClient.Do(promReq) if err != nil { _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to send requests to Prometheus")) return } + defer promResp.Body.Close() if promResp.StatusCode != http.StatusOK { _ = c.Error(ErrPrometheusQueryFailed.New("failed to query Prometheus")) return } + body, err := ioutil.ReadAll(promResp.Body) if err != nil { _ = c.Error(ErrPrometheusQueryFailed.Wrap(err, "failed to read Prometheus query result")) return } + c.Data(promResp.StatusCode, promResp.Header.Get("content-type"), body) } diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 15a4992c38..07ccdff504 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -125,11 +125,10 @@ func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.P } func (f *fetcher) getProfile(target *model.RequestTargetNode, source string) (*profile.Profile, error) { - req, err := http.NewRequest(http.MethodGet, source, nil) + req, err := http.NewRequestWithContext(f.ctx, http.MethodGet, source, nil) if err != nil { return nil, fmt.Errorf("failed to create a new request %s: %v", source, err) } - req = req.WithContext(f.ctx) if target.Kind == model.NodeKindPD { // forbidden PD follower proxy req.Header.Add("PD-Allow-follower-handle", "true") @@ -162,11 +161,10 @@ func profileAndWriteSVG(ctx context.Context, target *model.RequestTargetNode, fi func fetchTiKVFlameGraphSVG(ctx context.Context, httpClient *http.Client, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint, schema string) (string, error) { url := fmt.Sprintf("%s://%s:%d/debug/pprof/profile?seconds=%d", schema, target.IP, target.Port, profileDurationSecs) - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create a new request %s: %v", url, err) } - req = req.WithContext(ctx) resp, err := httpClient.Do(req) if err != nil { return "", fmt.Errorf("request %s failed: %v", url, err) diff --git a/pkg/keyvisual/decorator/tidb_requests.go b/pkg/keyvisual/decorator/tidb_requests.go index c465d8bbe6..b03801b472 100644 --- a/pkg/keyvisual/decorator/tidb_requests.go +++ b/pkg/keyvisual/decorator/tidb_requests.go @@ -69,7 +69,7 @@ func (s *tidbLabelStrategy) updateMap(ctx context.Context) { hostname, port := s.forwarder.GetStatusConnProps() tidbEndpoint := fmt.Sprintf("%s://%s:%d", reqScheme, hostname, port) if err := request(tidbEndpoint, "schema", &dbInfos, s.HTTPClient); err != nil { - log.Error("fail to send schema request to tidb", zap.String("endpoint", tidbEndpoint), zap.Error(err)) + log.Error("fail to send schema request to TiDB", zap.String("endpoint", tidbEndpoint), zap.Error(err)) return } @@ -81,7 +81,7 @@ func (s *tidbLabelStrategy) updateMap(ctx context.Context) { continue } if err := request(tidbEndpoint, fmt.Sprintf("schema/%s", db.Name.O), &tableInfos, s.HTTPClient); err != nil { - log.Error("fail to send schema request to tidb", zap.String("endpoint", tidbEndpoint), zap.Error(err)) + log.Error("fail to send schema request to TiDB", zap.String("endpoint", tidbEndpoint), zap.Error(err)) updateSuccess = false continue } @@ -119,17 +119,18 @@ func (s *tidbLabelStrategy) updateMap(ctx context.Context) { func request(endpoint string, uri string, v interface{}, httpClient *http.Client) error { url := fmt.Sprintf("%s/%s", endpoint, uri) + + // FIXME: Better to assign a context resp, err := httpClient.Get(url) //nolint:gosec - if err == nil { - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - err = ErrTiDBHTTPRequestFailed.New("http status code: %d", resp.StatusCode) - } - } if err != nil { - log.Warn("request failed", zap.String("url", url), zap.Error(err)) - return err + return ErrTiDBHTTPRequestFailed.Wrap(err, "TiDB HTTP API request failed") } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return ErrTiDBHTTPRequestFailed.New("TiDB HTTP API returns non success status code") + } + decoder := json.NewDecoder(resp.Body) return decoder.Decode(v) } diff --git a/pkg/pd/pd.go b/pkg/pd/pd.go new file mode 100644 index 0000000000..0abee5dacb --- /dev/null +++ b/pkg/pd/pd.go @@ -0,0 +1,18 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package pd + +import "github.com/joomcode/errorx" + +var ErrNS = errorx.NewNamespace("error.pd") diff --git a/pkg/pd/pd_client.go b/pkg/pd/pd_client.go new file mode 100644 index 0000000000..af02354087 --- /dev/null +++ b/pkg/pd/pd_client.go @@ -0,0 +1,74 @@ +// Copyright 2020 PingCAP, 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package pd + +import ( + "context" + "io/ioutil" + "net/http" + + "go.uber.org/fx" + + "github.com/pingcap-incubator/tidb-dashboard/pkg/config" +) + +var ( + ErrPDClientRequestFailed = ErrNS.NewType("client_request_failed") +) + +type Client struct { + endpointAddr string + httpClient *http.Client + lifecycleCtx context.Context +} + +func NewPDClient(lc fx.Lifecycle, httpClient *http.Client, config *config.Config) *Client { + client := &Client{ + httpClient: httpClient, + endpointAddr: config.PDEndPoint, + } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + client.lifecycleCtx = ctx + return nil + }, + }) + + return client +} + +func (pd *Client) SendRequest(path string) ([]byte, error) { + url := pd.endpointAddr + path + req, err := http.NewRequestWithContext(pd.lifecycleCtx, "GET", url, nil) + if err != nil { + return nil, ErrPDClientRequestFailed.Wrap(err, "failed to build request for PD API %s", path) + } + + resp, err := pd.httpClient.Do(req) + if err != nil { + return nil, ErrPDClientRequestFailed.Wrap(err, "failed to send request to PD API %s", path) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, ErrPDClientRequestFailed.New("received non success status code %d from PD API %s", resp.StatusCode, path) + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, ErrPDClientRequestFailed.Wrap(err, "failed to read response from PD API %s", path) + } + + return data, nil +} diff --git a/pkg/utils/topology/monitor.go b/pkg/utils/topology/monitor.go index 79f82619e9..2b18656178 100644 --- a/pkg/utils/topology/monitor.go +++ b/pkg/utils/topology/monitor.go @@ -14,11 +14,13 @@ package topology import ( + "context" + "go.etcd.io/etcd/clientv3" ) -func FetchAlertManagerTopology(etcdClient *clientv3.Client) (*AlertManagerInfo, error) { - i, err := fetchStandardComponentTopology("alertmanager", etcdClient) +func FetchAlertManagerTopology(ctx context.Context, etcdClient *clientv3.Client) (*AlertManagerInfo, error) { + i, err := fetchStandardComponentTopology(ctx, "alertmanager", etcdClient) if err != nil { return nil, err } @@ -28,8 +30,8 @@ func FetchAlertManagerTopology(etcdClient *clientv3.Client) (*AlertManagerInfo, return &AlertManagerInfo{StandardComponentInfo: *i}, nil } -func FetchGrafanaTopology(etcdClient *clientv3.Client) (*GrafanaInfo, error) { - i, err := fetchStandardComponentTopology("grafana", etcdClient) +func FetchGrafanaTopology(ctx context.Context, etcdClient *clientv3.Client) (*GrafanaInfo, error) { + i, err := fetchStandardComponentTopology(ctx, "grafana", etcdClient) if err != nil { return nil, err } @@ -39,8 +41,8 @@ func FetchGrafanaTopology(etcdClient *clientv3.Client) (*GrafanaInfo, error) { return &GrafanaInfo{StandardComponentInfo: *i}, nil } -func FetchPrometheusTopology(etcdClient *clientv3.Client) (*PrometheusInfo, error) { - i, err := fetchStandardComponentTopology("prometheus", etcdClient) +func FetchPrometheusTopology(ctx context.Context, etcdClient *clientv3.Client) (*PrometheusInfo, error) { + i, err := fetchStandardComponentTopology(ctx, "prometheus", etcdClient) if err != nil { return nil, err } diff --git a/pkg/utils/topology/pd.go b/pkg/utils/topology/pd.go index 964b45f82d..0791deb473 100644 --- a/pkg/utils/topology/pd.go +++ b/pkg/utils/topology/pd.go @@ -15,31 +15,25 @@ package topology import ( "encoding/json" - "io/ioutil" - "net/http" "sort" "github.com/pingcap/log" "go.uber.org/zap" + + "github.com/pingcap-incubator/tidb-dashboard/pkg/pd" ) -func FetchPDTopology(pdEndPoint string, httpClient *http.Client) ([]PDInfo, error) { +func FetchPDTopology(pdClient *pd.Client) ([]PDInfo, error) { nodes := make([]PDInfo, 0) - healthMap, err := fetchPDHealth(pdEndPoint, httpClient) + healthMap, err := fetchPDHealth(pdClient) if err != nil { return nil, err } - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/members") - if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD members API HTTP get failed") - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := pdClient.SendRequest("/pd/api/v1/members") if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD members API read failed") + return nil, err } - ds := struct { Count int `json:"count"` Members []struct { @@ -63,7 +57,7 @@ func FetchPDTopology(pdEndPoint string, httpClient *http.Client) ([]PDInfo, erro continue } - ts, err := fetchPDStartTimestamp(u, httpClient) + ts, err := fetchPDStartTimestamp(pdClient) if err != nil { log.Warn("Failed to fetch PD start timestamp", zap.String("targetPdNode", u), zap.Error(err)) ts = 0 @@ -94,15 +88,10 @@ func FetchPDTopology(pdEndPoint string, httpClient *http.Client) ([]PDInfo, erro return nodes, nil } -func fetchPDStartTimestamp(pdEndPoint string, httpClient *http.Client) (int64, error) { - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/status") - if err != nil { - return 0, ErrPDAccessFailed.Wrap(err, "PD status API HTTP get failed") - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) +func fetchPDStartTimestamp(pdClient *pd.Client) (int64, error) { + data, err := pdClient.SendRequest("/pd/api/v1/status") if err != nil { - return 0, ErrPDAccessFailed.Wrap(err, "PD status API read failed") + return 0, err } ds := struct { @@ -116,18 +105,10 @@ func fetchPDStartTimestamp(pdEndPoint string, httpClient *http.Client) (int64, e return ds.StartTimestamp, nil } -func fetchPDHealth(pdEndPoint string, httpClient *http.Client) (map[string]struct{}, error) { - // health member set - memberHealth := map[string]struct{}{} - resp, err := httpClient.Get(pdEndPoint + "/pd/api/v1/health") - if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD health API HTTP get failed") - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - +func fetchPDHealth(pdClient *pd.Client) (map[string]struct{}, error) { + data, err := pdClient.SendRequest("/pd/api/v1/health") if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD health API read failed") + return nil, err } var healths []struct { @@ -139,6 +120,7 @@ func fetchPDHealth(pdEndPoint string, httpClient *http.Client) (map[string]struc return nil, ErrInvalidTopologyData.Wrap(err, "PD health API unmarshal failed") } + memberHealth := map[string]struct{}{} for _, v := range healths { memberHealth[v.MemberID.String()] = struct{}{} } diff --git a/pkg/utils/topology/store.go b/pkg/utils/topology/store.go index fd0e99a91d..0aee4328da 100644 --- a/pkg/utils/topology/store.go +++ b/pkg/utils/topology/store.go @@ -15,18 +15,18 @@ package topology import ( "encoding/json" - "io/ioutil" - "net/http" "sort" "strings" "github.com/pingcap/log" "go.uber.org/zap" + + "github.com/pingcap-incubator/tidb-dashboard/pkg/pd" ) // FetchStoreTopology returns TiKV info and TiFlash info. -func FetchStoreTopology(pdEndpoint string, httpClient *http.Client) ([]StoreInfo, []StoreInfo, error) { - stores, err := fetchStores(pdEndpoint, httpClient) +func FetchStoreTopology(pdClient *pd.Client) ([]StoreInfo, []StoreInfo, error) { + stores, err := fetchStores(pdClient) if err != nil { return nil, nil, err } @@ -104,26 +104,23 @@ type store struct { StartTimestamp int64 `json:"start_timestamp"` } -func fetchStores(endpoint string, httpClient *http.Client) ([]store, error) { - resp, err := httpClient.Get(endpoint + "/pd/api/v1/stores") +func fetchStores(pdClient *pd.Client) ([]store, error) { + data, err := pdClient.SendRequest("/pd/api/v1/stores") if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD stores API HTTP get failed") + return nil, err } - defer resp.Body.Close() + storeResp := struct { Count int `json:"count"` Stores []struct { Store store } `json:"stores"` }{} - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD stores API read failed") - } err = json.Unmarshal(data, &storeResp) if err != nil { return nil, ErrInvalidTopologyData.Wrap(err, "PD stores API unmarshal failed") } + ret := make([]store, 0, storeResp.Count) for _, s := range storeResp.Stores { ret = append(ret, s.Store) diff --git a/pkg/utils/topology/tidb.go b/pkg/utils/topology/tidb.go index f144246098..ff8304688a 100644 --- a/pkg/utils/topology/tidb.go +++ b/pkg/utils/topology/tidb.go @@ -28,13 +28,13 @@ import ( const tidbTopologyKeyPrefix = "/topology/tidb/" -func FetchTiDBTopology(etcdClient *clientv3.Client) ([]TiDBInfo, error) { - ctx, cancel := context.WithTimeout(context.Background(), defaultFetchTimeout) +func FetchTiDBTopology(ctx context.Context, etcdClient *clientv3.Client) ([]TiDBInfo, error) { + ctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout) defer cancel() - resp, err := etcdClient.Get(ctx, tidbTopologyKeyPrefix, clientv3.WithPrefix()) + resp, err := etcdClient.Get(ctx2, tidbTopologyKeyPrefix, clientv3.WithPrefix()) if err != nil { - return nil, ErrPDAccessFailed.Wrap(err, "PD etcd get key %s failed", tidbTopologyKeyPrefix) + return nil, ErrEtcdRequestFailed.Wrap(err, "failed to get key %s from PD etcd", tidbTopologyKeyPrefix) } nodesAlive := map[string]bool{} diff --git a/pkg/utils/topology/topology.go b/pkg/utils/topology/topology.go index a020860328..d3b904750a 100644 --- a/pkg/utils/topology/topology.go +++ b/pkg/utils/topology/topology.go @@ -30,7 +30,7 @@ import ( var ( ErrNS = errorx.NewNamespace("error.topology") - ErrPDAccessFailed = ErrNS.NewType("pd_access_failed") + ErrEtcdRequestFailed = ErrNS.NewType("pd_etcd_request_failed") ErrInvalidTopologyData = ErrNS.NewType("invalid_topology_data") ) @@ -63,14 +63,14 @@ func parseHostAndPortFromAddressURL(urlString string) (string, uint, error) { return u.Hostname(), uint(port), nil } -func fetchStandardComponentTopology(componentName string, etcdClient *clientv3.Client) (*StandardComponentInfo, error) { - ctx, cancel := context.WithTimeout(context.Background(), defaultFetchTimeout) +func fetchStandardComponentTopology(ctx context.Context, componentName string, etcdClient *clientv3.Client) (*StandardComponentInfo, error) { + ctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout) defer cancel() key := "/topology/" + componentName - resp, err := etcdClient.Get(ctx, key, clientv3.WithPrefix()) + resp, err := etcdClient.Get(ctx2, key, clientv3.WithPrefix()) if err != nil { - return nil, ErrPDAccessFailed.New("PD etcd get key %s failed", key) + return nil, ErrEtcdRequestFailed.Wrap(err, "failed to get key %s from PD etcd", key) } if resp.Count == 0 { return nil, nil diff --git a/ui/lib/apps/SearchLogs/components/SearchProgress.tsx b/ui/lib/apps/SearchLogs/components/SearchProgress.tsx index 17c6f7badb..40172f122b 100644 --- a/ui/lib/apps/SearchLogs/components/SearchProgress.tsx +++ b/ui/lib/apps/SearchLogs/components/SearchProgress.tsx @@ -1,40 +1,38 @@ import { Button, Modal, Tree } from 'antd' import _ from 'lodash' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useMemo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { getValueFormat } from '@baurine/grafana-value-formats' import client, { LogsearchTaskModel } from '@lib/client' import { AnimatedSkeleton, Card } from '@lib/components' import { FailIcon, LoadingIcon, SuccessIcon } from './Icon' -import { namingMap, NodeKind, NodeKindList, TaskState } from '../utils' +import { TaskState } from '../utils' import styles from './Styles.module.less' +import { InstanceKindName, InstanceKinds } from '@lib/utils/instanceTable' const { confirm } = Modal -const { TreeNode } = Tree const taskStateIcons = { [TaskState.Running]: LoadingIcon, [TaskState.Finished]: SuccessIcon, [TaskState.Error]: FailIcon, } -function leafNodeProps(task: LogsearchTaskModel) { - return { - icon: taskStateIcons[task.state || TaskState.Error], - disableCheckbox: !task.size || task.state !== TaskState.Finished, - } -} - -function renderLeafNodes(tasks: LogsearchTaskModel[]) { +function getLeafNodes(tasks: LogsearchTaskModel[]) { return tasks.map((task) => { - let title = task.target?.display_name ?? '' - if (task.size) { - title += ' (' + getValueFormat('bytes')(task.size!, 1) + ')' - } - return ( - + const title = ( + + {task.target?.display_name ?? ''}{' '} + ({getValueFormat('bytes')(task.size!, 1)}) + ) + return { + key: String(task.id), + title, + icon: taskStateIcons[task.state || TaskState.Error], + disableCheckbox: !task.size || task.state !== TaskState.Finished, + } }) } @@ -81,79 +79,65 @@ export default function SearchProgress({ } }, [tasks]) - const descriptionArray = [ - t('search_logs.progress.running'), - t('search_logs.progress.success'), - t('search_logs.progress.failed'), - ] - - function progressDescription(tasks: LogsearchTaskModel[]) { - const arr = [0, 0, 0] - tasks.forEach((task) => { - const state = task.state - if (state !== undefined) { - arr[state - 1]++ - } - }) - const res: string[] = [] - arr.forEach((count, index) => { - if (index < 1 || count <= 0) { - return - } - const str = `${count} ${descriptionArray[index]}` - res.push(str) - }) - return ( - res.join(',') + - ' (' + - getValueFormat('bytes')(_.sumBy(tasks, 'size'), 1) + - ')' - ) - } + const descriptionArray = useMemo( + () => [ + t('search_logs.progress.running'), + t('search_logs.progress.success'), + t('search_logs.progress.failed'), + ], + [t] + ) - function renderTreeNodes(tasks: LogsearchTaskModel[]) { - const servers = { - [NodeKind.TiDB]: [], - [NodeKind.TiKV]: [], - [NodeKind.PD]: [], - [NodeKind.TiFlash]: [], - } + const describeProgress = useCallback( + (tasks: LogsearchTaskModel[]) => { + const arr = [0, 0, 0] + tasks.forEach((task) => { + const state = task.state + if (state !== undefined) { + arr[state - 1]++ + } + }) + const res: string[] = [] + arr.forEach((count, index) => { + if (index < 1 || count <= 0) { + return + } + const str = `${count} ${descriptionArray[index]}` + res.push(str) + }) + return ( + res.join(', ') + + ' (' + + getValueFormat('bytes')(_.sumBy(tasks, 'size'), 1) + + ')' + ) + }, + [descriptionArray] + ) - tasks.forEach((task) => { - if (task.target?.kind === undefined) { + const treeData = useMemo(() => { + const data: any[] = [] + const tasksByIK = _.groupBy(tasks, (t) => t.target?.kind) + InstanceKinds.forEach((ik) => { + const tasks = tasksByIK[ik] + if (!tasks) { return } - servers[task.target.kind].push(task) + const title = ( + + {InstanceKindName[ik]} {describeProgress(tasks)} + + ) + data.push({ + title, + key: ik, + icon: parentNodeIcon(tasks), + disableCheckbox: !parentNodeCheckable(tasks), + children: getLeafNodes(tasks), + }) }) - - return NodeKindList.filter((kind) => servers[kind].length > 0).map( - (kind) => { - const tasks: LogsearchTaskModel[] = servers[kind] - const title = ( - - {namingMap[kind]} - - {progressDescription(tasks)} - - - ) - return ( - - ) - } - ) - } + return data + }, [tasks, describeProgress]) async function handleDownload() { if (taskGroupID < 0) { @@ -161,7 +145,7 @@ export default function SearchProgress({ } // filter out all parent node const keys = checkedKeys.filter( - (key) => !Object.keys(namingMap).some((name) => name === key) + (key) => !InstanceKinds.some((ik) => ik === key) ) const res = await client.getInstance().logsDownloadAcquireTokenGet(keys) @@ -199,9 +183,9 @@ export default function SearchProgress({ }) } - const handleCheck = (checkedKeys) => { + const handleCheck = useCallback((checkedKeys) => { setCheckedKeys(checkedKeys as string[]) - } + }, []) return ( {tasks && ( <> -
{progressDescription(tasks)}
+
{describeProgress(tasks)}