106 Commits

Author SHA1 Message Date
Fine0830
56a02293cf fix: update alerting detail style (#317) 2023-09-01 15:38:22 +08:00
Fine0830
b7115a4e47 fix: update Marketplace title and breadcrumb component style (#316) 2023-09-01 11:18:11 +08:00
Fine0830
6b1a2fa9f8 fix: update name (#315) 2023-08-31 18:12:22 +08:00
Fine0830
ce4608ad4b fix: path (#314) 2023-08-30 20:05:59 +08:00
Fine0830
dce1035f2e feat: implement a breadcrumb component as navigation (#313) 2023-08-30 19:06:40 +08:00
Fine0830
60a4232759 feat: implement MQE on topology widget (#312) 2023-08-22 23:34:16 +08:00
SimonHu1993
8c1ddb109c fix: getEndpoints keyword blank (#310) 2023-08-09 09:56:32 +08:00
Fine0830
08db5a0440 fix: selector (#309) 2023-08-01 15:41:16 +08:00
Fine0830
344f2a6608 fix: cannot access uninitialized variable on Safari (#308) 2023-08-01 15:12:00 +08:00
lsq27
39c584bce6 fix: remove node-sass (#307) 2023-07-31 08:16:31 +08:00
lsq27
eb0b860c3a fix: commit error on Windows (#306) 2023-07-30 16:07:14 +08:00
Fine0830
1e55eb2029 fix: polish names and descriptions on the marketplace page (#305) 2023-07-24 11:21:21 +08:00
block666
90505810ab Fix Virtual_MQ spelling mistake (#304) 2023-07-22 12:09:19 -04:00
Fine0830
169793bdff feat: use title instead of i18nKey for menus. (#303) 2023-07-21 23:06:31 -07:00
weixiang1862
3bdd1d1c67 feat: add MongoDB menu i18n(#11045) (#302) 2023-07-19 11:02:08 +08:00
pg.yang
7478ec85b1 fix: update multiple language titles of menus for more accurate/consistent (#301) 2023-07-16 21:45:07 +08:00
Fine0830
115deecff1 fix: set chart label (#300) 2023-07-15 23:45:09 +08:00
Fine0830
077b68ebbd feat: menus support for multiple languages (#299) 2023-07-12 18:07:34 +08:00
Fine0830
5367af47c4 feat: enhance the marketplace page (#298) 2023-07-11 22:32:36 +08:00
Fine0830
a521e041a7 feat: Implement customize menus (#297) 2023-07-11 17:19:30 +08:00
Fine0830
64293da11c fix: minTraceDuration and maxTraceDuration types (#295) 2023-07-05 14:57:33 +08:00
pw151294
30a9cb1c87 fix: init minTime to Infinity (#294) 2023-07-01 14:39:00 +08:00
Fine0830
9827b6766a build: bump dependencies to fix vulnerabilities (#293) 2023-06-28 21:19:01 +08:00
Fine0830
9ac1265e7f feat(style): add scss variables (#291) 2023-06-28 15:46:27 +08:00
Fine0830
63db3fbc2e fix: update the title of instance list and notices in the continue profiling (#289) 2023-06-25 22:41:45 +08:00
Fine0830
cc367dd29c fix: add a link to explain the expression metric, add units in the continue profiling widget (#288) 2023-06-21 18:08:47 +08:00
Fine0830
1ba56ca0cd fix: calculate string width to set Tabs name width (#286) 2023-06-20 18:04:41 +08:00
Fine0830
6c4991bc56 fix: view related trace (#285) 2023-06-16 11:13:36 +08:00
Fine0830
77c1694383 build: check component types (#283) 2023-06-14 09:35:35 +08:00
Fine0830
7fe3257c32 fix: query process metrics (#284) 2023-06-13 20:11:38 +08:00
Fine0830
8c9c339417 feat: filter tasks with current process ID (#282) 2023-06-13 14:48:38 +08:00
Fine0830
62a82590c9 fix: bugs fix and polish components for continue profiling (#281) 2023-06-12 18:24:13 +08:00
Fine0830
22db68646c feat: Implement task timeline and policy list widget for continous profiling (#280) 2023-06-12 16:17:38 +08:00
Fine0830
7738695601 fix: show error messages and update style for no data (#279) 2023-06-09 21:03:51 +08:00
Fine0830
9b1a5f7a74 fix: expressions (#278) 2023-06-09 18:23:33 +08:00
Fine0830
54997794b4 feat: remove returnTypeOfMQE and add detail label (#277) 2023-06-08 20:52:44 +08:00
Fine0830
372aee2eb6 fix: remove name (#276) 2023-06-08 09:21:17 +08:00
Fine0830
d662a0fb54 feat: remove metric name from queries (#275) 2023-06-07 16:58:30 +08:00
Fine0830
b293f4ddb5 build: bump vite and fix code style (#272) 2023-06-07 13:37:41 +08:00
Fine0830
279ec60915 Revert "build(deps): bump @antfu/utils, unplugin-auto-import and unplugin-vue-components (#269)" (#274)
This reverts commit ec67b4148c.
2023-06-07 13:20:04 +08:00
Fine0830
05688f0888 Revert "build(deps-dev): bump vite from 4.0.0 to 4.0.5 (#271)" (#273)
This reverts commit 15cd839480.
2023-06-07 12:31:54 +08:00
dependabot[bot]
15cd839480 build(deps-dev): bump vite from 4.0.0 to 4.0.5 (#271) 2023-06-06 15:06:47 +08:00
Fine0830
dc22f8da6e feat: supporting expressions to query metrics data (#270) 2023-06-04 14:09:36 +08:00
dependabot[bot]
ec67b4148c build(deps): bump @antfu/utils, unplugin-auto-import and unplugin-vue-components (#269) 2023-06-03 16:02:34 +08:00
Fine0830
986fe8b90e fix: topn type (#267) 2023-05-25 09:12:46 +08:00
Fine0830
fe28fae27d fix: pod list (#266) 2023-05-15 10:52:54 +08:00
xu1009
359197a1c5 add grizzly icon (#265)
Co-authored-by: litexu <litexu@tencent.com>
2023-05-11 00:01:15 +08:00
Fine0830
903cf8e9bd fix: set endpoint and instance selectors with url parameters (#264) 2023-05-03 00:16:09 +08:00
xu1009
921c961dc1 add jersey icon (#263) 2023-04-28 18:57:16 +08:00
innerpeacez
d129c75c8c feat: add mq menu (#262)
Signed-off-by: innerpeacez <innerpeace.zhai@gmail.com>
Co-authored-by: Fine <fanxue0830@gmail.com>
2023-04-27 17:38:13 +08:00
dependabot[bot]
a4a2c4fefc build(deps): bump yaml, @commitlint/load, lint-staged, stylelint, stylelint-config-standard and stylelint-order (#261) 2023-04-25 10:20:27 +08:00
Fine0830
7dde4820de build(deps): bump element-plus from 2.0.2 to 2.1.0 (#260) 2023-04-16 10:53:09 +08:00
Fine0830
7257858921 feat: update options for line graph (#259) 2023-04-14 17:09:30 +08:00
Fine0830
ce585d6e08 feat: set default value for showSymbol in the line graph (#258) 2023-04-14 10:35:30 +08:00
Fine0830
3f178f89f8 feat: support isEmptyValue flag for metrics query (#256) 2023-04-13 21:29:23 +08:00
innerpeacez
ceae10cbfa feat: add elasticsearch menu (#257) 2023-04-13 17:08:00 +08:00
Fine0830
49a5481fb0 types: optimize data types (#254) 2023-04-06 21:50:57 +08:00
yswdqz
8bb45bb453 Add Redis Menu (#253) 2023-04-05 22:20:11 +08:00
hadesy
8077043c7e fix:incorrect node name length calculation (#252) 2023-04-05 17:23:26 +08:00
Fine0830
0e15c023cc fix: polish tooltips for trace profiling widget (#251) 2023-04-02 20:52:25 +08:00
Fine0830
55e4828adc feat: update trace profiling protocol (#250) 2023-03-30 09:50:14 +08:00
Fine0830
68eea52e88 fix: update menu (#249) 2023-03-27 21:37:45 +08:00
pg.yang
5973632f0f Add AWS API Gateway menu (#248) 2023-03-26 10:01:05 +08:00
Fine0830
1d189e82a7 fix: set filter ID when ReadRecords metric associates with trace (#247) 2023-03-23 21:40:38 +08:00
Fine0830
2491ab7176 fix: incorrect operation menu content in the topology widget (#246) 2023-03-23 11:13:03 +08:00
Fine0830
449dccdf36 refactor: redesign and implement new topology (#243) 2023-03-22 17:00:24 +08:00
Fine0830
8031c1b463 revert: cpm5d (#245) 2023-03-21 09:27:02 +08:00
Fine0830
1c905aeb06 fix: alerting link (#244) 2023-03-16 17:44:25 +08:00
Fine0830
4e64b9a4b1 fix: operation pop-up window (#242) 2023-02-28 16:26:33 +08:00
Fine0830
1be572a95f feat: add cpm4d + avg calculations (#241) 2023-02-28 12:50:55 +08:00
Fine0830
5cc913a332 fix: data (#240) 2023-02-24 17:46:46 +08:00
Fine0830
220525a2d9 feat: add a calculation for cpm5d (#239) 2023-02-22 09:14:23 +08:00
yswdqz
72060f8227 Add AWS DynanoDB menu (#237) 2023-02-20 20:57:52 +08:00
Fine0830
b247ed1c24 feat: optimize menus and add Windows monitoring menu (#236) 2023-02-17 13:29:42 +08:00
Fine0830
b2707e0e62 feat: add period (#235) 2023-02-15 13:37:14 +08:00
Fine0830
a1066f09e4 refactor: optimize the attached events visualization in the trace widget (#234) 2023-02-15 13:32:32 +08:00
XinweiLyu
efed817f73 Add logo for HTTPX (#232) 2023-02-13 09:57:55 +08:00
Fine0830
4613149759 fix: remove duplicate query message (#231) 2023-02-10 20:56:15 +08:00
Fine0830
1877776720 refactor: optimize side bar component to make it more friendly (#230) 2023-02-09 17:54:11 +08:00
Fine0830
2b88266d67 build: bump postcss to a non-vulnerable version (#229) 2023-02-09 15:02:26 +08:00
dependabot[bot]
17b627a5d9 build(deps): bump @sideway/formula from 3.0.0 to 3.0.1 (#228) 2023-02-09 13:56:40 +08:00
pg.yang
5b0a68fe18 Add AWS S3 menu (#227) 2023-02-08 22:43:05 +08:00
Fine0830
d93ed2c5d3 fix: reset duration for query conditions after time range changes (#226) 2023-02-08 12:08:45 +08:00
Fine0830
c73322a504 fix: clear trace ID on the Log and Trace widgets after using association (#225) 2023-02-07 21:23:13 +08:00
Fine0830
4486684183 feat: add auto fresh to widgets independent mode (#224) 2023-02-07 16:14:38 +08:00
Fine0830
1768a1641c feat: update menus for OpenFunction (#223) 2023-02-06 14:36:13 +08:00
Fine0830
224053c0f4 feat: Implement independent mode for widgets (#221) 2023-02-06 13:38:19 +08:00
dependabot[bot]
ca38366a60 build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#222) 2023-02-06 10:04:39 +08:00
Superskyyy
45f2985549 Add websockets icon (#220)
* Add websockets icon

* Better one

* compress png to 12k
2023-01-17 21:46:57 -05:00
wuwen
3ef790dc07 fix: Formatted display of json content type (#219) 2023-01-16 20:34:51 +08:00
Superskyyy (COVID)
70df7605cb Add missing fastapi logo (#218) 2023-01-14 07:23:49 +08:00
dependabot[bot]
4fc451f370 build(deps): bump json5 from 1.0.1 to 1.0.2 (#217) 2023-01-08 21:11:59 +08:00
Fine0830
163de5e5cf refactor: optimize graph tooltips to make them more friendly (#216) 2023-01-05 17:44:28 +08:00
Fine0830
8785817efe feat: add a iframe widget for zipkin ui (#215) 2023-01-03 16:12:45 +08:00
Fine0830
db793e6c05 docs: update (#214) 2022-12-29 17:55:44 +08:00
Fine0830
d11ceab59d fix: add ElPopconfirm (#213) 2022-12-28 19:02:44 +08:00
吴晟 Wu Sheng
1278454148 Update .gitignore (#212) 2022-12-28 10:51:39 +08:00
Fine0830
7768f6ef16 fix: drag nodes (#211) 2022-12-20 16:52:14 +08:00
Fine0830
210b9ba491 build: update vite config (#210) 2022-12-18 11:16:21 +08:00
Fine0830
969580b770 fix: Instance Relation and Endpoint Relation dashboards show up (#209) 2022-12-17 15:59:59 +08:00
Fine0830
44dcb1e7f6 build: migrate the build tool from vue-cli to vite4 (#208) 2022-12-17 14:07:03 +08:00
Mahmoud Anwer
1e0c253488 Update MySQL UI to support MariaDB (#207) 2022-12-12 10:45:07 +08:00
dependabot[bot]
141a288542 build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#204) 2022-12-08 11:10:01 +08:00
Fine0830
154372615e update menu icons (#203) 2022-12-07 11:53:45 +08:00
pg.yang
253f5c9261 Add AWS menu for supporting AWS monitoring (#202) 2022-12-06 21:41:15 +08:00
Marcin Grzejszczak
aa11a681ce Added Micrometer icon (#201) 2022-12-02 23:57:35 +08:00
304 changed files with 32545 additions and 56542 deletions

33
.eslintignore Normal file
View File

@@ -0,0 +1,33 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

53
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier",
],
overrides: [
{
files: ["cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}"],
extends: ["plugin:cypress/recommended"],
},
],
parserOptions: {
ecmaVersion: "latest",
},
env: {
browser: true,
node: true,
},
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"vue/script-setup-uses-vars": "error",
"@typescript-eslint/ban-ts-ignore'": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-this-alias": "off",
"vue/multi-word-component-names": "off",
},
};

View File

@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [12.x, 14.x, 16.x] node-version: [14.x, 16.x, 18.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
@@ -49,6 +49,7 @@ jobs:
npm ci npm ci
npm run lint npm run lint
npm run build --if-present npm run build --if-present
npm run check-components-types
npm run test:unit npm run test:unit
env: env:
CI: true CI: true

34
.gitignore vendored
View File

@@ -16,24 +16,40 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
.DS_Store
node_modules
/dist
/node
/tests/e2e/videos/
/tests/e2e/screenshots/
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local
# Log files # Logs
logs
*.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log*
node
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Editor directories and files # Editor directories and files
.idea .idea

27
.husky/commit-msg Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/sh
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# shellcheck source=./_/husky.sh
. "$(dirname "$0")/_/husky.sh"
PATH="/usr/local/bin:$PATH"
npx --no-install commitlint --edit "$1"

27
.husky/common.sh Normal file
View File

@@ -0,0 +1,27 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#!/bin/sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

29
.husky/pre-commit Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
[ -n "$CI" ] && exit 0
PATH="/usr/local/bin:$PATH"
# Format and submit code according to lintstagedrc configuration
npm run lint:lint-staged

22
.stylelintignore Normal file
View File

@@ -0,0 +1,22 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
/dist/*
/public/*
public/*

View File

@@ -1,20 +1,20 @@
Apache SkyWalking Booster UI # Apache SkyWalking Booster UI
===============
<img src="http://skywalking.apache.org/assets/logo.svg" alt="Sky Walking logo" height="90px" align="right" /> <img src="http://skywalking.apache.org/assets/logo.svg" alt="Sky Walking logo" height="90px" align="right" />
[Apache SkyWalking](https://github.com/apache/skywalking) Booster UI. [Apache SkyWalking](https://github.com/apache/skywalking) Booster UI.
![NPM BUILD](https://github.com/apache/skywalking-booster-ui/workflows/Node%20CI/badge.svg) ![NPM BUILD](https://github.com/apache/skywalking-booster-ui/workflows/Node%20CI/badge.svg)
This UI starts from SkyWalking OAP v9 core. This UI starts from SkyWalking OAP v9 core.
## Release ## Release
This repo wouldn't release separately. All source codes have been included in the main repo release. The tags match the [main repo](https://github.com/apache/skywalking) tags. This repo wouldn't release separately. All source codes have been included in the main repo release. The tags match the [main repo](https://github.com/apache/skywalking) tags.
## Development ## Development
The app was built with [Vue3.x + Typescript](https://github.com/vuejs/vue). The app was built with [Vue3.x + Typescript](https://github.com/vuejs/vue).
### Prepare ### Prepare
@@ -28,19 +28,21 @@ npm install
### Build ### Build
**All following builds are for dev.** **All following builds are for dev.**
``` ```
npm install npm install
npm run serve npm run dev
``` ```
The default UI address is `http://localhost:8080`. The default UI address is `http://localhost:8080`.
# Contact Us # Contact Us
* Submit an [issue](https://github.com/apache/skywalking/issues) if you face some issues. Submit a [discussion](https://github.com/apache/skywalking/discussions) if you want to propose new feature or have any question.
* Mailing list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe the mailing list. - Submit an [issue](https://github.com/apache/skywalking/issues) if you face some issues. Submit a [discussion](https://github.com/apache/skywalking/discussions) if you want to propose new feature or have any question.
* Join Slack. Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in. - Mailing list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe the mailing list.
* QQ Group: 392443393, 901167865 - Join Slack. Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
- QQ Group: 392443393, 901167865
# License # License
[Apache 2.0 License.](/LICENSE) [Apache 2.0 License.](/LICENSE)

View File

@@ -15,32 +15,37 @@
* limitations under the License. * limitations under the License.
*/ */
export default [ module.exports = {
{ ignores: [(commit) => commit.includes("init")],
path: "", extends: ["@commitlint/config-conventional"],
name: "Browser", rules: {
meta: { "body-leading-blank": [2, "always"],
title: "browser", "footer-leading-blank": [1, "always"],
icon: "language", "header-max-length": [2, "always", 108],
}, "subject-empty": [2, "never"],
redirect: "/browser", "type-empty": [2, "never"],
children: [ "subject-case": [0],
{ "type-enum": [
path: "/browser", 2,
name: "Browser", "always",
meta: { [
title: "browser", "feat",
layer: "BROWSER", "fix",
}, "perf",
}, "style",
{ "docs",
path: "/browser/tab/:activeTabIndex", "test",
name: "BrowserActiveTabIndex", "refactor",
meta: { "build",
notShow: true, "ci",
layer: "BROWSER", "chore",
}, "revert",
}, "wip",
"workflow",
"types",
"release",
"merge",
],
], ],
}, },
]; };

13
src/assets/icons/index.ts → cypress.config.ts Executable file → Normal file
View File

@@ -14,7 +14,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const requireAll = (requireContext: Recordable) =>
requireContext.keys().map(requireContext); import { defineConfig } from "cypress";
const req = require.context("./", true, /\.svg$/);
requireAll(req); export default defineConfig({
e2e: {
specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}",
baseUrl: "http://localhost:4173",
},
});

View File

@@ -14,10 +14,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
describe("My First Test", () => { describe("My First Test", () => {
it("renders props.msg when passed", () => { it("visits the app root url", () => {
const msg = "new message"; cy.visit("/");
console.log(msg); cy.contains("h1", "You did it!");
}); });
}); });

26
cypress/e2e/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["./**/*", "../support/**/*"],
"compilerOptions": {
"isolatedModules": false,
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
}
}

View File

@@ -14,13 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
module.exports = { {
plugins: ["cypress"], "name": "Using fixtures to represent data",
env: { "email": "hello@cypress.io",
mocha: true, "body": "Fixtures are a great way to mock data for responses to routes"
"cypress/globals": true, }
},
rules: {
strict: "off",
},
};

View File

@@ -14,8 +14,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/// <reference types="cypress" />
// *********************************************** // ***********************************************
// This example commands.js shows you how to // This example commands.ts shows you how to
// create various custom commands and overwrite // create various custom commands and overwrite
// existing commands. // existing commands.
// //
@@ -26,16 +27,29 @@
// //
// //
// -- This is a parent command -- // -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... }) // Cypress.Commands.add('login', (email, password) => { ... })
// //
// //
// -- This is a child command -- // -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
// //
// //
// -- This is a dual command -- // -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
// //
// //
// -- This is will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
export {};

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
// *********************************************************** // ***********************************************************
// This example support/index.js is processed and // This example support/index.js is processed and
// loaded automatically before your test files. // loaded automatically before your test files.

26
env.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_SW_PROXY_TARGET: string;
readonly VITE_DROP_CONSOLE: boolean;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -15,17 +15,13 @@ limitations under the License. -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title>Apache SkyWalking</title>
<title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

50765
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,21 @@
{ {
"name": "skywalking-booster-ui", "name": "skywalking-booster-ui",
"version": "9.3.0", "version": "9.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite",
"build": "vue-cli-service build", "build": "run-p type-check build-only",
"test:unit": "vue-cli-service test:unit", "preview": "vite preview",
"test:e2e": "vue-cli-service test:e2e", "test:unit": "vitest --environment jsdom --root src/",
"lint": "vue-cli-service lint" "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged",
"prepare": "husky install",
"check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi"
}, },
"dependencies": { "dependencies": {
"axios": "^0.24.0", "axios": "^0.24.0",
@@ -15,134 +23,82 @@
"d3-flame-graph": "^4.1.3", "d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.0.2", "element-plus": "^2.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0", "monaco-editor": "^0.34.1",
"pinia": "^2.0.5", "pinia": "^2.0.28",
"vis-timeline": "^7.5.1", "vis-timeline": "^7.5.1",
"vue": "^3.0.0", "vue": "^3.2.45",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-router": "^4.0.0-0", "vue-router": "^4.1.6",
"vue-types": "^4.1.1" "vue-types": "^4.1.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.6.6",
"@commitlint/config-conventional": "^17.6.6",
"@rushstack/eslint-patch": "^1.1.4",
"@types/d3": "^7.1.0", "@types/d3": "^7.1.0",
"@types/d3-tip": "^3.5.5", "@types/d3-tip": "^3.5.5",
"@types/echarts": "^4.9.12", "@types/echarts": "^4.9.12",
"@types/jest": "^24.0.19", "@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.179", "@types/lodash": "^4.14.179",
"@types/node": "^18.11.12",
"@types/three": "^0.131.0", "@types/three": "^0.131.0",
"@typescript-eslint/eslint-plugin": "^4.18.0", "@vitejs/plugin-vue": "^4.0.0",
"@typescript-eslint/parser": "^4.18.0", "@vitejs/plugin-vue-jsx": "^3.0.0",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/cli-plugin-e2e-cypress": "~5.0.8", "@vue/eslint-config-typescript": "^11.0.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/test-utils": "^2.2.6",
"@vue/cli-plugin-router": "~4.5.0", "@vue/tsconfig": "^0.1.3",
"@vue/cli-plugin-typescript": "~4.5.0", "@vueuse/core": "^9.6.0",
"@vue/cli-plugin-unit-jest": "~4.5.0", "cypress": "^12.0.2",
"@vue/cli-plugin-vuex": "~4.5.0", "eslint": "^8.22.0",
"@vue/cli-service": "~4.5.0", "eslint-plugin-cypress": "^2.12.1",
"@vue/compiler-sfc": "^3.0.0", "eslint-plugin-vue": "^9.3.0",
"@vue/eslint-config-prettier": "^6.0.0", "husky": "^8.0.2",
"@vue/eslint-config-typescript": "^7.0.0", "jsdom": "^20.0.3",
"@vue/test-utils": "^2.0.0-0", "lint-staged": "^13.2.1",
"babel-jest": "^24.9.0", "mockjs": "^1.1.0",
"eslint": "^6.7.2", "npm-run-all": "^4.1.5",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "postcss-scss": "^4.0.2",
"prettier": "^2.2.1", "prettier": "^2.7.1",
"sass-loader": "^10.2.0", "sass": "^1.56.1",
"stylelint": "^14.1.0", "start-server-and-test": "^1.15.2",
"stylelint": "15.9.0",
"stylelint-config-html": "^1.0.0", "stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "9.0.4",
"stylelint-config-standard": "^24.0.0", "stylelint-config-standard": "^33.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^6.0.3",
"svg-sprite-loader": "^6.0.11", "typescript": "~4.7.4",
"typescript": "~4.4.4",
"unplugin-auto-import": "^0.7.0", "unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2", "unplugin-vue-components": "^0.19.2",
"vue-jest": "^5.0.0-0" "vite": "^4.0.5",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6",
"vue-tsc": "^1.0.12"
}, },
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"vue/script-setup-uses-vars": "error",
"@typescript-eslint/ban-ts-ignore'": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-this-alias": "off"
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
},
"eslintIgnore": [
"vue.config.js"
],
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
"not dead" "not dead"
], ],
"jest": {
"preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel",
"transform": {
"^.+\\.vue$": "vue-jest"
}
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": { "lint-staged": {
"*.{js,jsx,ts,tsx}": [ "*.{js,jsx,ts,tsx,vue}": [
"eslint --fix", "eslint . --ext .vue,.js,.jsx,.ts,.tsx --fix --ignore-path .gitignore",
"prettier --write" "prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
"stylelint --cache --fix \"**/*.{vue}\" --cache --cache-location node_modules/.cache/stylelint/"
], ],
"*.vue": [ "*.{scss,less}": [
"eslint --fix", "prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
"prettier --write", "stylelint --cache --fix \"**/*.{less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
"stylelint --fix --custom-syntax postcss-html"
], ],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [ "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"package.json": [
"prettier --write" "prettier --write"
], ],
"*.{scss,less,styl}": [ "package.json": [
"stylelint --fix",
"prettier --write" "prettier --write"
], ],
"*.md": [ "*.md": [

View File

@@ -14,8 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
module.exports = { module.exports = {
plugins: { autoprefixer: {},
autoprefixer: {},
},
}; };

26
prettier.config.js Normal file
View File

@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
printWidth: 120,
semi: true,
vueIndentScriptAndStyle: true,
trailingComma: "all",
proseWrap: "never",
htmlWhitespaceSensitivity: "strict",
endOfLine: "auto",
};

View File

@@ -15,11 +15,22 @@ limitations under the License. -->
<template> <template>
<router-view /> <router-view />
</template> </template>
<script lang="ts" setup>
import { useRoute } from "vue-router";
const route = useRoute();
setTimeout(() => {
if (route.name === "ViewWidget") {
(document.querySelector("#app") as any).style.minWidth = "120px";
} else {
(document.querySelector("#app") as any).style.minWidth = "1024px";
}
}, 500);
</script>
<style> <style>
#app { #app {
color: #2c3e50; color: #2c3e50;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
min-width: 1024px; }
}
</style> </style>

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z" fill="#2c2c2c"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -12,6 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg t="1684376918107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7954">
<path d="M15 15.984h6v3q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-3h6q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109zM18.984 9v-3.984h-13.969v3.984h3.984q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109h3.984zM18.984 3q0.797 0 1.406 0.609t0.609 1.406v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-6.984q0-0.797 0.609-1.406t1.406-0.609h13.969z"></path> <path d="M243.921917 780.038686l357.445972 0L601.367889 422.592714 243.921917 422.592714 243.921917 780.038686zM288.679283 467.350081l268.036639 0 0 268.035616L288.679283 735.385696 288.679283 467.350081zM779.993149 65.25112 243.921917 65.25112c-98.640578 0-178.6314 79.990822-178.6314 178.716334L65.290517 780.038686c0 98.640578 79.990822 178.710194 178.6314 178.710194l536.071232 0c98.725512 0 178.716334-80.069617 178.716334-178.710194L958.709483 243.967454C958.709483 145.242965 878.717637 65.25112 779.993149 65.25112zM869.404528 735.385696c0 73.992201-60.07319 133.972271-134.084834 133.972271L288.679283 869.357967c-74.096579 0-134.063345-59.98007-134.063345-133.972271L154.615938 288.61328c0-73.984015 59.966767-134.057205 134.063345-134.057205l446.639386 0c74.011644 0 134.084834 60.07319 134.084834 134.057205L869.403504 735.385696zM511.957533 243.967454l268.035616 0 0 89.319282 0 268.035616-89.326445 0-44.645826 0 0-44.673455 89.298815 0L735.319693 288.61328l-268.014126 0 0 89.326445-44.652989 0 0-133.971247L511.957533 243.968477z" p-id="7955"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M18.984 18q1.219 0 2.109-0.891t0.891-2.109-0.891-2.109-2.109-0.891h-1.5v-0.516q0-2.297-1.594-3.891t-3.891-1.594q-1.875 0-3.328 1.125t-1.969 2.859h-0.703q-1.641 0-2.813 1.195t-1.172 2.836 1.172 2.813 2.813 1.172h12.984zM19.359 10.031q1.922 0.141 3.281 1.57t1.359 3.398q0 2.063-1.477 3.539t-3.539 1.477h-12.984q-2.484 0-4.242-1.758t-1.758-4.242q0-2.203 1.57-3.961t3.773-1.992q0.984-1.828 2.766-2.953t3.891-1.125q2.531 0 4.711 1.781t2.648 4.266z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M16.359 14.016h3.375q0.281-1.313 0.281-2.016t-0.281-2.016h-3.375q0.141 0.984 0.141 2.016t-0.141 2.016zM14.578 19.547q1.172-0.375 2.438-1.43t1.922-2.133h-2.953q-0.469 1.875-1.406 3.563zM14.344 14.016q0.141-0.984 0.141-2.016t-0.141-2.016h-4.688q-0.141 0.984-0.141 2.016t0.141 2.016h4.688zM12 19.969q1.313-1.922 1.922-3.984h-3.844q0.609 2.063 1.922 3.984zM8.016 8.016q0.563-2.016 1.406-3.563-1.172 0.375-2.461 1.43t-1.898 2.133h2.953zM5.063 15.984q0.609 1.078 1.898 2.133t2.461 1.43q-0.938-1.688-1.406-3.563h-2.953zM4.266 14.016h3.375q-0.141-0.984-0.141-2.016t0.141-2.016h-3.375q-0.281 1.313-0.281 2.016t0.281 2.016zM12 4.031q-1.313 1.922-1.922 3.984h3.844q-0.609-2.063-1.922-3.984zM18.938 8.016q-0.656-1.078-1.922-2.133t-2.438-1.43q0.844 1.547 1.406 3.563h2.953zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -12,6 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M18.984 18q1.219 0 2.109-0.891t0.891-2.109-0.891-2.109-2.109-0.891h-1.5v-0.516q0-2.297-1.594-3.891t-3.891-1.594q-1.875 0-3.328 1.125t-1.969 2.859h-0.703q-1.641 0-2.813 1.195t-1.172 2.836 1.172 2.813 2.813 1.172h12.984zM19.359 10.031q1.922 0.141 3.281 1.57t1.359 3.398q0 2.063-1.477 3.539t-3.539 1.477h-12.984q-2.484 0-4.242-1.758t-1.758-4.242q0-2.203 1.57-3.961t3.773-1.992q0.984-1.828 2.766-2.953t3.891-1.125q2.531 0 4.711 1.781t2.648 4.266z"></path> <path d="M18.984 18q1.219 0 2.109-0.891t0.891-2.109-0.891-2.109-2.109-0.891h-1.5v-0.516q0-2.297-1.594-3.891t-3.891-1.594q-1.875 0-3.328 1.125t-1.969 2.859h-0.703q-1.641 0-2.813 1.195t-1.172 2.836 1.172 2.813 2.813 1.172h12.984zM19.359 10.031q1.922 0.141 3.281 1.57t1.359 3.398q0 2.063-1.477 3.539t-3.539 1.477h-12.984q-2.484 0-4.242-1.758t-1.758-4.242q0-2.203 1.57-3.961t3.773-1.992q0.984-1.828 2.766-2.953t3.891-1.125q2.531 0 4.711 1.781t2.648 4.266z"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1684390612367" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12207">
<path d="M202.66008 885.33784A10.66632 10.66632 0 0 0 213.3264 874.67152v-85.33056a42.66528 42.66528 0 0 0-42.66528-42.66528H85.33056a42.66528 42.66528 0 0 0-42.66528 42.66528v85.33056a10.66632 10.66632 0 0 0 10.66632 10.66632zM458.65176 885.33784a10.66632 10.66632 0 0 0 10.66632-10.66632v-298.65696a42.66528 42.66528 0 0 0-42.66528-42.665281H341.32224a42.66528 42.66528 0 0 0-42.66528 42.665281v298.65696a10.66632 10.66632 0 0 0 10.66632 10.66632zM714.643441 885.33784a10.66632 10.66632 0 0 0 10.66632-10.66632v-213.3264a42.66528 42.66528 0 0 0-42.66528-42.66528h-85.33056a42.66528 42.66528 0 0 0-42.66528 42.66528v213.3264a10.66632 10.66632 0 0 0 10.66632 10.66632zM970.635121 885.33784a10.66632 10.66632 0 0 0 10.66632-10.66632v-511.983361a42.66528 42.66528 0 0 0-42.66528-42.66528h-85.33056a42.66528 42.66528 0 0 0-42.66528 42.66528v511.983361a10.66632 10.66632 0 0 0 10.66632 10.66632z" p-id="12208"></path><path d="M149.32848 576.01456a85.33056 85.33056 0 0 0 85.33056-85.330561 84.51992 84.51992 0 0 0-4.266528-25.599168l135.120942-112.636339a83.410622 83.410622 0 0 0 104.273945-19.626029l106.6632 35.582844A85.33056 85.33056 0 0 0 746.642401 362.688159a83.79461 83.79461 0 0 0-9.85568-38.910735l140.240776-163.621349A85.074568 85.074568 0 1 0 831.972961 85.363839a83.709279 83.709279 0 0 0 3.967871 24.361875L688.190967 282.136111a82.429321 82.429321 0 0 0-91.346364 25.300511l-106.663201-35.540179A85.117234 85.117234 0 1 0 324.256128 302.956767L189.263182 415.763768A84.263928 84.263928 0 0 0 149.32848 405.353439a85.33056 85.33056 0 0 0 0 170.661121zM981.301441 938.66944H42.66528a43.347925 43.347925 0 0 0-42.66528 42.66528 42.66528 42.66528 0 0 0 42.66528 42.66528h938.636161a42.66528 42.66528 0 0 0 42.66528-42.66528 43.305259 43.305259 0 0 0-42.66528-42.66528z" p-id="12209"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1680101648371" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15649" width="48" height="48"><path d="M832 272c0-62.4-51-112.9-113.6-112-60.7 0.9-110 50.6-110.4 111.3-0.3 52.6 35.6 96.8 84.2 109.2 14 3.6 23.8 16 24.1 30.4 0.5 27.3-4.4 57.4-22.3 82.5-28.7 40.3-80.7 54.9-126.6 67.8-29 8.1-50.1 10.2-68.7 12-26.4 2.6-51.4 5.1-82.6 23-6.6 3.8-13.1 8-19.2 12.6-5.3 4-12.8 0.2-12.8-6.4V241.3c0-12.2 6.8-23.5 17.7-28.9 37.1-18.4 62.6-56.8 62.3-101.1-0.5-62.8-53.2-113.4-116-111.2C288.1 2.1 240 51.4 240 112c0 44 25.4 82.1 62.3 100.4 10.9 5.4 17.7 16.5 17.7 28.6v541.7c0 12.2-6.8 23.5-17.7 28.9-37.1 18.4-62.6 56.8-62.3 101.1 0.4 62.8 53.1 113.3 115.9 111.2C416 1021.9 464 972.5 464 912c0-44-25.4-82.1-62.3-100.4-10.9-5.4-17.7-16.5-17.7-28.6v-19.2c0-42 19.9-81.8 54.3-105.9 3.1-2.2 6.4-4.3 9.7-6.2 19.3-11.1 33.5-12.5 57-14.8 20.2-2 45.3-4.5 79.7-14.1 50.5-14.2 119.6-33.5 161.4-92.3 24-33.7 35.4-75 34.1-123-0.2-6.9-0.7-13.8-1.4-20.9-1.1-10.7 3.5-21 11.8-27.8 25.3-20.4 41.4-51.7 41.4-86.8zM304 112c0-26.5 21.5-48 48-48s48 21.5 48 48-21.5 48-48 48-48-21.5-48-48z m96 800c0 26.5-21.5 48-48 48s-48-21.5-48-48 21.5-48 48-48 48 21.5 48 48z m320-592c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48z" p-id="15650"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M3.984 11.016v1.969h2.016v-1.969h-2.016zM2.016 14.016v-4.031h19.969v4.031h-19.969zM6 6.984v-1.969h-2.016v1.969h2.016zM2.016 3.984h19.969v4.031h-19.969v-4.031zM3.984 17.016v1.969h2.016v-1.969h-2.016zM2.016 20.016v-4.031h19.969v4.031h-19.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

17
src/assets/icons/edit.svg Normal file
View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1684722897341" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2408">
<path d="M853.333333 501.333333c-17.066667 0-32 14.933333-32 32v320c0 6.4-4.266667 10.666667-10.666666 10.666667H170.666667c-6.4 0-10.666667-4.266667-10.666667-10.666667V213.333333c0-6.4 4.266667-10.666667 10.666667-10.666666h320c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32H170.666667c-40.533333 0-74.666667 34.133333-74.666667 74.666666v640c0 40.533333 34.133333 74.666667 74.666667 74.666667h640c40.533333 0 74.666667-34.133333 74.666666-74.666667V533.333333c0-17.066667-14.933333-32-32-32z" fill="#666666" p-id="2409"></path><path d="M405.333333 484.266667l-32 125.866666c-2.133333 10.666667 0 23.466667 8.533334 29.866667 6.4 6.4 14.933333 8.533333 23.466666 8.533333h8.533334l125.866666-32c6.4-2.133333 10.666667-4.266667 14.933334-8.533333l300.8-300.8c38.4-38.4 38.4-102.4 0-140.8-38.4-38.4-102.4-38.4-140.8 0L413.866667 469.333333c-4.266667 4.266667-6.4 8.533333-8.533334 14.933334z m59.733334 23.466666L761.6 213.333333c12.8-12.8 36.266667-12.8 49.066667 0 12.8 12.8 12.8 36.266667 0 49.066667L516.266667 558.933333l-66.133334 17.066667 14.933334-68.266667z" p-id="2410"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1680083488716" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1096" width="48" height="48"><path d="M853.333333 512a42.666667 42.666667 0 0 0-42.666666-42.666667h-323.84l98.133333-97.706666a42.666667 42.666667 0 1 0-60.586667-60.586667l-170.666666 170.666667a42.666667 42.666667 0 0 0-8.96 14.08 42.666667 42.666667 0 0 0 0 32.426666 42.666667 42.666667 0 0 0 8.96 14.08l170.666666 170.666667a42.666667 42.666667 0 0 0 60.586667 0 42.666667 42.666667 0 0 0 0-60.586667L486.826667 554.666667H810.666667a42.666667 42.666667 0 0 0 42.666666-42.666667zM725.333333 85.333333H298.666667a128 128 0 0 0-128 128v597.333334a128 128 0 0 0 128 128h426.666666a128 128 0 0 0 128-128v-128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 1-42.666667 42.666666H298.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V213.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h426.666666a42.666667 42.666667 0 0 1 42.666667 42.666666v128a42.666667 42.666667 0 0 0 85.333333 0V213.333333a128 128 0 0 0-128-128z" p-id="1097"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

15
src/assets/icons/exit.svg Normal file
View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1680104481890" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7991" width="16" height="16"><path d="M918.4 489.6l-160-160c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8l105.6 105.6L512 480c-19.2 0-32 12.8-32 32s12.8 32 32 32l307.2 0-105.6 105.6c-12.8 12.8-12.8 32 0 44.8 6.4 6.4 12.8 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l160-163.2c0 0 0-3.2 3.2-3.2C931.2 518.4 931.2 499.2 918.4 489.6zM832 736c-19.2 0-32 12.8-32 32l0 64c0 19.2-12.8 32-32 32L224 864c-19.2 0-32-12.8-32-32L192 192c0-19.2 12.8-32 32-32l544 0c19.2 0 32 12.8 32 32l0 64c0 19.2 12.8 32 32 32s32-12.8 32-32L864 192c0-54.4-41.6-96-96-96L224 96C169.6 96 128 137.6 128 192l0 640c0 54.4 41.6 96 96 96l544 0c54.4 0 96-41.6 96-96l0-64C864 748.8 851.2 736 832 736z" p-id="7992"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1670381479745" class="icon" viewBox="0 0 1280 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2930"><path d="M577.450256 639.9892c0-104.678234 33.919428-206.436516 96.018379-289.895108 10.339826-13.879766 8.89985-33.079442-4.299927-44.279253l-49.379167-41.959292c-13.999764-11.899799-35.659398-10.179828-46.759211 4.459925C492.171695 374.833675 447.99244 505.551469 447.99244 639.9892c0 134.457731 44.179254 265.175525 125.03789 371.673728 11.119812 14.639753 32.759447 16.359724 46.759211 4.459925l49.379167-41.979292c13.179778-11.219811 14.619753-30.399487 4.299927-44.279252-62.118952-83.418592-96.01838-185.196875-96.018379-289.875109zM447.99244 31.99946c0-17.679702-14.319758-31.99946-31.99946-31.99946h-95.99838C205.116539 0 111.99811 93.118429 111.99811 207.99649v127.99784H31.99946c-17.679702 0-31.99946 14.319758-31.99946 31.99946v95.99838c0 17.679702 14.319758 31.99946 31.99946 31.99946h79.99865v255.99568c0 26.399555-21.599636 47.99919-47.99919 47.99919H31.99946c-17.679702 0-31.99946 14.319758-31.99946 31.99946v95.99838c0 17.679702 14.319758 31.99946 31.99946 31.99946h31.99946c114.878061 0 207.99649-93.118429 207.99649-207.99649V495.99163h79.99865c17.679702 0 31.99946-14.319758 31.99946-31.99946v-95.99838c0-17.679702-14.319758-31.99946-31.99946-31.99946h-79.99865v-127.99784c0-26.399555 21.599636-47.99919 47.99919-47.99919h95.99838c17.679702 0 31.99946-14.319758 31.99946-31.99946V31.99946z m706.94807 236.316012c-11.119812-14.639753-32.759447-16.359724-46.759211-4.459925l-49.379166 41.959292c-13.179778 11.219811-14.619753 30.399487-4.299928 44.279253 62.098952 83.418592 96.01838 185.216874 96.01838 289.895108 0 104.678234-33.919428 206.456516-96.01838 289.895108-10.339826 13.879766-8.89985 33.079442 4.299928 44.279253l49.379166 41.979292c13.999764 11.899799 35.659398 10.179828 46.759211-4.459925C1235.799146 905.124726 1279.9784 774.426932 1279.9784 639.9892c0-134.457731-44.179254-265.175525-125.03789-371.673728z m-108.338171 463.792174L954.483893 639.9892l92.118446-92.118445c12.499789-12.499789 12.499789-32.759447 0-45.259237l-45.239237-45.239236c-12.499789-12.499789-32.759447-12.499789-45.259236 0L863.98542 549.490727l-92.118445-92.118445c-12.499789-12.499789-32.759447-12.499789-45.259237 0l-45.239236 45.239236c-12.499789 12.499789-12.499789 32.759447 0 45.259237L773.486947 639.9892l-92.118445 92.118446c-12.499789 12.499789-12.499789 32.759447 0 45.259236l45.239236 45.239237c12.499789 12.499789 32.759447 12.499789 45.259237 0L863.98542 730.487673l92.118446 92.118446c12.499789 12.499789 32.759447 12.499789 45.259236 0l45.239237-45.239237c12.499789-12.499789 12.499789-32.759447 0-45.259236z" p-id="2931"></path></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,16 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/chart</title><path d="M5.55 3.824L6.853 5.78a.3.3 0 0 0 .384.102l1.526-.764a.3.3 0 0 1 .384.102l1.65 2.476a.3.3 0 0 0 .462.045l1.229-1.229a.3.3 0 0 1 .512.212v4.243H5V3.99a.3.3 0 0 1 .55-.167zM13 12a1 1 0 0 1 0 2H3.833A1.833 1.833 0 0 1 2 12.167V3a1 1 0 1 1 2 0v9h9z" id="a"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,18 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<title>scatter_plot</title>
<path d="M13.594 17.578q0-1.219 0.891-2.109t2.109-0.891 2.109 0.891 0.891 2.109-0.891 2.109-2.109 0.891-2.109-0.891-0.891-2.109zM8.016 6q0-1.219 0.891-2.109t2.109-0.891 2.109 0.891 0.891 2.109-0.891 2.109-2.109 0.891-2.109-0.891-0.891-2.109zM3.984 14.016q0-1.219 0.891-2.109t2.109-0.891 2.109 0.891 0.891 2.109-0.891 2.109-2.109 0.891-2.109-0.891-0.891-2.109z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12.984 14.859q1.266-0.375 1.875-1.875h7.125q-0.375 3.609-2.836 6.141t-6.164 2.859v-7.125zM14.859 11.016q-0.563-1.5-1.875-1.875v-7.125q3.703 0.328 6.164 2.859t2.836 6.141h-7.125zM11.016 9.141q-0.797 0.328-1.406 1.125t-0.609 1.734 0.609 1.734 1.406 1.125v7.125q-3.797-0.375-6.398-3.234t-2.602-6.75 2.602-6.75 6.398-3.234v7.125z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1688979849484" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2698"><path d="M384 469.333333H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333V213.333333c0-46.933333 38.4-85.333333 85.333333-85.333333h170.666667c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667c0 46.933333-38.4 85.333333-85.333333 85.333333zM213.333333 213.333333v170.666667h170.666667V213.333333H213.333333z" p-id="2699"></path><path d="M170.666667 554.666667h256c25.6 0 42.666667 17.066667 42.666666 42.666666v256c0 25.6-17.066667 42.666667-42.666666 42.666667H170.666667c-25.6 0-42.666667-17.066667-42.666667-42.666667v-256c0-25.6 17.066667-42.666667 42.666667-42.666666z" p-id="2700"></path><path d="M384 896H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-170.666667c0-46.933333 38.4-85.333333 85.333333-85.333333h170.666667c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m-170.666667-256v170.666667h170.666667v-170.666667H213.333333z" p-id="2701"></path><path d="M695.466667 115.2c17.066667-17.066667 42.666667-17.066667 59.733333 0l149.333333 149.333333c17.066667 17.066667 17.066667 42.666667 0 59.733334l-149.333333 149.333333c-17.066667 17.066667-42.666667 17.066667-59.733333 0l-149.333334-145.066667c-17.066667-17.066667-17.066667-42.666667 0-59.733333l149.333334-153.6z" p-id="2702"></path><path d="M810.666667 896h-170.666667c-46.933333 0-85.333333-38.4-85.333333-85.333333v-170.666667c0-46.933333 38.4-85.333333 85.333333-85.333333h170.666667c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m-170.666667-256v170.666667h170.666667v-170.666667h-170.666667z" p-id="2703"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

16
src/assets/icons/mq.svg Normal file
View File

@@ -0,0 +1,16 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M750.08 657.92c-16.384 0-30.208 2.56-44.032 8.192l-44.032-52.224c33.28-38.4 55.296-90.624 55.296-145.92s-19.456-101.888-52.224-140.288l27.648-24.576c16.384 8.192 33.28 13.824 52.224 13.824 63.488 0 115.712-52.224 115.712-115.712s-52.224-115.712-115.712-115.712c-63.488 0-115.712 52.224-115.712 115.712 0 13.824 2.56 30.208 8.192 41.472l-35.84 30.208c-34.304-19.968-73.216-30.208-112.64-30.208-44.032 0-85.504 13.824-121.344 32.768l-41.472-52.224c0-5.632 2.56-13.824 2.56-19.456 0-40.96-32.768-74.24-73.728-74.24h-0.512c-40.96 0-74.24 32.768-74.24 73.728v0.512c0 40.96 32.768 74.24 73.728 74.24H262.656L306.688 332.8c-27.648 38.4-46.592 85.504-46.592 134.656 0 35.84 8.192 71.68 24.576 101.888l-35.84 30.208c-8.192-5.632-19.456-5.632-30.208-5.632-49.664 0-90.624 41.472-90.624 90.624 0 49.664 41.472 90.624 90.624 90.624s90.624-41.472 90.624-90.624c0-8.192 0-16.384-2.56-24.576l30.208-27.648c41.472 35.84 93.696 57.856 154.112 57.856 33.28 0 63.488-5.632 90.624-19.456l46.592 57.856c-11.264 19.456-19.456 44.032-19.456 68.608 0 77.312 63.488 140.288 143.36 140.288s143.36-63.488 143.36-140.288c0.512-75.776-65.536-139.264-145.408-139.264z m-261.632-11.264c-99.328 0-181.76-79.872-181.76-178.688C306.688 368.64 389.12 289.28 488.448 289.28s181.76 79.872 181.76 178.688c0 99.328-82.432 178.688-181.76 178.688zM430.592 465.408c0.512 18.432-13.824 33.28-32.256 33.792-18.432 0.512-33.28-13.824-33.792-32.256v-1.536c0-18.432 14.848-32.768 33.28-32.768 18.432-0.512 32.768 14.336 32.768 32.768z m88.064 0c0 18.432-14.848 33.28-32.768 33.28-18.432 0-33.28-14.848-33.28-32.768 0-18.432 14.848-32.768 33.28-32.768s32.768 13.824 32.768 32.256z m91.136 0c0 18.432-14.848 33.28-32.768 33.28s-33.28-14.848-33.28-32.768c0-18.432 14.848-32.768 33.28-32.768 17.92-1.024 32.768 13.824 32.768 32.256z"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,29 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<?xml version="1.0" encoding="UTF-8"?>
<svg width="3450px" height="1823px" viewBox="0 0 3450 1823" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>Group</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(0.000000, -29.000000)">
<path d="M1050.01772,1394.31899 C1050.01772,1615.24051 912.21519,1851.47342 474.746835,1851.47342 C310.696203,1851.47342 192.579747,1836.16203 87.5873418,1812.10127 C65.7139241,1807.72658 46.0278481,1792.41519 46.0278481,1768.35443 L46.0278481,1610.86582 C46.0278481,1586.80506 65.7139241,1569.30633 87.5873418,1569.30633 L91.9620253,1569.30633 C179.455696,1580.24304 398.189873,1591.17975 479.121519,1591.17975 C673.794937,1591.17975 732.853165,1521.18481 732.853165,1394.31899 C732.853165,1309.01266 691.293671,1265.26582 546.929114,1179.95949 L258.2,1007.15949 C54.7772152,886.855696 0.0936708861,759.989873 0.0936708861,606.875949 C0.0936708861,366.268354 140.083544,191.281013 546.929114,191.281013 C691.293671,191.281013 892.529114,213.15443 966.898734,230.653165 C988.772152,235.027848 1006.27089,250.339241 1006.27089,272.212658 L1006.27089,434.075949 C1006.27089,455.949367 990.959494,473.448101 969.086076,473.448101 L964.711392,473.448101 C820.346835,460.324051 675.982278,451.574684 533.805063,451.574684 C371.941772,451.574684 304.134177,508.44557 304.134177,606.875949 C304.134177,679.058228 341.318987,722.805063 483.496203,801.549367 L745.977215,948.101266 C986.58481,1081.52911 1050.01772,1221.51899 1050.01772,1394.31899 Z M2852.63038,644.060759 C2852.63038,646.248101 2852.63038,648.435443 2852.63038,650.622785 L2653.58228,1656.8 C2627.33418,1788.04051 2592.33671,1840.53671 2458.90886,1840.53671 L2399.85063,1840.53671 C2281.73418,1840.53671 2220.48861,1783.66582 2192.05316,1669.92405 L2019.25316,1000.59747 C2017.06582,991.848101 2017.06582,989.660759 2012.69114,989.660759 C2008.31646,989.660759 2008.31646,991.848101 2006.12911,1000.59747 L1833.32911,1669.92405 C1804.89367,1783.66582 1743.6481,1840.53671 1625.53165,1840.53671 L1566.47342,1840.53671 C1433.04557,1840.53671 1398.0481,1788.04051 1371.8,1656.8 L1172.7519,650.622785 C1172.7519,648.435443 1172.7519,646.248101 1172.7519,644.060759 C1172.7519,620 1192.43797,600.313924 1216.49873,600.313924 L1428.67089,600.313924 C1450.5443,600.313924 1465.8557,620 1468.04304,639.686076 L1605.84557,1564.93165 C1608.03291,1584.61772 1612.40759,1595.55443 1616.78228,1595.55443 C1621.15696,1595.55443 1627.71899,1586.80506 1632.09367,1564.93165 L1813.64304,829.98481 C1835.51646,744.678481 1861.76456,735.929114 1936.13418,735.929114 L2089.2481,735.929114 C2163.61772,735.929114 2189.86582,744.678481 2211.73924,829.98481 L2393.28861,1564.93165 C2397.66329,1586.80506 2404.22532,1595.55443 2408.6,1595.55443 C2412.97468,1595.55443 2417.34937,1584.61772 2419.53671,1564.93165 L2557.33924,639.686076 C2559.52658,620 2574.83797,600.313924 2596.71139,600.313924 L2808.88354,600.313924 C2832.9443,600.313924 2852.63038,620 2852.63038,644.060759 Z" id="Sw" fill="#1368B3"></path>
<g id="moon-o" transform="translate(2932.164557, 596.000000) rotate(-183.000000) translate(-2932.164557, -596.000000) translate(2415.708861, 26.379747)" fill="#D8D8D8" fill-rule="nonzero">
<path d="M1025.31646,927.371333 C992.796119,932.841177 959.292071,935.576099 925.845888,935.576099 C590.40035,935.576099 318.259524,661.909325 318.259524,324.582876 C318.259524,209.134252 351.705707,96.3623597 412.290747,0 C171.802278,71.8062511 0,293.684076 0,557.342199 C0,878.317305 259.46831,1139.24051 578.65368,1139.24051 C753.17563,1139.24051 916.818891,1059.22949 1025.31646,927.371333 Z" id="Shape"></path>
</g>
</g>
</g>
</svg>

View File

@@ -0,0 +1,16 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/epic</title><path d="M5.156 4l-.811 2h7.31l-.811-2H5.156zM4.55 2h6.9c.368 0 .702.235.85.6l1.622 4c.205.505-.009 1.095-.478 1.316a.87.87 0 0 1-.371.084H2.927C2.415 8 2 7.552 2 7c0-.138.026-.274.078-.4l1.622-4c.148-.365.481-.6.85-.6zM3 9h10a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2zm0 3h10a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2z" id="a"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -12,6 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 15.516q1.453 0 2.484-1.031t1.031-2.484-1.031-2.484-2.484-1.031-2.484 1.031-1.031 2.484 1.031 2.484 2.484 1.031zM19.453 12.984l2.109 1.641q0.328 0.234 0.094 0.656l-2.016 3.469q-0.188 0.328-0.609 0.188l-2.484-0.984q-0.984 0.703-1.688 0.984l-0.375 2.625q-0.094 0.422-0.469 0.422h-4.031q-0.375 0-0.469-0.422l-0.375-2.625q-0.891-0.375-1.688-0.984l-2.484 0.984q-0.422 0.141-0.609-0.188l-2.016-3.469q-0.234-0.422 0.094-0.656l2.109-1.641q-0.047-0.328-0.047-0.984t0.047-0.984l-2.109-1.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path> <path d="M12 15.516q1.453 0 2.484-1.031t1.031-2.484-1.031-2.484-2.484-1.031-2.484 1.031-1.031 2.484 1.031 2.484 2.484 1.031zM19.453 12.984l2.109 1.641q0.328 0.234 0.094 0.656l-2.016 3.469q-0.188 0.328-0.609 0.188l-2.484-0.984q-0.984 0.703-1.688 0.984l-0.375 2.625q-0.094 0.422-0.469 0.422h-4.031q-0.375 0-0.469-0.422l-0.375-2.625q-0.891-0.375-1.688-0.984l-2.484 0.984q-0.422 0.141-0.609-0.188l-2.016-3.469q-0.234-0.422 0.094-0.656l2.109-1.641q-0.047-0.328-0.047-0.984t0.047-0.984l-2.109-1.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1685973573331" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2708">
<path d="M138 573.8V450.2c48.2-12.1 84-55.8 84-107.7 0-51.9-35.8-95.6-84-107.7V68.1c0-14.9-12.1-27-27-27s-27 12.1-27 27v166.7C35.8 246.9 0 290.6 0 342.5c0 51.9 35.8 95.6 84 107.7v123.7c-48.2 12-84 55.7-84 107.6s35.8 95.6 84 107.7v166.7c0 14.9 12.1 27 27 27s27-12.1 27-27V789.2c48.2-12.1 84-55.8 84-107.7s-35.8-95.6-84-107.7zM60 342.5c0-28.1 22.9-51 51-51s51 22.9 51 51-22.9 51-51 51-51-22.9-51-51z m51 390c-28.1 0-51-22.9-51-51s22.9-51 51-51 51 22.9 51 51-22.9 51-51 51zM942 283H352c-16.6 0-30-13.4-30-30s13.4-30 30-30h590c16.6 0 30 13.4 30 30s-13.4 30-30 30zM771.4 457H347.6c-14.2 0-25.6-11.5-25.6-25.6v-8.7c0-14.2 11.5-25.6 25.6-25.6h423.7c14.2 0 25.6 11.5 25.6 25.6v8.7c0.1 14.1-11.4 25.6-25.5 25.6z" p-id="2709"></path><path d="M942 625H352c-16.6 0-30-13.4-30-30s13.4-30 30-30h590c16.6 0 30 13.4 30 30s-13.4 30-30 30zM771.4 799H347.6c-14.2 0-25.6-11.5-25.6-25.6v-8.7c0-14.2 11.5-25.6 25.6-25.6h423.7c14.2 0 25.6 11.5 25.6 25.6v8.7c0.1 14.1-11.4 25.6-25.5 25.6z" p-id="2710"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -14,8 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const requireComponent = require.context("./technologies", false, /\.png$/); const requireComponent = import.meta.glob("./technologies/*.png", { eager: true });
const requireTool = require.context("./tools", false, /\.png$/); const requireTool = import.meta.glob("./tools/*.png", { eager: true });
const result: { [key: string]: string } = {}; const result: { [key: string]: string } = {};
const t: { [key: string]: string } = {}; const t: { [key: string]: string } = {};
@@ -24,25 +24,19 @@ function capitalizeFirstLetter(str: string) {
} }
function validateFileName(str: string): string | undefined { function validateFileName(str: string): string | undefined {
if (/^\S+\.png$/.test(str)) { if (/^\S+\.png$/.test(str)) {
return str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) => return str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) => capitalizeFirstLetter($1));
capitalizeFirstLetter($1)
);
} }
} }
[...requireComponent.keys()].forEach((filePath: string) => { Object.keys(requireComponent).forEach((filePath: string) => {
const componentConfig = requireComponent(filePath);
const fileName = validateFileName(filePath); const fileName = validateFileName(filePath);
if (fileName) { if (fileName) {
result[fileName] = componentConfig; result[fileName] = (requireComponent as Indexable)[filePath].default;
} }
}); });
[...requireTool.keys()].forEach((filePath: string) => { Object.keys(requireTool).forEach((filePath: string) => {
const componentConfig = requireTool(filePath);
const fileName = validateFileName(filePath); const fileName = validateFileName(filePath);
if (fileName) { if (fileName) {
t[fileName] = componentConfig; t[fileName] = (requireTool as Indexable)[filePath].default;
} }
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

File diff suppressed because it is too large Load Diff

View File

@@ -15,15 +15,19 @@ limitations under the License. -->
<template> <template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`"> <div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div> <div v-if="!available" class="no-data">No Data</div>
<div class="menus" v-show="visMenus" ref="menus"> <div
class="menus"
v-show="visMenus"
:style="{
top: menuPos.y + 'px',
left: menuPos.x + 'px',
}"
@mouseenter="hideTooltips"
>
<div class="tools" @click="associateMetrics" v-if="associate.length"> <div class="tools" @click="associateMetrics" v-if="associate.length">
{{ t("associateMetrics") }} {{ t("associateMetrics") }}
</div> </div>
<div <div class="tools" @click="viewTrace" v-if="relatedTrace && relatedTrace.enableRelate">
class="tools"
@click="viewTrace"
v-if="relatedTrace && relatedTrace.enableRelate"
>
{{ t("viewTrace") }} {{ t("viewTrace") }}
</div> </div>
</div> </div>
@@ -40,224 +44,230 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { import { watch, ref, onMounted, onBeforeUnmount, unref, computed, reactive } from "vue";
watch, import type { PropType, Ref } from "vue";
ref, import { useI18n } from "vue-i18n";
Ref, import type { EventParams } from "@/types/app";
onMounted, import type { Filters, RelatedTrace } from "@/types/dashboard";
onBeforeUnmount, import { useECharts } from "@/hooks/useEcharts";
unref, import { addResizeListener, removeResizeListener } from "@/utils/event";
computed, import Trace from "@/views/dashboard/related/trace/Index.vue";
} from "vue"; import associateProcessor from "@/hooks/useAssociateProcessor";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { EventParams } from "@/types/app";
import { Filters, RelatedTrace } from "@/types/dashboard";
import { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
/*global Nullable, defineProps, defineEmits*/ /*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
const { t } = useI18n(); const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null); const chartRef = ref<Nullable<HTMLDivElement>>(null);
const menus = ref<Nullable<HTMLDivElement>>(null); const visMenus = ref<boolean>(false);
const visMenus = ref<boolean>(false); const { setOptions, resize, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
const { setOptions, resize, getInstance } = useECharts( const currentParams = ref<Nullable<EventParams>>(null);
chartRef as Ref<HTMLDivElement> const showTrace = ref<boolean>(false);
); const traceOptions = ref<{ type: string; filters?: unknown }>({
const currentParams = ref<Nullable<EventParams>>(null); type: "Trace",
const showTrace = ref<boolean>(false); });
const traceOptions = ref<{ type: string; filters?: unknown }>({ const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
type: "Trace", const props = defineProps({
}); height: { type: String, default: "100%" },
const props = defineProps({ width: { type: String, default: "100%" },
height: { type: String, default: "100%" }, option: {
width: { type: String, default: "100%" }, type: Object as PropType<Indexable>,
option: { default: () => ({}),
type: Object as PropType<{ [key: string]: any }>, },
default: () => ({}), filters: {
}, type: Object as PropType<Filters>,
filters: { },
type: Object as PropType<Filters>, relatedTrace: {
}, type: Object as PropType<RelatedTrace>,
relatedTrace: { },
type: Object as PropType<RelatedTrace>, associate: {
}, type: Array as PropType<{ widgetId: string }[]>,
associate: { default: () => [],
type: Array as PropType<{ widgetId: string }[]>, },
default: () => [], });
}, const available = computed(
}); () =>
const available = computed( (Array.isArray(props.option.series) && props.option.series[0] && props.option.series[0].data) ||
() => (Array.isArray(props.option.series.data) && props.option.series.data[0]),
(Array.isArray(props.option.series) && );
props.option.series[0] && onMounted(async () => {
props.option.series[0].data) || await setOptions(props.option);
(Array.isArray(props.option.series.data) && props.option.series.data[0]) chartRef.value && addResizeListener(unref(chartRef), resize);
); instanceEvent();
onMounted(async () => { });
await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize);
instanceEvent();
});
function instanceEvent() { function instanceEvent() {
setTimeout(() => { setTimeout(() => {
const instance = getInstance();
if (!instance) {
return;
}
instance.on("click", (params: EventParams) => {
currentParams.value = params;
if (props.option.series.type === "sankey") {
emits("select", currentParams.value);
return;
}
instance.dispatchAction({
type: "hideTip",
});
visMenus.value = true;
if (!chartRef.value) {
return;
}
const w = chartRef.value.getBoundingClientRect().width || 0;
const h = chartRef.value.getBoundingClientRect().height || 0;
if (w - params.event.offsetX > 120) {
menuPos.x = params.event.offsetX;
} else {
menuPos.x = params.event.offsetX - 120;
}
if (h - params.event.offsetY < 50) {
menuPos.y = params.event.offsetY - 40;
} else {
menuPos.y = params.event.offsetY;
}
});
if (props.option.series.type === "sankey") {
return;
}
instance.on("mouseover", () => {
visMenus.value = false;
});
instance.on("mouseout", () => {
instance.dispatchAction({
type: "hideTip",
});
});
document.addEventListener(
"click",
() => {
if (instance.isDisposed()) {
return;
}
visMenus.value = false;
instance.dispatchAction({
type: "hideTip",
});
instance.dispatchAction({
type: "updateAxisPointer",
currTrigger: "leave",
});
},
true,
);
}, 1000);
}
function associateMetrics() {
emits("select", currentParams.value);
updateOptions(currentParams.value || undefined);
}
function updateOptions(params?: EventParams) {
const instance = getInstance(); const instance = getInstance();
if (!instance) { if (!instance) {
return; return;
} }
instance.on("click", (params: EventParams) => { if (!props.filters) {
currentParams.value = params; return;
if (!menus.value || !chartRef.value) { }
if (props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
const options = eventAssociate();
setOptions(options || props.option);
} else {
instance.dispatchAction({
type: "showTip",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0,
});
}
}
function viewTrace() {
const item = associateProcessor(props).traceFilters(currentParams.value);
traceOptions.value = {
...traceOptions.value,
filters: item,
};
showTrace.value = true;
visMenus.value = true;
}
function hideTooltips() {
const instance = getInstance();
instance.dispatchAction({
type: "hideTip",
});
}
watch(
() => props.option,
(newVal, oldVal) => {
if (!available.value) {
return; return;
} }
visMenus.value = true; if (JSON.stringify(newVal) === JSON.stringify(oldVal)) {
const w = chartRef.value.getBoundingClientRect().width || 0; return;
const h = chartRef.value.getBoundingClientRect().height || 0;
if (w - params.event.offsetX > 120) {
menus.value.style.left = params.event.offsetX + "px";
} else {
menus.value.style.left = params.event.offsetX - 120 + "px";
} }
if (h - params.event.offsetY < 50) { let options;
menus.value.style.top = params.event.offsetY - 40 + "px"; if (props.filters && props.filters.isRange) {
} else { const { eventAssociate } = associateProcessor(props);
menus.value.style.top = params.event.offsetY + 2 + "px"; options = eventAssociate();
} }
}); setOptions(options || props.option);
document.addEventListener( },
"click", );
() => { watch(
if (instance.isDisposed()) { () => props.filters,
return; () => {
} updateOptions();
visMenus.value = false; },
instance.dispatchAction({ );
type: "updateAxisPointer",
currTrigger: "leave",
});
},
true
);
}, 1000);
}
function associateMetrics() { onBeforeUnmount(() => {
emits("select", currentParams.value); removeResizeListener(unref(chartRef), resize);
const { dataIndex, seriesIndex } = currentParams.value || { });
dataIndex: 0,
seriesIndex: 0,
};
updateOptions({ dataIndex, seriesIndex });
}
function updateOptions(params?: { dataIndex: number; seriesIndex: number }) {
const instance = getInstance();
if (!instance) {
return;
}
if (!props.filters) {
return;
}
if (props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
const options = eventAssociate();
setOptions(options || props.option);
} else {
instance.dispatchAction({
type: "updateAxisPointer",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0,
});
const ids = props.option.series.map((_: unknown, index: number) => index);
instance.dispatchAction({
type: "highlight",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: ids,
});
}
}
function viewTrace() {
const item = associateProcessor(props).traceFilters(currentParams.value);
traceOptions.value = {
...traceOptions.value,
filters: item,
};
showTrace.value = true;
visMenus.value = true;
}
watch(
() => props.option,
(newVal, oldVal) => {
if (!available.value) {
return;
}
if (JSON.stringify(newVal) === JSON.stringify(oldVal)) {
return;
}
let options;
if (props.filters && props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
options = eventAssociate();
}
setOptions(options || props.option);
}
);
watch(
() => props.filters,
() => {
updateOptions();
}
);
onBeforeUnmount(() => {
removeResizeListener(unref(chartRef), resize);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.no-data { .no-data {
font-size: 12px; font-size: $font-size-smaller;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: horizontal; -webkit-box-orient: horizontal;
-webkit-box-pack: center; -webkit-box-pack: center;
-webkit-box-align: center; -webkit-box-align: center;
color: #666; color: #666;
} }
.chart { .chart {
overflow: hidden; overflow: hidden;
flex: 1; flex: 1;
} }
.menus { .menus {
position: absolute; position: absolute;
display: block; display: block;
white-space: nowrap; white-space: nowrap;
z-index: 9999999; z-index: 9999999;
box-shadow: #ddd 1px 2px 10px; box-shadow: #ddd 1px 2px 10px;
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear; transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
background-color: rgb(255, 255, 255); background-color: rgb(255 255 255);
border-radius: 4px; border-radius: 4px;
color: rgb(51, 51, 51); color: rgb(51 51 51);
padding: 5px; padding: 5px;
} }
.tools { .tools {
padding: 5px; padding: 5px;
color: #999; color: #999;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: #409eff; color: $active-color;
background-color: #eee; background-color: #eee;
}
} }
}
</style> </style>

View File

@@ -24,64 +24,62 @@ limitations under the License. -->
loading, loading,
}" }"
> >
<use :xlink:href="`#${iconName}`"></use> <use :href="`#${iconName}`"></use>
</svg> </svg>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import "@/assets/icons/index"; /*global defineProps */
defineProps({
/*global defineProps */ iconName: { type: String, default: "" },
defineProps({ size: { type: String, default: "sm" },
iconName: { type: String, default: "" }, loading: { type: Boolean, default: false },
size: { type: String, default: "sm" }, });
loading: { type: Boolean, default: false },
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
vertical-align: middle; vertical-align: middle;
fill: currentColor; fill: currentColor;
&.sm { &.sm {
width: 14px; width: 14px;
height: 14px; height: 14px;
} }
&.middle { &.middle {
width: 18px; width: 18px;
height: 18px; height: 18px;
} }
&.lg { &.lg {
width: 22px; width: 22px;
height: 22px; height: 22px;
} }
&.loading { &.loading {
animation: loading 1.5s linear infinite; animation: loading 1.5s linear infinite;
} }
&.logo { &.logo {
height: 30px; height: 30px;
width: 110px; width: 110px;
} }
&.xl { &.xl {
height: 30px; height: 30px;
width: 30px; width: 30px;
} }
}
@keyframes loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
} }
@keyframes loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to { to {
-webkit-transform: rotate(1turn); -webkit-transform: rotate(1turn);
transform: rotate(1turn); transform: rotate(1turn);
}
} }
}
</style> </style>

View File

@@ -20,31 +20,31 @@ limitations under the License. -->
</el-radio-group> </el-radio-group>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
interface Option { /*global defineProps, defineEmits */
label: string | number; const emit = defineEmits(["change"]);
value: string | number; const props = defineProps({
} options: {
type: Array as PropType<
{
label: string | number;
value: string | number;
}[]
>,
default: () => [],
},
value: {
type: String as PropType<string>,
default: "",
},
size: { type: null, default: "default" },
});
/*global defineProps, defineEmits */ const selected = ref<string>(props.value);
const emit = defineEmits(["change"]);
const props = defineProps({
options: {
type: Array as PropType<Option[]>,
default: () => [],
},
value: {
type: String as PropType<string>,
default: "",
},
size: { type: null, default: "default" },
});
const selected = ref<string>(props.value); function checked(opt: unknown) {
emit("change", opt);
function checked(opt: unknown) { }
emit("change", opt);
}
</script> </script>

View File

@@ -19,9 +19,7 @@ limitations under the License. -->
{{ selected.label }} {{ selected.label }}
</span> </span>
<span class="no-data" v-else>Please select a option</span> <span class="no-data" v-else>Please select a option</span>
<span class="remove-icon" @click="removeSelected" v-if="clearable"> <span class="remove-icon" @click="removeSelected" v-if="clearable"> × </span>
×
</span>
</div> </div>
<div class="opt-wrapper" v-show="visible"> <div class="opt-wrapper" v-show="visible">
<div <div
@@ -37,141 +35,141 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { Option } from "@/types/app"; import type { Option } from "@/types/app";
/*global defineProps, defineEmits*/ /*global defineProps, defineEmits*/
const emit = defineEmits(["change"]); const emit = defineEmits(["change"]);
const props = defineProps({ const props = defineProps({
options: { options: {
type: Array as PropType<Option[]>, type: Array as PropType<Option[]>,
default: () => [], default: () => [],
}, },
value: { value: {
type: String as PropType<string>, type: String as PropType<string>,
default: () => "", default: () => "",
}, },
clearable: { type: Boolean, default: false }, clearable: { type: Boolean, default: false },
}); });
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const opt = props.options.find((d: Option) => props.value === d.value); const opt = props.options.find((d: Option) => props.value === d.value);
const selected = ref<Option>(opt || { label: "", value: "" }); const selected = ref<Option>(opt || { label: "", value: "" });
function handleSelect(i: Option) { function handleSelect(i: Option) {
selected.value = i; selected.value = i;
emit("change", i.value); emit("change", i.value);
}
function removeSelected() {
selected.value = { label: "", value: "" };
emit("change", "");
}
watch(
() => props.value,
(data) => {
const opt = props.options.find((d: Option) => data === d.value);
selected.value = opt || { label: "", value: "" };
} }
); function removeSelected() {
document.body.addEventListener("click", handleClick, false); selected.value = { label: "", value: "" };
emit("change", "");
}
watch(
() => props.value,
(data) => {
const opt = props.options.find((d: Option) => data === d.value);
selected.value = opt || { label: "", value: "" };
},
);
document.body.addEventListener("click", handleClick, false);
function handleClick() { function handleClick() {
visible.value = false; visible.value = false;
} }
function setPopper(event: any) { function setPopper(event: MouseEvent) {
event.stopPropagation(); event.stopPropagation();
visible.value = !visible.value; visible.value = !visible.value;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.bar-select { .bar-select {
position: relative; position: relative;
justify-content: space-between; justify-content: space-between;
border: 1px solid #ddd; border: 1px solid #ddd;
background: #fff; background: $theme-background;
border-radius: 3px;
color: #000;
font-size: 12px;
height: 24px;
.selected {
padding: 0 3px;
border-radius: 3px; border-radius: 3px;
margin: 3px; color: #000;
color: #409eff; font-size: $font-size-smaller;
background-color: #fafafa; height: 24px;
border: 1px solid #e8e8e8;
text-align: center;
}
}
.no-data { .selected {
color: #c0c4cc; padding: 0 3px;
} border-radius: 3px;
margin: 3px;
.bar-i { color: $active-color;
height: 100%; background-color: #fafafa;
width: 100%; border: 1px solid #e8e8e8;
padding: 2px 10px; text-align: center;
overflow: auto;
color: #606266;
position: relative;
&:hover {
.remove-icon {
display: block;
} }
} }
}
.remove-icon { .no-data {
position: absolute; color: #c0c4cc;
right: 5px; }
top: 0;
font-size: 14px;
display: none;
color: #aaa;
cursor: pointer;
}
.opt-wrapper { .bar-i {
color: #606266; height: 100%;
position: absolute; width: 100%;
top: 26px; padding: 2px 10px;
left: 0; overflow: auto;
background: #fff; color: #606266;
box-shadow: 0 1px 6px rgba(99, 99, 99, 0.2); position: relative;
border: 1px solid #ddd;
width: 100%;
border-radius: 0 0 3px 3px;
border-right-width: 1px !important;
z-index: 10;
overflow: auto;
max-height: 200px;
padding-bottom: 2px;
.close {
position: absolute;
right: 10px;
top: 12px;
opacity: 0.6;
&:hover { &:hover {
opacity: 1; .remove-icon {
display: block;
}
} }
} }
}
.opt { .remove-icon {
padding: 7px 15px; position: absolute;
right: 5px;
&.select-disabled { top: 0;
color: #409eff; font-size: $font-size-normal;
cursor: not-allowed; display: none;
color: #aaa;
cursor: pointer;
} }
&:hover { .opt-wrapper {
background-color: #f5f5f5; color: #606266;
position: absolute;
top: 26px;
left: 0;
background-color: $theme-background;
box-shadow: 0 1px 6px rgb(99 99 99 / 20%);
border: 1px solid #ddd;
width: 100%;
border-radius: 0 0 3px 3px;
border-right-width: 1px !important;
z-index: 10;
overflow: auto;
max-height: 200px;
padding-bottom: 2px;
.close {
position: absolute;
right: 10px;
top: 12px;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
.opt {
padding: 7px 15px;
&.select-disabled {
color: $active-color;
cursor: not-allowed;
}
&:hover {
background-color: #f5f5f5;
}
} }
}
</style> </style>

View File

@@ -32,68 +32,72 @@ limitations under the License. -->
:key="item.value || ''" :key="item.value || ''"
:label="item.label || ''" :label="item.label || ''"
:value="item.value || ''" :value="item.value || ''"
:disabled="item.disabled || false"
> >
</el-option> </el-option>
</el-select> </el-select>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
interface Option { // interface Option {
label: string | number; // label: string | number;
value: string | number; // value: string | number;
} // }
/*global defineProps, defineEmits*/ /*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]); const emit = defineEmits(["change", "query"]);
const props = defineProps({ const props = defineProps({
options: { options: {
type: Array as PropType<(Option & { disabled?: boolean })[]>, type: Array as PropType<
default: () => [], ({
}, label: string | number;
value: { value: string | number;
type: [Array, String, Number, undefined] as PropType<any>, } & { disabled?: boolean })[]
default: () => [], >,
}, default: () => [],
size: { type: null, default: "default" }, },
placeholder: { value: {
type: [String, undefined] as PropType<string>, type: [Array, String, Number, undefined] as PropType<any>,
default: "Select a option", default: () => [],
}, },
borderRadius: { type: Number, default: 3 }, size: { type: null, default: "default" },
multiple: { type: Boolean, default: false }, placeholder: {
disabled: { type: Boolean, default: false }, type: [String, undefined] as PropType<string>,
clearable: { type: Boolean, default: false }, default: "Select a option",
isRemote: { type: Boolean, default: false }, },
filterable: { type: Boolean, default: true }, borderRadius: { type: Number, default: 3 },
}); multiple: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
});
const selected = ref<string[] | string>(props.value); const selected = ref<string[] | string>(props.value);
function changeSelected() { function changeSelected() {
const options = props.options.filter((d: any) => const options = props.options.filter((d: Indexable) =>
props.multiple props.multiple ? selected.value.includes(d.value) : selected.value === d.value,
? selected.value.includes(d.value) );
: selected.value === d.value emit("change", options);
}
function remoteMethod(query: string) {
if (props.isRemote) {
emit("query", query);
}
}
watch(
() => props.value,
(data) => {
selected.value = data;
},
); );
emit("change", options);
}
function remoteMethod(query: string) {
if (props.isRemote) {
emit("query", query);
}
}
watch(
() => props.value,
(data) => {
selected.value = data;
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-input__inner { .el-input__inner {
border-radius: unset !important; border-radius: unset !important;
} }
</style> </style>

87
src/components/Tags.vue Normal file
View File

@@ -0,0 +1,87 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<span :class="vertical ? 'vertical' : 'horizontal'" v-for="tag in dynamicTags" :key="tag">
<el-tag closable :disable-transitions="false" @close="handleClose(tag)">
{{ tag }}
</el-tag>
</span>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
class="ml-5 input-name"
size="small"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else size="small" @click="showInput"> + {{ text }} </el-button>
</template>
<script lang="ts" setup>
import { nextTick, ref } from "vue";
import type { PropType } from "vue";
import { ElInput } from "element-plus";
/*global defineProps, defineEmits*/
const emits = defineEmits(["change"]);
const props = defineProps({
tags: {
type: Array as PropType<string[]>,
default: () => [],
},
text: { type: String, default: "" },
vertical: { type: Boolean, default: false },
});
const inputValue = ref("");
const dynamicTags = ref<string[]>(props.tags || []);
const inputVisible = ref(false);
const InputRef = ref<InstanceType<typeof ElInput>>();
const handleClose = (tag: string) => {
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1);
};
const showInput = () => {
inputVisible.value = true;
nextTick(() => {
InputRef.value!.input!.focus();
});
};
const handleInputConfirm = () => {
if (inputValue.value) {
dynamicTags.value.push(inputValue.value);
}
inputVisible.value = false;
inputValue.value = "";
emits("change", dynamicTags.value);
};
</script>
<style lang="scss" scoped>
.input-name {
width: 300px;
}
.vertical {
display: block;
margin-bottom: 5px;
}
.horizontal {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@@ -35,56 +35,28 @@ limitations under the License. -->
<transition name="datepicker-anim"> <transition name="datepicker-anim">
<div <div
class="datepicker-popup" class="datepicker-popup"
:class="[ :class="[popupClass, { 'datepicker-inline': type === 'inline' }, position]"
popupClass,
{ 'datepicker-inline': type === 'inline' },
position,
]"
tabindex="-1" tabindex="-1"
v-if="show || type === 'inline'" v-if="show || type === 'inline'"
> >
<template v-if="range"> <template v-if="range">
<div class="datepicker-popup__sidebar"> <div class="datepicker-popup__sidebar">
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('quarter')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('quarter')"
>
{{ local.quarterHourCutTip }} {{ local.quarterHourCutTip }}
</button> </button>
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('half')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('half')"
>
{{ local.halfHourCutTip }} {{ local.halfHourCutTip }}
</button> </button>
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('hour')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('hour')"
>
{{ local.hourCutTip }} {{ local.hourCutTip }}
</button> </button>
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('day')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('day')"
>
{{ local.dayCutTip }} {{ local.dayCutTip }}
</button> </button>
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('week')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('week')"
>
{{ local.weekCutTip }} {{ local.weekCutTip }}
</button> </button>
<button <button type="button" class="datepicker-popup__shortcut" @click="quickPick('month')">
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('month')"
>
{{ local.monthCutTip }} {{ local.monthCutTip }}
</button> </button>
</div> </div>
@@ -123,16 +95,10 @@ limitations under the License. -->
/> />
</template> </template>
<div v-if="showButtons" class="datepicker__buttons"> <div v-if="showButtons" class="datepicker__buttons">
<button <button @click.prevent.stop="cancel" class="datepicker__button-cancel">
@click.prevent.stop="cancel"
class="datepicker__button-cancel"
>
{{ local.cancelTip }} {{ local.cancelTip }}
</button> </button>
<button <button @click.prevent.stop="submit" class="datepicker__button-select">
@click.prevent.stop="submit"
class="datepicker__button-select"
>
{{ local.submitTip }} {{ local.submitTip }}
</button> </button>
</div> </div>
@@ -142,438 +108,430 @@ limitations under the License. -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue"; import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import DateCalendar from "./DateCalendar.vue"; import DateCalendar from "./DateCalendar.vue";
import { useTimeoutFn } from "@/hooks/useTimeout"; import { useTimeoutFn } from "@/hooks/useTimeout";
/*global defineProps, defineEmits */ /*global defineProps, defineEmits*/
const datepicker = ref(null); const datepicker = ref(null);
const { t } = useI18n(); const { t } = useI18n();
const show = ref<boolean>(false); const show = ref<boolean>(false);
const dates = ref<Date | string[] | any>([]); const dates = ref<Date | string[] | any>([]);
const props = defineProps({ const props = defineProps({
position: { type: String, default: "bottom" }, position: { type: String, default: "bottom" },
name: [String], name: [String],
inputClass: [String], inputClass: [String],
popupClass: [String], popupClass: [String],
value: [Date, Array, String], value: [Date, Array, String],
disabled: [Boolean], disabled: [Boolean],
type: { type: {
type: String, type: String,
default: "normal", default: "normal",
}, },
rangeSeparator: { rangeSeparator: {
type: String, type: String,
default: "~", default: "~",
}, },
clearable: { clearable: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
placeholder: [String], placeholder: [String],
disabledDate: { disabledDate: {
type: Function, type: Function,
default: () => false, default: () => false,
}, },
format: { format: {
type: String, type: String,
default: "YYYY-MM-DD", default: "YYYY-MM-DD",
}, },
showButtons: { showButtons: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
dateRangeSelect: [Function], dateRangeSelect: [Function],
}); });
const emit = defineEmits(["clear", "input", "confirm", "cancel"]); const emit = defineEmits(["clear", "input", "confirm", "cancel"]);
const local = computed(() => { const local = computed(() => {
return { return {
dow: 1, // Monday is the first day of the week dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"), quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"), halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"), hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"), dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"), weekCutTip: t("weekCutTip"),
monthCutTip: t("monthCutTip"), monthCutTip: t("monthCutTip"),
};
});
const tf = (time: Date, format?: any): string => {
const year = time.getFullYear();
const month = time.getMonth();
const day = time.getDate();
const hours24 = time.getHours();
const hours = hours24 % 12 === 0 ? 12 : hours24 % 12;
const minutes = time.getMinutes();
const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = {
YYYY: year,
MM: dd(month + 1),
MMM: local.value.months[month],
MMMM: local.value.monthsHead[month],
M: month + 1,
DD: dd(day),
D: day,
HH: dd(hours24),
H: hours24,
hh: dd(hours),
h: hours,
mm: dd(minutes),
m: minutes,
ss: dd(seconds),
s: seconds,
S: milliseconds,
};
return (format || props.format).replace(/Y+|M+|D+|H+|h+|m+|s+|S+/g, (str: string) => map[str]);
}; };
}); const range = computed(() => {
const tf = (time: Date, format?: any): string => { return dates.value.length === 2;
const year = time.getFullYear(); });
const month = time.getMonth(); const text = computed(() => {
const day = time.getDate(); const val = props.value;
const hours24 = time.getHours(); const txt = dates.value.map((date: Date) => tf(date)).join(` ${props.rangeSeparator} `);
const hours = hours24 % 12 === 0 ? 12 : hours24 % 12; if (Array.isArray(val)) {
const minutes = time.getMinutes(); return val.length > 1 ? txt : "";
const seconds = time.getSeconds(); }
const milliseconds = time.getMilliseconds(); return val ? txt : "";
const dd = (t: number) => `0${t}`.slice(-2); });
const map: { [key: string]: string | number } = { const get = () => {
YYYY: year, return Array.isArray(props.value) ? dates.value : dates.value[0];
MM: dd(month + 1),
MMM: local.value.months[month],
MMMM: local.value.monthsHead[month],
M: month + 1,
DD: dd(day),
D: day,
HH: dd(hours24),
H: hours24,
hh: dd(hours),
h: hours,
mm: dd(minutes),
m: minutes,
ss: dd(seconds),
s: seconds,
S: milliseconds,
}; };
return (format || props.format).replace( const cls = () => {
/Y+|M+|D+|H+|h+|m+|s+|S+/g, emit("clear");
(str: string) => map[str] emit("input", range.value ? [] : "");
};
const vi = (val: any) => {
if (Array.isArray(val)) {
return val.length > 1 ? val.map((item) => new Date(item)) : [new Date(), new Date()];
}
return val ? [new Date(val)] : [new Date()];
};
const ok = (leaveOpened: boolean) => {
emit("input", get());
!leaveOpened &&
!props.showButtons &&
useTimeoutFn(() => {
show.value = range.value;
}, 1);
};
const setDates = (d: Date, pos: string) => {
if (pos === "right") {
dates.value[1] = d;
return;
}
dates.value[0] = d;
};
const dc = (e: MouseEvent) => {
show.value = (datepicker.value as any).contains(e.target) && !props.disabled;
};
const quickPick = (type: string) => {
const end = new Date();
const start = new Date();
switch (type) {
case "quarter":
start.setTime(start.getTime() - 60 * 15 * 1000); //15 mins
break;
case "half":
start.setTime(start.getTime() - 60 * 30 * 1000); //30 mins
break;
case "hour":
start.setTime(start.getTime() - 3600 * 1000); //1 hour
break;
case "day":
start.setTime(start.getTime() - 3600 * 1000 * 24); //1 day
break;
case "week":
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); //1 week
break;
case "month":
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); //1 month
break;
default:
break;
}
dates.value = [start, end];
emit("input", get());
};
const submit = () => {
emit("confirm", get());
show.value = false;
};
const cancel = () => {
emit("cancel");
show.value = false;
};
onMounted(() => {
dates.value = vi(props.value);
document.addEventListener("click", dc, true);
});
onBeforeUnmount(() => {
document.removeEventListener("click", dc, true);
});
watch(
() => props.value,
(val: unknown) => {
dates.value = vi(val);
},
); );
};
const range = computed(() => {
return dates.value.length === 2;
});
const text = computed(() => {
const val = props.value;
const txt = dates.value
.map((date: Date) => tf(date))
.join(` ${props.rangeSeparator} `);
if (Array.isArray(val)) {
return val.length > 1 ? txt : "";
}
return val ? txt : "";
});
const get = () => {
return Array.isArray(props.value) ? dates.value : dates.value[0];
};
const cls = () => {
emit("clear");
emit("input", range.value ? [] : "");
};
const vi = (val: any) => {
if (Array.isArray(val)) {
return val.length > 1
? val.map((item) => new Date(item))
: [new Date(), new Date()];
}
return val ? [new Date(val)] : [new Date()];
};
const ok = (leaveOpened: boolean) => {
emit("input", get());
!leaveOpened &&
!props.showButtons &&
useTimeoutFn(() => {
show.value = range.value;
}, 1);
};
const setDates = (d: Date, pos: string) => {
if (pos === "right") {
dates.value[1] = d;
return;
}
dates.value[0] = d;
};
const dc = (e: any) => {
show.value = (datepicker.value as any).contains(e.target) && !props.disabled;
};
const quickPick = (type: string) => {
const end = new Date();
const start = new Date();
switch (type) {
case "quarter":
start.setTime(start.getTime() - 60 * 15 * 1000); //15 mins
break;
case "half":
start.setTime(start.getTime() - 60 * 30 * 1000); //30 mins
break;
case "hour":
start.setTime(start.getTime() - 3600 * 1000); //1 hour
break;
case "day":
start.setTime(start.getTime() - 3600 * 1000 * 24); //1 day
break;
case "week":
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); //1 week
break;
case "month":
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); //1 month
break;
default:
break;
}
dates.value = [start, end];
emit("input", get());
};
const submit = () => {
emit("confirm", get());
show.value = false;
};
const cancel = () => {
emit("cancel");
show.value = false;
};
onMounted(() => {
dates.value = vi(props.value);
document.addEventListener("click", dc, true);
});
onBeforeUnmount(() => {
document.removeEventListener("click", dc, true);
});
watch(
() => props.value,
(val: unknown) => {
dates.value = vi(val);
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@keyframes datepicker-anim-in { @keyframes datepicker-anim-in {
0% { 0% {
opacity: 0; opacity: 0;
transform: scaleY(0.8); transform: scaleY(0.8);
} }
to { 100% {
opacity: 1; opacity: 1;
transform: scaleY(1); transform: scaleY(1);
}
}
@keyframes datepicker-anim-out {
0% {
opacity: 1;
transform: scaleY(1);
}
to {
opacity: 0;
transform: scaleY(0.8);
}
}
.datepicker {
display: inline-block;
position: relative;
}
.datepicker-icon {
display: block;
position: absolute;
top: 8px;
left: 8px;
color: #515a6ecc;
}
.datepicker-close {
display: none;
position: absolute;
width: 34px;
height: 100%;
top: 0;
right: 0;
cursor: pointer;
}
.datepicker-close:before {
display: block;
content: "";
position: absolute;
width: 16px;
height: 16px;
left: 50%;
top: 50%;
margin-left: -8px;
margin-top: -8px;
text-align: center;
color: #fff;
border-radius: 50%;
background: #ccc
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3IDciIHdpZHRoPSI3IiBoZWlnaHQ9IjciPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01LjU4LDVsMi44LTIuODFBLjQxLjQxLDAsMSwwLDcuOCwxLjZMNSw0LjQxLDIuMiwxLjZhLjQxLjQxLDAsMCwwLS41OC41OGgwTDQuNDIsNSwxLjYyLDcuOGEuNDEuNDEsMCwwLDAsLjU4LjU4TDUsNS41OCw3LjgsOC4zOWEuNDEuNDEsMCwwLDAsLjU4LS41OGgwWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEuNSAtMS40OCkiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4NCg==")
no-repeat 50% 50%;
}
.datepicker__clearable:hover:before {
display: none;
}
.datepicker__clearable:hover .datepicker-close {
display: block;
}
.datepicker-close:hover:before {
background-color: #afafaf;
}
.datepicker > input {
color: inherit;
// transition: all 200ms ease;
border-radius: 4px;
border: 0;
background: none;
height: 28px;
box-sizing: border-box;
outline: none;
padding: 0 5px;
width: 100%;
user-select: none;
font-family: "Monaco";
letter-spacing: -0.7px;
}
// .datepicker > input.focus {
// border-color: #3f97e3;
// -webkit-box-shadow: 0 0 5px rgba(59, 180, 242, 0.3);
// box-shadow: 0 0 5px rgba(59, 180, 242, 0.3);
// }
.datepicker > input:disabled {
cursor: not-allowed;
background-color: #ebebe4;
border-color: #e5e5e5;
-webkit-box-shadow: none;
box-shadow: none;
}
.datepicker-popup {
border-radius: 4px;
position: absolute;
transition: all 200ms ease;
opacity: 1;
transform: scaleY(1);
font-size: 12px;
background: #fff;
box-shadow: 0 1px 6px rgba(99, 99, 99, 0.2);
margin-top: 2px;
outline: 0;
padding: 5px;
overflow: hidden;
z-index: 999;
&.top {
bottom: 30px;
right: 0;
transform-origin: center bottom;
}
&.bottom {
top: 30px;
right: 0;
transform-origin: center top;
}
&.left {
top: 30px;
transform-origin: center top;
}
&.right {
right: -80px;
top: 30px;
transform-origin: center top;
}
&__sidebar {
position: absolute;
top: 0;
bottom: 0;
width: 100px;
height: 100%;
padding: 5px;
border-right: solid 1px #eaeaea;
}
&__shortcut {
display: block;
width: 100%;
border: 0;
background-color: transparent;
line-height: 34px;
font-size: 12px;
color: #666;
text-align: left;
outline: none;
cursor: pointer;
white-space: nowrap;
&:hover {
color: #3f97e3;
} }
} }
&__body { @keyframes datepicker-anim-out {
margin-left: 100px; 0% {
padding-left: 5px; opacity: 1;
transform: scaleY(1);
}
100% {
opacity: 0;
transform: scaleY(0.8);
}
} }
}
.datepicker-inline { .datepicker {
position: relative; display: inline-block;
margin-top: 0; position: relative;
} }
.datepicker-range { .datepicker-icon {
min-width: 238px; display: block;
} position: absolute;
top: 8px;
left: 8px;
color: #515a6ecc;
}
.datepicker-range .datepicker-popup { .datepicker-close {
width: 520px; display: none;
} position: absolute;
width: 34px;
height: 100%;
top: 0;
right: 0;
cursor: pointer;
}
.datepicker-bottom { .datepicker-close::before {
float: left; display: block;
width: 100%; content: "";
text-align: right; position: absolute;
} width: 16px;
height: 16px;
left: 50%;
top: 50%;
margin-left: -8px;
margin-top: -8px;
text-align: center;
color: $text-color;
border-radius: 50%;
background: $disabled-color
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3IDciIHdpZHRoPSI3IiBoZWlnaHQ9IjciPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01LjU4LDVsMi44LTIuODFBLjQxLjQxLDAsMSwwLDcuOCwxLjZMNSw0LjQxLDIuMiwxLjZhLjQxLjQxLDAsMCwwLS41OC41OGgwTDQuNDIsNSwxLjYyLDcuOGEuNDEuNDEsMCwwLDAsLjU4LjU4TDUsNS41OCw3LjgsOC4zOWEuNDEuNDEsMCwwLDAsLjU4LS41OGgwWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEuNSAtMS40OCkiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4NCg==")
no-repeat 50% 50%;
}
.datepicker-btn { .datepicker__clearable:hover::before {
padding: 5px 10px; display: none;
background: #3f97e3; }
color: #fff;
border-radius: 2px;
display: inline-block;
cursor: pointer;
}
.datepicker-anim-enter-active { .datepicker__clearable:hover .datepicker-close {
transform-origin: 0 0; display: block;
animation: datepicker-anim-in 0.2s cubic-bezier(0.23, 1, 0.32, 1); }
}
.datepicker-anim-leave-active { .datepicker-close:hover::before {
transform-origin: 0 0; background-color: #afafaf;
animation: datepicker-anim-out 0.2s cubic-bezier(0.755, 0.05, 0.855, 0.06); }
}
.datepicker__buttons { .datepicker > input {
display: block; color: inherit;
text-align: right; // transition: all 200ms ease;
} border-radius: 4px;
border: 0;
background: none;
height: 28px;
box-sizing: border-box;
outline: none;
padding: 0 5px;
width: 100%;
user-select: none;
font-family: Monaco;
letter-spacing: -0.7px;
}
.datepicker__buttons button { // .datepicker > input.focus {
display: inline-block; // border-color: #3f97e3;
font-size: 13px; // -webkit-box-shadow: 0 0 5px rgba(59, 180, 242, 0.3);
border: none; // box-shadow: 0 0 5px rgba(59, 180, 242, 0.3);
cursor: pointer; // }
margin: 10px 0 0 5px;
padding: 5px 15px;
color: #ffffff;
}
.datepicker__buttons .datepicker__button-select { .datepicker > input:disabled {
background: #3f97e3; cursor: not-allowed;
} background-color: #ebebe4;
border-color: #e5e5e5;
box-shadow: none;
}
.datepicker__buttons .datepicker__button-cancel { .datepicker-popup {
background: #666; border-radius: 4px;
} position: absolute;
transition: all 200ms ease;
opacity: 1;
transform: scaleY(1);
font-size: $font-size-smaller;
background: $theme-background;
box-shadow: 0 1px 6px rgb(99 99 99 / 20%);
margin-top: 2px;
outline: 0;
padding: 5px;
overflow: hidden;
z-index: 999;
&.top {
bottom: 30px;
right: 0;
transform-origin: center bottom;
}
&.bottom {
top: 30px;
right: 0;
transform-origin: center top;
}
&.left {
top: 30px;
transform-origin: center top;
}
&.right {
right: -80px;
top: 30px;
transform-origin: center top;
}
&__sidebar {
position: absolute;
top: 0;
bottom: 0;
width: 100px;
height: 100%;
padding: 5px;
border-right: solid 1px #eaeaea;
}
&__shortcut {
display: block;
width: 100%;
border: 0;
background-color: transparent;
line-height: 34px;
font-size: $font-size-smaller;
color: #666;
text-align: left;
outline: none;
cursor: pointer;
white-space: nowrap;
&:hover {
color: #3f97e3;
}
}
&__body {
margin-left: 100px;
padding-left: 5px;
}
}
.datepicker-inline {
position: relative;
margin-top: 0;
}
.datepicker-range {
min-width: 238px;
}
.datepicker-range .datepicker-popup {
width: 520px;
}
.datepicker-bottom {
float: left;
width: 100%;
text-align: right;
}
.datepicker-btn {
padding: 5px 10px;
background: #3f97e3;
color: $text-color;
border-radius: 2px;
display: inline-block;
cursor: pointer;
}
.datepicker-anim-enter-active {
transform-origin: 0 0;
animation: datepicker-anim-in 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
.datepicker-anim-leave-active {
transform-origin: 0 0;
animation: datepicker-anim-out 0.2s cubic-bezier(0.755, 0.05, 0.855, 0.06);
}
.datepicker__buttons {
display: block;
text-align: right;
}
.datepicker__buttons button {
display: inline-block;
font-size: 13px;
border: none;
cursor: pointer;
margin: 10px 0 0 5px;
padding: 5px 15px;
color: $text-color;
}
.datepicker__buttons .datepicker__button-select {
background: #3f97e3;
}
.datepicker__buttons .datepicker__button-cancel {
background: #666;
}
</style> </style>

View File

@@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it } from "vitest";
// import { mount } from '@vue/test-utils'
// import HelloWorld from '../HelloWorld.vue'
// describe('HelloWorld', () => {
// it('renders properly', () => {
// const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
// expect(wrapper.text()).toContain('Hello Vitest')
// })
// })
describe("My First Test", () => {
it("renders props.msg when passed", () => {
const msg = "new message";
console.log(msg);
});
});

View File

@@ -21,9 +21,10 @@ import Selector from "./Selector.vue";
import Graph from "./Graph.vue"; import Graph from "./Graph.vue";
import Radio from "./Radio.vue"; import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.vue"; import SelectSingle from "./SelectSingle.vue";
import Tags from "./Tags.vue";
import VueGridLayout from "vue-grid-layout"; import VueGridLayout from "vue-grid-layout";
const components: { [key: string]: any } = { const components: Indexable = {
Icon, Icon,
TimePicker, TimePicker,
VueGridLayout, VueGridLayout,
@@ -31,6 +32,7 @@ const components: { [key: string]: any } = {
Graph, Graph,
Radio, Radio,
SelectSingle, SelectSingle,
Tags,
}; };
const componentsName: string[] = Object.keys(components); const componentsName: string[] = Object.keys(components);

View File

@@ -14,22 +14,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import axios, { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken"; import { cancelToken } from "@/utils/cancelToken";
async function query(param: { async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
const res: AxiosResponse = await axios.post( const res: AxiosResponse = await axios.post(
"/graphql", "/graphql",
{ query: param.queryStr, variables: { ...param.conditions } }, { query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() } { cancelToken: cancelToken() },
); );
if (res.data.errors) { if (res.data.errors) {
res.data.errors = res.data.errors res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
.map((e: { message: string }) => e.message)
.join(" ");
} }
return res; return res;
} }

View File

@@ -16,8 +16,7 @@
*/ */
export const Alarm = { export const Alarm = {
variable: variable: "$keyword: String, $scope: Scope, $duration:Duration!, $tags:[AlarmTag], $paging: Pagination!",
"$keyword: String, $scope: Scope, $duration:Duration!, $tags:[AlarmTag], $paging: Pagination!",
query: ` query: `
getAlarm(keyword: $keyword, scope: $scope, duration: $duration, paging: $paging, tags: $tags) { getAlarm(keyword: $keyword, scope: $scope, duration: $duration, paging: $paging, tags: $tags) {
items: msgs { items: msgs {

View File

@@ -26,3 +26,26 @@ export const OAPTimeInfo = {
export const OAPVersion = { export const OAPVersion = {
query: `version { version }`, query: `version { version }`,
}; };
export const MenuItems = {
query: `
getMenuItems {
title
icon
layer
activate
description
documentLink
i18nKey
subItems {
title
icon
layer
activate
description
documentLink
i18nKey
}
}
`,
};

View File

@@ -34,20 +34,36 @@ export const createEBPFTask = {
}; };
export const queryEBPFTasks = { export const queryEBPFTasks = {
variable: variable:
"$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]", "$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!], $triggerType: EBPFProfilingTriggerType",
query: ` query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) { queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets, triggerType: $triggerType) {
taskId taskId
serviceName serviceName
serviceId serviceId
serviceInstanceId serviceInstanceId
serviceInstanceName serviceInstanceName
processLabels processLabels
processName
processId
taskStartTime taskStartTime
triggerType triggerType
fixedTriggerDuration fixedTriggerDuration
targetType targetType
createTime createTime
continuousProfilingCauses {
type
singleValue {
threshold
current
}
uri {
uriRegex
uriPath
threshold
current
}
message
}
}`, }`,
}; };
export const queryEBPFSchedules = { export const queryEBPFSchedules = {
@@ -112,3 +128,26 @@ export const keepNetworkProfiling = {
errorReason errorReason
}`, }`,
}; };
export const monitoringInstances = {
variable: "$serviceId: ID!, $target: ContinuousProfilingTargetType!",
query: `
instances: queryContinuousProfilingMonitoringInstances(serviceId: $serviceId, target: $target) {
id
name
attributes {
name
value
}
triggeredCount
lastTriggerTimestamp
processes {
id
name
detectType
labels
lastTriggerTimestamp
triggeredCount
}
}`,
};

View File

@@ -15,37 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
export const ProfileSegment = {
variable: "$segmentId: String",
query: `
segment: getProfiledSegment(segmentId: $segmentId) {
spans {
spanId
parentSpanId
serviceCode
startTime
endTime
endpointName
type
peer
component
isError
layer
tags {
key value
}
logs {
time
data {
key
value
}
}
}
}
`,
};
export const CreateProfileTask = { export const CreateProfileTask = {
variable: "$creationRequest: ProfileTaskCreationRequest", variable: "$creationRequest: ProfileTaskCreationRequest",
query: ` query: `
@@ -79,23 +48,55 @@ export const GetProfileTaskList = {
`, `,
}; };
export const GetProfileTaskSegmentList = { export const GetProfileTaskSegmentList = {
variable: "$taskID: String", variable: "$taskID: ID!",
query: ` query: `
segmentList: getProfileTaskSegmentList(taskID: $taskID) { segmentList: getProfileTaskSegments(taskID: $taskID) {
segmentId traceId
instanceId
instanceName
endpointNames endpointNames
start
duration duration
traceIds start
isError spans {
spanId
parentSpanId
segmentId
refs {
traceId
parentSegmentId
parentSpanId
type
}
serviceCode
serviceInstanceName
startTime
endTime
endpointName
type
peer
component
isError
layer
tags {
key value
}
logs {
time
data {
key
value
}
}
profiled
}
} }
`, `,
}; };
export const GetProfileAnalyze = { export const GetProfileAnalyze = {
variable: "$segmentId: String!, $timeRanges: [ProfileAnalyzeTimeRange!]!", variable: "$queries: [SegmentProfileAnalyzeQuery!]!",
query: ` query: `
analyze: getProfileAnalyze(segmentId: $segmentId, timeRanges: $timeRanges) { analyze: getSegmentsProfileAnalyze(queries: $queries) {
tip tip
trees { trees {
elements { elements {
@@ -122,3 +123,29 @@ export const GetProfileTaskLogs = {
} }
`, `,
}; };
export const GetStrategyList = {
variable: "$serviceId: ID!",
query: `
strategyList: queryContinuousProfilingServiceTargets(serviceId: $serviceId) {
type
checkItems {
type
threshold
period
count
uriList
uriRegex
}
}
`,
};
export const EditStrategy = {
variable: "$request: ContinuousProfilingPolicyCreation!",
query: `
strategy: setContinuousProfilingPolicy(request: $request) {
errorReason
status
}
`,
};

View File

@@ -53,8 +53,7 @@ export const EndpointTopology = {
}`, }`,
}; };
export const InstanceTopology = { export const InstanceTopology = {
variable: variable: "$clientServiceId: ID!, $serverServiceId: ID!, $duration: Duration!",
"$clientServiceId: ID!, $serverServiceId: ID!, $duration: Duration!",
query: ` query: `
topology: getServiceInstanceTopology(clientServiceId: $clientServiceId, topology: getServiceInstanceTopology(clientServiceId: $clientServiceId,
serverServiceId: $serverServiceId, duration: $duration) { serverServiceId: $serverServiceId, duration: $duration) {

View File

@@ -14,7 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import axios, { AxiosPromise, AxiosResponse } from "axios"; import type { AxiosPromise, AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken"; import { cancelToken } from "@/utils/cancelToken";
import * as app from "./query/app"; import * as app from "./query/app";
import * as selector from "./query/selector"; import * as selector from "./query/selector";
@@ -55,13 +56,11 @@ class Graphql {
query: query[this.queryData], query: query[this.queryData],
variables: variablesData, variables: variablesData,
}, },
{ cancelToken: cancelToken() } { cancelToken: cancelToken() },
) )
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
if (res.data.errors) { if (res.data.errors) {
res.data.errors = res.data.errors res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
.map((e: { message: string }) => e.message)
.join(" ");
} }
return res; return res;
}) })

View File

@@ -14,8 +14,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { OAPTimeInfo, OAPVersion } from "../fragments/app"; import { OAPTimeInfo, OAPVersion, MenuItems } from "../fragments/app";
export const queryOAPTimeInfo = `query queryOAPTimeInfo {${OAPTimeInfo.query}}`; export const queryOAPTimeInfo = `query queryOAPTimeInfo {${OAPTimeInfo.query}}`;
export const queryOAPVersion = `query ${OAPVersion.query}`; export const queryOAPVersion = `query ${OAPVersion.query}`;
export const queryMenuItems = `query menuItems {${MenuItems.query}}`;

View File

@@ -23,6 +23,7 @@ import {
analysisEBPFResult, analysisEBPFResult,
createNetworkProfiling, createNetworkProfiling,
keepNetworkProfiling, keepNetworkProfiling,
monitoringInstances,
} from "../fragments/ebpf"; } from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`; export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`;
@@ -38,3 +39,5 @@ export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.vari
export const newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`; export const newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`;
export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`; export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`;
export const getMonitoringInstances = `query continuousProfilingMonitoringInstances(${monitoringInstances.variable}) {${monitoringInstances.query}}`;

View File

@@ -16,16 +16,15 @@
*/ */
import { import {
ProfileSegment,
CreateProfileTask, CreateProfileTask,
GetProfileTaskList, GetProfileTaskList,
GetProfileTaskSegmentList, GetProfileTaskSegmentList,
GetProfileAnalyze, GetProfileAnalyze,
GetProfileTaskLogs, GetProfileTaskLogs,
GetStrategyList,
EditStrategy,
} from "../fragments/profile"; } from "../fragments/profile";
export const queryProfileSegment = `query queryProfileSegment(${ProfileSegment.variable}) {${ProfileSegment.query}}`;
export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`; export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`;
export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) { export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) {
@@ -37,3 +36,7 @@ export const getProfileTaskSegmentList = `query getProfileTaskSegmentList(${GetP
export const getProfileAnalyze = `query getProfileAnalyze(${GetProfileAnalyze.variable}) {${GetProfileAnalyze.query}}`; export const getProfileAnalyze = `query getProfileAnalyze(${GetProfileAnalyze.variable}) {${GetProfileAnalyze.query}}`;
export const getProfileTaskLogs = `query profileTaskLogs(${GetProfileTaskLogs.variable}) {${GetProfileTaskLogs.query}}`; export const getProfileTaskLogs = `query profileTaskLogs(${GetProfileTaskLogs.variable}) {${GetProfileTaskLogs.query}}`;
export const getStrategyList = `query getStrategyList(${GetStrategyList.variable}) {${GetStrategyList.query}}`;
export const editStrategy = `mutation editStrategy(${EditStrategy.variable}) {${EditStrategy.query}}`;

View File

@@ -14,12 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { import { InstanceTopology, EndpointTopology, ServicesTopology, ProcessTopology } from "../fragments/topology";
InstanceTopology,
EndpointTopology,
ServicesTopology,
ProcessTopology,
} from "../fragments/topology";
export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`; export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`; export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;

View File

@@ -15,12 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { Traces, TraceSpans, TraceTagKeys, TraceTagValues } from "../fragments/trace";
Traces,
TraceSpans,
TraceTagKeys,
TraceTagValues,
} from "../fragments/trace";
export const queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`; export const queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`;

View File

@@ -22,6 +22,7 @@ export enum MetricQueryTypes {
READHEATMAP = "readHeatMap", READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords", ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords", ReadRecords = "readRecords",
ReadNullableMetricsValue = "readNullableMetricsValue",
} }
export enum Calculations { export enum Calculations {
@@ -30,7 +31,6 @@ export enum Calculations {
ByteToMB = "byteToMB", ByteToMB = "byteToMB",
ByteToGB = "byteToGB", ByteToGB = "byteToGB",
Apdex = "apdex", Apdex = "apdex",
Precision = "precision",
ConvertSeconds = "convertSeconds", ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds", ConvertMilliseconds = "convertMilliseconds",
MsToS = "msTos", MsToS = "msTos",
@@ -67,14 +67,18 @@ screenMap.set(sizeEnum.LG, screenEnum.LG);
screenMap.set(sizeEnum.XL, screenEnum.XL); screenMap.set(sizeEnum.XL, screenEnum.XL);
screenMap.set(sizeEnum.XXL, screenEnum.XXL); screenMap.set(sizeEnum.XXL, screenEnum.XXL);
export const RespFields: any = { export const RespFields: Indexable = {
readMetricsValues: `{ readMetricsValues: `{
label label
values { values {
values {value} values {value isEmptyValue}
} }
}`, }`,
readMetricsValue: "", readMetricsValue: ``,
readNullableMetricsValue: `{
value
isEmptyValue
}`,
sortMetrics: `{ sortMetrics: `{
name name
id id
@@ -84,7 +88,7 @@ export const RespFields: any = {
readLabeledMetricsValues: `{ readLabeledMetricsValues: `{
label label
values { values {
values {value} values {value isEmptyValue}
} }
}`, }`,
readHeatMap: `{ readHeatMap: `{
@@ -108,4 +112,21 @@ export const RespFields: any = {
value value
refId refId
}`, }`,
execExpression: `{
type
results {
metric {
labels {
key
value
}
}
values {
name: id
value
refId: traceID
}
}
error
}`,
}; };

View File

@@ -17,9 +17,9 @@
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep from "@/utils/dateFormat"; import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime"; import getLocalTime from "@/utils/localtime";
import { EventParams } from "@/types/app"; import type { EventParams } from "@/types/app";
export default function associateProcessor(props: any) { export default function associateProcessor(props: Indexable) {
function eventAssociate() { function eventAssociate() {
if (!props.filters) { if (!props.filters) {
return; return;
@@ -30,9 +30,7 @@ export default function associateProcessor(props: any) {
if (!props.option.series[0]) { if (!props.option.series[0]) {
return; return;
} }
const list = props.option.series[0].data.map( const list = props.option.series[0].data.map((d: (number | string)[]) => d[0]);
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) { if (!list.includes(props.filters.duration.endTime)) {
return; return;
} }
@@ -77,16 +75,8 @@ export default function associateProcessor(props: any) {
if (start) { if (start) {
const end = start; const end = start;
duration = { duration = {
start: dateFormatStep( start: dateFormatStep(getLocalTime(appStore.utc, new Date(start)), step, true),
getLocalTime(appStore.utc, new Date(start)), end: dateFormatStep(getLocalTime(appStore.utc, new Date(end)), step, true),
step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(end)),
step,
true
),
step, step,
}; };
} }
@@ -95,44 +85,36 @@ export default function associateProcessor(props: any) {
const queryOrder = relatedTrace.queryOrder; const queryOrder = relatedTrace.queryOrder;
const latency = relatedTrace.latency; const latency = relatedTrace.latency;
const series = props.option.series || []; const series = props.option.series || [];
const item: any = { const item: Indexable = {
duration, duration,
queryOrder, queryOrder,
status, status,
}; };
if (latency) { if (latency) {
const latencyList = series.map( const latencyList = series.map((d: { name: string; data: number[][] }, index: number) => {
(d: { name: string; data: number[][] }, index: number) => { const data = [
const data = [ d.data[currentParams.dataIndex][1],
d.data[currentParams.dataIndex][1], series[index + 1] ? series[index + 1].data[currentParams.dataIndex][1] : Infinity,
series[index + 1] ];
? series[index + 1].data[currentParams.dataIndex][1] return {
: Infinity, label: d.name + "--" + (series[index + 1] ? series[index + 1].name : "Infinity"),
]; value: String(index),
return { data,
label: };
d.name + });
"--" +
(series[index + 1] ? series[index + 1].name : "Infinity"),
value: String(index),
data,
};
}
);
item.latency = latencyList; item.latency = latencyList;
} }
const value = series.map( const value = series.map((d: { name: string; data: number[][] }, index: number) => {
(d: { name: string; data: number[][] }, index: number) => { return {
return { label: d.name,
label: d.name, value: String(index),
value: String(index), data: d.data[currentParams.dataIndex][1],
data: d.data[currentParams.dataIndex][1], date: d.data[currentParams.dataIndex][0],
date: d.data[currentParams.dataIndex][0], };
}; });
}
);
item.metricValue = value; item.metricValue = value;
return item; return item;
} }
return { eventAssociate, traceFilters }; return { eventAssociate, traceFilters };
} }

View File

@@ -14,7 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { ref, computed, ComputedRef, unref } from "vue"; import type { ComputedRef } from "vue";
import { ref, computed, unref } from "vue";
import { useEventListener } from "./useEventListener"; import { useEventListener } from "./useEventListener";
import { screenMap, sizeEnum, screenEnum } from "./data"; import { screenMap, sizeEnum, screenEnum } from "./data";
@@ -31,7 +32,7 @@ export interface CreateCallbackParams {
sizeEnum: typeof sizeEnum; sizeEnum: typeof sizeEnum;
} }
export function useBreakpoint(): any { export function useBreakpoint(): Indexable {
return { return {
screenRef: computed(() => unref(globalScreenRef)), screenRef: computed(() => unref(globalScreenRef)),
widthRef: globalWidthRef, widthRef: globalWidthRef,
@@ -40,9 +41,7 @@ export function useBreakpoint(): any {
}; };
} }
export function createBreakpointListen( export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void): Indexable {
fn?: (opt: CreateCallbackParams) => void
): any {
const screenRef = ref<sizeEnum>(sizeEnum.XL || ""); const screenRef = ref<sizeEnum>(sizeEnum.XL || "");
const realWidthRef = ref(window.innerWidth); const realWidthRef = ref(window.innerWidth);

View File

@@ -16,19 +16,15 @@
*/ */
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard"; import type { LayoutConfig } from "@/types/dashboard";
export default function getDashboard(param?: { export default function getDashboard(param?: { name: string; layer: string; entity: string }) {
name: string;
layer: string;
entity: string;
}) {
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard; const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find( const dashboard = list.find(
(d: { name: string; layer: string; entity: string }) => (d: { name: string; layer: string; entity: string }) =>
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer,
); );
const all = dashboardStore.layout; const all = dashboardStore.layout;
const widgets: LayoutConfig[] = []; const widgets: LayoutConfig[] = [];
@@ -63,7 +59,7 @@ export default function getDashboard(param?: {
if (targetTabIndex[1] === undefined) { if (targetTabIndex[1] === undefined) {
container = document.querySelector(".ds-main"); container = document.querySelector(".ds-main");
} else { } else {
const w = widgets.find((d: any) => d.id === targetTabIndex[0]); const w = widgets.find((d: Indexable) => d.id === targetTabIndex[0]);
container = document.querySelector(".tab-layout"); container = document.querySelector(".tab-layout");
const layout: Nullable<Element> = document.querySelector(".ds-main"); const layout: Nullable<Element> = document.querySelector(".ds-main");
if (w && layout) { if (w && layout) {

View File

@@ -14,13 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { import type { BarSeriesOption, LineSeriesOption, HeatmapSeriesOption, SankeySeriesOption } from "echarts/charts";
BarSeriesOption, import type {
LineSeriesOption,
HeatmapSeriesOption,
SankeySeriesOption,
} from "echarts/charts";
import {
TitleComponentOption, TitleComponentOption,
TooltipComponentOption, TooltipComponentOption,
GridComponentOption, GridComponentOption,
@@ -48,10 +43,7 @@ export type ECOption = echarts.ComposeOption<
| SankeySeriesOption | SankeySeriesOption
>; >;
export function useECharts( export function useECharts(elRef: Ref<HTMLDivElement>, theme: "light" | "dark" | "default" = "default"): Indexable {
elRef: Ref<HTMLDivElement>,
theme: "light" | "dark" | "default" = "default"
): any {
const getDarkMode = computed(() => { const getDarkMode = computed(() => {
return theme === "default" ? "light" : theme; return theme === "default" ? "light" : theme;
}); });
@@ -131,7 +123,7 @@ export function useECharts(
initCharts(theme as "default"); initCharts(theme as "default");
setOptions(cacheOptions.value); setOptions(cacheOptions.value);
} }
} },
); );
tryOnUnmounted(() => { tryOnUnmounted(() => {

View File

@@ -20,7 +20,7 @@ import { useThrottleFn, useDebounceFn } from "@vueuse/core";
export type RemoveEventFn = () => void; export type RemoveEventFn = () => void;
export interface UseEventParams { export interface UseEventParams {
el?: Element | Ref<Element | undefined> | Window | any; el?: Element | Ref<Element | undefined> | Window | Recordable;
name: string; name: string;
listener: EventListener; listener: EventListener;
options?: boolean | AddEventListenerOptions; options?: boolean | AddEventListenerOptions;
@@ -43,16 +43,13 @@ export function useEventListener({
if (el) { if (el) {
const element = ref(el as Element) as Ref<Element>; const element = ref(el as Element) as Ref<Element>;
const handler = isDebounce const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);
? useDebounceFn(listener, wait)
: useThrottleFn(listener, wait);
const realHandler = wait ? handler : listener; const realHandler = wait ? handler : listener;
const removeEventListener = (e: Element) => { const removeEventListener = (e: Element) => {
isAddRef.value = true; isAddRef.value = true;
e.removeEventListener(name, realHandler, options); e.removeEventListener(name, realHandler, options);
}; };
const addEventListener = (e: Element) => const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options);
e.addEventListener(name, realHandler, options);
const removeWatch = watch( const removeWatch = watch(
element, element,
@@ -64,7 +61,7 @@ export function useEventListener({
}); });
} }
}, },
{ immediate: true } { immediate: true },
); );
remove = () => { remove = () => {

View File

@@ -0,0 +1,400 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RespFields } from "./data";
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { MetricConfigOpt } from "@/types/dashboard";
import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Node, Call } from "@/types/topology";
export async function useExpressionsQueryProcessor(config: Indexable) {
function expressionsGraphqlPods() {
if (!(config.metrics && config.metrics[0])) {
return;
}
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
return;
}
const conditions: Recordable = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
dashboardStore.entity,
);
if (isRelation && !selectorStore.currentDestService) {
return;
}
const fragment = config.metrics.map((name: string, index: number) => {
variables.push(`$expression${index}: String!`, `$entity${index}: Entity!`);
conditions[`expression${index}`] = name;
const entity = {
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation", "Process"].includes(
dashboardStore.entity,
)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation ? selectorStore.currentDestService.normal : undefined,
destServiceName: isRelation ? selectorStore.currentDestService.value : undefined,
destServiceInstanceName: ["ServiceInstanceRelation", "ProcessRelation"].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
: undefined,
};
conditions[`entity${index}`] = entity;
return `expression${index}: execExpression(expression: $expression${index}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
return {
queryStr,
conditions,
};
}
function expressionsSource(resp: { errors: string; data: Indexable }) {
if (resp.errors) {
ElMessage.error(resp.errors);
return { source: {}, tips: [], typesOfMQE: [] };
}
if (!resp.data) {
ElMessage.error("The query is wrong");
return { source: {}, tips: [], typesOfMQE: [] };
}
const tips: string[] = [];
const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data);
const typesOfMQE: string[] = [];
for (let i = 0; i < config.metrics.length; i++) {
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {};
const obj = resp.data[keys[i]] || {};
const results = obj.results || [];
const name = config.metrics[i];
const type = obj.type;
tips.push(obj.error);
typesOfMQE.push(type);
if (!obj.error) {
if (type === ExpressionResultType.TIME_SERIES_VALUES) {
if (results.length === 1) {
const label = results[0].metric && results[0].metric.labels[0] && results[0].metric.labels[0].value;
source[c.label || label || name] = results[0].values.map((d: { value: unknown }) => d.value) || [];
} else {
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (const item of results) {
const values = item.values.map((d: { value: unknown }) => d.value) || [];
const index = item.metric.labels[0].value;
const indexNum = labels.findIndex((_, i: number) => i === Number(index));
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
} else {
source[index] = values;
}
}
}
}
if (type === ExpressionResultType.SINGLE_VALUE) {
source[c.label || name] = (results[0].values[0] || {}).value;
}
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
source[name] = results[0].values;
}
}
}
return { source, tips, typesOfMQE };
}
const params = await expressionsGraphqlPods();
if (!params) {
return { source: {}, tips: [], typesOfMQE: [] };
}
const dashboardStore = useDashboardStore();
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return { source: {}, tips: [], typesOfMQE: [] };
}
const data = expressionsSource(json);
return data;
}
export async function useExpressionsQueryPodsMetrics(
pods: Array<(Instance | Endpoint | Service) & Indexable>,
config: {
expressions: string[];
subExpressions: string[];
metricConfig: MetricConfigOpt[];
},
scope: string,
) {
function expressionsGraphqlPods() {
const metrics: string[] = [];
const subMetrics: string[] = [];
config.expressions = config.expressions || [];
config.subExpressions = config.subExpressions || [];
for (let i = 0; i < config.expressions.length; i++) {
if (config.expressions[i]) {
metrics.push(config.expressions[i]);
subMetrics.push(config.subExpressions[i]);
}
}
if (!metrics.length) {
return;
}
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const currentService = selectorStore.currentService || {};
const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => {
const entity = {
serviceName: scope === "Service" ? d.label : currentService.label,
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
endpointName: scope === "Endpoint" ? d.label : undefined,
normal: scope === "Service" ? d.normal : currentService.normal,
};
variables.push(`$entity${index}: Entity!`);
conditions[`entity${index}`] = entity;
const f = metrics.map((name: string, idx: number) => {
variables.push(`$expression${index}${idx}: String!`);
conditions[`expression${index}${idx}`] = name;
let str = "";
if (config.subExpressions[idx]) {
variables.push(`$subExpression${index}${idx}: String!`);
conditions[`subExpression${index}${idx}`] = config.subExpressions[idx];
str = `subexpression${index}${idx}: execExpression(expression: $subExpression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
}
return (
str +
`expression${index}${idx}: execExpression(expression: $expression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`
);
});
return f;
});
const fragment = fragmentList.flat(1).join(" ");
const queryStr = `query queryData(${variables}) {${fragment}}`;
return { queryStr, conditions };
}
function expressionsPodsSource(resp: { errors: string; data: Indexable }): Indexable {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
const names: string[] = [];
const subNames: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const expressionsTips: string[] = [];
const subExpressionsTips: string[] = [];
const data = pods.map((d: any, idx: number) => {
for (let index = 0; index < config.expressions.length; index++) {
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[index]) || {};
const k = "expression" + idx + index;
const sub = "subexpression" + idx + index;
const obj = resp.data[k] || {};
const results = obj.results || [];
const typesOfMQE = obj.type || "";
const subObj = resp.data[sub] || {};
const subResults = subObj.results || [];
expressionsTips.push(obj.error);
subExpressionsTips.push(subObj.error);
if (results.length > 1) {
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (let i = 0; i < results.length; i++) {
let name = results[i].metric.labels[0].value || "";
const subValues = subResults[i] && subResults[i].values.map((d: { value: unknown }) => d.value);
const num = labelsIdx.findIndex((d: string) => d === results[i].metric.labels[0].value);
if (labels[num]) {
name = labels[num];
}
if (!d[name]) {
d[name] = {};
}
if (subValues) {
d[name]["values"] = subValues;
}
d[name]["avg"] = (results[i].values[0] || {}).value;
const j = names.find((d: string) => d === name);
if (!j) {
names.push(name);
metricConfigArr.push({ ...c, index: i });
metricTypesArr.push(typesOfMQE);
}
}
} else {
if (!results[0]) {
return d;
}
const name = config.expressions[index] || "";
const subName = config.subExpressions[index] || "";
if (!d[name]) {
d[name] = {};
}
d[name]["avg"] = [(results[0].values[0] || {}).value];
if (subResults[0]) {
if (!d[subName]) {
d[subName] = {};
}
d[subName]["values"] = subResults[0].values.map((d: { value: number }) => d.value);
}
const j = names.find((d: string) => d === name);
if (!j) {
names.push(name);
subNames.push(subName);
metricConfigArr.push(c);
metricTypesArr.push(typesOfMQE);
}
}
}
return d;
});
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
}
const dashboardStore = useDashboardStore();
const params = await expressionsGraphqlPods();
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return {};
}
const expressionParams = expressionsPodsSource(json);
return expressionParams;
}
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
function getExpressionQuery() {
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const fragmentList = instances.map((d: any, index: number) => {
let serviceName;
let destServiceName;
let endpointName;
let serviceInstanceName;
let destServiceInstanceName;
let destEndpointName;
if (d.sourceObj && d.targetObj) {
// instances = Calls
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
destServiceName = d.targetObj.serviceName || d.targetObj.name;
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.sourceObj.name;
destServiceInstanceName = d.targetObj.name;
}
if (EntityType[2].value === dashboardStore.entity) {
endpointName = d.sourceObj.name;
destEndpointName = d.targetObj.name;
}
} else {
// instances = Nodes
serviceName = d.serviceName || d.name;
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.name;
}
if (EntityType[2].value === dashboardStore.entity) {
endpointName = d.name;
}
}
const entity = {
serviceName,
normal: true,
serviceInstanceName,
endpointName,
destServiceName,
destNormal: destServiceName ? true : undefined,
destServiceInstanceName,
destEndpointName,
};
variables.push(`$entity${index}: Entity!`);
conditions[`entity${index}`] = entity;
const f = metrics.map((name: string, idx: number) => {
if (index === 0) {
variables.push(`$expression${idx}: String!`);
conditions[`expression${idx}`] = name;
}
return `expression${index}${idx}: execExpression(expression: $expression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
});
return f;
});
const fragment = fragmentList.flat(1).join(" ");
const queryStr = `query queryData(${variables}) {${fragment}}`;
return { queryStr, conditions };
}
function handleExpressionValues(resp: { [key: string]: any }) {
const obj: any = {};
for (let idx = 0; idx < instances.length; idx++) {
for (let index = 0; index < metrics.length; index++) {
const k = "expression" + idx + index;
if (metrics[index]) {
if (!obj[metrics[index]]) {
obj[metrics[index]] = {
values: [],
};
}
obj[metrics[index]].values.push({ value: resp[k].results[0].values[0].value, id: instances[idx].id });
}
}
}
return obj;
}
return { getExpressionQuery, handleExpressionValues };
}

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { LegendOptions } from "@/types/dashboard"; import type { LegendOptions } from "@/types/dashboard";
import { isDef } from "@/utils/is"; import { isDef } from "@/utils/is";
export default function useLegendProcess(legend?: LegendOptions) { export default function useLegendProcess(legend?: LegendOptions) {
@@ -37,14 +37,9 @@ export default function useLegendProcess(legend?: LegendOptions) {
} }
return true; return true;
} }
function aggregations( function aggregations(data: { [key: string]: number[] }, intervalTime: string[]) {
data: { [key: string]: number[] },
intervalTime: string[]
) {
const source: { [key: string]: unknown }[] = []; const source: { [key: string]: unknown }[] = [];
const keys = Object.keys(data || {}).filter( const keys = Object.keys(data || {}).filter((i: string) => Array.isArray(data[i]) && data[i].length);
(i: any) => Array.isArray(data[i]) && data[i].length
);
const headers = []; const headers = [];
for (const [key, value] of keys.entries()) { for (const [key, value] of keys.entries()) {
@@ -58,12 +53,7 @@ export default function useLegendProcess(legend?: LegendOptions) {
value: d, value: d,
}; };
}) })
.sort( .sort((a: { key: string; value: number }, b: { key: string; value: number }) => b.value - a.value)
(
a: { key: string; value: number },
b: { key: string; value: number }
) => b.value - a.value
)
.filter((_: unknown, index: number) => index < 10), .filter((_: unknown, index: number) => index < 10),
}; };
if (legend) { if (legend) {

View File

@@ -15,27 +15,25 @@
* limitations under the License. * limitations under the License.
*/ */
import { MetricQueryTypes, Calculations } from "./data"; import { MetricQueryTypes, Calculations } from "./data";
export function useListConfig(config: any, index: string) { import { MetricModes } from "@/views/dashboard/data";
export function useListConfig(config: Indexable, index: number) {
if (config.metricModes === MetricModes.Expression) {
return {
isLinear: false,
isAvg: true,
};
}
const i = Number(index); const i = Number(index);
const types = [ const types = [Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg];
Calculations.Average, const calculation = config.metricConfig && config.metricConfig[i] && config.metricConfig[i].calculation;
Calculations.ApdexAvg,
Calculations.PercentageAvg,
];
const calculation =
config.metricConfig &&
config.metricConfig[i] &&
config.metricConfig[i].calculation;
const isLinear = const isLinear =
[ [MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
MetricQueryTypes.ReadMetricsValues, !types.includes(calculation);
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && !types.includes(calculation);
const isAvg = const isAvg =
[ [MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
MetricQueryTypes.ReadMetricsValues, types.includes(calculation);
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && types.includes(calculation);
return { return {
isLinear, isLinear,
isAvg, isAvg,

View File

@@ -20,11 +20,10 @@ import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { Instance, Endpoint, Service } from "@/types/selector"; import type { Instance, Endpoint, Service } from "@/types/selector";
import { MetricConfigOpt } from "@/types/dashboard"; import type { MetricConfigOpt } from "@/types/dashboard";
import { MetricCatalog } from "@/views/dashboard/data";
export function useQueryProcessor(config: any) { export function useQueryProcessor(config: Indexable) {
if (!(config.metrics && config.metrics[0])) { if (!(config.metrics && config.metrics[0])) {
return; return;
} }
@@ -38,57 +37,35 @@ export function useQueryProcessor(config: any) {
if (!selectorStore.currentService && dashboardStore.entity !== "All") { if (!selectorStore.currentService && dashboardStore.entity !== "All") {
return; return;
} }
const conditions: { [key: string]: unknown } = { const conditions: Recordable = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
const isRelation = [ const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
"ServiceRelation", dashboardStore.entity,
"ServiceInstanceRelation", );
"EndpointRelation",
"ProcessRelation",
].includes(dashboardStore.entity);
if (isRelation && !selectorStore.currentDestService) { if (isRelation && !selectorStore.currentDestService) {
return; return;
} }
const fragment = config.metrics.map((name: string, index: number) => { const fragment = config.metrics.map((name: string, index: number) => {
const metricType = config.metricTypes[index] || ""; const metricType = config.metricTypes[index] || "";
const c = (config.metricConfig && config.metricConfig[index]) || {}; const c = (config.metricConfig && config.metricConfig[index]) || {};
if ( if ([MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics].includes(metricType)) {
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
].includes(metricType)
) {
variables.push(`$condition${index}: TopNCondition!`); variables.push(`$condition${index}: TopNCondition!`);
conditions[`condition${index}`] = { conditions[`condition${index}`] = {
name, name,
parentService: ["All"].includes(dashboardStore.entity) parentService: ["All"].includes(dashboardStore.entity) ? null : selectorStore.currentService.value,
? null normal: selectorStore.currentService ? selectorStore.currentService.normal : true,
: selectorStore.currentService.value, topN: Number(c.topN) || 10,
normal: selectorStore.currentService
? selectorStore.currentService.normal
: true,
scope: config.catalog,
topN: c.topN || 10,
order: c.sortOrder || "DES", order: c.sortOrder || "DES",
}; };
} else { } else {
const entity = { const entity = {
scope: config.catalog, serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
serviceName: normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
dashboardStore.entity === "All" serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].includes(
? undefined dashboardStore.entity,
: selectorStore.currentService.value, )
normal:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.normal,
serviceInstanceName: [
"ServiceInstance",
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentPod && selectorStore.currentPod.value ? selectorStore.currentPod && selectorStore.currentPod.value
: undefined, : undefined,
endpointName: dashboardStore.entity.includes("Endpoint") endpointName: dashboardStore.entity.includes("Endpoint")
@@ -97,16 +74,9 @@ export function useQueryProcessor(config: any) {
processName: dashboardStore.entity.includes("Process") processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value ? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined, : undefined,
destNormal: isRelation destNormal: isRelation ? selectorStore.currentDestService.normal : undefined,
? selectorStore.currentDestService.normal destServiceName: isRelation ? selectorStore.currentDestService.value : undefined,
: undefined, destServiceInstanceName: ["ServiceInstanceRelation", "ProcessRelation"].includes(dashboardStore.entity)
destServiceName: isRelation
? selectorStore.currentDestService.value
: undefined,
destServiceInstanceName: [
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value ? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined, : undefined,
destEndpointName: destEndpointName:
@@ -114,8 +84,7 @@ export function useQueryProcessor(config: any) {
? selectorStore.currentDestPod && selectorStore.currentDestPod.value ? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined, : undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation") destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess && ? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
selectorStore.currentDestProcess.value
: undefined, : undefined,
}; };
if ([MetricQueryTypes.ReadRecords].includes(metricType)) { if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
@@ -123,15 +92,12 @@ export function useQueryProcessor(config: any) {
conditions[`condition${index}`] = { conditions[`condition${index}`] = {
name, name,
parentEntity: entity, parentEntity: entity,
topN: c.topN || 10, topN: Number(c.topN) || 10,
order: c.sortOrder || "DES", order: c.sortOrder || "DES",
}; };
} else { } else {
entity.scope = dashboardStore.entity;
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) { if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "") const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`); variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels; conditions[`labels${index}`] = labels;
} }
@@ -144,9 +110,10 @@ export function useQueryProcessor(config: any) {
} }
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) { if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`; return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
} else {
return `${name}${index}: ${metricType}(condition: $condition${index}, duration: $duration)${RespFields[metricType]}`;
} }
const t = metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
return `${name}${index}: ${t}(condition: $condition${index}, duration: $duration)${RespFields[t]}`;
}); });
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
@@ -156,12 +123,12 @@ export function useQueryProcessor(config: any) {
}; };
} }
export function useSourceProcessor( export function useSourceProcessor(
resp: { errors: string; data: { [key: string]: any } }, resp: { errors: string; data: Indexable },
config: { config: {
metrics: string[]; metrics: string[];
metricTypes: string[]; metricTypes: string[];
metricConfig: MetricConfigOpt[]; metricConfig: MetricConfigOpt[];
} },
) { ) {
if (resp.errors) { if (resp.errors) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
@@ -179,22 +146,15 @@ export function useSourceProcessor(
const c = (config.metricConfig && config.metricConfig[index]) || {}; const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) { if (type === MetricQueryTypes.ReadMetricsValues) {
source[c.label || m] = source[c.label || m] = (resp.data[keys[index]] && calculateExp(resp.data[keys[index]].values.values, c)) || [];
(resp.data[keys[index]] &&
calculateExp(resp.data[keys[index]].values.values, c)) ||
[];
} }
if (type === MetricQueryTypes.ReadLabeledMetricsValues) { if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || []; const resVal = Object.values(resp.data)[0] || [];
const labels = (c.label || "") const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
.split(",") const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (const item of resVal) { for (const item of resVal) {
const values = item.values.values.map((d: { value: number }) => const values = item.values.values.map((d: { value: number; isEmptyValue: boolean }) =>
aggregation(Number(d.value), c) d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
); );
const indexNum = labelsIdx.findIndex((d: string) => d === item.label); const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
if (labels[indexNum] && indexNum > -1) { if (labels[indexNum] && indexNum > -1) {
@@ -205,28 +165,23 @@ export function useSourceProcessor(
} }
} }
if (type === MetricQueryTypes.ReadMetricsValue) { if (type === MetricQueryTypes.ReadMetricsValue) {
source[m] = aggregation(Number(Object.values(resp.data)[0]), c); const v = Object.values(resp.data)[0] || {};
source[m] = v.isEmptyValue ? NaN : aggregation(Number(v.value), c);
} }
if ( if (
( (
[ [MetricQueryTypes.ReadRecords, MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics] as string[]
MetricQueryTypes.ReadRecords,
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
] as string[]
).includes(type) ).includes(type)
) { ) {
source[m] = (Object.values(resp.data)[0] || []).map( source[m] = (Object.values(resp.data)[0] || []).map((d: { value: unknown; name: string }) => {
(d: { value: unknown; name: string }) => { d.value = aggregation(Number(d.value), c);
d.value = aggregation(Number(d.value), c);
return d; return d;
} });
);
} }
if (type === MetricQueryTypes.READHEATMAP) { if (type === MetricQueryTypes.READHEATMAP) {
const resVal = Object.values(resp.data)[0] || {}; const resVal = Object.values(resp.data)[0] || {};
const nodes = [] as any; const nodes = [] as Indexable[];
if (!(resVal && resVal.values)) { if (!(resVal && resVal.values)) {
source[m] = { nodes: [] }; source[m] = { nodes: [] };
return; return;
@@ -236,14 +191,9 @@ export function useSourceProcessor(
nodes.push(...grids); nodes.push(...grids);
}); });
let buckets = [] as any; let buckets = [] as Indexable[];
if (resVal.buckets.length) { if (resVal.buckets.length) {
buckets = [ buckets = [resVal.buckets[0].min, ...resVal.buckets.map((item: { min: string; max: string }) => item.max)];
resVal.buckets[0].min,
...resVal.buckets.map(
(item: { min: string; max: string }) => item.max
),
];
} }
source[m] = { nodes, buckets }; // nodes: number[][] source[m] = { nodes, buckets }; // nodes: number[][]
@@ -254,13 +204,13 @@ export function useSourceProcessor(
} }
export function useQueryPodsMetrics( export function useQueryPodsMetrics(
pods: Array<Instance | Endpoint | Service | any>, pods: Array<(Instance | Endpoint | Service) & Indexable>,
config: { config: {
metrics: string[]; metrics: string[];
metricTypes: string[]; metricTypes: string[];
metricConfig: MetricConfigOpt[]; metricConfig: MetricConfigOpt[];
}, },
scope: string scope: string,
) { ) {
const metricTypes = (config.metricTypes || []).filter((m: string) => m); const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) { if (!metricTypes.length) {
@@ -277,40 +227,34 @@ export function useQueryPodsMetrics(
}; };
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
const currentService = selectorStore.currentService || {}; const currentService = selectorStore.currentService || {};
const fragmentList = pods.map( const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => {
( const param = {
d: (Instance | Endpoint | Service) & { normal: boolean }, serviceName: scope === "Service" ? d.label : currentService.label,
index: number serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
) => { endpointName: scope === "Endpoint" ? d.label : undefined,
const param = { normal: scope === "Service" ? d.normal : currentService.normal,
scope, };
serviceName: scope === "Service" ? d.label : currentService.label, const f = metrics.map((name: string, idx: number) => {
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined, const metricType = metricTypes[idx] || "";
endpointName: scope === "Endpoint" ? d.label : undefined, variables.push(`$condition${index}${idx}: MetricsCondition!`);
normal: scope === "Service" ? d.normal : currentService.normal, conditions[`condition${index}${idx}`] = {
name,
entity: param,
}; };
const f = metrics.map((name: string, idx: number) => { let labelStr = "";
const metricType = metricTypes[idx] || ""; if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
variables.push(`$condition${index}${idx}: MetricsCondition!`); const c = config.metricConfig[idx] || {};
conditions[`condition${index}${idx}`] = { variables.push(`$labels${index}${idx}: [String!]!`);
name, labelStr = `labels: $labels${index}${idx}, `;
entity: param, const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
}; conditions[`labels${index}${idx}`] = labels;
let labelStr = ""; }
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) { const t =
const c = config.metricConfig[idx] || {}; metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
variables.push(`$labels${index}${idx}: [String!]!`); return `${name}${index}${idx}: ${t}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[t]}`;
labelStr = `labels: $labels${index}${idx}, `; });
const labels = (c.labelsIndex || "") return f;
.split(",") });
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
conditions[`labels${index}${idx}`] = labels;
}
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[metricType]}`;
});
return f;
}
);
const fragment = fragmentList.flat(1).join(" "); const fragment = fragmentList.flat(1).join(" ");
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
@@ -319,13 +263,13 @@ export function useQueryPodsMetrics(
export function usePodsSource( export function usePodsSource(
pods: Array<Instance | Endpoint>, pods: Array<Instance | Endpoint>,
resp: { errors: string; data: { [key: string]: any } }, resp: { errors: string; data: Indexable },
config: { config: {
metrics: string[]; metrics: string[];
metricTypes: string[]; metricTypes: string[];
metricConfig: MetricConfigOpt[]; metricConfig: MetricConfigOpt[];
} },
): any { ): Indexable {
if (resp.errors) { if (resp.errors) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
return {}; return {};
@@ -333,12 +277,13 @@ export function usePodsSource(
const names: string[] = []; const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = []; const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = []; const metricTypesArr: string[] = [];
const data = pods.map((d: Instance | any, idx: number) => { const data = pods.map((d: any, idx: number) => {
config.metrics.map((name: string, index: number) => { config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {}; const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index; const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) { if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
d[name] = aggregation(resp.data[key], c); const v = resp.data[key];
d[name] = v.isEmptyValue ? NaN : aggregation(v.value, c);
if (idx === 0) { if (idx === 0) {
names.push(name); names.push(name);
metricConfigArr.push(c); metricConfigArr.push(c);
@@ -347,17 +292,11 @@ export function usePodsSource(
} }
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) { if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {}; d[name] = {};
if ( if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
[
Calculations.Average,
Calculations.ApdexAvg,
Calculations.PercentageAvg,
].includes(c.calculation)
) {
d[name]["avg"] = calculateExp(resp.data[key].values.values, c); d[name]["avg"] = calculateExp(resp.data[key].values.values, c);
} }
d[name]["values"] = resp.data[key].values.values.map( d[name]["values"] = resp.data[key].values.values.map((val: { value: number; isEmptyValue: boolean }) =>
(val: { value: number }) => aggregation(val.value, c) val.isEmptyValue ? NaN : aggregation(val.value, c),
); );
if (idx === 0) { if (idx === 0) {
names.push(name); names.push(name);
@@ -365,20 +304,14 @@ export function usePodsSource(
metricTypesArr.push(config.metricTypes[index]); metricTypesArr.push(config.metricTypes[index]);
} }
} }
if ( if (config.metricTypes[index] === MetricQueryTypes.ReadLabeledMetricsValues) {
config.metricTypes[index] === MetricQueryTypes.ReadLabeledMetricsValues
) {
const resVal = resp.data[key] || []; const resVal = resp.data[key] || [];
const labels = (c.label || "") const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
.split(",") const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (let i = 0; i < resVal.length; i++) { for (let i = 0; i < resVal.length; i++) {
const item = resVal[i]; const item = resVal[i];
const values = item.values.values.map((d: { value: number }) => const values = item.values.values.map((d: { value: number; isEmptyValue: boolean }) =>
aggregation(Number(d.value), c) d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
); );
const indexNum = labelsIdx.findIndex((d: string) => d === item.label); const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
let key = item.label; let key = item.label;
@@ -388,13 +321,7 @@ export function usePodsSource(
if (!d[key]) { if (!d[key]) {
d[key] = {}; d[key] = {};
} }
if ( if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
[
Calculations.Average,
Calculations.ApdexAvg,
Calculations.PercentageAvg,
].includes(c.calculation)
) {
d[key]["avg"] = calculateExp(item.values.values, c); d[key]["avg"] = calculateExp(item.values.values, c);
} }
d[key]["values"] = values; d[key]["values"] = values;
@@ -435,13 +362,12 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
return { queryStr, conditions }; return { queryStr, conditions };
} }
function calculateExp( export function calculateExp(
arr: { value: number }[], list: { value: number; isEmptyValue: boolean }[],
config: { calculation?: string } config: { calculation?: string },
): (number | string)[] { ): (number | string)[] {
const sum = arr const arr = list.filter((d: { value: number; isEmptyValue: boolean }) => !d.isEmptyValue);
.map((d: { value: number }) => d.value) const sum = arr.length ? arr.map((d: { value: number }) => Number(d.value)).reduce((a, b) => a + b) : 0;
.reduce((a, b) => a + b);
let data: (number | string)[] = []; let data: (number | string)[] = [];
switch (config.calculation) { switch (config.calculation) {
case Calculations.Average: case Calculations.Average:
@@ -454,16 +380,15 @@ function calculateExp(
data = [(sum / arr.length / 10000).toFixed(2)]; data = [(sum / arr.length / 10000).toFixed(2)];
break; break;
default: default:
data = arr.map((d) => aggregation(d.value, config)); data = list.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(d.value, config),
);
break; break;
} }
return data; return data;
} }
export function aggregation( export function aggregation(val: number, config: { calculation?: string }): number | string {
val: number,
config: { calculation?: string }
): number | string {
let data: number | string = Number(val); let data: number | string = Number(val);
switch (config.calculation) { switch (config.calculation) {
@@ -491,9 +416,6 @@ export function aggregation(
case Calculations.ConvertMilliseconds: case Calculations.ConvertMilliseconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss"); data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break; break;
case Calculations.Precision:
data = data.toFixed(2);
break;
case Calculations.MsToS: case Calculations.MsToS:
data = (val / 1000).toFixed(2); data = (val / 1000).toFixed(2);
break; break;
@@ -503,6 +425,9 @@ export function aggregation(
case Calculations.NanosecondToMillisecond: case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2); data = (val / 1000 / 1000).toFixed(2);
break; break;
case Calculations.ApdexAvg:
data = (val / 10000).toFixed(2);
break;
default: default:
data; data;
break; break;
@@ -510,28 +435,3 @@ export function aggregation(
return data; return data;
} }
export async function useGetMetricEntity(metric: string, metricType: any) {
if (!metric || !metricType) {
return;
}
let catalog = "";
const dashboardStore = useDashboardStore();
if (
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
MetricQueryTypes.ReadRecords,
].includes(metricType)
) {
const res = await dashboardStore.fetchMetricList(metric);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const c: string = res.data.metrics[0].catalog;
catalog = (MetricCatalog as any)[c];
}
return catalog;
}

View File

@@ -18,11 +18,7 @@ import { ref, watch } from "vue";
import { tryOnUnmounted } from "@vueuse/core"; import { tryOnUnmounted } from "@vueuse/core";
import { isFunction } from "@/utils/is"; import { isFunction } from "@/utils/is";
export function useTimeoutFn( export function useTimeoutFn(handle: Fn<any>, wait: number, native = false): any {
handle: Fn<any>,
wait: number,
native = false
): any {
if (!isFunction(handle)) { if (!isFunction(handle)) {
throw new Error("handle is not Function!"); throw new Error("handle is not Function!");
} }
@@ -36,13 +32,13 @@ export function useTimeoutFn(
(maturity) => { (maturity) => {
maturity && handle(); maturity && handle();
}, },
{ immediate: false } { immediate: false },
); );
} }
return { readyRef, stop, start }; return { readyRef, stop, start };
} }
export function useTimeoutRef(wait: number): any { export function useTimeoutRef(wait: number) {
const readyRef = ref(false); const readyRef = ref(false);
let timer: TimeoutHandle; let timer: TimeoutHandle;

View File

@@ -22,15 +22,15 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { AppMain, SideBar, NavBar } from "./components"; import { AppMain, SideBar, NavBar } from "./components";
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.app-wrapper { .app-wrapper {
height: 100%; height: 100%;
} }
.main-container { .main-container {
flex-grow: 2; flex-grow: 2;
height: 100%; height: 100%;
} }
</style> </style>

View File

@@ -22,8 +22,9 @@ limitations under the License. -->
</section> </section>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.app-main { .app-main {
height: calc(100% - 40px); height: calc(100% - 40px);
background: #f7f9fa; background: #f7f9fa;
} overflow: auto;
}
</style> </style>

View File

@@ -14,7 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="nav-bar flex-h"> <div class="nav-bar flex-h">
<div class="title">{{ appStore.pageTitle || t(pageName) }}</div> <el-breadcrumb
:separator-icon="ArrowRight"
class="title flex-h"
v-if="pathNames.length"
:style="{ '--el-text-color-placeholder': '#666' }"
>
<el-breadcrumb-item
v-for="(path, index) in pathNames"
:key="index"
:replace="true"
:to="{ path: getName(path).path || '' }"
>
<el-dropdown size="small" placement="bottom" :persistent="false">
<span class="cp name">{{ getName(path).name }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="setName(p)" v-for="(p, index) in path" :key="index">
<span>{{ p.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-breadcrumb-item>
</el-breadcrumb>
<div class="title" v-else>{{ pageTitle }}</div>
<div class="app-config"> <div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span> <span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<TimePicker <TimePicker
@@ -23,21 +47,12 @@ limitations under the License. -->
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
@input="changeTimeRange" @input="changeTimeRange"
/> />
<span> <span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
UTC{{ appStore.utcHour >= 0 ? "+" : ""
}}{{ `${appStore.utcHour}:${appStore.utcMin}` }}
</span>
<span title="refresh" class="ghost ml-5 cp" @click="handleReload"> <span title="refresh" class="ghost ml-5 cp" @click="handleReload">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" /> <Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
</span> </span>
<span class="version ml-5 cp"> <span class="version ml-5 cp">
<el-popover <el-popover trigger="hover" width="250" placement="bottom" effect="light" :content="appStore.version">
trigger="hover"
width="250"
placement="bottom"
effect="light"
:content="appStore.version"
>
<template #reference> <template #reference>
<span> <span>
<Icon iconName="info_outline" size="middle" /> <Icon iconName="info_outline" size="middle" />
@@ -49,92 +64,255 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat"; import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus"; import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "@/views/dashboard/data";
import type { DashboardItem } from "@/types/dashboard";
import router from "@/router";
import { ArrowRight } from "@element-plus/icons-vue";
const { t } = useI18n(); /*global Indexable */
const appStore = useAppStoreWithOut(); const { t, te } = useI18n();
const route = useRoute(); const appStore = useAppStoreWithOut();
const pageName = ref<string>(""); const dashboardStore = useDashboardStore();
const timeRange = ref<number>(0); const route = useRoute();
const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
const timeRange = ref<number>(0);
const pageTitle = ref<string>("");
resetDuration(); resetDuration();
getVersion(); getVersion();
const setConfig = (value: string) => { getNavPaths();
pageName.value = value || "";
};
function handleReload() { function getName(list: any[]) {
const gap = return list.find((d: any) => d.selected) || {};
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(dates));
}
function changeTimeRange(val: Date[] | any) {
timeRange.value =
val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
if (timeRange.value) {
return;
} }
appStore.setDuration(timeFormat(val));
}
setConfig(String(route.meta.title));
watch(
() => route.meta.title,
(title: unknown) => {
setConfig(String(title));
}
);
async function getVersion() {
const res = await appStore.fetchVersion();
if (res.errors) {
ElMessage.error(res.errors);
}
}
function resetDuration() {
const { duration }: any = route.params;
if (duration) {
const d = JSON.parse(duration);
appStore.updateDurationRow({ function setName(item: any) {
start: new Date(d.start), pathNames.value = pathNames.value.map((list: { path?: string; name: string; selected: boolean }[]) => {
end: new Date(d.end), const p = list.find((i: any) => i.entity === item.entity && item.layer === i.layer && i.name === item.name);
step: d.step, if (p) {
list = list.map((d: any) => {
d.selected = false;
if (d.entity === item.entity && item.layer === d.layer && d.name === item.name) {
d.selected = true;
}
return d;
});
}
return list;
}); });
appStore.updateUTC(d.utc); item.path && router.push(item.path);
} }
}
function handleReload() {
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(dates));
}
function changeTimeRange(val: Date[]) {
timeRange.value = val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
if (timeRange.value) {
return;
}
appStore.setDuration(timeFormat(val));
}
function getNavPaths() {
pathNames.value = [];
pageTitle.value = "";
const dashboard = dashboardStore.currentDashboard;
if (!(dashboard && dashboard.name)) {
updateNavTitle();
return;
}
const root =
dashboardStore.dashboards.filter((d: DashboardItem) => d.isRoot && dashboard.layer === d.layer)[0] || {};
for (const item of appStore.allMenus) {
if (item.subItems && item.subItems.length) {
for (const subItem of item.subItems) {
if (subItem.layer === root.layer) {
root.path = subItem.path;
}
}
} else {
if (item.layer === root.layer) {
root.path = item.path;
}
}
}
pathNames.value.push([{ ...root, selected: true }]);
if (dashboard.entity === MetricCatalog.ALL) {
return;
}
if (dashboard.entity === MetricCatalog.SERVICE) {
pathNames.value.push([
{
name: dashboard.name,
selected: true,
},
]);
return;
}
const serviceDashboards = dashboardStore.dashboards.filter(
(d: DashboardItem) => MetricCatalog.SERVICE === d.entity && dashboard.layer === d.layer,
);
if (!serviceDashboards.length) {
return;
}
const serviceId = route.params.serviceId;
const list = serviceDashboards.map((d: { path: string } & DashboardItem, index: number) => {
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
if (serviceId) {
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${d.name}`;
}
const selected = index === 0;
return {
...d,
path,
selected,
};
});
pathNames.value.push(list);
const podId = route.params.podId;
if (dashboard.entity === MetricCatalog.ENDPOINT_RELATION) {
const endpointDashboards = dashboardStore.dashboards.filter(
(d: DashboardItem) => MetricCatalog.ENDPOINT === d.entity && dashboard.layer === d.layer,
);
const list = endpointDashboards.map((d: { path: string } & DashboardItem, index: number) => {
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
if (podId) {
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${podId}/${d.name}`;
}
const selected = index === 0;
return {
...d,
path,
selected,
};
});
pathNames.value.push(list);
}
const destServiceId = route.params.destServiceId;
if (dashboard.entity === MetricCatalog.SERVICE_INSTANCE_RELATION) {
const serviceRelationDashboards = dashboardStore.dashboards.filter(
(d: DashboardItem) => MetricCatalog.SERVICE_RELATION === d.entity && dashboard.layer === d.layer,
);
const list = serviceRelationDashboards.map((d: { path: string } & DashboardItem, index: number) => {
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
if (destServiceId) {
path = `/dashboard/related/${d.layer}/${d.entity}/${serviceId}/${destServiceId}/${d.name}`;
}
const selected = index === 0;
return {
...d,
path,
selected,
};
});
pathNames.value.push(list);
}
if ([MetricCatalog.Process, MetricCatalog.PROCESS_RELATION].includes(dashboard.entity)) {
const InstanceDashboards = dashboardStore.dashboards.filter(
(d: DashboardItem) => MetricCatalog.SERVICE_INSTANCE === d.entity && dashboard.layer === d.layer,
);
const list = InstanceDashboards.map((d: { path: string } & DashboardItem, index: number) => {
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
if (podId) {
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${podId}/${d.name}`;
}
const selected = index === 0;
return {
...d,
path,
selected,
};
});
pathNames.value.push(list);
}
pathNames.value.push([
{
name: dashboard.name,
selected: true,
},
]);
}
async function getVersion() {
const res = await appStore.fetchVersion();
if (res.errors) {
ElMessage.error(res.errors);
}
}
function resetDuration() {
const { duration }: Indexable = route.params;
if (duration) {
const d = JSON.parse(duration);
appStore.updateDurationRow({
start: new Date(d.start),
end: new Date(d.end),
step: d.step,
});
appStore.updateUTC(d.utc);
}
}
function updateNavTitle() {
const key = String(route.meta.i18nKey);
pageTitle.value = te(key) ? t(key) : String(route.meta.title);
}
watch(
() => [dashboardStore.currentDashboard, route.name],
() => {
getNavPaths();
},
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-bar { .nav-bar {
padding: 5px 10px 5px 28px; padding: 5px;
text-align: left; text-align: left;
justify-content: space-between; justify-content: space-between;
background-color: #fafbfc; background-color: #fafbfc;
border-bottom: 1px solid #dfe4e8; border-bottom: 1px solid #dfe4e8;
color: #222; color: #222;
font-size: 12px; font-size: $font-size-smaller;
} }
.nav-bar.dark { .nav-bar.dark {
background-color: #333840; background-color: #333840;
border-bottom: 1px solid #252a2f; border-bottom: 1px solid #252a2f;
color: #fafbfc; color: #fafbfc;
} }
.title { .title {
font-size: 14px; font-size: $font-size-normal;
font-weight: 500; font-weight: 500;
height: 28px; }
line-height: 28px;
}
.nav-tabs { .nav-tabs {
padding: 10px; padding: 10px;
} }
.name {
display: inline-block;
max-width: 250px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
</style> </style>

View File

@@ -13,190 +13,194 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="side-bar"> <div class="side-bar" v-if="showMenu" @click="isCollapse = false" @mouseleave="closeMenu">
<div :class="isCollapse ? 'logo-icon-collapse' : 'logo-icon'"> <div :class="isCollapse ? 'logo-icon-collapse' : 'logo-icon'">
<Icon <Icon :size="isCollapse ? 'xl' : 'logo'" :iconName="isCollapse ? 'logo' : 'logo-sw'" />
:size="isCollapse ? 'xl' : 'logo'"
:iconName="isCollapse ? 'logo' : 'logo-sw'"
/>
</div> </div>
<el-menu <div class="menu scroll_bar_dark" :style="isCollapse ? {} : { width: '220px' }">
active-text-color="#448dfe" <el-menu
background-color="#252a2f" active-text-color="#448dfe"
class="el-menu-vertical" background-color="#252a2f"
:default-active="name" class="el-menu-vertical"
text-color="#efefef" :default-active="name"
:unique-opened="true" text-color="#efefef"
:collapse="isCollapse" :collapse="isCollapse"
:style="{ border: 'none' }" :collapse-transition="false"
> :style="{ border: 'none' }"
<template v-for="(menu, index) in routes" :key="index"> >
<el-sub-menu :index="String(menu.name)" v-if="menu.meta.hasGroup"> <template v-for="(menu, index) in routes" :key="index">
<template #title> <el-sub-menu :index="String(menu.name)" v-if="menu.meta.hasGroup" popper-class="sub-list">
<router-link class="items" :to="menu.path"> <template #title>
<el-icon class="menu-icons" :style="{ marginRight: '12px' }"> <router-link class="items" :to="menu.path">
<Icon size="lg" :iconName="menu.meta.icon" /> <el-icon class="menu-icons" :style="{ marginRight: '12px' }" @mouseover="setCollapse">
</el-icon> <Icon size="lg" :iconName="menu.meta.icon" />
<span class="title" :class="isCollapse ? 'collapse' : ''"> </el-icon>
{{ t(menu.meta.title) }} <span class="title" :class="isCollapse ? 'collapse' : ''">
</span> {{ te(menu.meta.i18nKey) ? t(menu.meta.i18nKey) : menu.meta.title }}
</router-link> </span>
</template>
<el-menu-item-group>
<el-menu-item
v-for="(m, idx) in filterMenus(menu.children)"
:index="m.name"
:key="idx"
>
<router-link class="items" :to="m.path">
<span class="title">{{ m.meta && t(m.meta.title) }}</span>
</router-link> </router-link>
</el-menu-item> </template>
</el-menu-item-group> <el-menu-item-group>
</el-sub-menu> <el-menu-item v-for="(m, idx) in filterMenus(menu.children)" :index="m.name" :key="idx">
<el-menu-item <router-link class="items" :to="m.path">
:index="String(menu.name)" <span class="title">
@click="changePage(menu)" {{ m.meta && (te(m.meta.i18nKey) ? t(m.meta.i18nKey) : m.meta.title) }}
v-else </span>
> </router-link>
<el-icon class="menu-icons" :style="{ marginRight: '12px' }"> </el-menu-item>
<router-link class="items" :to="menu.children[0].path"> </el-menu-item-group>
<Icon size="lg" :iconName="menu.meta.icon" /> </el-sub-menu>
</router-link> <el-menu-item :index="String(menu.name)" @click="changePage(menu)" v-else>
</el-icon> <el-icon class="menu-icons" :style="{ marginRight: '12px' }" @mouseover="setCollapse">
<template #title> <router-link class="items menu-title" :to="menu.children[0].path">
<router-link class="items" :to="menu.children[0].path"> <Icon size="lg" :iconName="menu.meta.icon" />
<span class="title">{{ t(menu.meta.title) }}</span> </router-link>
</router-link> </el-icon>
</template> <template #title>
</el-menu-item> <router-link class="items menu-title" :to="menu.children[0].path">
</template> <span class="title">
</el-menu> {{ te(menu.meta.i18nKey) ? t(menu.meta.i18nKey) : menu.meta.title }}
<div </span>
class="menu-control" </router-link>
:class="isCollapse ? 'collapse' : ''" </template>
:style="{ </el-menu-item>
color: theme === 'light' ? '#eee' : '#252a2f', </template>
}" </el-menu>
>
<Icon
size="middle"
iconName="format_indent_decrease"
@click="controlMenu"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref, watch } from "vue";
import { useRouter, RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import { useI18n } from "vue-i18n"; import { useRouter, useRoute } from "vue-router";
import Icon from "@/components/Icon.vue"; import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app"; import Icon from "@/components/Icon.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
const appStore = useAppStoreWithOut(); /*global Recordable*/
const { t } = useI18n(); const { t, te } = useI18n();
const name = ref<string>(String(useRouter().currentRoute.value.name)); const appStore = useAppStoreWithOut();
const theme = ["VirtualMachine", "Kubernetes"].includes(name.value || "") const router = useRouter();
? ref("light") const name = ref<string>(String(router.currentRoute.value.name));
: ref("black"); const theme = ["VirtualMachine", "Kubernetes"].includes(name.value || "") ? ref("light") : ref("black");
const routes = ref<RouteRecordRaw[] | any>(useRouter().options.routes); const routes = ref<RouteRecordRaw[] | any>(
if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) { (router.options.routes || []).filter((d: any) => d.meta && d.meta.activate),
appStore.setIsMobile(true); );
} else { const route = useRoute();
appStore.setIsMobile(false); const isCollapse = ref(true);
} const showMenu = ref(true);
const isCollapse = ref(false); const open = ref<boolean>(false);
const controlMenu = () => {
isCollapse.value = !isCollapse.value; if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
}; appStore.setIsMobile(true);
const changePage = (menu: RouteRecordRaw) => { } else {
theme.value = ["VirtualMachine", "Kubernetes"].includes(String(menu.name)) appStore.setIsMobile(false);
? "light" }
: "black"; if (route.name === "ViewWidget") {
}; showMenu.value = false;
const filterMenus = (menus: any[]) => { }
return menus.filter((d) => d.meta && !d.meta.notShow); const changePage = (menu: RouteRecordRaw) => {
}; theme.value = ["VirtualMachine", "Kubernetes"].includes(String(menu.name)) ? "light" : "black";
};
const filterMenus = (menus: Recordable[]) => {
return menus.filter((d) => d.meta && !d.meta.notShow && d.meta.activate);
};
function setCollapse() {
open.value = true;
setTimeout(() => {
if (open.value) {
isCollapse.value = false;
}
open.value = false;
}, 1000);
}
function closeMenu() {
isCollapse.value = true;
open.value = false;
}
watch(
() => route.name,
() => {
name.value = String(route.name);
},
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.side-bar { .side-bar {
background: #252a2f; background: #252a2f;
height: 100%; height: 100%;
margin-bottom: 100px; margin-bottom: 180px;
overflow-y: auto; }
overflow-x: hidden;
}
.el-menu-vertical:not(.el-menu--collapse) { .menu {
width: 220px; height: calc(100% - 30px);
font-size: 16px; overflow: hidden;
} }
.logo-icon-collapse { .menu:hover {
width: 65px; overflow-y: auto;
margin: 15px 0 10px 0; }
text-align: center;
}
span.collapse { .el-menu-vertical:not(.el-menu--collapse) {
height: 0; width: 220px;
width: 0; font-size: $font-size-normal;
overflow: hidden; }
visibility: hidden;
display: inline-block;
}
.logo-icon { .logo-icon-collapse {
margin: 15px 0 10px 20px; width: 65px;
width: 110px; margin: 5px 0 10px;
} text-align: center;
}
.menu-control { span.collapse {
position: absolute; height: 0;
top: 7px; width: 0;
left: 220px; overflow: hidden;
cursor: pointer; visibility: hidden;
transition: all 0.2s linear; display: inline-block;
z-index: 99; }
color: #252a2f;
}
.menu-control.collapse { .logo-icon {
left: 70px; margin: 15px 0 10px 20px;
} width: 110px;
}
.el-icon.el-sub-menu__icon-arrow { .menu-control.collapse {
height: 12px; left: 70px;
} }
.items { .el-icon.el-sub-menu__icon-arrow {
display: inline-block; height: 12px;
width: 100%; }
}
.version { .items {
color: #eee; display: inline-block;
font-size: 12px; width: 100%;
cursor: pointer; }
padding-left: 23px;
margin-bottom: 10px;
position: absolute;
bottom: 0;
left: 10px;
}
.empty { .version {
width: 100%; color: #eee;
height: 60px; font-size: $font-size-smaller;
} cursor: pointer;
padding-left: 23px;
margin-bottom: 10px;
position: absolute;
bottom: 0;
left: 10px;
}
.title { .empty {
display: inline-block; width: 100%;
max-width: 110px; height: 60px;
text-overflow: ellipsis; }
overflow: hidden;
} .title {
display: inline-block;
max-width: 200px;
text-overflow: ellipsis;
overflow: hidden;
}
</style> </style>

View File

@@ -32,6 +32,7 @@ if (!savedLanguage) {
} }
language = savedLanguage ? savedLanguage : language; language = savedLanguage ? savedLanguage : language;
const i18n = createI18n({ const i18n = createI18n({
legacy: false,
locale: language, locale: language,
messages, messages,
}); });

View File

@@ -14,15 +14,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import enTitles from "./menus/en";
const msg = { const msg = {
general: "General Service", ...enTitles,
services: "Services", services: "Services",
service: "Service", service: "Service",
traces: "Traces", traces: "Traces",
metrics: "Metrics", metrics: "Metrics",
serviceMesh: "Service Mesh",
infrastructure: "Infrastructure",
virtualMachine: "Virtual Machine",
dashboardNew: "New Dashboard", dashboardNew: "New Dashboard",
dashboardList: "Dashboard List", dashboardList: "Dashboard List",
logs: "Logs", logs: "Logs",
@@ -32,15 +30,12 @@ const msg = {
dashboards: "Dashboards", dashboards: "Dashboards",
profiles: "Profiles", profiles: "Profiles",
database: "Database", database: "Database",
mySQL: "MySQL", mySQL: "MySQL/MariaDB",
serviceName: "Service Name", serviceName: "Service Name",
technologies: "Technologies", technologies: "Technologies",
generalServicePanel: "General Service Panel",
health: "Health", health: "Health",
groupName: "Group Name", groupName: "Group Name",
topologies: "Topologies", topologies: "Topologies",
dataPanel: "Data Plane",
controlPanel: "Control Plane",
eventList: "Event List", eventList: "Event List",
newDashboard: "Create a new dashboard", newDashboard: "Create a new dashboard",
dashboardEdit: "Edit the dashboard", dashboardEdit: "Edit the dashboard",
@@ -117,20 +112,9 @@ const msg = {
rename: "Rename", rename: "Rename",
deleteTitle: "Are you sure you want to delete this?", deleteTitle: "Are you sure you want to delete this?",
rootTitle: "Are you sure you want to set this?", rootTitle: "Are you sure you want to set this?",
selfObservability: "Self Observability",
satellite: "Satellite",
skyWalkingServer: "SkyWalking Server",
functions: "Functions",
browser: "Browser",
linux: "Linux",
editWarning: "You are entering edit mode", editWarning: "You are entering edit mode",
viewWarning: "You are entering view mode", viewWarning: "You are entering view mode",
virtualDatabase: "Virtual Database",
virtualCache: "Virtual Cache",
reloadDashboards: "Reload dashboards", reloadDashboards: "Reload dashboards",
kubernetesService: "Service",
kubernetesCluster: "Cluster",
kubernetes: "Kubernetes",
textUrl: "Text Hyperlink", textUrl: "Text Hyperlink",
textAlign: "Text Align", textAlign: "Text Align",
metricLabel: "Metric Label", metricLabel: "Metric Label",
@@ -156,9 +140,7 @@ const msg = {
enableAssociate: "Enable association", enableAssociate: "Enable association",
text: "Text", text: "Text",
query: "Query", query: "Query",
postgreSQL: "PostgreSQL",
endpointTips: "The table shows up to 20 pieces of endpoints.", endpointTips: "The table shows up to 20 pieces of endpoints.",
apisix: "APISIX",
viewTrace: "View Related Traces", viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options", relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics", setLatencyDuration: "Latency Related Metrics",
@@ -176,11 +158,16 @@ const msg = {
toTheRight: "To The Right", toTheRight: "To The Right",
legendValues: "Legend Values", legendValues: "Legend Values",
minDuration: "Minimal Request Duration", minDuration: "Minimal Request Duration",
when4xx: when4xx: "Sample HTTP requests and responses with tracing when response code between 400 and 499",
"Sample HTTP requests and responses with tracing when response code between 400 and 499", when5xx: "Sample HTTP requests and responses with tracing when response code between 500 and 599",
when5xx:
"Sample HTTP requests and responses with tracing when response code between 500 and 599",
taskTitle: "HTTP request and response collecting rules", taskTitle: "HTTP request and response collecting rules",
iframeWidgetTip: "Add a link to a widget",
iframeSrc: "Iframe Link",
generateLink: "Generate Link",
setDuration: "Lock Query Duration",
openFunction: "OpenFunction",
period: "Period",
windows: "Windows",
seconds: "Seconds", seconds: "Seconds",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
@@ -199,7 +186,7 @@ const msg = {
topology: "Topology", topology: "Topology",
trace: "Trace", trace: "Trace",
alarm: "Alerting", alarm: "Alerting",
auto: "Auto", auto: "Auto Fresh",
reload: "Reload", reload: "Reload",
version: "Version", version: "Version",
copy: "Copy", copy: "Copy",
@@ -277,7 +264,6 @@ const msg = {
aggregation: "Calculation", aggregation: "Calculation",
unit: "Unit", unit: "Unit",
labelsIndex: "Label Subscript", labelsIndex: "Label Subscript",
group: "Service Group",
browserView: "Browser", browserView: "Browser",
sortOrder: "Sort Order", sortOrder: "Sort Order",
chartType: "Chart Type", chartType: "Chart Type",
@@ -315,10 +301,10 @@ const msg = {
viewLogs: "View Logs", viewLogs: "View Logs",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable. logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`, Check more details on the Configuration Vocabulary page`,
keywordsOfContentLogTips: keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.",
"Current storage of SkyWalking OAP server does not support this.",
setEvent: "Set Event", setEvent: "Set Event",
viewAttributes: "View", viewAttributes: "View",
attributes: "Attributes",
serviceEvents: "Service Events", serviceEvents: "Service Events",
select: "Select", select: "Select",
eventID: "Event ID", eventID: "Event ID",
@@ -370,10 +356,31 @@ const msg = {
addKeywordsOfContent: "Please input a keyword of content", addKeywordsOfContent: "Please input a keyword of content",
addExcludingKeywordsOfContent: "Please input a keyword of excluding content", addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
noticeTag: "Please press Enter after inputting a tag(key=value).", noticeTag: "Please press Enter after inputting a tag(key=value).",
conditionNotice: conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language", language: "Language",
gateway: "Gateway", save: "Save",
virtualMQ: "Virtual MQ", editStrategy: "Edit Policies",
policyList: "Policy List",
targetTypes: "Target Type",
monitorType: "Monitor Type",
count: "Count",
threshold: "Threshold",
uriRegex: "URI Regex",
uriList: "URI List",
processes: "Processes",
monitorInstances: "Monitoring Instances",
processDashboards: "Process Dashboards",
instanceDashboards: "Instance Dashboards",
detailLabel: "Detail Label",
summary: "Summary",
detail: "Detail",
marketplace: "Marketplace",
menus: "Menus",
saveReload: "Save and reload the page",
document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions",
expressions: "Expression",
unhealthyExpression: "Unhealthy Expression",
}; };
export default msg; export default msg;

View File

@@ -14,15 +14,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import esTitles from "./menus/es";
const msg = { const msg = {
...esTitles,
general: "Servicio General", general: "Servicio General",
services: "Servicios", services: "Servicios",
service: "Servicio", service: "Servicio",
traces: "Trazas", traces: "Trazas",
metrics: "Métricas", metrics: "Métricas",
serviceMesh: "Malla de Servicios",
infrastructure: "Infraestructura",
virtualMachine: "Máquina Virtual",
dashboardNew: "Nuevo Panel", dashboardNew: "Nuevo Panel",
dashboardList: "Listado Paneles", dashboardList: "Listado Paneles",
logs: "Logs", logs: "Logs",
@@ -32,15 +31,12 @@ const msg = {
dashboards: "Paneles", dashboards: "Paneles",
profiles: "Perfiles", profiles: "Perfiles",
database: "Base de Datos", database: "Base de Datos",
mySQL: "MySQL", mySQL: "MySQL/MariaDB",
serviceName: "Nombre Servicio", serviceName: "Nombre Servicio",
technologies: "Tecnologías", technologies: "Tecnologías",
generalServicePanel: "Panel Servicio General",
health: "Salud", health: "Salud",
groupName: "Nombre Grupo", groupName: "Nombre Grupo",
topologies: "Topologías", topologies: "Topologías",
dataPanel: "Plano de Datos",
controlPanel: "Plano de Control",
eventList: "Listado Eventos", eventList: "Listado Eventos",
newDashboard: "Crear panel nuevo", newDashboard: "Crear panel nuevo",
dashboardEdit: "Editar el panel", dashboardEdit: "Editar el panel",
@@ -77,10 +73,8 @@ const msg = {
editGraph: "Editar Opciones", editGraph: "Editar Opciones",
dashboardName: "Selecciona Nombre del Panel", dashboardName: "Selecciona Nombre del Panel",
linkDashboard: "Nombre del panel relacionado con llamadas de la topología", linkDashboard: "Nombre del panel relacionado con llamadas de la topología",
linkServerMetrics: linkServerMetrics: "Métricas de servidor relacionadas con llamadas de la topología",
"Métricas de servidor relacionadas con llamadas de la topología", linkClientMetrics: "Métricas de cliente relacionadas con llamadas de la topología",
linkClientMetrics:
"Métricas de cliente relacionadas con llamadas de la topología",
nodeDashboard: "Nombre del panel relacionado con nodos de la topología", nodeDashboard: "Nombre del panel relacionado con nodos de la topología",
nodeMetrics: "Mêtricas relacionas con nodos de la topología", nodeMetrics: "Mêtricas relacionas con nodos de la topología",
instanceDashboard: "Nombre del panel relacionado con instancias de servicio", instanceDashboard: "Nombre del panel relacionado con instancias de servicio",
@@ -119,20 +113,9 @@ const msg = {
rename: "Renombrar", rename: "Renombrar",
deleteTitle: "¿Está seguro que quiere eliminarlo?", deleteTitle: "¿Está seguro que quiere eliminarlo?",
rootTitle: "¿Está seguro que quiere establecerlo?", rootTitle: "¿Está seguro que quiere establecerlo?",
selfObservability: "Autoobservabilidad",
satellite: "Satéllite",
skyWalkingServer: "Servidor SkyWalking",
functions: "Funciones",
browser: "Navegador",
linux: "Linux",
editWarning: "Estás entrando en modo edición", editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización", viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual",
virtualCache: "Caché virtual",
reloadDashboards: "Recargar Panel", reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio",
kubernetesCluster: "Cluster",
kubernetes: "Kubernetes",
textUrl: "Hipervínculo de Texto", textUrl: "Hipervínculo de Texto",
textAlign: "Alineación de Texto", textAlign: "Alineación de Texto",
metricLabel: "Etiqueta de Métrica", metricLabel: "Etiqueta de Métrica",
@@ -156,14 +139,17 @@ const msg = {
"El nombre sólo admite chino e inglés, líneas horizontales y subrayado, y la longitud del nombre no excederá de 300 caracteres", "El nombre sólo admite chino e inglés, líneas horizontales y subrayado, y la longitud del nombre no excederá de 300 caracteres",
enableAssociate: "Activar asociación", enableAssociate: "Activar asociación",
query: "Consulta", query: "Consulta",
postgreSQL: "PostgreSQL",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.", endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
apisix: "APISIX",
queryOrder: "Consulta por duración", queryOrder: "Consulta por duración",
setOrder: "Orden de consulta", setOrder: "Orden de consulta",
latency: "Retraso", latency: "Retraso",
metricValues: "Valor métrico", metricValues: "Valor métrico",
legendValues: "Valor de la leyenda", legendValues: "Valor de la leyenda",
iframeWidgetTip: "Añadir enlaces a los gadgets",
iframeSrc: "Enlace Iframe",
generateLink: "Generar enlaces",
setDuration: "Duración de la consulta de bloqueo",
openFunction: "OpenFunction",
seconds: "Segundos", seconds: "Segundos",
hourTip: "Seleccione Hora", hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto", minuteTip: "Seleccione Minuto",
@@ -180,11 +166,11 @@ const msg = {
asTable: "Como tabla", asTable: "Como tabla",
toTheRight: "Derecha", toTheRight: "Derecha",
minDuration: "Duración mínima de la solicitud", minDuration: "Duración mínima de la solicitud",
when4xx: when4xx: "Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 400 y 499",
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 400 y 499", when5xx: "Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 500 y 599",
when5xx:
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 500 y 599",
taskTitle: "Reglas de recolección de peticiones y respuestas HTTP", taskTitle: "Reglas de recolección de peticiones y respuestas HTTP",
period: "Period",
windows: "Windows",
second: "s", second: "s",
yearSuffix: "Año", yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic", monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
@@ -199,7 +185,7 @@ const msg = {
topology: "Topología", topology: "Topología",
trace: "Traza", trace: "Traza",
alarm: "Recordatorio en curso", alarm: "Recordatorio en curso",
auto: "Auto", auto: "Auto Fresh",
reload: "Recargar", reload: "Recargar",
version: "Versión", version: "Versión",
copy: "Copiar", copy: "Copiar",
@@ -277,7 +263,6 @@ const msg = {
aggregation: "Cálculo", aggregation: "Cálculo",
unit: "Unidad", unit: "Unidad",
labelsIndex: "Subíndice Etiqueta", labelsIndex: "Subíndice Etiqueta",
group: "Grupo Servicio",
browserView: "Navegador", browserView: "Navegador",
sortOrder: "Orden de clasificación", sortOrder: "Orden de clasificación",
chartType: "Tipo Gráfico", chartType: "Tipo Gráfico",
@@ -315,8 +300,7 @@ const msg = {
viewLogs: "Ver Registro de Datos", viewLogs: "Ver Registro de Datos",
logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas. logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`, Más información en la página de Vocabulario de Configuración`,
keywordsOfContentLogTips: keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
"El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
setEvent: "Establecer Evento", setEvent: "Establecer Evento",
viewAttributes: "Ver", viewAttributes: "Ver",
serviceEvents: "Eventos Servico", serviceEvents: "Eventos Servico",
@@ -347,8 +331,7 @@ const msg = {
destEndpoint: "Endpoint Destinación", destEndpoint: "Endpoint Destinación",
eventSource: "Fuente Envento", eventSource: "Fuente Envento",
modalTitle: "Inspección", modalTitle: "Inspección",
selectRedirectPage: selectRedirectPage: "Quiere inspeccionar las Trazas or Registros de datos del servicio %s?",
"Quiere inspeccionar las Trazas or Registros de datos del servicio %s?",
logAnalysis: "Lenguaje de Análisis de Registro de Datos", logAnalysis: "Lenguaje de Análisis de Registro de Datos",
logDataBody: "Contenido del Registro de Datos", logDataBody: "Contenido del Registro de Datos",
addType: "Por favor introduzca un tipo", addType: "Por favor introduzca un tipo",
@@ -369,14 +352,35 @@ const msg = {
addTraceID: "Por favor introduzca el ID de la traza", addTraceID: "Por favor introduzca el ID de la traza",
addTags: "Por favor introduzaca una etiqueta", addTags: "Por favor introduzaca una etiqueta",
addKeywordsOfContent: "Por favor introduzca una clave de contenido", addKeywordsOfContent: "Por favor introduzca una clave de contenido",
addExcludingKeywordsOfContent: addExcludingKeywordsOfContent: "Por favor introduzca una clave excluyente de contenido",
"Por favor introduzca una clave excluyente de contenido", noticeTag: "Por favor presione Intro después de introducir una etiqueta(clave=valor).",
noticeTag:
"Por favor presione Intro después de introducir una etiqueta(clave=valor).",
conditionNotice: conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).", "Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje", language: "Lenguaje",
gateway: "Puerta", save: "Salvar",
virtualMQ: "MQ virtual", editStrategy: "Estrategia editorial",
policyList: "Lista de políticas",
targetTypes: "Tipo de objetivo",
monitorType: "Tipo de Monitor",
count: "Contar",
threshold: "Umbral",
uriRegex: "Lista URI",
uriList: "Lista URI",
processes: "Proceso",
attributes: "Atributos",
monitorInstances: "Ejemplo de Monitor",
processDashboards: "Tablero de proceso",
instanceDashboards: "Tablero de ejemplo",
detailLabel: "Detail Label",
summary: "Summary",
detail: "Detail",
marketplace: "Marketplace",
menus: "Menus",
saveReload: "Save and reload the page",
document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions",
expressions: "Expression",
unhealthyExpression: "Unhealthy Expression",
}; };
export default msg; export default msg;

View File

@@ -0,0 +1,124 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const titles = {
// General Service
general_service: "General Service",
general_service_desc:
"Observe services and relative direct dependencies through telemetry data collected from SkyWalking Agents.",
general_service_services: "Services",
general_service_services_desc: "Observe services through telemetry data collected from SkyWalking Agent.",
general_service_virtual_database: "Visual Database",
general_service_virtual_database_desc:
"Observe the virtual databases which are conjectured by language agents through various plugins.",
general_service_virtual_cache: "Visual Cache",
general_service_virtual_cache_desc:
"Observe the virtual cache servers which are conjectured by language agents through various plugins.",
general_service_virtual_mq: "Virtual MQ",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Service Mesh
service_mesh: "Service Mesh",
service_mesh_desc:
"Service Mesh(Istio) addresses the challenges developers and operators face with a distributed or microservices architecture.",
service_mesh_service: "Services",
service_mesh_service_desc:
"Observe Service Mesh through telemetry data collected from Envoy Access Log Service (ALS).",
service_mesh_control_plane: "Control Plane",
service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.",
service_mesh_data_plane: "Data Plane",
service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.",
// Functions
functions: "Functions",
functions_desc:
"FaaS (Function-as-a-Service) is a type of cloud-computing service that allows you to execute code in response to events without the complex infrastructure typically associated with building and launching microservices applications.",
functions_openfunction: "OpenFunction",
functions_openfunction_desc:
"OpenFunction as a FaaS platform, provides out-of-box observability with SkyWalking integration.",
// Kubernetes
kubernetes: "Kubernetes",
kubernetes_desc:
"Kubernetes is an open-source container orchestration system for automating software deployment, scaling, and management.",
kubernetes_cluster: "Cluster",
kubernetes_cluster_desc: "Provide monitoring of the status and resources of the K8S Cluster.",
kubernetes_service: "Service",
kubernetes_service_desc: "Observe Service status and resources from Kubernetes.",
// Infrastructure
infrastructure: "Infrastructure",
infrastructure_desc:
"Operation Systems act as the infrastructure of the whole IT system. Its observabilities provide the fundamentals for all distributed and modern complex systems running.",
infrastructure_linux: "Linux",
infrastructure_linux_desc: "Provide Linux Operation System(OS) monitoring.",
infrastructure_windows: "Windows",
infrastructure_windows_desc: "Provide Windows Operation System(OS) monitoring.",
// AWS Cloud
aws_cloud: "AWS Cloud",
aws_cloud_desc: "Amazon Web Services(AWS) offers reliable, scalable, and inexpensive cloud computing services.",
aws_cloud_eks: "EKS",
aws_cloud_eks_desc: "Provide AWS Cloud EKS monitoring through AWS Container Insights Receiver.",
aws_cloud_s3: "S3",
aws_cloud_s3_desc: "Provide AWS Cloud S3 monitoring through AWS FireHose Receiver.",
aws_cloud_dynamodb: "DynamoDB",
aws_cloud_dynamodb_desc: "Provide DynamoDB monitoring through AWS FireHose Receiver.",
aws_cloud_api_gateway: "API Gateway",
aws_cloud_api_gateway_desc: "Provide AWS Cloud API Gateway monitoring through AWS FireHose Receiver.",
// Browser
browser: "Browser",
browser_desc: "Provide Browser-Side monitoring of Web-App, Versions and Pages, through Apache SkyWalking Client JS.",
// Gateway
gateway: "Gateway",
gateway_desc:
"API gateway is an API management tool that sits between a client and a collection of backend services.",
gateway_apisix: "APISIX",
gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_aws_api_gateway: "AWS API Gateway",
gateway_aws_api_gateway_desc: "Provide AWS Cloud API Gateway monitoring through AWS FireHose Receiver.",
// Database
database: "Database",
database_desc:
"The database is the organized collection of structured information, or data, typically stored electronically in a computer system.",
database_mysql_mariadb: "MySQL/MariaDB",
database_mysql_mariadb_desc:
"Provide MySQL and MariaDB Server monitoring through OpenTelemetry's Prometheus Receiver.",
database_postgresql: "PostgreSQL",
database_postgresql_desc: "Provide PostgreSQL monitoring through OpenTelemetry's Prometheus Receiver.",
database_dynamodb: "DynamoDB",
database_dynamodb_desc: "Provide DynamoDB monitoring through Amazon CloudWatch.",
database_redis: "Redis",
database_redis_desc: "Provide Redis monitoring through OpenTelemetry's Prometheus Receiver.",
database_elasticsearch: "Elasticsearch",
database_elasticsearch_desc: "Provide Elasticsearch Server monitoring through OpenTelemetry's Prometheus Receiver.",
database_mongodb: "MongoDB",
database_mongodb_desc: "Provide MongoDB monitoring through OpenTelemetry's Prometheus Receiver.",
// Message Queue
mq: "Message Queue",
mq_desc:
"A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.",
mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability
self_observability: "Self Observability",
self_observability_desc:
"Self Observability provides the observabilities for running components and servers from the SkyWalking ecosystem.",
self_observability_oap: "SkyWalking Server",
self_observability_oap_desc:
"The OAP backend cluster itself is a distributed streaming process system, this is the monitoring for the OAP backend itself.",
self_observability_satellite: "Satellite",
self_observability_satellite_desc:
"Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.",
};
export default titles;

View File

@@ -0,0 +1,124 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const titles = {
// General Service
general_service: "Servicio General",
general_service_desc:
"Observar el servicio y las dependencias relativamente directas a través de los datos de telemetría recopilados del agente skywalking.",
general_service_services: "Servicios",
general_service_services_desc: "Observe services through telemetry data collected from SkyWalking Agent.",
general_service_virtual_database: "Base de Datos Virtual",
general_service_virtual_database_desc:
"Observe the virtual databases which are conjectured by language agents through various plugins.",
general_service_virtual_cache: "Caché virtual",
general_service_virtual_cache_desc:
"Observe the virtual cache servers which are conjectured by language agents through various plugins.",
general_service_virtual_mq: "MQ virtual",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Service Mesh
service_mesh: "Malla de Servicios",
service_mesh_desc:
"La red de servicios (istio) resuelve los desafíos que enfrentan los desarrolladores y operadores a través de una arquitectura distribuida o de microservicio.",
service_mesh_service: "Servicios",
service_mesh_service_desc:
"Observe Service Mesh through telemetry data collected from Envoy Access Log Service (ALS).",
service_mesh_control_plane: "Plano de Control",
service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.",
service_mesh_data_plane: "Plano de Datos",
service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.",
// Functions
functions: "Funciones",
functions_desc:
"FaaS (Function-as-a-Service) is a type of cloud-computing service that allows you to execute code in response to events without the complex infrastructure typically associated with building and launching microservices applications.",
functions_openfunction: "OpenFunction",
functions_openfunction_desc:
"OpenFunction as a FaaS platform, provides out-of-box observability with SkyWalking integration.",
// Kubernetes
kubernetes: "Kubernetes",
kubernetes_desc:
"Kubernetes is an open-source container orchestration system for automating software deployment, scaling, and management.",
kubernetes_cluster: "Cluster",
kubernetes_cluster_desc: "Provide monitoring of the status and resources of the K8S Cluster.",
kubernetes_service: "Servicio",
kubernetes_service_desc: "Observe Service status and resources from Kubernetes.",
// Infrastructure
infrastructure: "Infraestructura",
infrastructure_desc:
"Operation Systems act as the infrastructure of the whole IT system. Its observabilities provide the fundamentals for all distributed and modern complex systems running.",
infrastructure_linux: "Linux",
infrastructure_linux_desc: "Provide Linux Operation System(OS) monitoring.",
infrastructure_windows: "Windows",
infrastructure_windows_desc: "Provide Windows Operation System(OS) monitoring.",
// AWS Cloud
aws_cloud: "AWS Cloud",
aws_cloud_desc: "Amazon Web Services(AWS) offers reliable, scalable, and inexpensive cloud computing services.",
aws_cloud_eks: "EKS",
aws_cloud_eks_desc: "Provide AWS Cloud EKS monitoring through AWS Container Insights Receiver.",
aws_cloud_s3: "S3",
aws_cloud_s3_desc: "Provide AWS Cloud S3 monitoring through AWS FireHose Receiver.",
aws_cloud_dynamodb: "DynamoDB",
aws_cloud_dynamodb_desc: "Provide DynamoDB monitoring through Amazon CloudWatch.",
aws_cloud_api_gateway: "API Gateway",
aws_cloud_api_gateway_desc: "Provide AWS Cloud API Gateway monitoring through AWS FireHose Receiver.",
// Browser
browser: "Navegador",
browser_desc: "Provide Browser-Side monitoring of Web-App, Versions and Pages, through Apache SkyWalking Client JS.",
// Gateway
gateway: "Puerta",
gateway_desc:
"API gateway is an API management tool that sits between a client and a collection of backend services.",
gateway_apisix: "APISIX",
gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_aws_api_gateway: "AWS API Gateway",
gateway_aws_api_gateway_desc: "Provide AWS Cloud API Gateway monitoring through AWS FireHose Receiver.",
// Database
database: "Base de Datos",
database_desc:
"The database is the organized collection of structured information, or data, typically stored electronically in a computer system.",
database_mysql_mariadb: "MySQL/MariaDB",
database_mysql_mariadb_desc:
"Provide MySQL and MariaDB Server monitoring through OpenTelemetry's Prometheus Receiver.",
database_postgresql: "PostgreSQL",
database_postgresql_desc: "Provide PostgreSQL monitoring through OpenTelemetry's Prometheus Receiver.",
database_dynamodb: "DynamoDB",
database_dynamodb_desc: "Provide DynamoDB monitoring through Amazon CloudWatch.",
database_redis: "Redis",
database_redis_desc: "Provide Redis monitoring through OpenTelemetry's Prometheus Receiver.",
database_elasticsearch: "Elasticsearch",
database_elasticsearch_desc: "Provide Elasticsearch Server monitoring through OpenTelemetry's Prometheus Receiver.",
database_mongodb: "MongoDB",
database_mongodb_desc: "Provide MongoDB monitoring through OpenTelemetry's Prometheus Receiver.",
// Message Queue
mq: "Message Queue",
mq_desc:
"A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.",
mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability
self_observability: "Self Observability",
self_observability_desc:
"Self Observability provides the observabilities for running components and servers from the SkyWalking ecosystem.",
self_observability_oap: "SkyWalking Server",
self_observability_oap_desc:
"The OAP backend cluster itself is a distributed streaming process system, this is the monitoring for the OAP backend itself.",
self_observability_satellite: "Satellite",
self_observability_satellite_desc:
"Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.",
};
export default titles;

View File

@@ -0,0 +1,109 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const titles = {
// General Service
general_service: "常规服务",
general_service_desc: "通过从SkyWalking代理收集的遥测数据来观察服务和相对直接的依赖关系。",
general_service_services: "服务",
general_service_services_desc: "通过SkyWalking Agent收集的遥测数据观察服务。",
general_service_virtual_database: "虚拟数据库",
general_service_virtual_database_desc: "观察语言代理通过各种插件推测的虚拟数据库。",
general_service_virtual_cache: "虚拟缓存",
general_service_virtual_cache_desc: "观察语言代理通过各种插件推测的虚拟缓存服务器。",
general_service_virtual_mq: "虚拟消息队列",
general_service_virtual_mq_desc: "观察语言代理通过各种插件推测的虚拟消息队列服务器。",
// Service Mesh
service_mesh: "服务网格",
service_mesh_desc: "服务网格Istio通过分布式或微服务架构解决了开发人员和运营商面临的挑战。",
service_mesh_service: "服务",
service_mesh_service_desc: "通过从Envoy访问日志服务ALS收集的遥测数据观察服务网格。",
service_mesh_control_plane: "控制平面",
service_mesh_control_plane_desc: "通过Istio的自我监控指标提供对其行为的监控。",
service_mesh_data_plane: "数据平面",
service_mesh_data_plane_desc: "通过Envoy Metrics Service观察Envoy Proxy。",
// Functions
functions: "Functions",
functions_desc:
"FaaS功能即服务是一种云计算服务允许您在没有通常与构建和启动微服务应用程序相关的复杂基础设施的情况下执行代码以响应事件。",
functions_openfunction: "OpenFunction",
functions_openfunction_desc: "OpenFunction作为一个FaaS平台通过SkyWalking集成提供开箱即用的可观察性。",
// Kubernetes
kubernetes: "Kubernetes",
kubernetes_desc: "Kubernetes是一个开源的容器编排系统用于自动化软件部署、扩展和管理。",
kubernetes_cluster: "集群",
kubernetes_cluster_desc: "提供对K8S集群的状态和资源的监控。",
kubernetes_service: "服务",
kubernetes_service_desc: "从Kubernetes中观察服务状态和资源。",
// Infrastructure
infrastructure: "基础设施",
infrastructure_desc: "操作系统是整个IT系统的基础设施。它的可观察性为所有分布式和现代复杂系统的运行提供了基础。",
infrastructure_linux: "Linux",
infrastructure_linux_desc: "提供Linux操作系统OS监控。",
infrastructure_windows: "Windows",
infrastructure_windows_desc: "提供Windows操作系统OS监控。",
// AWS Cloud
aws_cloud: "AWS云服务",
aws_cloud_desc: "亚马逊网络服务AWS提供可靠、可扩展且价格低廉的云计算服务。",
aws_cloud_eks: "EKS",
aws_cloud_eks_desc: "通过AWS Container Insights Receiver提供AWS Cloud EKS监控。",
aws_cloud_s3: "S3",
aws_cloud_s3_desc: "通过AWS FireHose Receiver提供AWS Cloud S3监控",
aws_cloud_dynamodb: "DynamoDB",
aws_cloud_dynamodb_desc: "通过AWS FireHose Receiver提供DynamoDB监控。",
aws_cloud_api_gateway: "API Gateway",
aws_cloud_api_gateway_desc: "通过AWS FireHose Receiver提供AWS Cloud API网关监控。",
// Browser
browser: "Browser",
browser_desc: "通过Apache SkyWalking Client JS提供Web应用程序、版本和页面的浏览器端监控。",
// Gateway
gateway: "网关",
gateway_desc: "API网关是位于客户端和后端服务集合之间的API管理工具。",
gateway_apisix: "APISIX",
gateway_apisix_desc: "通过OpenTelemetry的Prometheus接收器提供APISIX监控。",
gateway_aws_api_gateway: "AWS API Gateway",
gateway_aws_api_gateway_desc: "通过AWS FireHose Receiver提供AWS Cloud API网关监控。",
// Database
database: "数据库",
database_desc: "数据库是结构化信息或数据的有组织的集合,通常以电子方式存储在计算机系统中。",
database_mysql_mariadb: "MySQL/MariaDB",
database_mysql_mariadb_desc: "通过OpenTelemetry的Prometheus接收器提供MySQL和MariaDB服务器监控。",
database_postgresql: "PostgreSQL",
database_postgresql_desc: "通过OpenTelemetry的Prometheus接收器提供PostgreSQL监控。",
database_dynamodb: "DynamoDB",
database_dynamodb_desc: "通过AWS FireHose Receiver提供DynamoDB监控。",
database_redis: "Redis",
database_redis_desc: "通过OpenTelemetry的Prometheus接收器提供Redis监控。",
database_elasticsearch: "Elasticsearch",
database_elasticsearch_desc: "通过OpenTelemetry的Prometheus接收器提供Elasticsearch服务器监控。",
database_mongodb: "MongoDB",
database_mongodb_desc: "通过OpenTelemetry的Prometheus接收器提供MongoDB监控。",
// Message Queue
mq: "消息队列",
mq_desc: "消息队列是无服务器和微服务架构中使用的异步服务对服务通信的一种形式。",
mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "通过OpenTelemetry的Prometheus接收器提供RabbitMQ监控。",
// self observability
self_observability: "自监控",
self_observability_desc: "自观察性为运行SkyWalking生态系统中的组件和服务器提供了可观察性。",
self_observability_oap: "SkyWalking服务",
self_observability_oap_desc: "OAP后端集群本身是一个分布式流处理系统这是对OAP后端本身的监控。",
self_observability_satellite: "Satellite",
self_observability_satellite_desc:
"Satellite为云原生基础设施设计的开源代理提供了一种低成本、高效、更安全的遥测数据收集方式。它是遥测采集的推荐负载均衡器。",
};
export default titles;

View File

@@ -14,14 +14,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import zhTitles from "./menus/zh";
const msg = { const msg = {
general: "普通服务", ...zhTitles,
services: "服务", services: "服务",
traces: "跟踪", traces: "跟踪",
metrics: "指标", metrics: "指标",
serviceMesh: "服务网格",
infrastructure: "基础设施",
virtualMachine: "虚拟机",
dashboardNew: "新建仪表板", dashboardNew: "新建仪表板",
dashboardHome: "仪表盘首页", dashboardHome: "仪表盘首页",
dashboardList: "仪表盘列表", dashboardList: "仪表盘列表",
@@ -32,14 +30,12 @@ const msg = {
dashboards: "仪表盘", dashboards: "仪表盘",
profiles: "性能剖析", profiles: "性能剖析",
database: "数据库", database: "数据库",
mySQL: "MySQL", mySQL: "MySQL/MariaDB",
serviceName: "服务名称", serviceName: "服务名称",
technologies: "技术", technologies: "技术",
health: "健康", health: "健康",
groupName: "群名称", groupName: "群名称",
topologies: "拓扑图", topologies: "拓扑图",
dataPanel: "数据平面",
controlPanel: "控制平面",
eventList: "事件列表", eventList: "事件列表",
newDashboard: "新增仪表盘", newDashboard: "新增仪表盘",
dashboardEdit: "编辑仪表盘", dashboardEdit: "编辑仪表盘",
@@ -115,20 +111,9 @@ const msg = {
rename: "重命名", rename: "重命名",
deleteTitle: "确定删除吗?", deleteTitle: "确定删除吗?",
rootTitle: "确定设置为Root吗", rootTitle: "确定设置为Root吗",
selfObservability: "自监控",
satellite: "Satellite",
skyWalkingServer: "SkyWalking服务",
functions: "Functions",
linux: "Linux",
browser: "浏览器",
editWarning: "你正在进入编辑模式", editWarning: "你正在进入编辑模式",
viewWarning: "你正在进入预览模式", viewWarning: "你正在进入预览模式",
virtualDatabase: "虚拟数据库",
virtualCache: "虚拟缓存",
reloadDashboards: "重新加载仪表盘", reloadDashboards: "重新加载仪表盘",
kubernetesService: "服务",
kubernetesCluster: "集群",
kubernetes: "Kubernetes",
textUrl: "文本超链接", textUrl: "文本超链接",
textAlign: "文本对齐", textAlign: "文本对齐",
metricLabel: "指标标签", metricLabel: "指标标签",
@@ -153,9 +138,7 @@ const msg = {
duplicateName: "重复的名称", duplicateName: "重复的名称",
text: "文本", text: "文本",
query: "查询", query: "查询",
postgreSQL: "PostgreSQL",
endpointTips: "这里最多展示20条endpoints。", endpointTips: "这里最多展示20条endpoints。",
apisix: "APISIX",
viewTrace: "查看相关Trace", viewTrace: "查看相关Trace",
relatedTraceOptions: "相关的Trace选项", relatedTraceOptions: "相关的Trace选项",
setLatencyDuration: "延迟相关指标", setLatencyDuration: "延迟相关指标",
@@ -176,6 +159,13 @@ const msg = {
when4xx: "当响应代码介于400和499之间时带有跟踪的HTTP请求和响应示例", when4xx: "当响应代码介于400和499之间时带有跟踪的HTTP请求和响应示例",
when5xx: "当响应代码介于500和599之间时带有跟踪的HTTP请求和响应示例", when5xx: "当响应代码介于500和599之间时带有跟踪的HTTP请求和响应示例",
taskTitle: "HTTP请求和响应收集规则", taskTitle: "HTTP请求和响应收集规则",
iframeWidgetTip: "添加widget的链接",
iframeSrc: "Iframe链接",
generateLink: "生成链接",
setDuration: "锁定查询持续时间",
openFunction: "OpenFunction",
period: "周期",
windows: "Windows",
seconds: "秒", seconds: "秒",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
@@ -194,7 +184,7 @@ const msg = {
trace: "追踪", trace: "追踪",
alarm: "告警", alarm: "告警",
event: "事件", event: "事件",
auto: "自动", auto: "自动更新",
reload: "刷新", reload: "刷新",
editmode: "编辑模式", editmode: "编辑模式",
version: "版本", version: "版本",
@@ -276,18 +266,14 @@ const msg = {
aggregation: "计算", aggregation: "计算",
unit: "单位", unit: "单位",
labelsIndex: "标签下标", labelsIndex: "标签下标",
group: "服务组",
browserView: "浏览器视图", browserView: "浏览器视图",
sortOrder: "排序方式", sortOrder: "排序方式",
chartType: "图表类型", chartType: "图表类型",
currentDepth: "当前深度", currentDepth: "当前深度",
defaultDepth: "默认深度", defaultDepth: "默认深度",
traceTagsTip: traceTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。", logTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
logTagsTip: alarmTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
alarmTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
tagsLink: "配置词汇页", tagsLink: "配置词汇页",
addTag: "请添加标签", addTag: "请添加标签",
logCategory: "日志类别", logCategory: "日志类别",
@@ -310,8 +296,7 @@ const msg = {
contentType: "内容类型", contentType: "内容类型",
content: "内容", content: "内容",
viewLogs: "查看日志", viewLogs: "查看日志",
logsTagsTip: logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
"只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
keywordsOfContentLogTips: "SkyWalking OAP服务器的当前存储不支持此操作", keywordsOfContentLogTips: "SkyWalking OAP服务器的当前存储不支持此操作",
setEvent: "设置事件", setEvent: "设置事件",
viewAttributes: "查看", viewAttributes: "查看",
@@ -368,10 +353,32 @@ const msg = {
addKeywordsOfContent: "请输入一个内容关键词", addKeywordsOfContent: "请输入一个内容关键词",
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词", addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车", noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice: conditionNotice: "请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言", language: "语言",
gateway: "网关", save: "保存",
virtualMQ: "虚拟消息队列", editStrategy: "编辑策略",
policyList: "策略列表",
targetTypes: "目标类型",
monitorType: "监视器类型",
count: "总数",
threshold: "阈值",
uriRegex: "URI规则",
uriList: "URI列表",
processes: "进程",
attributes: "属性",
monitorInstances: "监视实例",
processDashboards: "进程仪表板",
instanceDashboards: "实例仪表板",
detailLabel: "详细标签",
summary: "概括",
detail: "详细",
marketplace: "市场",
menusManagement: "菜单",
saveReload: "保存并重新加载页面",
document: "文档",
metricMode: "指标模式",
addExpressions: "添加表达式",
expressions: "表达式",
unhealthyExpression: "非健康表达式",
}; };
export default msg; export default msg;

View File

@@ -16,12 +16,12 @@
*/ */
import { createApp } from "vue"; import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router";
import { store } from "./store"; import { store } from "./store";
import components from "@/components"; import components from "@/components";
import i18n from "./locales"; import i18n from "./locales";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import "./styles/index.ts"; import "./styles/index.ts";
import "virtual:svg-icons-register";
const app = createApp(App); const app = createApp(App);
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
@@ -32,6 +32,9 @@ app.use(store);
mountApp(); mountApp();
async function mountApp() { async function mountApp() {
await appStore.getActivateMenus();
await appStore.queryOAPTimeInfo(); await appStore.queryOAPTimeInfo();
app.use(router).mount("#app");
const router = await import("./router");
app.use(router.default).mount("#app");
} }

View File

@@ -14,34 +14,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Mock from "mockjs";
export default [ const Random = Mock.Random;
{ const nodes = Mock.mock({
path: "", "nodes|500": [
name: "Gateway", {
meta: { //id
title: "gateway", id: "@guid",
icon: "gateway", name: "@name",
hasGroup: true, "type|1": ["ActiveMQ", "activemq-consumer", "H2", "APISIX", "Express", "USER", "Flash"],
"isReal|1": [true, false],
}, },
redirect: "/apisix", ],
children: [ });
{ const calls = Mock.mock({
path: "/apisix", "links|500": [
name: "APISIX", {
meta: { //id
title: "apisix", id: "@guid",
layer: "APISIX", detectPoints: ["SERVER", "CLIENT"],
}, source: function () {
const d = Random.integer(0, 250);
return nodes.nodes[d].id;
}, },
{ target: function () {
path: "/apisix/tab/:activeTabIndex", const d = Random.integer(250, 499);
name: "APISIXActiveTabIndex", return nodes.nodes[d].id;
meta: {
notShow: true,
layer: "APISIX",
},
}, },
], },
}, ],
]; });
const callsMock = calls.links;
const nodesMock = nodes.nodes;
export { callsMock, nodesMock };

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