Merge branch 'apache:main' into main

This commit is contained in:
Brandon Fergerson 2022-06-26 15:18:44 +04:00 committed by GitHub
commit 4f1b54c719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 2384 additions and 631 deletions

274
package-lock.json generated
View File

@ -15,7 +15,9 @@
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.0.2", "element-plus": "^2.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
@ -50,6 +52,7 @@
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.3", "lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "postcss-scss": "^4.0.2",
@ -1811,6 +1814,18 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@element-plus/icons-vue": { "node_modules/@element-plus/icons-vue": {
"version": "0.2.7", "version": "0.2.7",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz", "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz",
@ -3300,6 +3315,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==",
"peer": true
},
"node_modules/@types/http-proxy": { "node_modules/@types/http-proxy": {
"version": "1.17.8", "version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@ -7882,8 +7903,7 @@
"node_modules/component-emitter": { "node_modules/component-emitter": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
"dev": true
}, },
"node_modules/compressible": { "node_modules/compressible": {
"version": "2.0.18", "version": "2.0.18",
@ -8695,6 +8715,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
"peer": true
},
"node_modules/cssnano": { "node_modules/cssnano": {
"version": "4.1.11", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@ -11080,9 +11106,9 @@
} }
}, },
"node_modules/eventsource": { "node_modules/eventsource": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"original": "^1.0.0" "original": "^1.0.0"
@ -16438,6 +16464,12 @@
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"node_modules/killable": { "node_modules/killable": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@ -18229,11 +18261,42 @@
"version": "2.24.0", "version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true,
"engines": { "engines": {
"node": "*" "node": "*"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"node_modules/monaco-editor-webpack-plugin": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz",
"integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0"
},
"peerDependencies": {
"monaco-editor": "0.25.x || 0.26.x || 0.27.x",
"webpack": "^4.5.0 || 5.x"
}
},
"node_modules/monaco-editor-webpack-plugin/node_modules/loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/move-concurrently": { "node_modules/move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -21698,6 +21761,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/propagating-hammerjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
"integrity": "sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==",
"peer": true,
"peerDependencies": {
"@egjs/hammerjs": "^2.0.17"
}
},
"node_modules/proto-list": { "node_modules/proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -26837,7 +26909,6 @@
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
@ -26897,6 +26968,59 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"node_modules/vis-data": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz",
"integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==",
"hasInstallScript": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"uuid": "^7.0.0 || ^8.0.0",
"vis-util": "^4.0.0 || ^5.0.0"
}
},
"node_modules/vis-timeline": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-7.5.1.tgz",
"integrity": "sha512-XZMHHbA8xm9/Y/iu3mE9MT7J5tfWgbdsW+PmqrgINU2QRX24AiqifNHZHV4YYzeJstiTSOg9Gs5qRkxQ0BvZJw==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0",
"keycharm": "^0.3.0 || ^0.4.0",
"moment": "^2.24.0",
"propagating-hammerjs": "^1.4.0 || ^2.0.0",
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0",
"vis-data": "^6.3.0 || ^7.0.0",
"vis-util": "^3.0.0 || ^4.0.0 || ^5.0.0",
"xss": "^1.0.0"
}
},
"node_modules/vis-util": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz",
"integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==",
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0"
}
},
"node_modules/vm-browserify": { "node_modules/vm-browserify": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -28745,6 +28869,28 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true "dev": true
}, },
"node_modules/xss": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz",
"integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==",
"peer": true,
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/xss/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -30199,6 +30345,15 @@
} }
} }
}, },
"@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"requires": {
"@types/hammerjs": "^2.0.36"
}
},
"@element-plus/icons-vue": { "@element-plus/icons-vue": {
"version": "0.2.7", "version": "0.2.7",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz", "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz",
@ -31462,6 +31617,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==",
"peer": true
},
"@types/http-proxy": { "@types/http-proxy": {
"version": "1.17.8", "version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@ -35153,8 +35314,7 @@
"component-emitter": { "component-emitter": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
"dev": true
}, },
"compressible": { "compressible": {
"version": "2.0.18", "version": "2.0.18",
@ -35795,6 +35955,12 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true "dev": true
}, },
"cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
"peer": true
},
"cssnano": { "cssnano": {
"version": "4.1.11", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@ -37656,9 +37822,9 @@
"dev": true "dev": true
}, },
"eventsource": { "eventsource": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true, "dev": true,
"requires": { "requires": {
"original": "^1.0.0" "original": "^1.0.0"
@ -41882,6 +42048,12 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"killable": { "killable": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@ -43268,8 +43440,34 @@
"moment": { "moment": {
"version": "2.24.0", "version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
"dev": true },
"monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"monaco-editor-webpack-plugin": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz",
"integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0"
},
"dependencies": {
"loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
}, },
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
@ -45934,6 +46132,13 @@
"sisteransi": "^1.0.5" "sisteransi": "^1.0.5"
} }
}, },
"propagating-hammerjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
"integrity": "sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==",
"peer": true,
"requires": {}
},
"proto-list": { "proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -50035,8 +50240,7 @@
"uuid": { "uuid": {
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"dev": true
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.3.0", "version": "2.3.0",
@ -50085,6 +50289,26 @@
} }
} }
}, },
"vis-data": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz",
"integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==",
"peer": true,
"requires": {}
},
"vis-timeline": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-7.5.1.tgz",
"integrity": "sha512-XZMHHbA8xm9/Y/iu3mE9MT7J5tfWgbdsW+PmqrgINU2QRX24AiqifNHZHV4YYzeJstiTSOg9Gs5qRkxQ0BvZJw==",
"requires": {}
},
"vis-util": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz",
"integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==",
"peer": true,
"requires": {}
},
"vm-browserify": { "vm-browserify": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -51596,6 +51820,24 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true "dev": true
}, },
"xss": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz",
"integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==",
"peer": true,
"requires": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
}
}
},
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -17,7 +17,9 @@
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.0.2", "element-plus": "^2.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
@ -52,6 +54,7 @@
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.3", "lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "postcss-scss": "^4.0.2",

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="1655799536378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9286" width="48" height="48"><path d="M563.2 614.4v51.2c0 30.72-20.48 51.2-51.2 51.2s-51.2-20.48-51.2-51.2v-51.2H409.6c-30.72 0-51.2-20.48-51.2-51.2s20.48-51.2 51.2-51.2h51.2V460.8c0-30.72 20.48-51.2 51.2-51.2s51.2 20.48 51.2 51.2v51.2h51.2c30.72 0 51.2 20.48 51.2 51.2s-20.48 51.2-51.2 51.2h-51.2z m51.2-563.2c158.72 15.36 281.6 143.36 281.6 307.2v512c0 56.32-46.08 102.4-102.4 102.4h-563.2c-56.32 0-102.4-46.08-102.4-102.4V153.6c0-56.32 46.08-102.4 102.4-102.4H614.4z m163.84 230.4c-25.6-61.44-76.8-107.52-138.24-122.88v71.68c0 30.72 20.48 51.2 51.2 51.2h87.04zM537.6 153.6h-256c-30.72 0-51.2 20.48-51.2 51.2v614.4c0 30.72 20.48 51.2 51.2 51.2h460.8c30.72 0 51.2-20.48 51.2-51.2V384h-153.6c-56.32 0-102.4-46.08-102.4-102.4V153.6z" fill="#707070" p-id="9287"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 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="1654161407133" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1721" width="16" height="16"><path d="M804.224 86.144c0-19.264-15.616-34.944-34.944-34.944l-734.336 0c-19.264 0-34.944 15.68-34.944 34.944l0 82.048 804.224 0 0-82.048zM738.88 602.432c0 47.072-38.176 85.248-85.248 85.248s-85.248-38.176-85.248-85.248c0-47.072 38.176-85.248 85.248-85.248s85.248 38.176 85.248 85.248zM804.992 264.64l0-62.976-804.224 0 0 665.408c0 18.56 14.656 33.472 32.96 34.56l402.24 0c61.12 44.544 136.192 71.168 217.664 71.168 204.544 0 370.368-165.824 370.368-370.368 0-150.592-89.984-279.936-219.008-337.792zM412.096 298.24l30.528 0c-10.624 7.36-20.8 15.36-30.528 23.744l0-23.744zM63.04 298.24l153.024 0 0 153.024-153.024 0 0-153.024zM216.064 805.056l-153.024 0 0-153.024 153.024 0 0 153.024zM219.136 631.232l-153.024 0 0-153.024 153.024 0 0 153.024zM237.568 805.056l0-153.024 49.408 0c7.488 55.936 27.264 107.904 56.832 153.024l-106.24 0zM284.672 631.232l-44.032 0 0-153.024 64.384 0c-13.824 38.848-21.824 80.576-21.824 124.224 0 9.728 0.768 19.264 1.472 28.8zM390.592 341.76c-31.168 31.424-56.512 68.544-74.88 109.44l-78.144 0 0-152.96 153.024 0 0 43.52zM899.136 638.4l-63.36 12.864c-4.288 16.064-10.688 31.296-18.816 45.376l35.712 53.888-50.944 50.944-53.888-35.712c-14.08 8.128-29.312 14.528-45.376 18.816l-12.864 63.36-72 0-12.864-63.36c-16.064-4.288-31.296-10.688-45.376-18.816l-53.888 35.712-50.944-50.944 35.712-53.888c-8.128-14.08-14.528-29.312-18.816-45.376l-63.36-12.864 0-72 63.36-12.864c4.352-16.064 10.688-31.296 18.816-45.312l-35.712-53.952 50.944-50.944 53.888 35.776c14.08-8.128 29.312-14.464 45.376-18.816l12.864-63.36 72 0 12.864 63.36c16.064 4.288 31.296 10.688 45.376 18.816l53.888-35.712 50.944 50.944-35.712 53.824c8.128 14.08 14.528 29.312 18.816 45.376l63.36 12.864 0 72z" p-id="1722" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -28,15 +28,26 @@ export const Languages = [
export const RoutesMap: { [key: string]: string } = { export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL", GeneralServices: "GENERAL",
GeneralServicesActiveTabIndex: "GENERAL",
Database: "VIRTUAL_DATABASE", Database: "VIRTUAL_DATABASE",
DatabaseActiveTabIndex: "VIRTUAL_DATABASE",
MeshServices: "MESH", MeshServices: "MESH",
MeshServicesActiveTabIndex: "MESH",
ControlPanel: "MESH_CP", ControlPanel: "MESH_CP",
ControlPanelActiveTabIndex: "MESH_CP",
DataPanel: "MESH_DP", DataPanel: "MESH_DP",
DataPanelActiveTabIndex: "MESH_DP",
Linux: "OS_LINUX", Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP", SkyWalkingServer: "SO11Y_OAP",
SkyWalkingServerActiveTabIndex: "SO11Y_OAP",
SatelliteActiveTabIndex: "SO11Y_SATELLITE",
Satellite: "SO11Y_SATELLITE", Satellite: "SO11Y_SATELLITE",
Functions: "FAAS", Functions: "FAAS",
FunctionsActiveTabIndex: "FAAS",
Browser: "BROWSER", Browser: "BROWSER",
BrowserActiveTabIndex: "BROWSER",
KubernetesCluster: "K8S", KubernetesCluster: "K8S",
KubernetesClusterActiveTabIndex: "K8S",
KubernetesService: "K8S_SERVICE", KubernetesService: "K8S_SERVICE",
KubernetesServiceActiveTabIndex: "K8S_SERVICE",
}; };

View File

@ -0,0 +1,36 @@
/**
* 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 const queryContainers = {
variable: "$condition: OndemandContainergQueryCondition!",
query: `
containers: listContainers(condition: $condition) {
errorReason
containers
}`,
};
export const queryStreamingLogs = {
variable: "$condition: OndemandLogQueryCondition",
query: `
logs: ondemandPodLogs(condition: $condition) {
errorReason
logs {
content
}
}`,
};

View File

@ -26,6 +26,7 @@ import * as profile from "./query/profile";
import * as alarm from "./query/alarm"; import * as alarm from "./query/alarm";
import * as event from "./query/event"; import * as event from "./query/event";
import * as ebpf from "./query/ebpf"; import * as ebpf from "./query/ebpf";
import * as demandLog from "./query/demand-log";
const query: { [key: string]: string } = { const query: { [key: string]: string } = {
...app, ...app,
@ -38,6 +39,7 @@ const query: { [key: string]: string } = {
...alarm, ...alarm,
...event, ...event,
...ebpf, ...ebpf,
...demandLog,
}; };
class Graphql { class Graphql {
private queryData = ""; private queryData = "";

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.
*/
import { queryContainers, queryStreamingLogs } from "../fragments/demand-log";
export const fetchContainers = `query listContainers(${queryContainers.variable}) {${queryContainers.query}}`;
export const fetchDemandPodLogs = `query ondemandPodLogs(${queryStreamingLogs.variable}) {${queryStreamingLogs.query}}`;

View File

@ -79,7 +79,11 @@ export function useECharts(
if (!el || !unref(el)) { if (!el || !unref(el)) {
return; return;
} }
const { width, height } = el.getBoundingClientRect();
if (!width || !height) {
return;
}
chartInstance = echarts.init(el, t); chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({ const { removeEvent } = useEventListener({
el: window, el: window,

View File

@ -55,9 +55,9 @@ limitations under the License. -->
<router-link <router-link
class="items" class="items"
:to="m.path" :to="m.path"
:exact="m.meta.exact || false" :exact="(m.meta && m.meta.exact) || false"
> >
<span class="title">{{ t(m.meta.title) }}</span> <span class="title">{{ m.meta && t(m.meta.title) }}</span>
</router-link> </router-link>
</el-menu-item> </el-menu-item>
</el-menu-item-group> </el-menu-item-group>

View File

@ -136,7 +136,13 @@ const msg = {
targetType: "Target Type", targetType: "Target Type",
ebpfTip: "Don't have a process for profiling", ebpfTip: "Don't have a process for profiling",
processSelect: "Click to select processes", processSelect: "Click to select processes",
container: "Container",
limit: "Limit",
page: "Page", page: "Page",
interval: "Refresh Interval",
pause: "Pause",
begin: "Start",
seconds: "Seconds",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",
@ -155,7 +161,7 @@ const msg = {
dashboard: "Dashboard", dashboard: "Dashboard",
topology: "Topology", topology: "Topology",
trace: "Trace", trace: "Trace",
alarm: "Alarms", alarm: "Alerting",
auto: "Auto", auto: "Auto",
reload: "Reload", reload: "Reload",
version: "Version", version: "Version",

View File

@ -74,8 +74,10 @@ const msg = {
editGraph: "Editar Opciones", editGraph: "Editar Opciones",
dashboardName: "Selecciona Nombre del Panel", dashboardName: "Selecciona Nombre del Panel",
linkDashboard: "Nombre del panel relacionado con llamadas de la topología", linkDashboard: "Nombre del panel relacionado con llamadas de la topología",
linkServerMetrics: "Métricas de servidor relacionadas con llamadas de la topología", linkServerMetrics:
linkClientMetrics: "Métricas de cliente relacionadas con llamadas de la topología", "Métricas de servidor relacionadas con llamadas de la topología",
linkClientMetrics:
"Métricas de cliente relacionadas con llamadas de la topología",
nodeDashboard: "Nombre del panel relacionado con nodos de la topología", nodeDashboard: "Nombre del panel relacionado con nodos de la topología",
nodeMetrics: "Mêtricas relacionas con nodos de la topología", nodeMetrics: "Mêtricas relacionas con nodos de la topología",
instanceDashboard: "Nombre del panel relacionado con instancias de servicio", instanceDashboard: "Nombre del panel relacionado con instancias de servicio",
@ -137,6 +139,10 @@ const msg = {
ebpfTip: "Le falta el proceso para perfilar", ebpfTip: "Le falta el proceso para perfilar",
processSelect: "Click para seleccionar proceso", processSelect: "Click para seleccionar proceso",
page: "Página", page: "Página",
interval: "Intervalo de actualización",
pause: "Pausa",
begin: "Inicio",
seconds: "Segundos",
hourTip: "Seleccione Hora", hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto", minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo", secondTip: "Seleccione Segundo",
@ -155,7 +161,7 @@ const msg = {
dashboard: "Panel", dashboard: "Panel",
topology: "Topología", topology: "Topología",
trace: "Traza", trace: "Traza",
alarm: "Alarmas", alarm: "Recordatorio en curso",
auto: "Auto", auto: "Auto",
reload: "Recargar", reload: "Recargar",
version: "Versión", version: "Versión",
@ -303,7 +309,8 @@ const msg = {
destEndpoint: "Endpoint Destinación", destEndpoint: "Endpoint Destinación",
eventSource: "Fuente Envento", eventSource: "Fuente Envento",
modalTitle: "Inspección", 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", logAnalysis: "Lenguaje de Análisis de Registro de Datos",
logDataBody: "Contenido del Registro de Datos", logDataBody: "Contenido del Registro de Datos",
addType: "Por favor introduzca un tipo", addType: "Por favor introduzca un tipo",
@ -324,8 +331,10 @@ const msg = {
addTraceID: "Por favor introduzca el ID de la traza", addTraceID: "Por favor introduzca el ID de la traza",
addTags: "Por favor introduzaca una etiqueta", addTags: "Por favor introduzaca una etiqueta",
addKeywordsOfContent: "Por favor introduzca una clave de contenido", addKeywordsOfContent: "Por favor introduzca una clave de contenido",
addExcludingKeywordsOfContent: "Por favor introduzca una clave excluyente de contenido", addExcludingKeywordsOfContent:
noticeTag: "Por favor presione Intro después de introducir una etiqueta(clave=valor).", "Por favor introduzca una clave excluyente de contenido",
noticeTag:
"Por favor presione Intro después de introducir una etiqueta(clave=valor).",
conditionNotice: conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).", "Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje", language: "Lenguaje",

View File

@ -134,7 +134,13 @@ const msg = {
targetType: "目标类型", targetType: "目标类型",
processSelect: "点击选择进程", processSelect: "点击选择进程",
ebpfTip: "没有进程可以分析", ebpfTip: "没有进程可以分析",
container: "容器",
limit: "范围",
page: "页面", page: "页面",
interval: "刷新间隔时间",
pause: "暂停",
begin: "开始",
seconds: "秒",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",

View File

@ -30,13 +30,13 @@ export const routesAlarm: Array<RouteRecordRaw> = [
component: Layout, component: Layout,
children: [ children: [
{ {
path: "/alarm", path: "/alerting",
name: "Alarm", name: "Alarm",
meta: { meta: {
exact: false, exact: false,
}, },
component: () => component: () =>
import(/* webpackChunkName: "alarms" */ "@/views/Alarm.vue"), import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
}, },
], ],
}, },

View File

@ -33,12 +33,20 @@ export const routesBrowser: Array<RouteRecordRaw> = [
name: "Browser", name: "Browser",
meta: { meta: {
title: "browser", title: "browser",
headPath: "/browser",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
], ],
}, },
]; ];

View File

@ -53,32 +53,79 @@ export const routesDashboard: Array<RouteRecordRaw> = [
exact: false, exact: false,
}, },
}, },
{
path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
meta: {
notShow: true,
},
children: [
{ {
path: "/dashboard/:layerId/:entity/:name", path: "/dashboard/:layerId/:entity/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "Create", name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateActiveTabIndex",
},
],
},
{
path: "",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
}, children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "View", name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
}, children: [
{ {
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => component: () =>
@ -86,12 +133,29 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewServiceRelation", name: "ViewServiceRelation",
},
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelationActiveTabIndex",
},
],
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
}, children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () => component: () =>
@ -99,12 +163,30 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPod", name: "ViewPod",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
}, children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => component: () =>
@ -112,11 +194,16 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPodRelation", name: "ViewPodRelation",
meta: {
title: "dashboardEdit",
exact: true,
notShow: true,
}, },
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelationActiveTabIndex",
},
],
}, },
], ],
}, },

View File

@ -34,12 +34,20 @@ export const routesDatabase: Array<RouteRecordRaw> = [
name: "Database", name: "Database",
meta: { meta: {
title: "virtualDatabase", title: "virtualDatabase",
headPath: "/database",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/database/tab/:activeTabIndex",
name: "DatabaseActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
], ],
}, },
]; ];

View File

@ -32,13 +32,17 @@ export const routesFunctions: Array<RouteRecordRaw> = [
path: "/functions", path: "/functions",
name: "Functions", name: "Functions",
meta: { meta: {
title: "functions",
headPath: "/functions",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/functions/tab/:activeTabIndex",
name: "FunctionsActiveTabIndex",
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
], ],
}, },
]; ];

View File

@ -33,8 +33,15 @@ export const routesGen: Array<RouteRecordRaw> = [
path: "/general", path: "/general",
name: "GeneralServices", name: "GeneralServices",
meta: { meta: {
title: "services", exact: true,
headPath: "/general/service", },
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true, exact: true,
}, },
component: () => component: () =>

View File

@ -20,7 +20,6 @@ import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database"; import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure"; import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard"; import { routesDashboard } from "./dashboard";
import { routesEvent } from "./event";
import { routesSetting } from "./setting"; import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm"; import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability"; import { routesSelf } from "./selfObservability";
@ -39,7 +38,6 @@ const routes: Array<RouteRecordRaw> = [
...routesSelf, ...routesSelf,
...routesDashboard, ...routesDashboard,
...routesAlarm, ...routesAlarm,
...routesEvent,
...routesSetting, ...routesSetting,
]; ];

View File

@ -39,6 +39,16 @@ export const routesInfra: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/linux/tab/:activeTabIndex",
name: "LinuxActiveTabIndex",
meta: {
title: "linux",
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// { // {
// path: "/infrastructure/vm", // path: "/infrastructure/vm",
// name: "VirtualMachine", // name: "VirtualMachine",

View File

@ -33,20 +33,42 @@ export const routesK8s: Array<RouteRecordRaw> = [
path: "/kubernetes/cluster", path: "/kubernetes/cluster",
name: "KubernetesCluster", name: "KubernetesCluster",
meta: { meta: {
notShow: false,
title: "kubernetesCluster", title: "kubernetesCluster",
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
name: "KubernetesClusterActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{ {
path: "/kubernetes/service", path: "/kubernetes/service",
name: "KubernetesService", name: "KubernetesService",
meta: { meta: {
notShow: false,
title: "kubernetesService", title: "kubernetesService",
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/kubernetes/service/tab/:activeTabIndex",
name: "KubernetesServiceActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
], ],
}, },
]; ];

View File

@ -34,7 +34,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "SkyWalkingServer", name: "SkyWalkingServer",
meta: { meta: {
title: "skyWalkingServer", title: "skyWalkingServer",
headPath: "/mesh/services", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -44,7 +52,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "Satellite", name: "Satellite",
meta: { meta: {
title: "satellite", title: "satellite",
headPath: "/mesh/controlPanel", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),

View File

@ -33,8 +33,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/services", path: "/mesh/services",
name: "MeshServices", name: "MeshServices",
meta: { meta: {
notShow: false,
title: "services", title: "services",
headPath: "/mesh/services", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -43,8 +52,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/controlPanel", path: "/mesh/controlPanel",
name: "ControlPanel", name: "ControlPanel",
meta: { meta: {
notShow: false,
title: "controlPanel", title: "controlPanel",
headPath: "/mesh/controlPanel", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -53,10 +71,21 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/dataPanel", path: "/mesh/dataPanel",
name: "DataPanel", name: "DataPanel",
meta: { meta: {
notShow: false,
title: "dataPanel", title: "dataPanel",
headPath: "/mesh/dataPanel",
}, },
component: () => import("@/views/Layer.vue"), component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
name: "DataPanelActiveTabIndex",
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@ -34,6 +34,7 @@ interface AppState {
pageTitle: string; pageTitle: string;
version: string; version: string;
isMobile: boolean; isMobile: boolean;
reloadTimer: Nullable<any>;
} }
export const appStore = defineStore({ export const appStore = defineStore({
@ -53,6 +54,7 @@ export const appStore = defineStore({
pageTitle: "", pageTitle: "",
version: "", version: "",
isMobile: false, isMobile: false,
reloadTimer: null,
}), }),
getters: { getters: {
duration(): Duration { duration(): Duration {
@ -173,6 +175,9 @@ export const appStore = defineStore({
this.version = res.data.data.version; this.version = res.data.data.version;
return res.data; return res.data;
}, },
setReloadTimer(timer: any): void {
this.reloadTimer = timer;
},
}, },
}); });
export function useAppStoreWithOut(): any { export function useAppStoreWithOut(): any {

View File

@ -110,12 +110,7 @@ export const dashboardStore = defineStore({
: 3, : 3,
}; };
} }
if ( if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf"
) {
newItem.h = 36; newItem.h = 36;
} }
if (type === "Text") { if (type === "Text") {
@ -170,7 +165,7 @@ export const dashboardStore = defineStore({
showDepth: true, showDepth: true,
}; };
} }
if (type === "Trace" || type === "Profile" || type === "Log") { if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
newItem.h = 32; newItem.h = 32;
} }
if (type === "Text") { if (type === "Text") {

View File

@ -0,0 +1,126 @@
/**
* 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 { defineStore } from "pinia";
import { Instance } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { Conditions, Log } from "@/types/demand-log";
interface DemandLogState {
containers: Instance[];
instances: Instance[];
conditions: Conditions;
selectorStore: any;
logs: Log[];
loadLogs: boolean;
message: string;
total: number;
}
export const demandLogStore = defineStore({
id: "demandLog",
state: (): DemandLogState => ({
containers: [{ label: "", value: "" }],
instances: [{ value: "", label: "" }],
conditions: {
container: "",
serviceInstanceId: "",
duration: useAppStoreWithOut().durationTime,
},
selectorStore: useSelectorStore(),
logs: [],
loadLogs: false,
message: "",
total: 0,
}),
actions: {
setLogCondition(data: Conditions) {
this.conditions = { ...this.conditions, ...data };
},
setLogs(logs: Log[], message?: string) {
this.logs = logs;
this.message = message || "";
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
: id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
}
this.instances = res.data.data.pods || [];
return res.data;
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
return new Promise((resolve) =>
resolve({ errors: "No service instance" })
);
}
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql
.query("fetchContainers")
.params({ condition });
if (res.data.errors) {
return res.data;
}
if (res.data.data.containers.errorReason) {
this.containers = [{ label: "", value: "" }];
return res.data;
}
this.containers = res.data.data.containers.containers.map((d: string) => {
return { label: d, value: d };
});
return res.data;
},
async getDemandLogs() {
this.loadLogs = true;
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);
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");
this.setLogs(logs);
return res.data;
},
},
});
export function useDemandLogStore(): any {
return demandLogStore(store);
}

View File

@ -35,6 +35,7 @@ interface EbpfStore {
couldProfiling: boolean; couldProfiling: boolean;
tip: string; tip: string;
selectedTask: Recordable<EBPFTaskList>; selectedTask: Recordable<EBPFTaskList>;
aggregateType: string;
} }
export const ebpfStore = defineStore({ export const ebpfStore = defineStore({
@ -48,6 +49,7 @@ export const ebpfStore = defineStore({
couldProfiling: false, couldProfiling: false,
tip: "", tip: "",
selectedTask: {}, selectedTask: {},
aggregateType: "COUNT",
}), }),
actions: { actions: {
setSelectedTask(task: EBPFTaskList) { setSelectedTask(task: EBPFTaskList) {
@ -131,6 +133,7 @@ export const ebpfStore = defineStore({
timeRanges: Array<{ start: number; end: number }>; timeRanges: Array<{ start: number; end: number }>;
aggregateType: string; aggregateType: string;
}) { }) {
this.aggregateType = params.aggregateType;
if (!params.scheduleIdList.length) { if (!params.scheduleIdList.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }

View File

@ -19,16 +19,16 @@ import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events"; import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint, Service } from "@/types/selector"; import { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
interface eventState { interface eventState {
loading: boolean; loading: boolean;
events: Event[]; events: Event[];
services: Service[];
instances: Instance[]; instances: Instance[];
endpoints: Endpoint[]; endpoints: Endpoint[];
condition: QueryEventCondition | any; condition: Nullable<QueryEventCondition>;
} }
export const eventStore = defineStore({ export const eventStore = defineStore({
@ -36,32 +36,18 @@ export const eventStore = defineStore({
state: (): eventState => ({ state: (): eventState => ({
loading: false, loading: false,
events: [], events: [],
services: [{ value: "", label: "All" }],
instances: [{ value: "", label: "All" }], instances: [{ value: "", label: "All" }],
endpoints: [{ value: "", label: "All" }], endpoints: [{ value: "", label: "All" }],
condition: { condition: null,
paging: { pageNum: 1, pageSize: 15 },
},
}), }),
actions: { actions: {
setEventCondition(data: any) { setEventCondition(data: QueryEventCondition) {
this.condition = { ...this.condition, ...data }; this.condition = data;
}, },
async getServices(layer: string) { async getInstances() {
if (!layer) { const serviceId = useSelectorStore().currentService
this.services = [{ value: "", label: "All" }]; ? useSelectorStore().currentService.id
return new Promise((resolve) => resolve([])); : "";
}
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
return res.data;
},
async getInstances(serviceId: string) {
const res: AxiosResponse = await graphql.query("queryInstances").params({ const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
@ -75,7 +61,13 @@ export const eventStore = defineStore({
]; ];
return res.data; return res.data;
}, },
async getEndpoints(serviceId: string) { async getEndpoints() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,

View File

@ -53,6 +53,12 @@ export const logStore = defineStore({
setLogCondition(data: any) { setLogCondition(data: any) {
this.conditions = { ...this.conditions, ...data }; this.conditions = { ...this.conditions, ...data };
}, },
resetCondition() {
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15 },
};
},
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryServices").params({
layer, layer,

View File

@ -154,3 +154,38 @@ pre {
.switch { .switch {
margin: 0 5px; margin: 0 5px;
} }
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: #fff !important;
white-space: normal !important;
font-size: 12px !important;
}
.vis-item {
cursor: pointer;
height: 17px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: #fff !important;
}
.vis-item.Normal {
background-color: #fac858;
border-color: #fac858;
color: #666 !important;
}
.vis-item .vis-item-content {
padding: 0 5px !important;
}
.vis-item.vis-selected.Error,
.vis-item.vis-selected.Normal {
color: #1a1a1a !important;
}

4
src/types/app.d.ts vendored
View File

@ -28,3 +28,7 @@ export interface DurationTime {
end: string; end: string;
step: string; step: string;
} }
export type Paging = {
pageNum: number;
pageSize: number;
};

31
src/types/demand-log.ts Normal file
View File

@ -0,0 +1,31 @@
/**
* 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 { DurationTime } from "./app";
export interface Conditions {
container: string;
serviceInstanceId: string;
duration: DurationTime;
keywordsOfContent?: string[];
excludingKeywordsOfContent?: string;
}
export interface Log {
content: string;
timestamp: number;
contentType: string;
}

2
src/types/ebpf.d.ts vendored
View File

@ -65,6 +65,8 @@ export type StackElement = {
stackType: string; stackType: string;
value: number; value: number;
children?: StackElement[]; children?: StackElement[];
rateOfRoot?: string;
rateOfParent: string;
}; };
export type AnalyzationTrees = { export type AnalyzationTrees = {
id: string; id: string;

View File

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

View File

@ -46,8 +46,15 @@ export interface Span {
children?: Span[]; children?: Span[];
tags?: Array<Map<string, string>>; tags?: Array<Map<string, string>>;
logs?: log[]; logs?: log[];
parentSegmentId?: string;
refs?: Ref[];
} }
export type Ref = {
type: string;
parentSegmentId: string;
parentSpanId: number;
traceId: string;
};
export interface log { export interface log {
time: number; time: number;
data: Map<string, string>; data: Map<string, string>;

View File

@ -24,7 +24,7 @@ import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue"; import Content from "./alarm/Content.vue";
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alarm"); appStore.setPageTitle("Alerting");
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.alarm { .alarm {

View File

@ -69,7 +69,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat"; import timeFormat from "@/utils/timeFormat";
@ -78,9 +78,6 @@ import Selector from "@/components/Selector.vue";
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({
timer: null,
});
const lang = ref<string>(locale.value || "en"); const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6); const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false); const auto = ref<boolean>(appStore.autoRefresh || false);
@ -101,10 +98,10 @@ const handleAuto = () => {
appStore.setAutoRefresh(auto.value); appStore.setAutoRefresh(auto.value);
if (auto.value) { if (auto.value) {
handleReload(); handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000); appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else { } else {
if (state.timer) { if (appStore.reloadTimer) {
clearInterval(state.timer); clearInterval(appStore.reloadTimer);
} }
} }
}; };
@ -112,12 +109,12 @@ const changeAutoTime = () => {
if (autoTime.value < 1) { if (autoTime.value < 1) {
return; return;
} }
if (state.timer) { if (appStore.reloadTimer) {
clearInterval(state.timer); clearInterval(appStore.reloadTimer);
} }
if (auto.value) { if (auto.value) {
handleReload(); handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000); appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} }
}; };
const setLang = (): void => { const setLang = (): void => {

View File

@ -0,0 +1,93 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header />
</div>
<Content />
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/demand-log/Header.vue";
import Content from "../related/demand-log/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span> <span>{{ t("delete") }}</span>
</div> </div>
</el-popover> </el-popover>
<Header /> <Header :needQuery="needQuery" />
<Content /> <Content />
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -0,0 +1,102 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="event-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header :needQuery="needQuery" />
</div>
<div class="event">
<Content />
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/event/Header.vue";
import Content from "../related/event/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.event-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 9999;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.event {
width: 100%;
height: calc(100% - 80px);
}
</style>

View File

@ -30,7 +30,7 @@ limitations under the License. -->
</div> </div>
</el-popover> </el-popover>
<div class="header"> <div class="header">
<Header /> <Header :needQuery="needQuery" />
</div> </div>
<div class="log"> <div class="log">
<List /> <List />
@ -50,6 +50,7 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span> <span>{{ t("delete") }}</span>
</div> </div>
</el-popover> </el-popover>
<Header /> <Header :needQuery="needQuery" />
<Content /> <Content />
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -37,10 +37,17 @@ limitations under the License. -->
v-if="dashboardStore.editMode && canEditTabName" v-if="dashboardStore.editMode && canEditTabName"
/> />
</span> </span>
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
<Icon size="middle" iconName="review-list" class="tab-icon" />
</i>
</el-tooltip>
</span>
<span class="tab-icons" v-if="dashboardStore.editMode"> <span class="tab-icons" v-if="dashboardStore.editMode">
<el-tooltip content="Add tab items" placement="bottom"> <el-tooltip content="Add tab items" placement="bottom">
<i @click="addTabItem"> <i @click="addTabItem">
<Icon size="middle" iconName="add" /> <Icon size="middle" iconName="add_fill" class="tab-icon" />
</i> </i>
</el-tooltip> </el-tooltip>
</span> </span>
@ -99,6 +106,7 @@ limitations under the License. -->
<script lang="ts"> <script lang="ts">
import { ref, watch, defineComponent, toRefs } from "vue"; import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard"; import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
@ -109,7 +117,10 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue"; import Ebpf from "./Ebpf.vue";
import Event from "./Event.vue";
import { dragIgnoreFrom } from "../data"; import { dragIgnoreFrom } from "../data";
import DemandLog from "./DemandLog.vue";
import copy from "@/utils/copy";
const props = { const props = {
data: { data: {
@ -120,16 +131,31 @@ const props = {
}; };
export default defineComponent({ export default defineComponent({
name: "Tab", name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf }, components: {
Topology,
Widget,
Trace,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
},
props, props,
setup(props) { setup(props) {
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const activeTabIndex = ref<number>(0); const route = useRoute();
const activeTabIndex = ref<number>(
Number(route.params.activeTabIndex) || 0
);
const activeTabWidget = ref<string>(""); const activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false); const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false); const needQuery = ref<boolean>(false);
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex( const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i (d: LayoutConfig) => d.i === props.data.i
); );
@ -153,6 +179,11 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children dashboardStore.layout[l].children[activeTabIndex.value].children
); );
needQuery.value = true; needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
} }
function removeTab(e: Event) { function removeTab(e: Event) {
e.stopPropagation(); e.stopPropagation();
@ -179,6 +210,7 @@ export default defineComponent({
editTabIndex.value = index; editTabIndex.value = index;
} }
function handleClick(el: any) { function handleClick(el: any) {
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) { if (["tab-name", "edit-tab"].includes(el.target.className)) {
return; return;
} }
@ -201,6 +233,16 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children dashboardStore.layout[l].children[activeTabIndex.value].children
); );
} }
function copyLink() {
let path = "";
if (route.params.activeTabIndex === undefined) {
path = location.href + "/tab/" + activeTabIndex.value;
} else {
const p = location.href.split("/tab/")[0];
path = p + "/tab/" + activeTabIndex.value;
}
copy(path);
}
document.body.addEventListener("click", handleClick, false); document.body.addEventListener("click", handleClick, false);
watch( watch(
() => dashboardStore.activedGridItem, () => dashboardStore.activedGridItem,
@ -226,6 +268,7 @@ export default defineComponent({
deleteTabItem, deleteTabItem,
removeTab, removeTab,
clickTabs, clickTabs,
copyLink,
...toRefs(props), ...toRefs(props),
activeTabWidget, activeTabWidget,
dashboardStore, dashboardStore,
@ -250,21 +293,23 @@ export default defineComponent({
span { span {
display: inline-block; display: inline-block;
padding: 0 10px;
margin: 0 10px;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
cursor: pointer; cursor: pointer;
text-align: center;
} }
.tab-name { .tab-name {
max-width: 130px; max-width: 110px;
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
outline: none; outline: none;
color: #333; color: #333;
font-style: normal; font-style: normal;
margin-right: 5px; margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
} }
.tab-icons { .tab-icons {
@ -327,6 +372,10 @@ export default defineComponent({
overflow: auto; overflow: auto;
} }
.tab-icon {
color: #666;
}
.vue-grid-item.active { .vue-grid-item.active {
border: 1px solid #409eff; border: 1px solid #409eff;
} }

View File

@ -30,7 +30,7 @@ limitations under the License. -->
</div> </div>
</el-popover> </el-popover>
<div class="header"> <div class="header">
<Filter /> <Filter :needQuery="needQuery" />
</div> </div>
<div class="trace flex-h"> <div class="trace flex-h">
<TraceList /> <TraceList />
@ -53,6 +53,7 @@ const props = defineProps({
default: () => ({ graph: {} }), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -22,5 +22,18 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue"; import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue";
export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf }; export default {
Tab,
Widget,
Trace,
Topology,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
};

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
export const dragIgnoreFrom = export const dragIgnoreFrom =
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules"; "svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules, .vis-item, .vis-timeline";
export const PodsChartTypes = ["EndpointList", "InstanceList"]; export const PodsChartTypes = ["EndpointList", "InstanceList"];
@ -178,14 +178,16 @@ export const AllTools = [
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
]; ];
export const ServiceTools = [ export const ServiceTools = [
{ name: "playlist_add", content: "Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Tab", id: "addTab" }, { name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "library_books", content: "Text", id: "addText" }, { name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" }, { name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Trace Profiling", id: "addProfile" }, { name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" }, { name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
]; ];
export const InstanceTools = [ export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -193,6 +195,8 @@ export const InstanceTools = [
{ name: "library_books", content: "Add Text", id: "addText" }, { name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
]; ];
export const EndpointTools = [ export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -201,6 +205,7 @@ export const EndpointTools = [
{ name: "device_hub", content: "Add Topology", id: "addTopology" }, { name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
]; ];
export const ServiceRelationTools = [ export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },

View File

@ -46,6 +46,7 @@ limitations under the License. -->
:intervalTime="intervalTime" :intervalTime="intervalTime"
:colMetrics="colMetrics" :colMetrics="colMetrics"
:config="config" :config="config"
v-if="colMetrics.length"
/> />
</el-table> </el-table>
</div> </div>
@ -96,7 +97,9 @@ const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false); const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]); const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>(""); const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d)); const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) { if (props.needQuery) {
queryEndpoints(); queryEndpoints();
@ -119,7 +122,7 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) { if (!currentPods.length) {
return; return;
} }
const metrics = (props.config.metrics || []).filter((d: string) => d); const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || []; const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) { if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics( const params = await useQueryPodsMetrics(

View File

@ -43,6 +43,7 @@ limitations under the License. -->
</template> </template>
</el-table-column> </el-table-column>
<ColumnGraph <ColumnGraph
v-if="colMetrics.length"
:intervalTime="intervalTime" :intervalTime="intervalTime"
:colMetrics="colMetrics" :colMetrics="colMetrics"
:config="config" :config="config"
@ -126,7 +127,9 @@ const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances const instances = ref<Instance[]>([]); // current instances
const pageSize = 10; const pageSize = 10;
const searchText = ref<string>(""); const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d)); const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) { if (props.needQuery) {
queryInstance(); queryInstance();
} }
@ -151,7 +154,8 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) { if (!currentInstances.length) {
return; return;
} }
const { metrics, metricTypes } = props.config; const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) { if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics( const params = await useQueryPodsMetrics(

View File

@ -58,6 +58,7 @@ limitations under the License. -->
:intervalTime="intervalTime" :intervalTime="intervalTime"
:colMetrics="colMetrics" :colMetrics="colMetrics"
:config="config" :config="config"
v-if="colMetrics.length"
/> />
</el-table> </el-table>
</div> </div>
@ -117,7 +118,7 @@ const searchText = ref<string>("");
const groups = ref<any>({}); const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]); const sortServices = ref<(Service & { merge: boolean })[]>([]);
const colMetrics = computed(() => const colMetrics = computed(() =>
props.config.metrics.filter((d: string) => d) (props.config.metrics || []).filter((d: string) => d)
); );
queryServices(); queryServices();
@ -195,7 +196,8 @@ async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) { if (!currentServices.length) {
return; return;
} }
const { metrics, metricTypes } = props.config; const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) { if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics( const params = await useQueryPodsMetrics(

View File

@ -17,13 +17,13 @@ limitations under the License. -->
<div class="top-list" v-if="available"> <div class="top-list" v-if="available">
<div class="chart-slow-i" v-for="(i, index) in data[key]" :key="index"> <div class="chart-slow-i" v-for="(i, index) in data[key]" :key="index">
<div class="ell tools flex-h"> <div class="ell tools flex-h">
<div> <div class="desc">
<span class="calls mr-10">{{ i.value }}</span> <span class="calls mr-10">{{ i.value }}</span>
<span class="cp mr-20"> <span class="cp mr-20">
{{ i.name }} {{ i.name }}
</span> </span>
</div> </div>
<div> <div class="copy">
<Icon <Icon
iconName="review-list" iconName="review-list"
size="middle" size="middle"
@ -103,6 +103,16 @@ function handleClick(i: string) {
height: 100%; height: 100%;
} }
.desc {
flex-grow: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.copy {
width: 30px;
}
.calls { .calls {
font-size: 12px; font-size: 12px;
padding: 0 5px; padding: 0 5px;

View File

@ -155,7 +155,7 @@ function getLabel(metric: string, index: string) {
.value { .value {
display: inline-block; display: inline-block;
width: calc(100% - 30px); flex-grow: 2;
height: 100%; height: 100%;
} }
</style> </style>

View File

@ -29,7 +29,7 @@ limitations under the License. -->
:h="item.h" :h="item.h"
:i="item.i" :i="item.i"
:key="item.i" :key="item.i"
@click="clickGrid(item)" @click="clickGrid(item, $event)"
:class="{ active: dashboardStore.activedGridItem === item.i }" :class="{ active: dashboardStore.activedGridItem === item.i }"
:drag-ignore-from="dragIgnoreFrom" :drag-ignore-from="dragIgnoreFrom"
> >
@ -55,10 +55,13 @@ export default defineComponent({
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
function clickGrid(item: LayoutConfig) { function clickGrid(item: LayoutConfig, event: Event) {
dashboardStore.activeGridItem(item.i); dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item); dashboardStore.selectWidget(item);
if (item.type === "Tab") { if (
item.type === "Tab" &&
(event.target as HTMLDivElement)?.className !== "tab-layout"
) {
dashboardStore.setActiveTabIndex(0); dashboardStore.setActiveTabIndex(0);
} }
} }

View File

@ -80,7 +80,7 @@ limitations under the License. -->
</div> </div>
<div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile"> <div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile">
<div class="tool-icons flex-h" v-if="dashboardStore.editMode"> <div class="tool-icons flex-h" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom"> <el-dropdown content="Controls" placement="bottom" :persistent="false">
<i> <i>
<Icon class="icon-btn" size="sm" iconName="control" /> <Icon class="icon-btn" size="sm" iconName="control" />
</i> </i>
@ -455,6 +455,12 @@ function setTabControls(id: string) {
case "addText": case "addText":
dashboardStore.addTabControls("Text"); dashboardStore.addTabControls("Text");
break; break;
case "addDemandLog":
dashboardStore.addTabControls("DemandLog");
break;
case "addEvent":
dashboardStore.addTabControls("Event");
break;
default: default:
ElMessage.info("Don't support this control"); ElMessage.info("Don't support this control");
break; break;
@ -487,6 +493,12 @@ function setControls(id: string) {
case "addText": case "addText":
dashboardStore.addControl("Text"); dashboardStore.addControl("Text");
break; break;
case "addDemandLog":
dashboardStore.addControl("DemandLog");
break;
case "addEvent":
dashboardStore.addControl("Event");
break;
default: default:
dashboardStore.addControl("Widget"); dashboardStore.addControl("Widget");
} }

View File

@ -15,20 +15,19 @@ limitations under the License. -->
<template> <template>
<div class="log"> <div class="log">
<div class="log-header">
<template v-for="(item, index) in columns">
<div <div
class="method" class="log-header"
:style="`width: ${item.method}px`" :class="
v-if="item.drag" type === 'browser' ? ['browser-header', 'flex-h'] : 'service-header'
:key="index" "
>
<template v-for="(item, index) in columns" :key="`col${index}`">
<div
:class="[
item.label,
['message', 'stack'].includes(item.label) ? 'max-item' : '',
]"
> >
<span class="r cp" ref="dragger" :data-index="index">
<Icon iconName="settings_ethernet" size="sm" />
</span>
{{ t(item.value) }}
</div>
<div v-else :class="item.label" :key="`col${index}`">
{{ t(item.value) }} {{ t(item.value) }}
</div> </div>
</template> </template>
@ -58,7 +57,7 @@ limitations under the License. -->
@closed="showDetail = false" @closed="showDetail = false"
:title="t('logDetail')" :title="t('logDetail')"
> >
<LogDetail :currentLog="currentLog" /> <LogDetail :currentLog="currentLog" :columns="columns" />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -70,7 +69,7 @@ import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue"; import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue"; import LogDetail from "./LogDetail.vue";
/*global defineProps, Nullable */ /*global defineProps */
const props = defineProps({ const props = defineProps({
type: { type: String, default: "service" }, type: { type: String, default: "service" },
tableData: { type: Array, default: () => [] }, tableData: { type: Array, default: () => [] },
@ -79,8 +78,6 @@ const props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const currentLog = ref<any>({}); const currentLog = ref<any>({});
const showDetail = ref<boolean>(false); const showDetail = ref<boolean>(false);
const dragger = ref<Nullable<HTMLSpanElement>>(null);
// const method = ref<number>(380);
const columns: any[] = const columns: any[] =
props.type === "browser" ? BrowserLogConstants : ServiceLogConstants; props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
@ -99,13 +96,12 @@ function setCurrentLog(log: any) {
} }
.log-header { .log-header {
/*display: flex;*/
white-space: nowrap; white-space: nowrap;
user-select: none; user-select: none;
border-left: 0; border-left: 0;
border-right: 0; border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
/*background-color: #f3f4f9;*/
.traceId { .traceId {
width: 390px; width: 390px;
} }
@ -123,11 +119,8 @@ function setCurrentLog(log: any) {
} }
.log-header div { .log-header div {
/*min-width: 140px;*/
width: 140px;
/*flex-grow: 1;*/
display: inline-block; display: inline-block;
padding: 0 4px; padding: 0 5px;
border: 1px solid transparent; border: 1px solid transparent;
border-right: 1px dotted silver; border-right: 1px dotted silver;
line-height: 30px; line-height: 30px;
@ -136,4 +129,19 @@ function setCurrentLog(log: any) {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.browser-header {
div {
min-width: 140px;
width: 10%;
}
.max-item {
width: 20%;
}
}
.service-header div {
width: 140px;
}
</style> </style>

View File

@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div @click="showSelectSpan" :class="['log-item', 'clearfix']" ref="logItem"> <div
@click="showSelectSpan"
:class="['log-item', 'clearfix', 'flex-h']"
ref="logItem"
>
<div <div
v-for="(item, index) in columns" v-for="(item, index) in columns"
:key="index" :key="index"
:class="[ :class="[
'method', 'log',
['message', 'stack'].includes(item.label) ? 'autoHeight' : '', ['message', 'stack'].includes(item.label) ? 'max-item' : '',
]" ]"
:style="{
lineHeight: 1.3,
width: `${item.drag ? item.method : ''}px`,
}"
> >
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span> <span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span> <span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
@ -44,7 +44,7 @@ import { BrowserLogConstants } from "./data";
/*global defineProps, defineEmits, NodeListOf */ /*global defineProps, defineEmits, NodeListOf */
const props = defineProps({ const props = defineProps({
data: { type: Array as any, default: () => [] }, data: { type: Object as any, default: () => ({}) },
}); });
const columns = BrowserLogConstants; const columns = BrowserLogConstants;
const emit = defineEmits(["select"]); const emit = defineEmits(["select"]);
@ -84,7 +84,8 @@ function showSelectSpan() {
} }
.log-item > div { .log-item > div {
width: 140px; width: 10%;
min-width: 140px;
padding: 0 5px; padding: 0 5px;
display: inline-block; display: inline-block;
border: 1px solid transparent; border: 1px solid transparent;
@ -95,6 +96,10 @@ function showSelectSpan() {
white-space: nowrap; white-space: nowrap;
} }
.max-item.log {
width: 20%;
}
.log-item .text { .log-item .text {
width: 100% !important; width: 100% !important;
display: inline-block; display: inline-block;
@ -103,8 +108,7 @@ function showSelectSpan() {
white-space: nowrap; white-space: nowrap;
} }
.log-item > div.method { .log-item > div.log {
padding: 7px 5px;
line-height: 30px; line-height: 30px;
} }
</style> </style>

View File

@ -20,7 +20,10 @@ limitations under the License. -->
:key="index" :key="index"
> >
<span class="g-sm-4 grey">{{ t(item.value) }}:</span> <span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'timestamp'" class="g-sm-8 mb-10"> <span
v-if="['timestamp', 'time'].includes(item.label)"
class="g-sm-8 mb-10"
>
{{ dateFormat(currentLog[item.label]) }} {{ dateFormat(currentLog[item.label]) }}
</span> </span>
<textarea <textarea
@ -41,14 +44,14 @@ import { computed } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ServiceLogDetail } from "./data"; import { Option } from "@/types/app";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
currentLog: { type: Object as PropType<any>, default: () => ({}) }, currentLog: { type: Object as PropType<any>, default: () => ({}) },
columns: { type: Array as PropType<Option[]>, default: () => [] },
}); });
const { t } = useI18n(); const { t } = useI18n();
const columns = ServiceLogDetail;
const logTags = computed(() => { const logTags = computed(() => {
if (!props.currentLog.tags) { if (!props.currentLog.tags) {
return []; return [];

View File

@ -0,0 +1,99 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<span v-if="demandLogStore.message">{{ demandLogStore.message }}</span>
<div
v-else
v-loading="demandLogStore.loadLogs"
class="log-content"
ref="logContent"
style="width: calc(100% - 10px); height: calc(100% - 140px)"
></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, onUnmounted, watch, toRaw } from "vue";
import { useDemandLogStore } from "@/store/modules/demand-log";
/*global Nullable */
const demandLogStore = useDemandLogStore();
const monacoInstance = ref();
const logContent = ref<Nullable<HTMLDivElement>>(null);
onMounted(() => {
init();
});
async function init() {
const monaco = await import("monaco-editor");
setTimeout(() => {
monacoInstanceGen(monaco);
}, 500);
window.addEventListener("resize", () => {
editorLayout();
});
}
function monacoInstanceGen(monaco: any) {
monacoInstance.value = monaco.editor.create(logContent.value, {
value: "",
language: "text",
wordWrap: true,
minimap: { enabled: false },
readonly: true,
});
toRaw(monacoInstance.value).updateOptions({ readOnly: true });
editorLayout();
}
function editorLayout() {
if (!logContent.value) {
return;
}
const { width, height } = logContent.value.getBoundingClientRect();
toRaw(monacoInstance.value).layout({
height: height,
width: width,
});
}
onUnmounted(() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).dispose();
monacoInstance.value = null;
demandLogStore.setLogs("");
});
watch(
() => demandLogStore.logs,
() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).setValue(demandLogStore.logs);
if (!demandLogStore.logs) {
return;
}
setTimeout(() => {
toRaw(monacoInstance.value).revealPosition({
column: 1,
lineNumber: demandLogStore.total,
});
}, 1000);
}
);
</script>
<style lang="scss" scoped>
.log-content {
min-width: 600px;
min-height: 400px;
}
</style>

View File

@ -0,0 +1,433 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h row">
<div class="mr-5 mb-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5"> {{ t("instance") }}: </span>
<Selector
size="small"
:value="state.instance.value"
:options="demandLogStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
class="selectors"
/>
</div>
<div class="mr-5 mb-5" v-if="state.container">
<span class="grey mr-5">{{ t("container") }}:</span>
<Selector
size="small"
:value="state.container.value"
:options="demandLogStore.containers"
placeholder="Select a container"
@change="changeField('container', $event)"
class="selectors"
/>
</div>
<!-- <div class="mr-5">
<span class="grey mr-5">{{ t("limit") }}:</span>
<el-input-number
v-model="limit"
:min="1"
:max="1000"
size="small"
controls-position="right"
@change="changeField('limit', $event)"
/>
</div> -->
<div class="mr-5">
<span class="grey mr-5">{{ t("duration") }}:</span>
<Selector
size="small"
:value="state.duration.value"
:options="TimeRanges"
placeholder="Select a time range"
@change="changeField('duration', $event)"
class="duration-range"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("interval") }}:</span>
<Selector
size="small"
:value="state.interval.value"
:options="IntervalOpts"
@change="changeField('interval', $event)"
/>
</div>
</div>
<div class="flex-h row">
<div class="mr-5">
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
</span>
<el-input
size="small"
class="inputs-max"
:placeholder="t('addKeywordsOfContent')"
v-model="contentStr"
@change="addLabels('keywordsOfContent')"
/>
</div>
<div class="mr-5">
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in excludingKeywordsOfContent"
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
</span>
</span>
</span>
<el-input
class="inputs-max"
size="small"
:placeholder="t('addExcludingKeywordsOfContent')"
v-model="excludingContentStr"
@change="addLabels('excludingKeywordsOfContent')"
/>
</div>
</div>
<div class="flex-h row btn-row">
<el-button
class="search-btn mt-10"
size="small"
type="primary"
@click="runInterval"
:disabled="disabled"
>
<Icon
size="middle"
iconName="retry"
:loading="!!intervalFn"
class="mr-5"
/>
{{ intervalFn ? t("pause") : t("start") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { TimeRanges, IntervalOpts } from "./data";
import getLocalTime from "@/utils/localtime";
import dateFormatStep from "@/utils/dateFormat";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const demandLogStore = useDemandLogStore();
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
// const limit = ref<number>(20);
const state = reactive<any>({
instance: { value: "", label: "" },
container: { value: "", label: "" },
duration: { label: "From 30 minutes ago", value: 1800 },
interval: { label: "30 seconds", value: 30 },
});
const disabled = ref<boolean>(true);
/*global Nullable */
const intervalFn = ref<Nullable<any>>(null);
onMounted(() => {
fetchSelectors();
});
async function fetchSelectors() {
if (dashboardStore.entity !== EntityType[3].value) {
await getInstances();
}
getContainers();
if (intervalFn.value) {
clearTimer();
}
}
async function getContainers() {
if (
!(
state.instance.id ||
(selectorStore.currentPod && selectorStore.currentPod.id)
)
) {
return;
}
const resp = await demandLogStore.getContainers(
state.instance.id || selectorStore.currentPod.id
);
if (resp.errors) {
disabled.value = true;
ElMessage.error(resp.errors);
return;
}
if (resp.data.containers.errorReason) {
disabled.value = true;
ElMessage.warning(resp.data.containers.errorReason);
return;
}
if (demandLogStore.containers.length) {
state.container = demandLogStore.containers[0];
disabled.value = false;
}
}
async function getInstances() {
const resp = await demandLogStore.getInstances();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = demandLogStore.instances[0];
}
function runInterval() {
if (intervalFn.value) {
clearTimer();
return;
}
searchLogs();
if (state.interval.value === 0) {
return;
}
intervalFn.value = setInterval(searchLogs, state.interval.value * 1000);
setTimeout(() => {
clearTimer();
}, state.duration.value * 1000);
}
function searchLogs() {
let instance = "";
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
const serviceInstanceId =
instance || (state.instance && state.instance.id) || "";
demandLogStore.setLogCondition({
serviceInstanceId,
container: state.container.value,
duration: rangeTime(),
keywordsOfContent: keywordsOfContent.value.length
? keywordsOfContent.value
: undefined,
excludingKeywordsOfContent: excludingKeywordsOfContent.value.length
? excludingKeywordsOfContent.value
: undefined,
});
if (!serviceInstanceId) {
return;
}
queryLogs();
}
function rangeTime() {
{
const times = {
start: getLocalTime(
appStore.utc,
new Date(new Date().getTime() - state.duration.value * 1000)
),
end: getLocalTime(appStore.utc, new Date()),
step: "SECOND",
};
return {
start: dateFormatStep(times.start, times.step, false),
end: dateFormatStep(times.end, times.step, false),
step: times.step,
};
}
}
async function queryLogs() {
const res = await demandLogStore.getDemandLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
clearTimer();
// if (["limit"].includes(type)) {
// state[type] = opt;
// return;
// }
state[type] = opt[0];
if (type === "instance") {
getContainers();
}
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
demandLogStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
clearTimer();
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
demandLogStore.setLogCondition({
[type]: keywordsOfContent.value,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
demandLogStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
clearTimer();
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
demandLogStore.setLogCondition({
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
clearTimer();
}
function clearTimer() {
if (!intervalFn.value) {
return;
}
clearInterval(intervalFn.value);
intervalFn.value = null;
}
onUnmounted(() => {
clearTimer();
});
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[3].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.row {
margin-bottom: 5px;
position: relative;
flex-wrap: wrap;
}
.inputs-max {
width: 270px;
}
.traceId {
margin-top: 2px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.tips {
color: #888;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.selectors {
width: 250px;
}
.duration-range {
width: 210px;
}
.btn-row {
justify-content: flex-end;
}
.help {
color: #999;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,37 @@
/**
* 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 const TimeRanges = [
{ label: "From 5 seconds ago -- Now", value: 5 },
{ label: "From 10 seconds ago -- Now", value: 10 },
{ label: "From 15 seconds ago -- Now", value: 15 },
{ label: "From 30 seconds ago -- Now", value: 30 },
{ label: "From 45 seconds ago -- Now", value: 45 },
{ label: "From 1 minute ago -- Now", value: 60 },
{ label: "From 5 minutes ago -- Now", value: 300 },
{ label: "From 15 minutes ago -- Now", value: 900 },
{ label: "From 30 minutes ago -- Now", value: 1800 },
];
export const IntervalOpts = [
{ label: "None", value: 0 },
{ label: "5 seconds", value: 5 },
{ label: "10 seconds", value: 10 },
{ label: "15 seconds", value: 15 },
{ label: "30 seconds", value: 30 },
{ label: "45 seconds", value: 45 },
{ label: "1 minute", value: 60 },
];

View File

@ -38,7 +38,7 @@ import EBPFStack from "./components/EBPFStack.vue";
.vis-graph { .vis-graph {
height: 100%; height: 100%;
width: calc(100% - 300px); flex-grow: 2;
min-width: 700px; min-width: 700px;
overflow: auto; overflow: auto;
} }

View File

@ -39,6 +39,10 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const ebpfStore = useEbpfStore(); const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
@ -46,7 +50,9 @@ const dashboardStore = useDashboardStore();
const { t } = useI18n(); const { t } = useI18n();
const newTask = ref<boolean>(false); const newTask = ref<boolean>(false);
searchTasks(); if (props.needQuery) {
searchTasks();
}
async function searchTasks() { async function searchTasks() {
const serviceId = const serviceId =

View File

@ -40,7 +40,12 @@ limitations under the License. -->
@change="changeAggregateType" @change="changeAggregateType"
class="selector mr-10" class="selector mr-10"
/> />
<el-popover placement="bottom" :width="680" trigger="click"> <el-popover
placement="bottom"
:width="680"
trigger="click"
:persistent="false"
>
<template #reference> <template #reference>
<el-button type="primary" size="small"> <el-button type="primary" size="small">
{{ t("processSelect") }} {{ t("processSelect") }}
@ -173,7 +178,7 @@ async function analyzeEBPF() {
timeRanges, timeRanges,
aggregateType: aggregateType.value, aggregateType: aggregateType.value,
}); });
if (res.data.errors) { if (res.data && res.data.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.data.errors);
return; return;
} }
@ -252,15 +257,8 @@ watch(
min-width: 560px; min-width: 560px;
} }
.schedules {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: calc(100% - 60px);
min-height: 150px;
}
.inputs { .inputs {
width: 350px; width: 400px;
} }
.input-with-search { .input-with-search {

View File

@ -24,11 +24,13 @@ import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph"; import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf"; import { useEbpfStore } from "@/store/modules/ebpf";
import { StackElement } from "@/types/ebpf"; import { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css"; import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/ /*global Nullable*/
const ebpfStore = useEbpfStore(); const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null); const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null); const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null); const flameChart = ref<any>(null);
const min = ref<number>(1); const min = ref<number>(1);
@ -51,6 +53,8 @@ function drawGraph() {
symbol: "Virtual Root", symbol: "Virtual Root",
dumpCount: 0, dumpCount: 0,
stackType: "", stackType: "",
rateOfRoot: "",
rateOfParent: "",
}; };
countRange(); countRange();
for (const tree of ebpfStore.analyzeTrees) { for (const tree of ebpfStore.analyzeTrees) {
@ -80,16 +84,37 @@ function drawGraph() {
.title("") .title("")
.selfValue(false) .selfValue(false)
.inverted(true) .inverted(true)
.onClick((d: { data: StackElement }) => {
selectStack.value = d.data;
})
.setColorMapper((d, originalColor) => .setColorMapper((d, originalColor) =>
d.highlight ? "#6aff8f" : originalColor d.highlight ? "#6aff8f" : originalColor
); );
const tip = (d3tip as any)() const tip = (d3tip as any)()
.attr("class", "d3-tip") .attr("class", "d3-tip")
.direction("w") .direction("w")
.html( .html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
(d: { data: StackElement }) => const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;");
`<div class="mb-5 name">Symbol: ${d.data.name}</div><div class="mb-5">Dump Count: ${d.data.dumpCount}</div>` const valStr =
) ebpfStore.aggregateType === AggregateTypes[0].value
? `<div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
: `<div class="mb-5">Duration: ${d.data.dumpCount} ns</div>`;
const rateOfParent =
(d.parent &&
`<div class="mb-5">Percentage Of Selected: ${
(
(d.data.dumpCount /
((selectStack.value && selectStack.value.dumpCount) ||
root.dumpCount)) *
100
).toFixed(3) + "%"
}</div>`) ||
"";
const rateOfRoot = `<div class="mb-5">Percentage Of Root: ${
((d.data.dumpCount / root.dumpCount) * 100).toFixed(3) + "%"
}</div>`;
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
})
.style("max-width", "500px"); .style("max-width", "500px");
flameChart.value.tooltip(tip); flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value); d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
@ -118,6 +143,7 @@ function processTree(arr: StackElement[]) {
obj[item.originId] = item; obj[item.originId] = item;
} }
const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]); const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]);
for (const item of copyArr) { for (const item of copyArr) {
if (item.parentId === "1") { if (item.parentId === "1") {
const val = Number(scale(item.dumpCount).toFixed(4)); const val = Number(scale(item.dumpCount).toFixed(4));

View File

@ -0,0 +1,129 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div ref="timeline" class="events"></div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import dayjs from "dayjs";
import { useThrottleFn } from "@vueuse/core";
import { useEventStore } from "@/store/modules/event";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
const eventStore = useEventStore();
/*global Nullable */
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
new Date(dayjs(date).format(pattern));
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
onMounted(() => {
oldVal.value = (timeline.value && timeline.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
useThrottleFn(resize, 500)();
});
function visTimeline() {
if (!timeline.value) {
return;
}
if (visGraph.value) {
visGraph.value.destroy();
}
const h = timeline.value.getBoundingClientRect().height;
const events = eventStore.events.map((d, index) => {
return {
id: index + 1,
content: d.name,
start: dateFormat(d.startTime),
end: dateFormat(d.endTime),
data: d,
className: d.type,
};
});
const items: any = new DataSet(events);
const options: any = {
height: h,
width: "100%",
locale: "en",
groupHeightMode: "fitItems",
autoResize: false,
tooltip: {
overflowMethod: "cap",
template(item) {
const data = item.data || {};
let tmp = `<div>ID: ${data.uuid || ""}</div>
<div>Name: ${data.name || ""}</div>
<div>Event Type: ${data.type || ""}</div>
<div>Start Time: ${data.startTime ? visDate(data.startTime) : ""}</div>
<div>End Time: ${data.endTime ? visDate(data.endTime) : ""}</div>
<div>Message: ${data.message || ""}</div>
<div>Service: ${data.source.service || ""}</div>`;
if (data.source.endpoint) {
tmp += `<div>Endpoint: ${data.source.endpoint}</div>`;
}
if (data.source.instance) {
tmp += `<div>Service Instance: ${data.source.instance}</div>`;
}
return tmp;
},
},
};
visGraph.value = new Timeline(timeline.value, items, options);
}
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (
Math.abs(cr.width - oldVal.value.width) < 3 &&
Math.abs(cr.height - oldVal.value.height) < 3
) {
return;
}
visTimeline();
oldVal.value = { width: cr.width, height: cr.height };
});
if (timeline.value) {
observer.observe(timeline.value);
}
}
watch(
() => eventStore.events,
() => {
visTimeline();
}
);
</script>
<style lang="scss" scoped>
.events {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: 100%;
min-height: 150px;
}
.message {
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,235 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h row">
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5"> {{ t("instance") }}: </span>
<Selector
size="small"
:value="state.instance.value"
:options="eventStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5"> {{ t("endpoint") }}: </span>
<Selector
size="small"
:value="state.endpoint.value"
:options="eventStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("eventsType") }}: </span>
<Selector
v-model="state.eventType"
:options="EventTypes"
placeholder="Select a type"
@change="changeField('eventType', $event)"
class="event-tool-input"
size="small"
/>
</div>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
layout="prev, pager, next"
:total="total"
@current-change="updatePage"
:pager-count="5"
small
/>
<el-button
class="search-btn"
size="small"
type="primary"
@click="queryEvents"
>
{{ t("search") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEventStore } from "@/store/modules/event";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EventTypes } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const eventStore = useEventStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<any>({
instance: { value: "", label: "All", id: "" },
endpoint: { value: "", label: "All", id: "" },
eventType: { value: "", label: "All" },
});
const total = computed(() =>
eventStore.events.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
if (props.needQuery) {
init();
}
async function init() {
fetchSelectors();
await queryEvents();
state.instance = { value: "", label: "All" };
state.endpoint = { value: "", label: "All" };
}
function fetchSelectors() {
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
}
}
async function getEndpoints(id?: string) {
const resp = await eventStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0];
}
async function queryEvents() {
let endpoint = state.endpoint.value,
instance = state.instance.value;
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
eventStore.setEventCondition({
// layer: dashboardStore.layerId,
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
},
source: {
service: selectorStore.currentService.value || "",
endpoint: endpoint || "",
serviceInstance: instance || "",
},
type: state.eventType.value || undefined,
});
const res = await eventStore.getEvents();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any[]) {
state[type] = opt[0];
}
async function searchEndpoints(keyword: string) {
const resp = await eventStore.getEndpoints(keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
watch(
() => [selectorStore.currentService],
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.inputs-max {
width: 270px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
</style>

View File

@ -50,6 +50,16 @@ limitations under the License. -->
@query="searchEndpoints" @query="searchEndpoints"
/> />
</div> </div>
<div class="mr-5" v-if="isBrowser">
<span class="grey mr-5"> {{ t("category") }}: </span>
<Selector
size="small"
:value="state.category.value"
:options="ErrorCategory"
placeholder="Select a category"
@change="changeField('category', $event)"
/>
</div>
<el-button <el-button
class="search-btn" class="search-btn"
size="small" size="small"
@ -59,14 +69,14 @@ limitations under the License. -->
{{ t("search") }} {{ t("search") }}
</el-button> </el-button>
</div> </div>
<div class="flex-h row"> <div class="flex-h row" v-show="!isBrowser">
<div class="mr-5 traceId" v-show="!isBrowser"> <div class="mr-5 traceId">
<span class="grey mr-5">{{ t("traceID") }}:</span> <span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input v-model="traceId" class="inputs-max" size="small" /> <el-input v-model="traceId" class="inputs-max" size="small" />
</div> </div>
<ConditionTags :type="'LOG'" @update="updateTags" /> <ConditionTags :type="'LOG'" @update="updateTags" />
</div> </div>
<div class="row tips"> <div class="row tips" v-show="!isBrowser">
<b>{{ t("conditionNotice") }}</b> <b>{{ t("conditionNotice") }}</b>
</div> </div>
<div class="flex-h" v-show="!isBrowser"> <div class="flex-h" v-show="!isBrowser">
@ -120,7 +130,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from "vue"; import { ref, reactive, watch, onUnmounted } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log"; import { useLogStore } from "@/store/modules/log";
@ -130,7 +140,12 @@ import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue"; import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
@ -148,9 +163,11 @@ const state = reactive<any>({
instance: { value: "0", label: "All" }, instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" }, endpoint: { value: "0", label: "All" },
service: { value: "", label: "" }, service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
}); });
if (props.needQuery) {
init(); init();
}
async function init() { async function init() {
const resp = await logStore.getLogsByKeywords(); const resp = await logStore.getLogsByKeywords();
@ -219,6 +236,18 @@ function searchLogs() {
if (dashboardStore.entity === EntityType[3].value) { if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id; instance = selectorStore.currentPod.id;
} }
if (dashboardStore.layerId === "BROWSER") {
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
pagePathId: endpoint || state.endpoint.id || undefined,
serviceVersionId: instance || state.instance.id || undefined,
paging: { pageNum: 1, pageSize: 15 },
queryDuration: appStore.durationTime,
category: state.category.value,
});
} else {
logStore.setLogCondition({ logStore.setLogCondition({
serviceId: selectorStore.currentService serviceId: selectorStore.currentService
? selectorStore.currentService.id ? selectorStore.currentService.id
@ -226,18 +255,13 @@ function searchLogs() {
endpointId: endpoint || state.endpoint.id || undefined, endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined, serviceInstanceId: instance || state.instance.id || undefined,
queryDuration: appStore.durationTime, queryDuration: appStore.durationTime,
keywordsOfContent: keywordsOfContent: keywordsOfContent.value,
dashboardStore.layerId === "BROWSER" excludingKeywordsOfContent: excludingKeywordsOfContent.value,
? undefined
: keywordsOfContent.value,
excludingKeywordsOfContent:
dashboardStore.layerId === "BROWSER"
? undefined
: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined, tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15 }, paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined, relatedTrace: traceId.value ? { traceId: traceId.value } : undefined,
}); });
}
queryLogs(); queryLogs();
} }
async function queryLogs() { async function queryLogs() {
@ -299,6 +323,9 @@ function removeExcludeContent(index: number) {
}); });
excludingContentStr.value = ""; excludingContentStr.value = "";
} }
onUnmounted(() => {
logStore.resetCondition();
});
watch( watch(
() => selectorStore.currentService, () => selectorStore.currentService,
() => { () => {

View File

@ -14,30 +14,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesEvent: Array<RouteRecordRaw> = [ export const ErrorCategory = [
{ { label: "All", value: "ALL" },
path: "", { label: "AJAX", value: "AJAX" },
name: "Events", { label: "Resource", value: "RESOURCE" },
meta: { { label: "Vue", value: "VUE" },
title: "events", { label: "Promise", value: "PROMISE" },
icon: "av_timer", { label: "JS", value: "JS" },
hasGroup: false, { label: "Unknown", value: "UNKNOWN" },
exact: true,
},
component: Layout,
children: [
{
path: "/events",
name: "Events",
meta: {
exact: false,
},
component: () =>
import(/* webpackChunkName: "events" */ "@/views/Event.vue"),
},
],
},
]; ];

View File

@ -56,7 +56,7 @@ function loadTrees(l: boolean) {
.item { .item {
height: 100%; height: 100%;
width: calc(100% - 300px); flex-grow: 2;
overflow: auto; overflow: auto;
} }

View File

@ -59,6 +59,10 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const profileStore = useProfileStore(); const profileStore = useProfileStore();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
@ -67,8 +71,10 @@ const { t } = useI18n();
const endpointName = ref<string>(""); const endpointName = ref<string>("");
const newTask = ref<boolean>(false); const newTask = ref<boolean>(false);
searchTasks(); if (props.needQuery) {
searchEndpoints(""); searchTasks();
searchEndpoints("");
}
async function searchEndpoints(keyword: string) { async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) { if (!selectorStore.currentService) {
@ -109,6 +115,7 @@ watch(
() => selectorStore.currentService, () => selectorStore.currentService,
() => { () => {
searchTasks(); searchTasks();
console.log("service");
} }
); );
watch( watch(

View File

@ -538,6 +538,9 @@ function setNodeTools(nodeDashboard: any) {
} }
} }
async function freshNodes() { async function freshNodes() {
if (!svg.value) {
return;
}
svg.value.selectAll(".topo-svg-graph").remove(); svg.value.selectAll(".topo-svg-graph").remove();
await init(); await init();
update(); update();

View File

@ -153,7 +153,6 @@ import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace"; import { useTraceStore } from "@/store/modules/trace";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import copy from "@/utils/copy"; import copy from "@/utils/copy";
import List from "./components/List.vue";
import graphs from "./components/index"; import graphs from "./components/index";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue"; import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
@ -162,7 +161,6 @@ export default defineComponent({
name: "TraceDetail", name: "TraceDetail",
components: { components: {
...graphs, ...graphs,
List,
LogTable, LogTable,
}, },
setup() { setup() {
@ -182,14 +180,8 @@ export default defineComponent({
dayjs(date).format(pattern); dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false); const showTraceLogs = ref<boolean>(false);
function handleClick(ids: string[] | any) { function handleClick() {
let copyValue = null; copy(traceId.value || traceStore.currentTrace.traceIds[0].value);
if (ids.length === 1) {
copyValue = ids[0];
} else {
copyValue = ids.join(",");
}
copy(copyValue);
} }
async function changeTraceId(opt: Option[] | any) { async function changeTraceId(opt: Option[] | any) {
@ -235,6 +227,7 @@ export default defineComponent({
pageNum, pageNum,
loading, loading,
total, total,
traceId,
}; };
}, },
}); });

View File

@ -104,6 +104,10 @@ import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
@ -121,7 +125,10 @@ const state = reactive<any>({
service: { value: "", label: "" }, service: { value: "", label: "" },
}); });
init(); if (props.needQuery) {
init();
}
async function init() { async function init() {
if (dashboardStore.entity === EntityType[1].value) { if (dashboardStore.entity === EntityType[1].value) {
await getServices(); await getServices();

View File

@ -31,7 +31,7 @@ import _ from "lodash";
import * as d3 from "d3"; import * as d3 from "d3";
import ListGraph from "../../utils/d3-trace-list"; import ListGraph from "../../utils/d3-trace-list";
import TreeGraph from "../../utils/d3-trace-tree"; import TreeGraph from "../../utils/d3-trace-tree";
import { Span } from "@/types/trace"; import { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue"; import SpanDetail from "./SpanDetail.vue";
/* global defineProps, Nullable, defineExpose*/ /* global defineProps, Nullable, defineExpose*/
@ -45,6 +45,7 @@ const showDetail = ref<boolean>(false);
const fixSpansSize = ref<number>(0); const fixSpansSize = ref<number>(0);
const segmentId = ref<any>([]); const segmentId = ref<any>([]);
const currentSpan = ref<Array<Span>>([]); const currentSpan = ref<Array<Span>>([]);
const refSpans = ref<Array<Ref>>([]);
const tree = ref<any>(null); const tree = ref<any>(null);
const traceGraph = ref<Nullable<HTMLDivElement>>(null); const traceGraph = ref<Nullable<HTMLDivElement>>(null);
defineExpose({ defineExpose({
@ -103,14 +104,17 @@ function changeTree() {
segmentId.value = []; segmentId.value = [];
const segmentGroup: any = {}; const segmentGroup: any = {};
const segmentIdGroup: any = []; const segmentIdGroup: any = [];
const fixSpans: any[] = []; const fixSpans: Span[] = [];
const segmentHeaders: any = []; const segmentHeaders: Span[] = [];
for (const span of props.data) { for (const span of props.data) {
if (span.refs.length) {
refSpans.value.push(...span.refs);
}
if (span.parentSpanId === -1) { if (span.parentSpanId === -1) {
segmentHeaders.push(span); segmentHeaders.push(span);
} else { } else {
const index = props.data.findIndex( const item = props.data.find(
(i: any) => (i: Span) =>
i.segmentId === span.segmentId && i.spanId === span.spanId - 1 i.segmentId === span.segmentId && i.spanId === span.spanId - 1
); );
const fixSpanKeyContent = { const fixSpanKeyContent = {
@ -119,7 +123,7 @@ function changeTree() {
spanId: span.spanId - 1, spanId: span.spanId - 1,
parentSpanId: span.spanId - 2, parentSpanId: span.spanId - 2,
}; };
if (index === -1 && !_.find(fixSpans, fixSpanKeyContent)) { if (!item && !_.find(fixSpans, fixSpanKeyContent)) {
fixSpans.push({ fixSpans.push({
...fixSpanKeyContent, ...fixSpanKeyContent,
refs: [], refs: [],
@ -133,6 +137,8 @@ function changeTree() {
layer: "Broken", layer: "Broken",
tags: [], tags: [],
logs: [], logs: [],
startTime: 0,
endTime: 0,
}); });
} }
} }
@ -167,6 +173,8 @@ function changeTree() {
layer: "Broken", layer: "Broken",
tags: [], tags: [],
logs: [], logs: [],
startTime: 0,
endTime: 0,
}); });
} }
// if root broken node is not exist, create a root broken node. // if root broken node is not exist, create a root broken node.
@ -191,6 +199,8 @@ function changeTree() {
layer: "Broken", layer: "Broken",
tags: [], tags: [],
logs: [], logs: [],
startTime: 0,
endTime: 0,
}); });
} }
} }
@ -268,14 +278,28 @@ function changeTree() {
} }
function collapse(d: Span) { function collapse(d: Span) {
if (d.children) { if (d.children) {
const item = refSpans.value.find(
(s: Ref) =>
s.parentSpanId === d.spanId && s.parentSegmentId === d.segmentId
);
let dur = d.endTime - d.startTime; let dur = d.endTime - d.startTime;
d.children.forEach((i: Span) => { d.children.forEach((i: Span) => {
dur -= i.endTime - i.startTime; dur -= i.endTime - i.startTime;
}); });
d.dur = dur < 0 ? 0 : dur; d.dur = dur < 0 ? 0 : dur;
if (item) {
d.children = d.children.sort(compare("startTime"));
}
d.children.forEach((i: Span) => collapse(i)); d.children.forEach((i: Span) => collapse(i));
} }
} }
function compare(p: string) {
return (m: any, n: any) => {
const a = m[p];
const b = n[p];
return a - b;
};
}
onBeforeUnmount(() => { onBeforeUnmount(() => {
d3.selectAll(".d3-tip").remove(); d3.selectAll(".d3-tip").remove();
window.removeEventListener("resize", resize); window.removeEventListener("resize", resize);

View File

@ -52,7 +52,7 @@ limitations under the License. -->
class="grey link-hover cp ml-5" class="grey link-hover cp ml-5"
@click="copy(i.value)" @click="copy(i.value)"
> >
<Icon iconName="review-list" /> <Icon size="middle" iconName="review-list" />
</span> </span>
</span> </span>
</div> </div>
@ -166,7 +166,7 @@ function showCurrentSpanDetail(text: string) {
} }
.item span { .item span {
height: 21px; min-height: 21px;
} }
.link-hover { .link-hover {

View File

@ -1,121 +0,0 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="timeline-table clear">
<div
v-for="(i, index) in eventStore.events"
:key="index"
class="mb-10 clear timeline-item"
@click="showEventDetails(i)"
>
<div class="g-sm-3 grey sm hide-xs time-line tr">
{{ dateFormat(parseInt(i.startTime)) }}
</div>
<div class="timeline-table-i g-sm-9">
<div class="message mb-5 b">
{{ i.message }}
</div>
<div
class="timeline-table-i-scope mr-10 l sm"
:class="{
blue: i.scope === 'Service',
green: i.scope === 'Endpoint',
yellow: i.scope === 'ServiceInstance',
}"
>
{{ i.scope }}
</div>
<div class="grey sm show-xs">
{{ dateFormat(parseInt(i.startTime)) }}
</div>
</div>
</div>
<div v-if="!eventStore.events.length" class="tips">{{ t("noData") }}</div>
</div>
<el-dialog
:title="t('eventDetail')"
v-model="showDetails"
fullscreen
:destroy-on-close="true"
@closed="showDetails = false"
>
<div>
<div
class="mb-10"
v-for="(eventKey, index) in EventsDetailKeys"
:key="index"
>
<span class="keys">{{ t(eventKey.text) }}</span>
<span v-if="eventKey.class === 'parameters'">
<span v-for="(d, index) of currentEvent[eventKey.class]" :key="index"
>{{ d.key }}={{ d.value }};
</span>
</span>
<span
v-else-if="
eventKey.class === 'startTime' || eventKey.class === 'endTime'
"
>{{ dateFormat(currentEvent[eventKey.class]) }}</span
>
<span v-else-if="eventKey.class === 'source'" class="source">
<span
>{{ t("service") }}:
{{ currentEvent[eventKey.class].service }}</span
>
<div v-show="currentEvent[eventKey.class].endpoint">
{{ t("endpoint") }}:
{{ currentEvent[eventKey.class].endpoint }}
</div>
<div v-show="currentEvent[eventKey.class].serviceInstance">
{{ t("instance") }}:
{{ currentEvent[eventKey.class].serviceInstance }}
</div>
</span>
<span v-else>{{ currentEvent[eventKey.class] }}</span>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
import { useEventStore } from "@/store/modules/event";
import { EventsDetailKeys } from "./data";
import { Event } from "@/types/events";
const { t } = useI18n();
const eventStore = useEventStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const showDetails = ref<boolean>(false);
const currentEvent = ref<any>({});
function showEventDetails(item: Event) {
showDetails.value = true;
currentEvent.value = item;
}
</script>
<style lang="scss" scoped>
@import "../components/style.scss";
.tips {
width: 100%;
margin: 20px 0;
text-align: center;
font-size: 14px;
}
</style>

View File

@ -1,251 +0,0 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<nav class="event-tool flex-v">
<div class="flex-h">
<div class="mr-5">
<span class="grey">{{ t("layer") }}: </span>
<Selector
v-model="state.currentLayer"
:options="state.layers"
placeholder="Select a layer"
@change="selectLayer"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("service") }}: </span>
<Selector
v-model="state.service"
:options="eventStore.services"
placeholder="Select a service"
@change="selectService"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("instance") }}: </span>
<Selector
v-model="state.instance"
:options="eventStore.instances"
placeholder="Select a instance"
@change="selectInstance"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("endpoint") }}: </span>
<Selector
v-model="state.endpoint"
:options="eventStore.endpoints"
placeholder="Select a endpoint"
@change="selectEndpoint"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("eventsType") }}: </span>
<Selector
v-model="state.eventType"
:options="EventTypes"
placeholder="Select a type"
@change="selectType"
class="event-tool-input"
size="small"
/>
</div>
</div>
<div class="mt-5">
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
layout="prev, pager, next"
:total="total"
@current-change="updatePage"
:pager-count="5"
small
:style="`--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;`"
/>
<!-- <div>
<el-button class="search" type="primary" @click="queryEvents">
<Icon iconName="search" class="mr-5" />
<span class="vm">{{ t("search") }}</span>
</el-button>
</div> -->
</div>
</nav>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { useI18n } from "vue-i18n";
import { EventTypes } from "./data";
import { useEventStore } from "@/store/modules/event";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const eventStore = useEventStore();
const selectorStore = useSelectorStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<{
currentLayer: string;
layers: string[];
eventType: string;
service: string;
instance: string;
endpoint: string;
}>({
currentLayer: "",
layers: [],
eventType: "",
service: "",
instance: "",
endpoint: "",
});
const total = computed(() =>
eventStore.events.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
getSelectors();
async function getSelectors() {
await getLayers();
getServices();
}
async function getServices() {
const resp = await eventStore.getServices(state.currentLayer);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = eventStore.services[0].value;
if (!eventStore.services[0].id) {
queryEvents();
return;
}
getEndpoints(eventStore.services[0].id);
getInstances(eventStore.services[0].id);
queryEvents();
}
async function getEndpoints(id: string) {
const resp = await eventStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0].value;
}
async function getInstances(id: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0].value;
}
async function getLayers() {
const resp = await selectorStore.fetchLayers();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.currentLayer = "";
state.layers = [
{ label: "All", value: "" },
...resp.data.layers.map((d: string) => {
return { label: d, value: d };
}),
];
}
async function queryEvents() {
eventStore.setEventCondition({
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
},
source: {
service: state.service || "",
endpoint: state.endpoint || "",
serviceInstance: state.instance || "",
},
type: state.eventType || undefined,
});
const resp = await eventStore.getEvents();
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
async function selectLayer(opt: any) {
state.currentLayer = opt[0].value;
await getServices();
}
function selectService(opt: any) {
state.service = opt[0].value;
queryEvents();
if (!opt[0].id) {
return;
}
getEndpoints(opt[0].id);
getInstances(opt[0].id);
}
function selectInstance(opt: any) {
state.instance = opt[0].value;
queryEvents();
}
function selectEndpoint(opt: any) {
state.endpoint = opt[0].value;
queryEvents();
}
function selectType(opt: any) {
state.eventType = opt[0].value;
queryEvents();
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
</script>
<style lang="scss" scoped>
.event-tool {
background-color: #f0f2f5;
width: 100%;
padding: 10px;
}
.event-tool-input {
width: 200px;
}
.search {
margin-left: 20px;
}
</style>

View File

@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const AutoImport = require("unplugin-auto-import/webpack"); const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack"); const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers"); const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
@ -66,6 +67,11 @@ module.exports = {
test: /[\\/]node_modules[\\/]echarts|zrender[\\/]/, test: /[\\/]node_modules[\\/]echarts|zrender[\\/]/,
priority: 30, priority: 30,
}, },
monacoEditor: {
name: "monaco-editor",
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
priority: 40,
},
elementPlus: { elementPlus: {
name: "element-plus", name: "element-plus",
test: /[\\/]node_modules[\\/]element-plus|@element-plus[\\/]/, test: /[\\/]node_modules[\\/]element-plus|@element-plus[\\/]/,
@ -100,7 +106,8 @@ module.exports = {
Components({ Components({
resolvers: [ElementPlusResolver({ importStyle: "css" })], resolvers: [ElementPlusResolver({ importStyle: "css" })],
dts: "./src/types/components.d.ts", dts: "./src/types/components.d.ts",
}) }),
new MonacoWebpackPlugin()
); );
}, },
}; };