83 Commits

Author SHA1 Message Date
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
Fine0830
1f98619a5b fix: update attached event's details (#200) 2022-12-02 15:03:29 +08:00
Fine0830
aab44626da fix: update attached event details (#199) 2022-12-01 13:06:46 +08:00
Fine0830
0ff5d4d6bb fix: update attached event‘s occurrence date (#198) 2022-12-01 10:47:30 +08:00
Fine0830
611731d6d0 fix: update attached event details (#197) 2022-11-30 16:48:49 +08:00
Fine0830
221751f034 fix: optimize metrics association (#196) 2022-11-29 18:47:53 +08:00
Fine0830
d4dde7e73b fix: optimize config UI for related trace and metrics (#195) 2022-11-29 14:24:15 +08:00
Fine0830
5be106fc4f fix: add ProcessRelation to entity types (#194) 2022-11-28 22:49:23 +08:00
Fine0830
d8f91bbdf3 feat: Implement creating tasks UI for network profiling widget (#193) 2022-11-28 17:57:23 +08:00
Fine0830
23e9742946 feat: enhance associating metrics with traces by refId (#192) 2022-11-25 17:33:51 +08:00
Fine0830
7a1c83b5fb fix: update metric processor for the readRecords and remove readSampledRecords from metrics selector (#191) 2022-11-24 18:51:21 +08:00
Fine0830
7d802d490e feat: visualize attached events on the trace widget (#190) 2022-11-24 11:19:25 +08:00
Fine0830
da1db8def6 fix: update query conditions for metrics related traces (#189) 2022-11-22 22:39:22 +08:00
Fine0830
2230d05508 fix: optimize metrics related trace (#188) 2022-11-21 14:28:37 +08:00
WD
e8d909792d chore: fix typo (#187) 2022-11-21 10:07:01 +08:00
Fine0830
dc842609ba fix: update condition logic for trace tree data (#186) 2022-11-19 16:53:15 +08:00
drgnchan
670bef1d69 fix typo (#185) 2022-11-18 11:55:52 +08:00
Fine0830
882828b04a feat: enhance tags component to search tags with the input value (#184) 2022-11-17 17:57:23 +08:00
WD
ed6fb0448b feat: solve the problem of floating loading (#183) 2022-11-17 16:07:18 +08:00
Fine0830
a0fc879eb1 feat: enhance graph legend for the single metric (#182) 2022-11-10 15:13:17 +08:00
Fine0830
b37d65eaac feat: enhance the legend of metrics graph widget with the summary table (#181) 2022-11-10 14:55:19 +08:00
heihei180
fd46211a37 add apache eventMesh logo file (#180) 2022-11-06 15:12:30 +08:00
Fine0830
ae0b8c056d fix trace profiling widget, select the first span by default (#179) 2022-11-04 20:31:46 +08:00
Fine0830
4b88d8bbb3 fix: reset tag keys list and duration condition (#178) 2022-10-31 12:05:25 +08:00
Fine0830
09051e916b feat: support labeled value on the service/instance/endpoint list widgets (#177) 2022-10-31 10:27:37 +08:00
Fine0830
e597f91448 remove unuse icon (#176) 2022-10-26 21:53:27 +08:00
Fine0830
4232161d36 fix: set selector props and update configuration panel styles (#175) 2022-10-25 16:48:49 +08:00
Fine0830
eda44db0cd feat: associate metrics with trace widget on dashboards (#174) 2022-10-25 11:36:49 +08:00
pg.yang
78f0096c00 add menu for virtual mq (#173) 2022-10-20 20:36:28 +08:00
Fine0830
5e161f17c2 feat: add readRecords to metric types (#172) 2022-10-17 21:55:14 +08:00
WD
77d189cdfb fix: added name verification to avoid creating blank dashboard name (#171) 2022-10-17 21:25:59 +08:00
Fine0830
9f57e35119 revert logs on trace widget (#170) 2022-10-14 11:28:10 +08:00
dependabot[bot]
2bf90d6a6d build(deps): bump d3-color from 3.0.1 to 3.1.0 (#166) 2022-10-01 10:33:48 +08:00
Fine0830
0f4319499a fix: query logs with the specific service ID (#165) 2022-09-30 10:25:05 +08:00
pg.yang
5bb58a00cd feat: add gateway,apisix menu (#163) 2022-09-27 10:36:15 +08:00
吴晟 Wu Sheng
d50e9fc261 Remove commented codes (#162) 2022-09-21 12:34:06 +08:00
Fine0830
b235929c77 feat: enhance menu configuration to make it easier to change (#161) 2022-09-20 16:24:42 +08:00
Fine0830
4561e2e374 fix: remove All from the endpoints selector from profiling 2022-09-19 14:05:09 +08:00
pg.yang
214b34ddfd feat: add virtual cache dashboard (#159) 2022-09-19 10:02:22 +08:00
Fine0830
26817e9f92 feat: enhance the process topology graph to support dragging nodes (#158) 2022-09-15 17:18:39 +08:00
Fine0830
9ed0121fd0 fix: update styles for an adaptive height (#157) 2022-09-13 16:31:30 +08:00
Fine0830
0d63d538c3 fix: set up a new time range after clicking the refresh button (#156) 2022-09-08 22:41:02 +08:00
Fine0830
5da441ff9a fix: polish the endpoint list graph (#155) 2022-09-08 21:46:37 +08:00
kezhenxu94
49bc349064 Keep package.json and package-lock.json in sync (#154) 2022-09-06 14:31:52 +08:00
Lv Lifeng
61a4d2f759 Add impala icon 4 impala jdbc plugin (#153) 2022-09-03 19:45:54 +08:00
云泥
0b4e738699 fix: tab active incorrectly, when click tab space (#152) 2022-09-01 15:49:15 +08:00
263 changed files with 29860 additions and 53819 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,16 +37,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
node-version: [14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: npm install, lint, build, and test
- name: npm ci, lint, build, and test
run: |
npm install
npm ci
npm run lint
npm run build --if-present
npm run test:unit

34
.gitignore vendored
View File

@@ -16,24 +16,40 @@
# specific language governing permissions and limitations
# under the License.
#
.DS_Store
node_modules
/dist
/node
/tests/e2e/videos/
/tests/e2e/screenshots/
# local env files
.env.local
.env.*.local
# Log files
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.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
.idea

27
.husky/commit-msg Executable 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
# 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 @@
#
# 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
. "$(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" />
[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)
This UI starts from SkyWalking OAP v9 core.
## 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.
## 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
@@ -28,19 +28,21 @@ npm install
### Build
**All following builds are for dev.**
```
npm install
npm run serve
npm run dev
```
The default UI address is `http://localhost:8080`.
# 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.
* 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
- 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.
- 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
[Apache 2.0 License.](/LICENSE)

50
commitlint.config.js Normal file
View File

@@ -0,0 +1,50 @@
/**
* 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 = {
ignores: [(commit) => commit.includes("init")],
extends: ["@commitlint/config-conventional"],
rules: {
"body-leading-blank": [2, "always"],
"footer-leading-blank": [1, "always"],
"header-max-length": [2, "always", 108],
"subject-empty": [2, "never"],
"type-empty": [2, "never"],
"subject-case": [0],
"type-enum": [
2,
"always",
[
"feat",
"fix",
"perf",
"style",
"docs",
"test",
"refactor",
"build",
"ci",
"chore",
"revert",
"wip",
"workflow",
"types",
"release",
],
],
},
};

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
* limitations under the License.
*/
const requireAll = (requireContext: Recordable) =>
requireContext.keys().map(requireContext);
const req = require.context("./", true, /\.svg$/);
requireAll(req);
import { defineConfig } from "cypress";
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
* limitations under the License.
*/
describe("My First Test", () => {
it("renders props.msg when passed", () => {
const msg = "new message";
console.log(msg);
it("visits the app root url", () => {
cy.visit("/");
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,6 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -14,8 +14,9 @@
* See the License for the specific language governing permissions and
* 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
// existing commands.
//
@@ -26,16 +27,29 @@
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- 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 --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// -- This will overwrite an existing command --
// 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
* limitations under the License.
*/
// ***********************************************************
// This example support/index.js is processed and
// 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>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Apache SkyWalking</title>
</head>
<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>
<!-- built files will be auto injected -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>

50860
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,20 @@
{
"name": "skywalking-booster-ui",
"version": "9.3.0",
"version": "9.4.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint"
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"test:unit": "vitest --environment jsdom --root src/",
"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"
},
"dependencies": {
"axios": "^0.24.0",
@@ -17,132 +24,80 @@
"echarts": "^5.2.2",
"element-plus": "^2.0.2",
"lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5",
"monaco-editor": "^0.34.1",
"pinia": "^2.0.28",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0",
"vue": "^3.2.45",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.0-0",
"vue-router": "^4.1.6",
"vue-types": "^4.1.1"
},
"devDependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/d3": "^7.1.0",
"@types/d3-tip": "^3.5.5",
"@types/echarts": "^4.9.12",
"@types/jest": "^24.0.19",
"@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.179",
"@types/node": "^18.11.12",
"@types/three": "^0.131.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~5.0.8",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^2.0.0-0",
"babel-jest": "^24.9.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/test-utils": "^2.2.6",
"@vue/tsconfig": "^0.1.3",
"@vueuse/core": "^9.6.0",
"cypress": "^12.0.2",
"eslint": "^8.22.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^9.3.0",
"husky": "^8.0.2",
"jsdom": "^20.0.3",
"lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1",
"node-sass": "^8.0.0",
"npm-run-all": "^4.1.5",
"postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2",
"prettier": "^2.2.1",
"sass-loader": "^10.2.0",
"prettier": "^2.7.1",
"sass": "^1.56.1",
"start-server-and-test": "^1.15.2",
"stylelint": "^14.1.0",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0",
"svg-sprite-loader": "^6.0.11",
"typescript": "~4.4.4",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2",
"vue-jest": "^5.0.0-0"
"vite": "^4.0.0",
"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": [
"> 1%",
"last 2 versions",
"not dead"
],
"jest": {
"preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel",
"transform": {
"^.+\\.vue$": "vue-jest"
}
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
"*.{js,jsx,ts,tsx,vue}": [
"eslint . --ext .vue,.js,.jsx,.ts,.tsx --fix --ignore-path .gitignore",
"prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
"stylelint --cache --fix \"**/*.{vue}\" --cache --cache-location node_modules/.cache/stylelint/"
],
"*.vue": [
"eslint --fix",
"prettier --write",
"stylelint --fix --custom-syntax postcss-html"
"*.{scss,less}": [
"prettier --write \"src/**/*.{js,tsx,css,less,scss,vue,html,md}\"",
"stylelint --cache --fix \"**/*.{less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"package.json": [
"prettier --write"
],
"*.{scss,less,styl}": [
"stylelint --fix",
"package.json": [
"prettier --write"
],
"*.md": [

View File

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

View File

@@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
plugins: ["cypress"],
env: {
mocha: true,
"cypress/globals": true,
},
rules: {
strict: "off",
},
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>
<router-view />
</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>
#app {
color: #2c3e50;
height: 100%;
overflow: auto;
min-width: 1024px;
}
#app {
color: #2c3e50;
height: 100%;
overflow: hidden;
}
</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

@@ -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="1667899293763" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4705" width="16" height="16"><path d="M512 512m-368 0a368 368 0 1 0 736 0 368 368 0 1 0-736 0Z" p-id="4706"></path></svg>

After

Width:  |  Height:  |  Size: 1001 B

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.
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">
<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>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 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 t="1666624449554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2649" width="48" height="48"><path d="M381.482667 673.877333a90.389333 90.389333 0 0 1 85.226666 60.245334H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666a90.389333 90.389333 0 0 1 85.205334-60.245334z m0 64a26.346667 26.346667 0 1 0 0 52.693334 26.346667 26.346667 0 0 0 0-52.693334z m261.034666-304.938666a90.389333 90.389333 0 0 1 85.205334 60.245333H853.333333v64h-127.04a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h386.624a90.389333 90.389333 0 0 1 85.226666-60.245333z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333zM381.482667 192a90.389333 90.389333 0 0 1 85.226666 60.224H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666A90.389333 90.389333 0 0 1 381.482667 192z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333z" p-id="2650"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

15
src/assets/icons/copy.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="1664265269855" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4109" width="48" height="48"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="4110"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 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,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="1664244255409" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" width="48" height="48"><path d="M523.776 430.592l-153.088 88.576 153.088 88.576 152.576-88.576-152.576-88.576z m-165.888 108.544l0.512 177.152 153.6 87.552-1.024-176.64-153.088-88.064z m330.24 0l-153.6 87.552-1.024 176.64 153.6-87.552 1.024-176.64z m131.072 205.824l-68.096-40.96 39.936-8.704-5.632-26.112-67.072 14.848-13.824 23.04 101.376 60.928 13.312-23.04z m-142.848 7.68l68.096 40.96-39.936 8.704 5.632 26.112 67.072-14.848 13.824-23.04-101.888-60.928-12.8 23.04zM481.28 424.96h26.624V306.176H481.28v79.36l-27.648-29.696-19.456 18.432L481.28 424.96z m53.76-118.784V424.96h26.624V345.088l27.648 29.696 19.456-18.432-47.104-50.176h-26.624z m-190.464 401.92l-13.312-23.04-68.608 40.448 11.264-38.912-25.6-7.168-18.944 66.048 13.312 23.04 101.888-60.416z m-89.088 82.944l13.312 23.04 68.608-39.936-11.264 38.912 25.6 7.168 19.456-66.048-13.312-23.04-102.4 59.904z m622.08-45.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-17.92 40.448-40.448 40.448zM521.728 292.864c45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432z m0-122.88c22.528 0 40.448 17.92 40.448 40.448s-17.92 40.448-40.448 40.448-40.448-17.92-40.448-40.448 18.432-40.448 40.448-40.448zM167.936 749.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-18.432 40.448-40.448 40.448z" p-id="2267"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -13,6 +13,5 @@ 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>info_outline</title>
<path d="M11.016 9v-2.016h1.969v2.016h-1.969zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 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.93zM11.016 17.016v-6h1.969v6h-1.969z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

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="1664266918236" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" width="48" height="48"><path d="M571.178667 643.328a144 144 0 0 1-189.098667-193.450667l77.781333 77.866667a48 48 0 1 0 67.882667-67.84l-77.824-77.909333a144 144 0 0 1 193.450667 189.141333l226.517333 207.061333a64.896 64.896 0 1 1-91.690667 91.690667l-207.018666-226.56z m51.498666 134.656a288.298667 288.298667 0 0 1-38.656 12.928v95.488c0 5.290667-4.309333 9.6-9.642666 9.6h-124.757334a9.6 9.6 0 0 1-9.6-9.6v-95.488a286.293333 286.293333 0 0 1-74.325333-30.805333l-67.541333 67.541333a9.6 9.6 0 0 1-13.568 0L196.352 739.413333a9.6 9.6 0 0 1 0-13.568l67.541333-67.541333a286.293333 286.293333 0 0 1-30.805333-74.325333H137.6A9.6 9.6 0 0 1 128 574.378667v-124.757334c0-5.290667 4.309333-9.6 9.6-9.6h95.488c6.826667-26.453333 17.28-51.370667 30.805333-74.325333L196.352 298.154667a9.6 9.6 0 0 1 0-13.568L284.586667 196.352a9.6 9.6 0 0 1 13.568 0l67.541333 67.498667a287.146667 287.146667 0 0 1 74.325333-30.848V137.6c0-5.290667 4.266667-9.6 9.6-9.6h124.8c5.248 0 9.6 4.309333 9.6 9.6v95.488c26.368 6.826667 51.328 17.28 74.282667 30.805333l67.541333-67.541333a9.6 9.6 0 0 1 13.568 0l88.234667 88.234667a9.6 9.6 0 0 1 0 13.568l-67.498667 67.541333a287.146667 287.146667 0 0 1 30.848 74.282667h95.402667c5.290667 0 9.6 4.352 9.6 9.642666v124.757334c0 5.333333-4.266667 9.6-9.6 9.6h-95.488c-4.693333 18.133333-11.178667 35.754667-19.328 52.650666a9.6 9.6 0 0 1-15.018667 2.986667l-10.112-9.173333-38.314666-34.261334-12.16-10.88a9.6 9.6 0 0 1-2.688-10.24A192.298667 192.298667 0 0 0 512 320a192 192 0 1 0 63.018667 373.333333 9.6 9.6 0 0 1 10.24 2.645334l10.837333 12.074666 35.285333 39.338667 8.149334 9.130667a9.6 9.6 0 0 1-2.901334 15.061333 283.306667 283.306667 0 0 1-13.952 6.4z" p-id="5379"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -15,190 +15,259 @@ limitations under the License. -->
<template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div>
<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">
{{ t("associateMetrics") }}
</div>
<div class="tools" @click="viewTrace" v-if="relatedTrace && relatedTrace.enableRelate">
{{ t("viewTrace") }}
</div>
</div>
<el-drawer
v-model="showTrace"
size="100%"
:destroy-on-close="true"
:before-close="() => (showTrace = false)"
:append-to-body="true"
title="The Related Traces"
>
<Trace :data="traceOptions" />
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import {
watch,
ref,
Ref,
onMounted,
onBeforeUnmount,
unref,
computed,
} from "vue";
import type { PropType } from "vue";
import { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event";
import { watch, ref, onMounted, onBeforeUnmount, unref, computed, reactive } from "vue";
import type { PropType, Ref } from "vue";
import { useI18n } from "vue-i18n";
import type { EventParams } from "@/types/app";
import type { 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*/
const emits = defineEmits(["select"]);
const chartRef = ref<Nullable<HTMLDivElement>>(null);
const { setOptions, resize, getInstance } = useECharts(
chartRef as Ref<HTMLDivElement>
);
const props = defineProps({
height: { type: String, default: "100%" },
width: { type: String, default: "100%" },
option: {
type: Object as PropType<{ [key: string]: any }>,
default: () => ({}),
},
filters: {
type: Object as PropType<{
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
sourceId: string;
}>,
},
});
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])
);
onMounted(async () => {
await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize);
setTimeout(() => {
const instance = getInstance();
/*global Nullable, defineProps, defineEmits*/
const emits = defineEmits(["select"]);
const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null);
const visMenus = ref<boolean>(false);
const { setOptions, resize, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
const currentParams = ref<Nullable<EventParams>>(null);
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const props = defineProps({
height: { type: String, default: "100%" },
width: { type: String, default: "100%" },
option: {
type: Object as PropType<{ [key: string]: any }>,
default: () => ({}),
},
filters: {
type: Object as PropType<Filters>,
},
relatedTrace: {
type: Object as PropType<RelatedTrace>,
},
associate: {
type: Array as PropType<{ widgetId: string }[]>,
default: () => [],
},
});
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]),
);
onMounted(async () => {
await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize);
instanceEvent();
});
if (!instance) {
return;
}
instance.on("click", (params: unknown) => {
emits("select", params);
});
document.addEventListener(
"click",
() => {
if (instance.isDisposed()) {
function instanceEvent() {
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: "updateAxisPointer",
currTrigger: "leave",
type: "hideTip",
});
},
true
);
}, 1000);
});
});
document.addEventListener(
"click",
() => {
if (instance.isDisposed()) {
return;
}
visMenus.value = false;
instance.dispatchAction({
type: "hideTip",
});
instance.dispatchAction({
type: "updateAxisPointer",
currTrigger: "leave",
});
},
true,
);
}, 1000);
}
function updateOptions() {
const instance = getInstance();
if (!instance) {
return;
function associateMetrics() {
emits("select", currentParams.value);
updateOptions(currentParams.value || undefined);
}
if (!props.filters) {
return;
function updateOptions(params?: EventParams) {
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: "showTip",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0,
});
}
}
if (props.filters.isRange) {
const options = eventAssociate();
setOptions(options || props.option);
} else {
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: "showTip",
dataIndex: props.filters.dataIndex,
seriesIndex: 0,
type: "hideTip",
});
}
}
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map(
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
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);
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
}
);
watch(
() => props.filters,
() => {
updateOptions();
},
);
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) {
options = eventAssociate();
}
setOptions(options || props.option);
}
);
watch(
() => props.filters,
() => {
updateOptions();
}
);
onBeforeUnmount(() => {
removeResizeListener(unref(chartRef), resize);
});
onBeforeUnmount(() => {
removeResizeListener(unref(chartRef), resize);
});
</script>
<style lang="scss" scoped>
.no-data {
font-size: 12px;
height: 100%;
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
color: #666;
}
.no-data {
font-size: 12px;
height: 100%;
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
color: #666;
}
.chart {
overflow: hidden;
}
.chart {
overflow: hidden;
flex: 1;
}
.menus {
position: absolute;
display: block;
white-space: nowrap;
z-index: 9999999;
box-shadow: #ddd 1px 2px 10px;
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
background-color: rgb(255, 255, 255);
border-radius: 4px;
color: rgb(51, 51, 51);
padding: 5px;
}
.tools {
padding: 5px;
color: #999;
cursor: pointer;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ limitations under the License. -->
v-model="selected"
:placeholder="placeholder"
@change="changeSelected"
filterable
:multiple="multiple"
:disabled="disabled"
:style="{ borderRadius }"
@@ -26,73 +25,73 @@ limitations under the License. -->
:remote="isRemote"
:reserve-keyword="isRemote"
:remote-method="remoteMethod"
:filterable="filterable"
>
<el-option
v-for="item in options"
:key="item.value || ''"
:label="item.label || ''"
:value="item.value || ''"
>
<el-option v-for="item in options" :key="item.value || ''" :label="item.label || ''" :value="item.value || ''">
</el-option>
</el-select>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import type { PropType } from "vue";
import { ref, watch } from "vue";
import type { PropType } from "vue";
interface Option {
label: string | number;
value: string | number;
}
// interface Option {
// label: string | number;
// value: string | number;
// }
/*global defineProps, defineEmits*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
options: {
type: Array as PropType<(Option & { disabled?: boolean })[]>,
default: () => [],
},
value: {
type: [Array, String, Number, undefined] as PropType<any>,
default: () => [],
},
size: { type: null, default: "default" },
placeholder: {
type: [String, undefined] as PropType<string>,
default: "Select a option",
},
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 },
});
/*global defineProps, defineEmits*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
options: {
type: Array as PropType<
({
label: string | number;
value: string | number;
} & { disabled?: boolean })[]
>,
default: () => [],
},
value: {
type: [Array, String, Number, undefined] as PropType<any>,
default: () => [],
},
size: { type: null, default: "default" },
placeholder: {
type: [String, undefined] as PropType<string>,
default: "Select a option",
},
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);
function changeSelected() {
const options = props.options.filter((d: any) =>
props.multiple
? selected.value.includes(d.value)
: selected.value === d.value
const selected = ref<string[] | string>(props.value);
function changeSelected() {
const options = props.options.filter((d: any) =>
props.multiple ? 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>
<style lang="scss" scoped>
.el-input__inner {
border-radius: unset !important;
}
.el-input__inner {
border-radius: unset !important;
}
</style>

View File

@@ -35,56 +35,28 @@ limitations under the License. -->
<transition name="datepicker-anim">
<div
class="datepicker-popup"
:class="[
popupClass,
{ 'datepicker-inline': type === 'inline' },
position,
]"
:class="[popupClass, { 'datepicker-inline': type === 'inline' }, position]"
tabindex="-1"
v-if="show || type === 'inline'"
>
<template v-if="range">
<div class="datepicker-popup__sidebar">
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('quarter')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('quarter')">
{{ local.quarterHourCutTip }}
</button>
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('half')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('half')">
{{ local.halfHourCutTip }}
</button>
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('hour')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('hour')">
{{ local.hourCutTip }}
</button>
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('day')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('day')">
{{ local.dayCutTip }}
</button>
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('week')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('week')">
{{ local.weekCutTip }}
</button>
<button
type="button"
class="datepicker-popup__shortcut"
@click="quickPick('month')"
>
<button type="button" class="datepicker-popup__shortcut" @click="quickPick('month')">
{{ local.monthCutTip }}
</button>
</div>
@@ -123,16 +95,10 @@ limitations under the License. -->
/>
</template>
<div v-if="showButtons" class="datepicker__buttons">
<button
@click.prevent.stop="cancel"
class="datepicker__button-cancel"
>
<button @click.prevent.stop="cancel" class="datepicker__button-cancel">
{{ local.cancelTip }}
</button>
<button
@click.prevent.stop="submit"
class="datepicker__button-select"
>
<button @click.prevent.stop="submit" class="datepicker__button-select">
{{ local.submitTip }}
</button>
</div>
@@ -142,438 +108,431 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
import { useI18n } from "vue-i18n";
import DateCalendar from "./DateCalendar.vue";
import { useTimeoutFn } from "@/hooks/useTimeout";
/*global defineProps, defineEmits */
const datepicker = ref(null);
const { t } = useI18n();
const show = ref<boolean>(false);
const dates = ref<Date | string[] | any>([]);
const props = defineProps({
position: { type: String, default: "bottom" },
name: [String],
inputClass: [String],
popupClass: [String],
value: [Date, Array, String],
disabled: [Boolean],
type: {
type: String,
default: "normal",
},
rangeSeparator: {
type: String,
default: "~",
},
clearable: {
type: Boolean,
default: false,
},
placeholder: [String],
disabledDate: {
type: Function,
default: () => false,
},
format: {
type: String,
default: "YYYY-MM-DD",
},
showButtons: {
type: Boolean,
default: false,
},
dateRangeSelect: [Function],
});
const emit = defineEmits(["clear", "input", "confirm", "cancel"]);
const local = computed(() => {
return {
dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"),
monthCutTip: t("monthCutTip"),
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
import { useI18n } from "vue-i18n";
import DateCalendar from "./DateCalendar.vue";
import { useTimeoutFn } from "@/hooks/useTimeout";
/*global defineProps, defineEmits */
const datepicker = ref(null);
const { t } = useI18n();
const show = ref<boolean>(false);
const dates = ref<Date | string[] | any>([]);
const props = defineProps({
position: { type: String, default: "bottom" },
name: [String],
inputClass: [String],
popupClass: [String],
value: [Date, Array, String],
disabled: [Boolean],
type: {
type: String,
default: "normal",
},
rangeSeparator: {
type: String,
default: "~",
},
clearable: {
type: Boolean,
default: false,
},
placeholder: [String],
disabledDate: {
type: Function,
default: () => false,
},
format: {
type: String,
default: "YYYY-MM-DD",
},
showButtons: {
type: Boolean,
default: false,
},
dateRangeSelect: [Function],
});
const emit = defineEmits(["clear", "input", "confirm", "cancel"]);
const local = computed(() => {
return {
dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"),
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 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,
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];
};
return (format || props.format).replace(
/Y+|M+|D+|H+|h+|m+|s+|S+/g,
(str: string) => map[str]
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);
},
);
};
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>
<style lang="scss" scoped>
@keyframes datepicker-anim-in {
0% {
opacity: 0;
transform: scaleY(0.8);
}
@keyframes datepicker-anim-in {
0% {
opacity: 0;
transform: scaleY(0.8);
}
to {
opacity: 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;
to {
opacity: 1;
transform: scaleY(1);
}
}
&__body {
margin-left: 100px;
padding-left: 5px;
@keyframes datepicker-anim-out {
0% {
opacity: 1;
transform: scaleY(1);
}
to {
opacity: 0;
transform: scaleY(0.8);
}
}
}
.datepicker-inline {
position: relative;
margin-top: 0;
}
.datepicker {
display: inline-block;
position: relative;
}
.datepicker-range {
min-width: 238px;
}
.datepicker-icon {
display: block;
position: absolute;
top: 8px;
left: 8px;
color: #515a6ecc;
}
.datepicker-range .datepicker-popup {
width: 520px;
}
.datepicker-close {
display: none;
position: absolute;
width: 34px;
height: 100%;
top: 0;
right: 0;
cursor: pointer;
}
.datepicker-bottom {
float: left;
width: 100%;
text-align: right;
}
.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-btn {
padding: 5px 10px;
background: #3f97e3;
color: #fff;
border-radius: 2px;
display: inline-block;
cursor: pointer;
}
.datepicker__clearable:hover:before {
display: none;
}
.datepicker-anim-enter-active {
transform-origin: 0 0;
animation: datepicker-anim-in 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
.datepicker__clearable:hover .datepicker-close {
display: block;
}
.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-close:hover:before {
background-color: #afafaf;
}
.datepicker__buttons {
display: block;
text-align: right;
}
.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__buttons button {
display: inline-block;
font-size: 13px;
border: none;
cursor: pointer;
margin: 10px 0 0 5px;
padding: 5px 15px;
color: #ffffff;
}
// .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__buttons .datepicker__button-select {
background: #3f97e3;
}
.datepicker > input:disabled {
cursor: not-allowed;
background-color: #ebebe4;
border-color: #e5e5e5;
-webkit-box-shadow: none;
box-shadow: none;
}
.datepicker__buttons .datepicker__button-cancel {
background: #666;
}
.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 {
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: #fff;
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: #ffffff;
}
.datepicker__buttons .datepicker__button-select {
background: #3f97e3;
}
.datepicker__buttons .datepicker__button-cancel {
background: #666;
}
</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

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum TimeType {
MINUTE_TIME = "MINUTE",
HOUR_TIME = "HOUR",
@@ -25,33 +26,3 @@ export const Languages = [
{ label: "Chinese", value: "zh" },
{ label: "Spanish", value: "es" },
];
export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL",
GeneralServicesActiveTabIndex: "GENERAL",
VirtualDatabase: "VIRTUAL_DATABASE",
VirtualDatabaseActiveTabIndex: "VIRTUAL_DATABASE",
MeshServices: "MESH",
MeshServicesActiveTabIndex: "MESH",
ControlPanel: "MESH_CP",
ControlPanelActiveTabIndex: "MESH_CP",
DataPanel: "MESH_DP",
DataPanelActiveTabIndex: "MESH_DP",
Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP",
SkyWalkingServerActiveTabIndex: "SO11Y_OAP",
SatelliteActiveTabIndex: "SO11Y_SATELLITE",
Satellite: "SO11Y_SATELLITE",
Functions: "FAAS",
FunctionsActiveTabIndex: "FAAS",
Browser: "BROWSER",
BrowserActiveTabIndex: "BROWSER",
KubernetesCluster: "K8S",
KubernetesClusterActiveTabIndex: "K8S",
KubernetesService: "K8S_SERVICE",
KubernetesServiceActiveTabIndex: "K8S_SERVICE",
MySQL: "MYSQL",
MySQLActiveTabIndex: "MYSQL",
PostgreSQL: "POSTGRESQL",
PostgreSQLActiveTabIndex: "POSTGRESQL",
};

View File

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

View File

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

View File

@@ -33,8 +33,7 @@ export const createEBPFTask = {
}`,
};
export const queryEBPFTasks = {
variable:
"$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]",
variable: "$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]",
query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) {
taskId

View File

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

View File

@@ -69,6 +69,25 @@ export const TraceSpans = {
value
}
}
attachedEvents {
startTime {
seconds
nanos
}
event
endTime {
seconds
nanos
}
tags {
key
value
}
summary {
key
value
}
}
}
}
`,

View File

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

View File

@@ -32,6 +32,6 @@ export const queryInstances = `query queryInstances(${Instances.variable}) {${In
export const queryLayers = `query listLayer {${Layers.query}}`;
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`;
export const queryEndpoint = `query queryInstance(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryEndpoint = `query queryEndpoint(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ export enum MetricQueryTypes {
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
}
export enum Calculations {
@@ -29,7 +30,6 @@ export enum Calculations {
ByteToMB = "byteToMB",
ByteToGB = "byteToGB",
Apdex = "apdex",
Precision = "precision",
ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds",
MsToS = "msTos",
@@ -38,6 +38,8 @@ export enum Calculations {
ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
CPM5D = "cpm5d",
CPM5DAvg = "cpm5dAvg",
}
export enum sizeEnum {
XS = "XS",
@@ -66,7 +68,7 @@ screenMap.set(sizeEnum.LG, screenEnum.LG);
screenMap.set(sizeEnum.XL, screenEnum.XL);
screenMap.set(sizeEnum.XXL, screenEnum.XXL);
export const RespFields: any = {
export const RespFields: { [key: string]: string } = {
readMetricsValues: `{
label
values {
@@ -101,4 +103,10 @@ export const RespFields: any = {
value
refId
}`,
readRecords: `{
id
name
value
refId
}`,
};

View File

@@ -0,0 +1,120 @@
/**
* 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 { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import type { EventParams } from "@/types/app";
export default function associateProcessor(props: any) {
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map((d: (number | string)[]) => d[0]);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
}
function traceFilters(currentParams: Nullable<EventParams>) {
const appStore = useAppStoreWithOut();
if (!currentParams) {
return;
}
const start = appStore.intervalUnix[currentParams.dataIndex];
const { step } = appStore.durationRow;
let duration = undefined;
if (start) {
const end = start;
duration = {
start: dateFormatStep(getLocalTime(appStore.utc, new Date(start)), step, true),
end: dateFormatStep(getLocalTime(appStore.utc, new Date(end)), step, true),
step,
};
}
const relatedTrace = props.relatedTrace || {};
const status = relatedTrace.status;
const queryOrder = relatedTrace.queryOrder;
const latency = relatedTrace.latency;
const series = props.option.series || [];
const item: any = {
duration,
queryOrder,
status,
};
if (latency) {
const latencyList = series.map((d: { name: string; data: number[][] }, index: number) => {
const data = [
d.data[currentParams.dataIndex][1],
series[index + 1] ? series[index + 1].data[currentParams.dataIndex][1] : Infinity,
];
return {
label: d.name + "--" + (series[index + 1] ? series[index + 1].name : "Infinity"),
value: String(index),
data,
};
});
item.latency = latencyList;
}
const value = series.map((d: { name: string; data: number[][] }, index: number) => {
return {
label: d.name,
value: String(index),
data: d.data[currentParams.dataIndex][1],
date: d.data[currentParams.dataIndex][0],
};
});
item.metricValue = value;
return item;
}
return { eventAssociate, traceFilters };
}

View File

@@ -14,7 +14,8 @@
* See the License for the specific language governing permissions and
* 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 { screenMap, sizeEnum, screenEnum } from "./data";
@@ -40,9 +41,7 @@ export function useBreakpoint(): any {
};
}
export function createBreakpointListen(
fn?: (opt: CreateCallbackParams) => void
): any {
export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void): any {
const screenRef = ref<sizeEnum>(sizeEnum.XL || "");
const realWidthRef = ref(window.innerWidth);

View File

@@ -16,19 +16,15 @@
*/
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
export default function getDashboard(param?: {
name: string;
layer: string;
entity: string;
}) {
export default function getDashboard(param?: { name: string; layer: string; entity: string }) {
const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find(
(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 widgets: LayoutConfig[] = [];

View File

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

View File

@@ -43,16 +43,13 @@ export function useEventListener({
if (el) {
const element = ref(el as Element) as Ref<Element>;
const handler = isDebounce
? useDebounceFn(listener, wait)
: useThrottleFn(listener, wait);
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);
const realHandler = wait ? handler : listener;
const removeEventListener = (e: Element) => {
isAddRef.value = true;
e.removeEventListener(name, realHandler, options);
};
const addEventListener = (e: Element) =>
e.addEventListener(name, realHandler, options);
const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options);
const removeWatch = watch(
element,
@@ -64,7 +61,7 @@ export function useEventListener({
});
}
},
{ immediate: true }
{ immediate: true },
);
remove = () => {

View File

@@ -0,0 +1,132 @@
/**
* 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 type { LegendOptions } from "@/types/dashboard";
import { isDef } from "@/utils/is";
export default function useLegendProcess(legend?: LegendOptions) {
let isRight = false;
if (legend && legend.toTheRight) {
isRight = true;
}
function showEchartsLegend(keys: string[]) {
if (legend && isDef(legend.show)) {
if (legend.asTable && legend.show) {
return false;
}
return legend.show;
}
if (keys.length === 1) {
return false;
}
if (legend && legend.asTable) {
return false;
}
return true;
}
function aggregations(data: { [key: string]: number[] }, intervalTime: string[]) {
const source: { [key: string]: unknown }[] = [];
const keys = Object.keys(data || {}).filter((i: any) => Array.isArray(data[i]) && data[i].length);
const headers = [];
for (const [key, value] of keys.entries()) {
const arr = JSON.parse(JSON.stringify(data[value]));
const item: { [key: string]: unknown } = {
name: value,
topN: arr
.map((d: number, index: number) => {
return {
key: intervalTime[index],
value: d,
};
})
.sort((a: { key: string; value: number }, b: { key: string; value: number }) => b.value - a.value)
.filter((_: unknown, index: number) => index < 10),
};
if (legend) {
if (legend.min) {
item.min = Math.min(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "min", label: "Min" });
}
}
if (legend.max) {
item.max = Math.max(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "max", label: "Max" });
}
}
if (legend.mean) {
const total = data[value].reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0);
item.mean = (total / data[value].length).toFixed(4);
if (key === 0) {
headers.push({ value: "mean", label: "Mean" });
}
}
if (legend.total) {
item.total = data[value]
.reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0)
.toFixed(2);
if (key === 0) {
headers.push({ value: "total", label: "Total" });
}
}
}
source.push(item);
}
return { source, headers };
}
function chartColors(keys: string[]) {
let color: string[] = [];
switch (keys.length) {
case 2:
color = ["#FF6A84", "#a0b1e6"];
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
return color;
}
return { showEchartsLegend, isRight, aggregations, chartColors };
}

View File

@@ -17,23 +17,16 @@
import { MetricQueryTypes, Calculations } from "./data";
export function useListConfig(config: any, index: string) {
const i = Number(index);
const types = [
Calculations.Average,
Calculations.ApdexAvg,
Calculations.PercentageAvg,
];
const calculation =
config.metricConfig &&
config.metricConfig[i] &&
config.metricConfig[i].calculation;
const line =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues &&
const types = [Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg, Calculations.CPM5DAvg];
const calculation = config.metricConfig && config.metricConfig[i] && config.metricConfig[i].calculation;
const isLinear =
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
!types.includes(calculation);
const isAvg =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues &&
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
types.includes(calculation);
return {
isLinear: line,
isLinear,
isAvg,
};
}

View File

@@ -20,8 +20,8 @@ import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Instance, Endpoint, Service } from "@/types/selector";
import { MetricConfigOpt } from "@/types/dashboard";
import type { Instance, Endpoint, Service } from "@/types/selector";
import type { MetricConfigOpt } from "@/types/dashboard";
import { MetricCatalog } from "@/views/dashboard/data";
export function useQueryProcessor(config: any) {
@@ -42,94 +42,75 @@ export function useQueryProcessor(config: any) {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const isRelation = [
"ServiceRelation",
"ServiceInstanceRelation",
"EndpointRelation",
"ProcessRelation",
].includes(dashboardStore.entity);
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
dashboardStore.entity,
);
if (isRelation && !selectorStore.currentDestService) {
return;
}
const fragment = config.metrics.map((name: string, index: number) => {
const metricType = config.metricTypes[index] || "";
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
].includes(metricType)
) {
if ([MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics].includes(metricType)) {
variables.push(`$condition${index}: TopNCondition!`);
conditions[`condition${index}`] = {
name,
parentService: ["All"].includes(dashboardStore.entity)
? null
: selectorStore.currentService.value,
normal: selectorStore.currentService
? selectorStore.currentService.normal
: true,
parentService: ["All"].includes(dashboardStore.entity) ? null : selectorStore.currentService.value,
normal: selectorStore.currentService ? selectorStore.currentService.normal : true,
scope: config.catalog,
topN: c.topN || 10,
order: c.sortOrder || "DES",
};
} else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity: {
scope: dashboardStore.entity,
serviceName:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.value,
normal:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.normal,
serviceInstanceName: [
"ServiceInstance",
"ServiceInstanceRelation",
"ProcessRelation",
].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)
const entity = {
scope: config.catalog,
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].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,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess &&
selectorStore.currentDestProcess.value
: undefined,
},
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
: undefined,
};
if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
variables.push(`$condition${index}: RecordCondition!`);
conditions[`condition${index}`] = {
name,
parentEntity: entity,
topN: c.topN || 10,
order: c.sortOrder || "DES",
};
} else {
entity.scope = dashboardStore.entity;
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity,
};
}
}
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
@@ -138,6 +119,7 @@ export function useQueryProcessor(config: any) {
}
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
return {
queryStr,
conditions,
@@ -149,7 +131,7 @@ export function useSourceProcessor(
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
}
},
) {
if (resp.errors) {
ElMessage.error(resp.errors);
@@ -167,23 +149,14 @@ export function useSourceProcessor(
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) {
source[m] =
(resp.data[keys[index]] &&
calculateExp(resp.data[keys[index]].values.values, c)) ||
[];
source[c.label || m] = (resp.data[keys[index]] && calculateExp(resp.data[keys[index]].values.values, c)) || [];
}
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || [];
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, ""));
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 (const item of resVal) {
const values = item.values.values.map((d: { value: number }) =>
aggregation(Number(d.value), c)
);
const values = item.values.values.map((d: { value: number }) => aggregation(Number(d.value), c));
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
@@ -196,16 +169,15 @@ export function useSourceProcessor(
source[m] = aggregation(Number(Object.values(resp.data)[0]), c);
}
if (
type === MetricQueryTypes.SortMetrics ||
type === MetricQueryTypes.ReadSampledRecords
(
[MetricQueryTypes.ReadRecords, MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics] as string[]
).includes(type)
) {
source[m] = (Object.values(resp.data)[0] || []).map(
(d: { value: unknown; name: string }) => {
d.value = aggregation(Number(d.value), c);
source[m] = (Object.values(resp.data)[0] || []).map((d: { value: unknown; name: string }) => {
d.value = aggregation(Number(d.value), c);
return d;
}
);
return d;
});
}
if (type === MetricQueryTypes.READHEATMAP) {
const resVal = Object.values(resp.data)[0] || {};
@@ -221,12 +193,7 @@ export function useSourceProcessor(
});
let buckets = [] as any;
if (resVal.buckets.length) {
buckets = [
resVal.buckets[0].min,
...resVal.buckets.map(
(item: { min: string; max: string }) => item.max
),
];
buckets = [resVal.buckets[0].min, ...resVal.buckets.map((item: { min: string; max: string }) => item.max)];
}
source[m] = { nodes, buckets }; // nodes: number[][]
@@ -238,13 +205,19 @@ export function useSourceProcessor(
export function useQueryPodsMetrics(
pods: Array<Instance | Endpoint | Service | any>,
config: { metrics: string[]; metricTypes: string[] },
scope: string
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
scope: string,
) {
if (!(config.metrics && config.metrics[0])) {
const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) {
return;
}
if (!(config.metricTypes && config.metricTypes[0])) {
const metrics = (config.metrics || []).filter((m: string) => m);
if (!metrics.length) {
return;
}
const appStore = useAppStoreWithOut();
@@ -254,35 +227,39 @@ export function useQueryPodsMetrics(
};
const variables: string[] = [`$duration: Duration!`];
const currentService = selectorStore.currentService || {};
const fragmentList = pods.map(
(
d: (Instance | Endpoint | Service) & { normal: boolean },
index: number
) => {
const param = {
scope,
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,
const fragmentList = pods.map((d: (Instance | Endpoint | Service) & { normal: boolean }, index: number) => {
const param = {
scope,
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,
};
const f = metrics.map((name: string, idx: number) => {
const metricType = metricTypes[idx] || "";
variables.push(`$condition${index}${idx}: MetricsCondition!`);
conditions[`condition${index}${idx}`] = {
name,
entity: param,
};
const f = config.metrics.map((name: string, idx: number) => {
const metricType = config.metricTypes[idx] || "";
conditions[`condition${index}${idx}`] = {
name,
entity: param,
};
variables.push(`$condition${index}${idx}: MetricsCondition!`);
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, duration: $duration)${RespFields[metricType]}`;
});
return f;
}
);
let labelStr = "";
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const c = config.metricConfig[idx] || {};
variables.push(`$labels${index}${idx}: [String!]!`);
labelStr = `labels: $labels${index}${idx}, `;
const labels = (c.labelsIndex || "").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 queryStr = `query queryData(${variables}) {${fragment}}`;
return { queryStr, conditions };
}
export function usePodsSource(
pods: Array<Instance | Endpoint>,
resp: { errors: string; data: { [key: string]: any } },
@@ -290,39 +267,77 @@ export function usePodsSource(
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
}
},
): any {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const data = pods.map((d: Instance | any, idx: number) => {
config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
d[name] = aggregation(resp.data[key], c);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {};
if (
[
Calculations.Average,
Calculations.ApdexAvg,
Calculations.PercentageAvg,
].includes(c.calculation)
[Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg, Calculations.CPM5DAvg].includes(
c.calculation,
)
) {
d[name]["avg"] = calculateExp(resp.data[key].values.values, c);
}
d[name]["values"] = resp.data[key].values.values.map(
(val: { value: number }) => aggregation(val.value, c)
);
d[name]["values"] = resp.data[key].values.values.map((val: { value: number }) => aggregation(val.value, c));
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = resp.data[key] || [];
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 < resVal.length; i++) {
const item = resVal[i];
const values = item.values.values.map((d: { value: number }) => aggregation(Number(d.value), c));
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
let key = item.label;
if (labels[indexNum] && indexNum > -1) {
key = labels[indexNum];
}
if (!d[key]) {
d[key] = {};
}
if (
[Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg, Calculations.CPM5DAvg].includes(
c.calculation,
)
) {
d[key]["avg"] = calculateExp(item.values.values, c);
}
d[key]["values"] = values;
if (idx === 0) {
names.push(key);
metricConfigArr.push({ ...c, index: i });
metricTypesArr.push(config.metricTypes[index]);
}
}
}
});
return d;
});
return data;
return { data, names, metricConfigArr, metricTypesArr };
}
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut();
@@ -349,13 +364,8 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
return { queryStr, conditions };
}
function calculateExp(
arr: { value: number }[],
config: { calculation?: string }
): (number | string)[] {
const sum = arr
.map((d: { value: number }) => d.value)
.reduce((a, b) => a + b);
function calculateExp(arr: { value: number }[], config: { calculation?: string }): (number | string)[] {
const sum = arr.map((d: { value: number }) => d.value).reduce((a, b) => a + b);
let data: (number | string)[] = [];
switch (config.calculation) {
case Calculations.Average:
@@ -367,6 +377,13 @@ function calculateExp(
case Calculations.ApdexAvg:
data = [(sum / arr.length / 10000).toFixed(2)];
break;
case Calculations.CPM5DAvg:
data = [
sum / arr.length / 100000 < 1 && sum / arr.length / 100000 !== 0
? (sum / arr.length / 100000).toFixed(5)
: (sum / arr.length / 100000).toFixed(2),
];
break;
default:
data = arr.map((d) => aggregation(d.value, config));
break;
@@ -374,10 +391,7 @@ function calculateExp(
return data;
}
export function aggregation(
val: number,
config: { calculation?: string }
): number | string {
export function aggregation(val: number, config: { calculation?: string }): number | string {
let data: number | string = Number(val);
switch (config.calculation) {
@@ -399,15 +413,15 @@ export function aggregation(
case Calculations.Apdex:
data = (val / 10000).toFixed(2);
break;
case Calculations.CPM5D:
data = val / 100000 < 1 && val / 100000 !== 0 ? (val / 100000).toFixed(5) : (val / 100000).toFixed(2);
break;
case Calculations.ConvertSeconds:
data = dayjs(val * 1000).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.ConvertMilliseconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.Precision:
data = data.toFixed(2);
break;
case Calculations.MsToS:
data = (val / 1000).toFixed(2);
break;
@@ -417,6 +431,12 @@ export function aggregation(
case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2);
break;
case Calculations.ApdexAvg:
data = (val / 10000).toFixed(2);
break;
case Calculations.CPM5DAvg:
data = val / 100000 < 1 && val / 100000 !== 0 ? (val / 100000).toFixed(5) : (val / 100000).toFixed(2);
break;
default:
data;
break;
@@ -432,10 +452,9 @@ export async function useGetMetricEntity(metric: string, metricType: any) {
let catalog = "";
const dashboardStore = useDashboardStore();
if (
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
].includes(metricType)
[MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics, MetricQueryTypes.ReadRecords].includes(
metricType,
)
) {
const res = await dashboardStore.fetchMetricList(metric);
if (res.errors) {

View File

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

View File

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

View File

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

View File

@@ -14,30 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="nav-bar flex-h">
<div class="title">{{ appStore.pageTitle || t(pageName) }}</div>
<div class="title">{{ route.name === "ViewWidget" ? "" : appStore.pageTitle || t(pageName) }}</div>
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<TimePicker
:value="time"
:value="[appStore.durationRow.start, appStore.durationRow.end]"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
/>
<span>
UTC{{ appStore.utcHour >= 0 ? "+" : ""
}}{{ `${appStore.utcHour}:${appStore.utcMin}` }}
</span>
<span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
<span title="refresh" class="ghost ml-5 cp" @click="handleReload">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
</span>
<span class="version ml-5 cp">
<el-popover
trigger="hover"
width="250"
placement="bottom"
effect="light"
:content="appStore.version"
>
<el-popover trigger="hover" width="250" placement="bottom" effect="light" :content="appStore.version">
<template #reference>
<span>
<Icon iconName="info_outline" size="middle" />
@@ -49,100 +40,90 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import getLocalTime from "@/utils/localtime";
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const route = useRoute();
const pageName = ref<string>("");
const timeRange = ref<number>(0);
const time = ref<Date[]>([
appStore.durationRow.start,
appStore.durationRow.end,
]);
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const route = useRoute();
const pageName = ref<string>("");
const timeRange = ref<number>(0);
resetDuration();
getVersion();
const setConfig = (value: string) => {
pageName.value = value || "";
};
resetDuration();
getVersion();
const setConfig = (value: string) => {
pageName.value = value || "";
};
const handleReload = () => {
const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, 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;
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));
}
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({
start: new Date(d.start),
end: new Date(d.end),
step: d.step,
});
appStore.updateUTC(d.utc);
time.value = [new Date(d.start), new Date(d.end)];
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({
start: new Date(d.start),
end: new Date(d.end),
step: d.step,
});
appStore.updateUTC(d.utc);
}
}
}
</script>
<style lang="scss" scoped>
.nav-bar {
padding: 5px 10px 5px 28px;
text-align: left;
justify-content: space-between;
background-color: #fafbfc;
border-bottom: 1px solid #dfe4e8;
color: #222;
font-size: 12px;
}
.nav-bar {
padding: 5px 10px;
text-align: left;
justify-content: space-between;
background-color: #fafbfc;
border-bottom: 1px solid #dfe4e8;
color: #222;
font-size: 12px;
}
.nav-bar.dark {
background-color: #333840;
border-bottom: 1px solid #252a2f;
color: #fafbfc;
}
.nav-bar.dark {
background-color: #333840;
border-bottom: 1px solid #252a2f;
color: #fafbfc;
}
.title {
font-size: 14px;
font-weight: 500;
height: 28px;
line-height: 28px;
}
.title {
font-size: 14px;
font-weight: 500;
height: 28px;
line-height: 28px;
}
.nav-tabs {
padding: 10px;
}
.nav-tabs {
padding: 10px;
}
</style>

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ const msg = {
dashboards: "Dashboards",
profiles: "Profiles",
database: "Database",
mySQL: "MySQL",
mySQL: "MySQL/MariaDB",
serviceName: "Service Name",
technologies: "Technologies",
generalServicePanel: "General Service Panel",
@@ -52,17 +52,19 @@ const msg = {
instance: "Instance",
create: "Create",
loading: "Loading",
selectVisualization: "Visualize your metrics",
selectVisualization: "Visualize Metrics",
visualization: "Visualization",
graphStyles: "Graph styles",
widgetOptions: "Widget options",
standardOptions: "Standard options",
graphStyles: "Graph Styles",
widgetOptions: "Widget Options",
standardOptions: "Standard Options",
max: "Max",
min: "Min",
plus: "Plus",
mean: "Mean",
minus: "Minus",
multiply: "Multiply",
divide: "Divide",
total: "Total",
convertToMilliseconds: "Convert Unix Timestamp(milliseconds)",
convertToSeconds: "Convert Unix Timestamp(seconds)",
smooth: "Smooth",
@@ -108,6 +110,7 @@ const msg = {
showXAxis: "Show X Axis",
showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate",
nameEmptyError: "The dashboard name cannot be empty",
showGroup: "Show Group",
noRoot: "Please set a root dashboard for",
noWidget: "Please add widgets.",
@@ -123,6 +126,7 @@ const msg = {
editWarning: "You are entering edit mode",
viewWarning: "You are entering view mode",
virtualDatabase: "Virtual Database",
virtualCache: "Virtual Cache",
reloadDashboards: "Reload dashboards",
kubernetesService: "Service",
kubernetesCluster: "Cluster",
@@ -144,6 +148,7 @@ const msg = {
pause: "Pause",
begin: "Start",
associateOptions: "Association Options",
associateMetrics: "Association Metrics",
widget: "Widget",
nameTip:
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters",
@@ -152,6 +157,35 @@ const msg = {
text: "Text",
query: "Query",
postgreSQL: "PostgreSQL",
endpointTips: "The table shows up to 20 pieces of endpoints.",
apisix: "APISIX",
viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics",
queryOrder: "Query By Duration",
setOrder: "Query Order",
latency: "Latency",
metricValues: "Metric Values",
queryConditions: "Query Conditions",
enableRelatedTrace: "Enable Related Trace",
maxTraceDuration: "Maximum Duration",
minTraceDuration: "Minimum Duration",
legendOptions: "Legend Options",
showLegend: "Show Legend",
asTable: "As Table",
toTheRight: "To The Right",
legendValues: "Legend Values",
minDuration: "Minimal Request Duration",
when4xx: "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",
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",
hourTip: "Select Hour",
minuteTip: "Select Minute",
@@ -170,7 +204,7 @@ const msg = {
topology: "Topology",
trace: "Trace",
alarm: "Alerting",
auto: "Auto",
auto: "Auto Fresh",
reload: "Reload",
version: "Version",
copy: "Copy",
@@ -244,7 +278,7 @@ const msg = {
entityType: "Entity Type",
maxItemNum: "Max number of Item",
unknownMetrics: "Unknown Metrics",
labels: "Labels",
labels: "Label",
aggregation: "Calculation",
unit: "Unit",
labelsIndex: "Label Subscript",
@@ -286,8 +320,7 @@ const msg = {
viewLogs: "View Logs",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`,
keywordsOfContentLogTips:
"Current storage of SkyWalking OAP server does not support this.",
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.",
setEvent: "Set Event",
viewAttributes: "View",
serviceEvents: "Service Events",
@@ -305,6 +338,7 @@ const msg = {
eventsParameters: "Event Parameters",
eventDetail: "Event Detail",
value: "Value",
key: "Key",
show: "Show",
hide: "Hide",
statistics: "Statistics",
@@ -340,8 +374,13 @@ const msg = {
addKeywordsOfContent: "Please input a keyword of content",
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
noticeTag: "Please press Enter after inputting a tag(key=value).",
conditionNotice:
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language",
gateway: "Gateway",
virtualMQ: "Virtual MQ",
AWSCloud: "AWS Cloud",
AWSCloudEKS: "EKS",
AWSCloudS3: "S3",
AWSCloudDynamoDB: "DynamoDB",
};
export default msg;

View File

@@ -32,7 +32,7 @@ const msg = {
dashboards: "Paneles",
profiles: "Perfiles",
database: "Base de Datos",
mySQL: "MySQL",
mySQL: "MySQL/MariaDB",
serviceName: "Nombre Servicio",
technologies: "Tecnologías",
generalServicePanel: "Panel Servicio General",
@@ -59,9 +59,11 @@ const msg = {
standardOptions: "Opciones estandar",
max: "Máx",
min: "Mín",
mean: "Promedio",
plus: "Más",
minus: "Menoss",
multiply: "Multiplcar",
total: "Todo",
divide: "Dividir",
convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
convertToSeconds: "Convertir Unix Timestamp(segundos)",
@@ -75,10 +77,8 @@ const msg = {
editGraph: "Editar Opciones",
dashboardName: "Selecciona Nombre del Panel",
linkDashboard: "Nombre del panel relacionado con llamadas de la topología",
linkServerMetrics:
"Métricas de servidor relacionadas con llamadas de la topología",
linkClientMetrics:
"Métricas de cliente relacionadas con llamadas de la topología",
linkServerMetrics: "Métricas de servidor 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",
nodeMetrics: "Mêtricas relacionas con nodos de la topología",
instanceDashboard: "Nombre del panel relacionado con instancias de servicio",
@@ -110,6 +110,7 @@ const msg = {
showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado",
nameEmptyError: "El nombre del panel no puede estar vacío",
showGroup: "Mostrar Grupo",
noRoot: "Por favor ponga la raíz del panel",
noWidget: "Por favor añada widgets.",
@@ -125,6 +126,7 @@ const msg = {
editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual",
virtualCache: "Caché virtual",
reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio",
kubernetesCluster: "Cluster",
@@ -144,6 +146,7 @@ const msg = {
pause: "Pausa",
begin: "Inicio",
associateOptions: "Opciones de asociación",
associateMetrics: "Índice de correlación",
widget: "Dispositivo pequeño",
text: "Texto",
duplicateName: "Nombre duplicado",
@@ -152,10 +155,39 @@ const msg = {
enableAssociate: "Activar asociación",
query: "Consulta",
postgreSQL: "PostgreSQL",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
apisix: "APISIX",
queryOrder: "Consulta por duración",
setOrder: "Orden de consulta",
latency: "Retraso",
metricValues: "Valor métrico",
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",
hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo",
viewTrace: "Ver trazas relacionadas",
relatedTraceOptions: "Opciones de seguimiento relacionadas",
setLatencyDuration: "Índice de correlación retardada",
enableRelatedTrace: "Activar trazas relacionadas",
queryConditions: "Condiciones de consulta",
maxTraceDuration: "Duración máxima",
minTraceDuration: "Duración mínima",
legendOptions: "Opciones de leyenda",
showLegend: "Mostrar leyenda",
asTable: "Como tabla",
toTheRight: "Derecha",
minDuration: "Duración mínima de la solicitud",
when4xx: "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",
taskTitle: "Reglas de recolección de peticiones y respuestas HTTP",
period: "Period",
windows: "Windows",
second: "s",
yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
@@ -170,7 +202,7 @@ const msg = {
topology: "Topología",
trace: "Traza",
alarm: "Recordatorio en curso",
auto: "Auto",
auto: "Auto Fresh",
reload: "Recargar",
version: "Versión",
copy: "Copiar",
@@ -286,8 +318,7 @@ const msg = {
viewLogs: "Ver Registro de Datos",
logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`,
keywordsOfContentLogTips:
"El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
setEvent: "Establecer Evento",
viewAttributes: "Ver",
serviceEvents: "Eventos Servico",
@@ -305,6 +336,7 @@ const msg = {
eventsParameters: "Parámetro del Evento",
eventDetail: "Detalle del Evento",
value: "Valor",
key: "Clave",
show: "Mostrar",
hide: "Oculatr",
statistics: "Estadísticas",
@@ -317,8 +349,7 @@ const msg = {
destEndpoint: "Endpoint Destinación",
eventSource: "Fuente Envento",
modalTitle: "Inspección",
selectRedirectPage:
"Quiere inspeccionar las Trazas or Registros de datos del servicio %s?",
selectRedirectPage: "Quiere inspeccionar las Trazas or Registros de datos del servicio %s?",
logAnalysis: "Lenguaje de Análisis de Registro de Datos",
logDataBody: "Contenido del Registro de Datos",
addType: "Por favor introduzca un tipo",
@@ -339,12 +370,16 @@ const msg = {
addTraceID: "Por favor introduzca el ID de la traza",
addTags: "Por favor introduzaca una etiqueta",
addKeywordsOfContent: "Por favor introduzca una clave de contenido",
addExcludingKeywordsOfContent:
"Por favor introduzca una clave excluyente de contenido",
noticeTag:
"Por favor presione Intro después de introducir una etiqueta(clave=valor).",
addExcludingKeywordsOfContent: "Por favor introduzca una clave excluyente de contenido",
noticeTag: "Por favor presione Intro después de introducir una etiqueta(clave=valor).",
conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje",
gateway: "Puerta",
virtualMQ: "MQ virtual",
AWSCloud: "AWS Cloud",
AWSCloudEKS: "EKS",
AWSCloudS3: "S3",
AWSCloudDynamoDB: "DynamoDB",
};
export default msg;

View File

@@ -32,7 +32,7 @@ const msg = {
dashboards: "仪表盘",
profiles: "性能剖析",
database: "数据库",
mySQL: "MySQL",
mySQL: "MySQL/MariaDB",
serviceName: "服务名称",
technologies: "技术",
health: "健康",
@@ -56,10 +56,12 @@ const msg = {
standardOptions: "标准选项",
max: "最大值",
min: "最小值",
mean: "平均值",
plus: "加法",
minus: "减法",
multiply: "乘法",
divide: "除法",
total: "总计",
convertToMilliseconds: "转换Unix时间戳毫秒",
convertToSeconds: "转换Unix时间戳",
smooth: "光滑的",
@@ -106,6 +108,7 @@ const msg = {
showXAxis: "显示X轴",
showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复",
nameEmptyError: "仪表板名称不能为空",
noRoot: "请设置根仪表板,为",
showGroup: "显示分组",
noWidget: "请添加组件",
@@ -121,6 +124,7 @@ const msg = {
editWarning: "你正在进入编辑模式",
viewWarning: "你正在进入预览模式",
virtualDatabase: "虚拟数据库",
virtualCache: "虚拟缓存",
reloadDashboards: "重新加载仪表盘",
kubernetesService: "服务",
kubernetesCluster: "集群",
@@ -142,6 +146,7 @@ const msg = {
pause: "暂停",
begin: "开始",
associateOptions: "关联选项",
associateMetrics: "关联指标",
widget: "部件",
enableAssociate: "启用关联",
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
@@ -149,6 +154,35 @@ const msg = {
text: "文本",
query: "查询",
postgreSQL: "PostgreSQL",
endpointTips: "这里最多展示20条endpoints。",
apisix: "APISIX",
viewTrace: "查看相关Trace",
relatedTraceOptions: "相关的Trace选项",
setLatencyDuration: "延迟相关指标",
queryOrder: "按持续时间查询",
setOrder: "查询顺序",
latency: "延迟",
metricValues: "指标值",
enableRelatedTrace: "启用相关Trace",
queryConditions: "查询条件",
maxTraceDuration: "最大持续时间",
minTraceDuration: "最小持续时间",
legendOptions: "图例选项",
showLegend: "显示图例",
asTable: "作为表格",
toTheRight: "在右边",
legendValues: "图例值",
minDuration: "最小请求持续时间",
when4xx: "当响应代码介于400和499之间时带有跟踪的HTTP请求和响应示例",
when5xx: "当响应代码介于500和599之间时带有跟踪的HTTP请求和响应示例",
taskTitle: "HTTP请求和响应收集规则",
iframeWidgetTip: "添加widget的链接",
iframeSrc: "Iframe链接",
generateLink: "生成链接",
setDuration: "锁定查询持续时间",
openFunction: "OpenFunction",
period: "周期",
windows: "Windows",
seconds: "秒",
hourTip: "选择小时",
minuteTip: "选择分钟",
@@ -167,7 +201,7 @@ const msg = {
trace: "追踪",
alarm: "告警",
event: "事件",
auto: "自动",
auto: "自动更新",
reload: "刷新",
editmode: "编辑模式",
version: "版本",
@@ -255,12 +289,9 @@ const msg = {
chartType: "图表类型",
currentDepth: "当前深度",
defaultDepth: "默认深度",
traceTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
logTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
alarmTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
traceTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
logTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
alarmTagsTip: "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
tagsLink: "配置词汇页",
addTag: "请添加标签",
logCategory: "日志类别",
@@ -283,8 +314,7 @@ const msg = {
contentType: "内容类型",
content: "内容",
viewLogs: "查看日志",
logsTagsTip:
"只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
keywordsOfContentLogTips: "SkyWalking OAP服务器的当前存储不支持此操作",
setEvent: "设置事件",
viewAttributes: "查看",
@@ -303,6 +333,7 @@ const msg = {
eventsParameters: "事件参数",
eventDetail: "事件详情",
value: "数值",
key: "Key",
tableHeader: "表头名称",
tableValues: "表值",
show: "展示",
@@ -340,8 +371,13 @@ const msg = {
addKeywordsOfContent: "请输入一个内容关键词",
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice:
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
conditionNotice: "请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言",
gateway: "网关",
virtualMQ: "虚拟消息队列",
AWSCloud: "AWS云服务",
AWSCloudEKS: "EKS",
AWSCloudS3: "S3",
AWSCloudDynamoDB: "DynamoDB",
};
export default msg;

View File

@@ -22,6 +22,7 @@ import components from "@/components";
import i18n from "./locales";
import { useAppStoreWithOut } from "@/store/modules/app";
import "./styles/index.ts";
import "virtual:svg-icons-register";
const app = createApp(App);
const appStore = useAppStoreWithOut();

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesAlarm: Array<RouteRecordRaw> = [
@@ -25,18 +25,13 @@ export const routesAlarm: Array<RouteRecordRaw> = [
title: "alarm",
icon: "spam",
hasGroup: false,
exact: true,
},
component: Layout,
children: [
{
path: "/alerting",
name: "Alarm",
meta: {
exact: false,
},
component: () =>
import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
component: () => import("@/views/Alarm.vue"),
},
],
},

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesDashboard: Array<RouteRecordRaw> = [
@@ -26,69 +26,48 @@ export const routesDashboard: Array<RouteRecordRaw> = [
title: "dashboards",
icon: "dashboard_customize",
hasGroup: true,
exact: true,
},
children: [
{
path: "/dashboard/list",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/List.vue"
),
component: () => import("@/views/dashboard/List.vue"),
name: "List",
meta: {
title: "dashboardList",
exact: false,
},
},
{
path: "/dashboard/new",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/New.vue"
),
component: () => import("@/views/dashboard/New.vue"),
name: "New",
meta: {
title: "dashboardNew",
exact: false,
},
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
meta: {
notShow: true,
},
children: [
{
path: "/dashboard/:layerId/:entity/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "CreateActiveTabIndex",
},
],
},
{
path: "",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: {
@@ -97,30 +76,20 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
redirect: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewServiceRelation",
meta: {
notShow: true,
@@ -128,18 +97,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewServiceRelation",
},
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewServiceRelationActiveTabIndex",
},
],
@@ -147,10 +110,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{
path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewPod",
meta: {
notShow: true,
@@ -158,30 +118,20 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewPod",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewPodActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => import("@/views/dashboard/Edit.vue"),
name: "PodRelation",
meta: {
notShow: true,
@@ -189,18 +139,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewPodRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewPodRelationActiveTabIndex",
},
],
@@ -209,10 +153,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ProcessRelation",
meta: {
notShow: true,
@@ -220,30 +161,36 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewProcessRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewProcessRelationActiveTabIndex",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/duration/:duration",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
component: () => import("@/views/dashboard/Edit.vue"),
name: "ViewProcessRelationDuration",
},
],
},
{
path: "",
name: "Widget",
component: () => import("@/views/dashboard/Widget.vue"),
meta: {
notShow: true,
},
children: [
{
path: "/page/:layer/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:config/:duration?",
component: () => import("@/views/dashboard/Widget.vue"),
name: "ViewWidget",
},
],
},
],
},
];

79
src/router/data/aws.ts Normal file
View File

@@ -0,0 +1,79 @@
/**
* 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.
*/
export default [
{
path: "",
name: "AWSCloud",
meta: {
title: "AWSCloud",
icon: "cloud_queue",
hasGroup: true,
},
redirect: "/aws-eks",
children: [
{
path: "/aws-eks",
name: "AWSCloudEKS",
meta: {
title: "AWSCloudEKS",
layer: "AWS_EKS",
},
},
{
path: "/aws-eks/tab/:activeTabIndex",
name: "EKSActiveTabIndex",
meta: {
notShow: true,
layer: "AWS_EKS",
},
},
{
path: "/aws-s3",
name: "AWSCloudS3",
meta: {
title: "AWSCloudS3",
layer: "AWS_S3",
},
},
{
path: "/aws-s3/tab/:activeTabIndex",
name: "S3ActiveTabIndex",
meta: {
notShow: true,
layer: "AWS_S3",
},
},
{
path: "/aws-dynamodb",
name: "AWSCloudDynamoDB",
meta: {
title: "AWSCloudDynamoDB",
layer: "AWS_DYNAMODB",
},
},
{
path: "/aws-dynamodb/tab/:activeTabIndex",
name: "DynamoDBActiveTabIndex",
meta: {
notShow: true,
layer: "AWS_DYNAMODB",
},
},
],
},
];

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesBrowser: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Browser",
@@ -26,26 +24,22 @@ export const routesBrowser: Array<RouteRecordRaw> = [
icon: "language",
},
redirect: "/browser",
component: Layout,
children: [
{
path: "/browser",
name: "Browser",
meta: {
title: "browser",
exact: true,
layer: "BROWSER",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
layer: "BROWSER",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesDatabase: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Database",
@@ -27,45 +25,54 @@ export const routesDatabase: Array<RouteRecordRaw> = [
hasGroup: true,
},
redirect: "/mySQL",
component: Layout,
children: [
{
path: "/mySQL",
name: "MySQL",
meta: {
title: "mySQL",
exact: true,
layer: "MYSQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mySQL/tab/:activeTabIndex",
name: "MySQLActiveTabIndex",
meta: {
notShow: true,
layer: "MYSQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL",
name: "PostgreSQL",
meta: {
title: "postgreSQL",
exact: true,
layer: "POSTGRESQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL/tab/:activeTabIndex",
name: "PostgreSQLActiveTabIndex",
meta: {
notShow: true,
layer: "POSTGRESQL",
},
},
{
path: "/aws-dynamodb",
name: "AWSCloudDynamoDB",
meta: {
title: "AWSCloudDynamoDB",
layer: "AWS_DYNAMODB",
},
},
{
path: "/aws-dynamodb/tab/:activeTabIndex",
name: "DynamoDBActiveTabIndex",
meta: {
notShow: true,
layer: "AWS_DYNAMODB",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,34 +14,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesFunctions: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Functions",
meta: {
title: "functions",
icon: "cloud_queue",
icon: "functions",
hasGroup: true,
},
redirect: "/functions",
component: Layout,
children: [
{
path: "/functions",
name: "Functions",
path: "/openFunction",
name: "OpenFunction",
meta: {
exact: true,
title: "openFunction",
layer: "FAAS",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/functions/tab/:activeTabIndex",
name: "FunctionsActiveTabIndex",
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
path: "/openFunction/tab/:activeTabIndex",
name: "OpenFunctionActiveTabIndex",
meta: {
notShow: true,
layer: "FAAS",
},
},
],
},

View File

@@ -0,0 +1,47 @@
/**
* 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.
*/
export default [
{
path: "",
name: "Gateway",
meta: {
title: "gateway",
icon: "gateway",
hasGroup: true,
},
redirect: "/apisix",
children: [
{
path: "/apisix",
name: "APISIX",
meta: {
title: "apisix",
layer: "APISIX",
},
},
{
path: "/apisix/tab/:activeTabIndex",
name: "APISIXActiveTabIndex",
meta: {
notShow: true,
layer: "APISIX",
},
},
],
},
];

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesGen: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "General",
@@ -25,48 +23,71 @@ export const routesGen: Array<RouteRecordRaw> = [
title: "general",
icon: "chart",
hasGroup: true,
exact: true,
},
component: Layout,
children: [
{
path: "/general",
name: "GeneralServices",
meta: {
exact: true,
title: "services",
layer: "GENERAL",
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true,
notShow: true,
layer: "GENERAL",
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/database",
name: "VirtualDatabase",
meta: {
title: "virtualDatabase",
exact: true,
layer: "VIRTUAL_DATABASE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/database/tab/:activeTabIndex",
name: "VirtualDatabaseActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_DATABASE",
},
},
{
path: "/cache",
name: "VirtualCache",
meta: {
title: "virtualCache",
layer: "VIRTUAL_CACHE",
},
},
{
path: "/cache/tab/:activeTabIndex",
name: "VirtualCacheActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_CACHE",
},
},
{
path: "/mq",
name: "VirtualMQ",
meta: {
title: "virtualMQ",
layer: "VIRTUAL_MQ",
},
},
{
path: "/mq/tab/:activeTabIndex",
name: "VirtualMQActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_MQ",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

39
src/router/data/index.ts Normal file
View File

@@ -0,0 +1,39 @@
/**
* 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 general from "./general";
import serviceMesh from "./serviceMesh";
import database from "./database";
import infrastructure from "./infrastructure";
import selfObservability from "./selfObservability";
import functions from "./functions";
import browser from "./browser";
import k8s from "./k8s";
import gateway from "./gateway";
import aws from "./aws";
export default [
...general,
...serviceMesh,
...functions,
...k8s,
...infrastructure,
...aws,
...browser,
...gateway,
...database,
...selfObservability,
];

View File

@@ -14,30 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesInfra: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Infrastructure",
meta: {
title: "infrastructure",
icon: "scatter_plot",
exact: true,
hasGroup: true,
},
redirect: "/linux",
component: Layout,
children: [
{
path: "/linux",
name: "Linux",
meta: {
title: "linux",
layer: "OS_LINUX",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/linux/tab/:activeTabIndex",
@@ -45,26 +40,26 @@ export const routesInfra: Array<RouteRecordRaw> = [
meta: {
title: "linux",
notShow: true,
layer: "OS_LINUX",
},
},
{
path: "/windows",
name: "Windows",
meta: {
title: "windows",
layer: "OS_WINDOWS",
},
},
{
path: "/windows/tab/:activeTabIndex",
name: "WindowsActiveTabIndex",
meta: {
title: "windows",
notShow: true,
layer: "OS_WINDOWS",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// {
// path: "/infrastructure/vm",
// name: "VirtualMachine",
// meta: {
// title: "virtualMachine",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
// {
// path: "/infrastructure/k8s",
// name: "Kubernetes",
// meta: {
// title: "kubernetes",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
],
},
];

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesK8s: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Kubernetes",
@@ -27,7 +25,6 @@ export const routesK8s: Array<RouteRecordRaw> = [
hasGroup: true,
},
redirect: "/kubernetes/cluster",
component: Layout,
children: [
{
path: "/kubernetes/cluster",
@@ -35,9 +32,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "kubernetesCluster",
layer: "K8S",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
@@ -45,9 +41,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
layer: "K8S",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service",
@@ -55,9 +50,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "kubernetesService",
layer: "K8S_SERVICE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service/tab/:activeTabIndex",
@@ -65,9 +59,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
layer: "K8S_SERVICE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesSelf: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "SelfObservability",
@@ -27,43 +25,38 @@ export const routesSelf: Array<RouteRecordRaw> = [
icon: "logo",
hasGroup: true,
},
component: Layout,
children: [
{
path: "/self/skyWalkingServer",
name: "SkyWalkingServer",
meta: {
title: "skyWalkingServer",
layer: "SO11Y_OAP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_OAP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite",
name: "Satellite",
meta: {
title: "satellite",
layer: "SO11Y_SATELLITE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_SATELLITE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesMesh: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "ServiceMesh",
@@ -27,7 +25,6 @@ export const routesMesh: Array<RouteRecordRaw> = [
icon: "epic",
hasGroup: true,
},
component: Layout,
children: [
{
path: "/mesh/services",
@@ -35,18 +32,16 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "services",
layer: "MESH",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
layer: "MESH",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel",
@@ -54,18 +49,16 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "controlPanel",
layer: "MESH_CP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
layer: "MESH_CP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel",
@@ -73,9 +66,8 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "dataPanel",
layer: "MESH_DP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
@@ -83,9 +75,8 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
layer: "MESH_DP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,35 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import { routesGen } from "./general";
import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import type { RouteRecordRaw } from "vue-router";
import { createRouter, createWebHistory } from "vue-router";
import { routesDashboard } from "./dashboard";
import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability";
import { routesFunctions } from "./functions";
import { routesBrowser } from "./browser";
import { routesK8s } from "./k8s";
import routesLayers from "./layer";
const routes: Array<RouteRecordRaw> = [
...routesGen,
...routesMesh,
...routesFunctions,
...routesK8s,
...routesInfra,
...routesBrowser,
...routesDatabase,
...routesSelf,
...routesDashboard,
...routesAlarm,
...routesSetting,
];
const routes: Array<RouteRecordRaw> = [...routesLayers, ...routesDashboard, ...routesAlarm, ...routesSetting];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});

View File

@@ -14,11 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// https://docs.cypress.io/api/introduction/api.html
import LayerJson from "./data";
import Layout from "@/layout/Index.vue";
describe("My First Test", () => {
it("Visits the app root url", () => {
cy.visit("/");
cy.contains("h1", "Welcome to Your Vue.js + TypeScript App");
function layerDashboards() {
const routes = LayerJson.map((item: any) => {
item.component = Layout;
if (item.children) {
item.children = item.children.map((d: any) => {
d.component = () => import("@/views/Layer.vue");
return d;
});
}
return item;
});
});
return routes;
}
export default layerDashboards();

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesSetting: Array<RouteRecordRaw> = [
@@ -25,7 +25,6 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings",
icon: "settings",
hasGroup: false,
exact: false,
},
component: Layout,
children: [
@@ -36,10 +35,8 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings",
icon: "settings",
hasGroup: false,
exact: false,
},
component: () =>
import(/* webpackChunkName: "settings" */ "@/views/Settings.vue"),
component: () => import("@/views/Settings.vue"),
},
],
},

View File

@@ -37,3 +37,5 @@ export const TimeRangeConfig = {
textAlign: "center",
text: "text",
};
export const ControlsTypes = ["Trace", "Profile", "Log", "DemandLog", "Ebpf", "NetworkProfiling", "ThirdPartyApp"];

View File

@@ -17,8 +17,8 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Alarm } from "@/types/alarm";
import type { AxiosResponse } from "axios";
import type { Alarm } from "@/types/alarm";
interface AlarmState {
loading: boolean;
@@ -35,9 +35,7 @@ export const alarmStore = defineStore({
}),
actions: {
async getAlarms(params: any) {
const res: AxiosResponse = await graphql
.query("queryAlarms")
.params(params);
const res: AxiosResponse = await graphql.query("queryAlarms").params(params);
if (res.data.errors) {
return res.data;
}

View File

@@ -17,9 +17,9 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import { Duration, DurationTime } from "@/types/app";
import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime";
import { AxiosResponse } from "axios";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data";
/*global Nullable*/
@@ -71,7 +71,7 @@ export const appStore = defineStore({
step: this.duration.step,
};
},
intervalTime(): string[] {
intervalUnix(): number[] {
let interval = 946080000000;
switch (this.duration.step) {
case "MINUTE":
@@ -92,17 +92,20 @@ export const appStore = defineStore({
this.duration.start.getMonth());
break;
}
const utcSpace =
(this.utcHour + new Date().getTimezoneOffset() / 60) * 3600000 +
this.utcMin * 60000;
const utcSpace = (this.utcHour + new Date().getTimezoneOffset() / 60) * 3600000 + this.utcMin * 60000;
const startUnix: number = this.duration.start.getTime();
const endUnix: number = this.duration.end.getTime();
const timeIntervals: string[] = [];
const timeIntervals: number[] = [];
for (let i = 0; i <= endUnix - startUnix; i += interval) {
const temp: string = dateFormatTime(
new Date(startUnix + i - utcSpace),
this.duration.step
);
timeIntervals.push(startUnix + i - utcSpace);
}
return timeIntervals;
},
intervalTime(): string[] {
const arr = this.intervalUnix;
const timeIntervals: string[] = [];
for (const item of arr) {
const temp: string = dateFormatTime(new Date(item), this.duration.step);
timeIntervals.push(temp);
}
return timeIntervals;
@@ -152,13 +155,11 @@ export const appStore = defineStore({
this.eventStack.forEach((event: any) => {
setTimeout(event(), 0);
}),
500
500,
);
},
async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql
.query("queryOAPTimeInfo")
.params({});
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else {
@@ -171,9 +172,7 @@ export const appStore = defineStore({
return res.data;
},
async fetchVersion(): Promise<void> {
const res: AxiosResponse = await graphql
.query("queryOAPVersion")
.params({});
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) {
return res.data;
}

View File

@@ -16,13 +16,13 @@
*/
import { defineStore } from "pinia";
import { store } from "@/store";
import { LayoutConfig } from "@/types/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import { DashboardItem } from "@/types/dashboard";
import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig } from "../data";
import { AxiosResponse } from "axios";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import { EntityType } from "@/views/dashboard/data";
@@ -40,6 +40,7 @@ interface DashboardState {
currentDashboard: Nullable<DashboardItem>;
editMode: boolean;
currentTabIndex: number;
showLinkConfig: boolean;
}
export const dashboardStore = defineStore({
@@ -58,6 +59,7 @@ export const dashboardStore = defineStore({
currentDashboard: null,
editMode: false,
currentTabIndex: 0,
showLinkConfig: false,
}),
actions: {
setLayout(data: LayoutConfig[]) {
@@ -66,6 +68,9 @@ export const dashboardStore = defineStore({
setMode(mode: boolean) {
this.editMode = mode;
},
setWidgetLink(show: boolean) {
this.showLinkConfig = show;
},
resetDashboards(list: DashboardItem[]) {
this.dashboards = list;
sessionStorage.setItem("dashboards", JSON.stringify(list));
@@ -105,24 +110,10 @@ export const dashboardStore = defineStore({
newItem.h = 36;
newItem.graph = {
showDepth: true,
depth:
this.entity === EntityType[1].value
? 1
: this.entity === EntityType[0].value
? 2
: 3,
depth: this.entity === EntityType[1].value ? 1 : this.entity === EntityType[0].value ? 2 : 3,
};
}
if (
[
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) {
if (ControlsTypes.includes(type)) {
newItem.h = 36;
}
if (type === "Text") {
@@ -156,14 +147,12 @@ export const dashboardStore = defineStore({
},
addTabControls(type: string) {
const activedGridItem = this.activedGridItem.split("-")[0];
const idx = this.layout.findIndex(
(d: LayoutConfig) => d.i === activedGridItem
);
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === activedGridItem);
if (idx < 0) {
return;
}
const tabIndex = this.layout[idx].activedTabIndex || 0;
const { children } = this.layout[idx].children[tabIndex];
const { children } = (this.layout[idx].children || [])[tabIndex];
const arr = children.map((d: any) => Number(d.i));
let index = String(Math.max(...arr) + 1);
if (!children.length) {
@@ -184,16 +173,7 @@ export const dashboardStore = defineStore({
showDepth: true,
};
}
if (
[
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) {
if (ControlsTypes.includes(type)) {
newItem.h = 32;
}
if (type === "Text") {
@@ -211,7 +191,7 @@ export const dashboardStore = defineStore({
return d;
});
items.push(newItem);
this.layout[idx].children[tabIndex].children = items;
(this.layout[idx].children || [])[tabIndex].children = items;
this.currentTabItems = items;
}
},
@@ -238,19 +218,15 @@ export const dashboardStore = defineStore({
},
removeControls(item: LayoutConfig) {
const actived = this.activedGridItem.split("-");
const index = this.layout.findIndex(
(d: LayoutConfig) => actived[0] === d.i
);
const index = this.layout.findIndex((d: LayoutConfig) => actived[0] === d.i);
if (this.selectedGrid && this.selectedGrid.i === item.i) {
this.selectedGrid = null;
}
if (actived.length === 3) {
const tabIndex = Number(actived[1]);
this.currentTabItems = this.currentTabItems.filter(
(d: LayoutConfig) => actived[2] !== d.i
);
this.layout[index].children[tabIndex].children = this.currentTabItems;
this.currentTabItems = this.currentTabItems.filter((d: LayoutConfig) => actived[2] !== d.i);
(this.layout[index].children || [])[tabIndex].children = this.currentTabItems;
return;
}
this.layout = this.layout.filter((d: LayoutConfig) => d.i !== item.i);
@@ -258,8 +234,8 @@ export const dashboardStore = defineStore({
removeTabItem(item: LayoutConfig, index: number) {
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === item.i);
if (this.selectedGrid) {
for (const item of this.layout[idx].children[index].children) {
if (this.selectedGrid.i === item.i) {
for (const item of (this.layout[idx].children || [])[index].children) {
if (this.selectedGrid?.i === item.i) {
this.selectedGrid = null;
}
}
@@ -285,22 +261,19 @@ export const dashboardStore = defineStore({
},
setConfigs(param: { [key: string]: unknown }) {
const actived = this.activedGridItem.split("-");
const index = this.layout.findIndex(
(d: LayoutConfig) => actived[0] === d.i
);
const index = this.layout.findIndex((d: LayoutConfig) => actived[0] === d.i);
if (actived.length === 3) {
const tabIndex = Number(actived[1]);
const itemIndex = this.layout[index].children[
tabIndex
].children.findIndex((d: LayoutConfig) => actived[2] === d.i);
const itemIndex = (this.layout[index].children || [])[tabIndex].children.findIndex(
(d: LayoutConfig) => actived[2] === d.i,
);
this.layout[index].children[tabIndex].children[itemIndex] = {
...this.layout[index].children[tabIndex].children[itemIndex],
(this.layout[index].children || [])[tabIndex].children[itemIndex] = {
...(this.layout[index].children || [])[tabIndex].children[itemIndex],
...param,
};
this.selectedGrid =
this.layout[index].children[tabIndex].children[itemIndex];
this.setCurrentTabItems(this.layout[index].children[tabIndex].children);
this.selectedGrid = (this.layout[index].children || [])[tabIndex].children[itemIndex];
this.setCurrentTabItems((this.layout[index].children || [])[tabIndex].children);
return;
}
this.layout[index] = {
@@ -312,8 +285,8 @@ export const dashboardStore = defineStore({
setWidget(param: LayoutConfig) {
for (let i = 0; i < this.layout.length; i++) {
if (this.layout[i].type === "Tab") {
if (this.layout[i].children && this.layout[i].children.length) {
for (const child of this.layout[i].children) {
if ((this.layout[i].children || []).length) {
for (const child of this.layout[i].children || []) {
if (child.children && child.children.length) {
for (let c = 0; c < child.children.length; c++) {
if (child.children[c].id === param.id) {
@@ -332,23 +305,16 @@ export const dashboardStore = defineStore({
}
},
async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql
.query("queryTypeOfMetrics")
.params({ name: item });
const res: AxiosResponse = await graphql.query("queryTypeOfMetrics").params({ name: item });
return res.data;
},
async fetchMetricList(regex: string) {
const res: AxiosResponse = await graphql
.query("queryMetrics")
.params({ regex });
const res: AxiosResponse = await graphql.query("queryMetrics").params({ regex });
return res.data;
},
async fetchMetricValue(param: {
queryStr: string;
conditions: { [key: string]: unknown };
}) {
async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
return res.data;
},
@@ -371,10 +337,7 @@ export const dashboardStore = defineStore({
name: c.name,
isRoot: c.isRoot,
});
sessionStorage.setItem(
key,
JSON.stringify({ id: t.id, configuration: c })
);
sessionStorage.setItem(key, JSON.stringify({ id: t.id, configuration: c }));
}
list = list.sort((a, b) => {
const nameA = a.name.toUpperCase();
@@ -399,9 +362,7 @@ export const dashboardStore = defineStore({
return;
}
}
this.dashboards = JSON.parse(
sessionStorage.getItem("dashboards") || "[]"
);
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
},
async resetTemplates() {
const res = await this.fetchTemplates();
@@ -410,9 +371,7 @@ export const dashboardStore = defineStore({
ElMessage.error(res.errors);
return;
}
this.dashboards = JSON.parse(
sessionStorage.getItem("dashboards") || "[]"
);
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
},
async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({
@@ -431,7 +390,7 @@ export const dashboardStore = defineStore({
return res.data;
},
async saveDashboard() {
if (!this.currentDashboard.name) {
if (!this.currentDashboard?.name) {
ElMessage.error("The dashboard name is needed.");
return;
}
@@ -452,43 +411,37 @@ export const dashboardStore = defineStore({
c.isRoot = false;
const index = this.dashboards.findIndex(
(d: DashboardItem) =>
d.name === this.currentDashboard.name &&
d.name === this.currentDashboard?.name &&
d.entity === this.currentDashboard.entity &&
d.layer === this.currentDashboard.layerId
d.layer === this.currentDashboard?.layer,
);
if (index > -1) {
const { t } = useI18n();
ElMessage.error(t("nameError"));
return;
}
res = await graphql
.query("addNewTemplate")
.params({ setting: { configuration: JSON.stringify(c) } });
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate;
if (!json.status) {
ElMessage.error(json.message);
}
}
if (res.data.errors || res.errors) {
ElMessage.error(res.data.errors);
return res.data;
}
if (!json.status) {
ElMessage.error(json.message);
return json;
}
if (!this.currentDashboard.id) {
ElMessage.success("Saved successfully");
}
const key = [
this.currentDashboard.layer,
this.currentDashboard.entity,
this.currentDashboard.name,
].join("_");
const key = [this.currentDashboard.layer, this.currentDashboard.entity, this.currentDashboard.name].join("_");
this.currentDashboard.id = json.id;
if (this.currentDashboard.id) {
sessionStorage.removeItem(key);
this.dashboards = this.dashboards.filter(
(d: DashboardItem) => d.id !== this.currentDashboard.id
);
this.dashboards = this.dashboards.filter((d: DashboardItem) => d.id !== this.currentDashboard?.id);
}
this.dashboards.push(this.currentDashboard);
const l = { id: json.id, configuration: c };
@@ -498,9 +451,7 @@ export const dashboardStore = defineStore({
return json;
},
async deleteDashboard() {
const res: AxiosResponse = await graphql
.query("removeTemplate")
.params({ id: this.currentDashboard.id });
const res: AxiosResponse = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id });
if (res.data.errors) {
ElMessage.error(res.data.errors);
@@ -511,14 +462,8 @@ export const dashboardStore = defineStore({
ElMessage.error(json.message);
return res.data;
}
this.dashboards = this.dashboards.filter(
(d: any) => d.id !== this.currentDashboard.id
);
const key = [
this.currentDashboard.layer,
this.currentDashboard.entity,
this.currentDashboard.name,
].join("_");
this.dashboards = this.dashboards.filter((d: any) => d.id !== this.currentDashboard?.id);
const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_");
sessionStorage.removeItem(key);
},
},

View File

@@ -15,13 +15,13 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Instance } from "@/types/selector";
import type { Instance } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { Conditions, Log } from "@/types/demand-log";
import type { Conditions, Log } from "@/types/demand-log";
interface DemandLogState {
containers: Instance[];
@@ -59,9 +59,7 @@ export const demandLogStore = defineStore({
this.message = message || "";
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
: id;
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -75,16 +73,12 @@ export const demandLogStore = defineStore({
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
return new Promise((resolve) =>
resolve({ errors: "No service instance" })
);
return new Promise((resolve) => resolve({ errors: "No service instance" }));
}
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql
.query("fetchContainers")
.params({ condition });
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) {
return res.data;
@@ -100,21 +94,17 @@ export const demandLogStore = defineStore({
},
async getDemandLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("fetchDemandPodLogs")
.params({ condition: this.conditions });
const res: AxiosResponse = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
}
if (res.data.data.logs.errorReason) {
this.setLogs("", res.data.data.logs.errorReason);
this.setLogs([], res.data.data.logs.errorReason);
return res.data;
}
this.total = res.data.data.logs.logs.length;
const logs = res.data.data.logs.logs
.map((d: Log) => d.content)
.join("\n");
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n");
this.setLogs(logs);
return res.data;
},

View File

@@ -15,18 +15,13 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Option } from "@/types/app";
import {
EBPFTaskCreationRequest,
EBPFProfilingSchedule,
EBPFTaskList,
AnalyzationTrees,
} from "@/types/ebpf";
import type { Option } from "@/types/app";
import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import type { AxiosResponse } from "axios";
interface EbpfState {
taskList: EBPFTaskList[];
taskList: Array<Recordable<EBPFTaskList>>;
eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>;
analyzeTrees: AnalyzationTrees[];
@@ -51,7 +46,7 @@ export const ebpfStore = defineStore({
aggregateType: "COUNT",
}),
actions: {
setSelectedTask(task: EBPFTaskList) {
setSelectedTask(task: Recordable<EBPFTaskList>) {
this.selectedTask = task || {};
},
setCurrentSchedule(s: EBPFProfilingSchedule) {
@@ -61,9 +56,7 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getCreateTaskData")
.params({ serviceId });
const res: AxiosResponse = await graphql.query("getCreateTaskData").params({ serviceId });
if (res.data.errors) {
return res.data;
@@ -76,9 +69,7 @@ export const ebpfStore = defineStore({
return res.data;
},
async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveEBPFTask")
.params({ request: param });
const res: AxiosResponse = await graphql.query("saveEBPFTask").params({ request: param });
if (res.data.errors) {
return res.data;
@@ -89,17 +80,11 @@ export const ebpfStore = defineStore({
});
return res.data;
},
async getTaskList(params: {
serviceId: string;
serviceInstanceId: string;
targets: string[];
}) {
async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params(params);
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
this.tip = "";
if (res.data.errors) {
@@ -111,16 +96,14 @@ export const ebpfStore = defineStore({
if (!this.taskList.length) {
return res.data;
}
this.getEBPFSchedules({ taskId: this.taskList[0].taskId });
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data;
},
async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFSchedules")
.params({ ...params });
const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params });
if (res.data.errors) {
this.eBPFSchedules = [];
@@ -148,9 +131,7 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFResult")
.params(params);
const res: AxiosResponse = await graphql.query("getEBPFResult").params(params);
if (res.data.errors) {
this.analyzeTrees = [];

View File

@@ -17,9 +17,9 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint } from "@/types/selector";
import type { AxiosResponse } from "axios";
import type { Event, QueryEventCondition } from "@/types/events";
import type { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
@@ -45,9 +45,7 @@ export const eventStore = defineStore({
this.condition = data;
},
async getInstances() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -56,15 +54,11 @@ export const eventStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [
{ value: "", label: "All" },
];
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
return res.data;
},
async getEndpoints() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
if (!serviceId) {
return;
}
@@ -76,9 +70,7 @@ export const eventStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [
{ value: "", label: "All" },
];
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
return res.data;
},
async getEvents() {
@@ -94,22 +86,20 @@ export const eventStore = defineStore({
return res.data;
}
if (res.data.data.fetchEvents) {
this.events = (res.data.data.fetchEvents.events || []).map(
(item: Event) => {
let scope = "Service";
if (item.source.serviceInstance) {
scope = "ServiceInstance";
}
if (item.source.endpoint) {
scope = "Endpoint";
}
item.scope = scope;
if (!item.endTime || item.endTime === item.startTime) {
item.endTime = Number(item.startTime) + 60000;
}
return item;
this.events = (res.data.data.fetchEvents.events || []).map((item: Event) => {
let scope = "Service";
if (item.source.serviceInstance) {
scope = "ServiceInstance";
}
);
if (item.source.endpoint) {
scope = "Endpoint";
}
item.scope = scope;
if (!item.endTime || item.endTime === item.startTime) {
item.endTime = Number(item.startTime) + 60000;
}
return item;
});
}
return res.data;
},

View File

@@ -15,10 +15,10 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Instance, Endpoint, Service } from "@/types/selector";
import type { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
@@ -71,9 +71,7 @@ export const logStore = defineStore({
return res.data;
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
: id;
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -82,16 +80,11 @@ export const logStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.instances = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: " 0", label: "All" }];
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: " 0", label: "All" }];
return res.data;
},
async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
: id;
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -100,16 +93,11 @@ export const logStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.endpoints = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: "0", label: "All" }];
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: "0", label: "All" }];
return res.data;
},
async getLogsByKeywords() {
const res: AxiosResponse = await graphql
.query("queryLogsByKeywords")
.params({});
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) {
return res.data;
@@ -127,9 +115,7 @@ export const logStore = defineStore({
},
async getServiceLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("queryServiceLogs")
.params({ condition: this.conditions });
const res: AxiosResponse = await graphql.query("queryServiceLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
@@ -140,9 +126,7 @@ export const logStore = defineStore({
},
async getBrowserLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("queryBrowserErrorLogs")
.params({ condition: this.conditions });
const res: AxiosResponse = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {

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