mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
@@ -56,6 +56,7 @@
|
||||
<url>https://github.com/alibaba/arthas</url>
|
||||
|
||||
<modules>
|
||||
<module>web-ui</module>
|
||||
<module>math-game</module>
|
||||
<module>common</module>
|
||||
<module>spy</module>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.husky/*
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar","statelyai.stately-vscode"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# a Web client based on HTTP API for arthas
|
||||
|
||||
## usage
|
||||
|
||||
* Through clicking the button in the top right corner, you can quickly get or clear sessionId
|
||||
* When you are in trouble, refreshing the page is a good way
|
||||
* Some features must be used with sessionId. but some features must be used without sessionId
|
||||
* In classloader module, you must selecte a classloader before using classloader to load class or resourse
|
||||
|
||||
## develop
|
||||
|
||||
* Strongly recommand devloping with vscode
|
||||
* TS + Vue3 + Tailwindcss + xstate
|
||||
* You can view the Graphical http requesting process with xstate
|
||||
* The final bundle will be placed in `../target/static`
|
||||
|
||||
|
||||
### notice
|
||||
|
||||
* When use pull_results, you can't use other cmd, such as ```sc class```.
|
||||
* The consoleMachine.ts will be replaced perRequestMachine.ts + pinia sooner or later
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link href="/src/main.css" rel="stylesheet" />
|
||||
<title>Arthas Console</title>
|
||||
</head>
|
||||
|
||||
<body style="background: black;">
|
||||
<nav class="navbar navbar-expand navbar-light bg-light flex-column flex-md-row bd-navbar">
|
||||
<a href="https://github.com/alibaba/arthas" target="_blank" title="" class="navbar-brand"><img src="logo.png"
|
||||
alt="Arthas" title="Welcome to Arthas web console" style="height: 25px;" class="img-responsive"></a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="https://arthas.aliyun.com/doc" target="_blank">Documentation
|
||||
<span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://arthas.aliyun.com/doc/arthas-tutorials.html"
|
||||
target="_blank">Online Tutorials</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/alibaba/arthas" target="_blank">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form class="form-inline my-2 my-lg-0">
|
||||
<div class="col">
|
||||
<div class="input-group ">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="ip-addon">IP</span>
|
||||
</div>
|
||||
<input value="127.0.0.1" v-model="ip" type="text" class="form-control" name="ip" id="ip"
|
||||
placeholder="please enter ip address" aria-label="ip" aria-describedby="ip-addon">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="input-group ">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="port-addon">Port</span>
|
||||
</div>
|
||||
<input value="3658" v-model="port" type="text" class="form-control" name="port" id="port"
|
||||
placeholder="please enter port" aria-label="port" aria-describedby="port-addon">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-inline">
|
||||
<button title="connect" type="button" class="btn btn-info form-control"
|
||||
onclick="startConnect()">Connect</button>
|
||||
<button title="disconnect" type="button" class="btn btn-info form-control"
|
||||
onclick="disconnect()">Disconnect</button>
|
||||
<a target="_blank" href="arthas-output/" class="btn btn-info" role="button">Arthas Output</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid px-0">
|
||||
<div class="col px-0" id="terminal-card">
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div title="fullscreen" id="fullSc" class="fullSc">
|
||||
<button id="fullScBtn" onclick="xtermFullScreen()"><img src="fullsc.png"></button>
|
||||
</div>
|
||||
<script src="/src/web-console.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "arthaswebconsole",
|
||||
"private": false,
|
||||
"version": "0.1.4",
|
||||
"scripts": {
|
||||
"dev": "vite --port 8000",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.6.6",
|
||||
"@heroicons/vue": "^1.0.6",
|
||||
"@highlightjs/vue-plugin": "^2.1.0",
|
||||
"@xstate/vue": "^2.0.0",
|
||||
"bootstrap": "4.6.2",
|
||||
"daisyui": "^2.31.0",
|
||||
"echarts": "^5.3.3",
|
||||
"global": "^4.4.0",
|
||||
"highlight.js": "^11.6.0",
|
||||
"jquery": "^3.6.1",
|
||||
"npminstall": "^6.5.1",
|
||||
"pinia": "^2.0.15",
|
||||
"popper.js": "^1.16.1",
|
||||
"typings": "^2.1.1",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "4",
|
||||
"xstate": "^4.32.1",
|
||||
"xterm": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.2",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^2.9.9",
|
||||
"vue-tsc": "^0.34.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 542 B |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,20 @@
|
||||
#terminal:-webkit-full-screen{
|
||||
background-color: rgb(255, 255, 12);
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
.fullSc {
|
||||
z-index: 10000;
|
||||
position: fixed;
|
||||
top: 25%;
|
||||
left: 90%;
|
||||
display: none;
|
||||
}
|
||||
#fullScBtn {
|
||||
border-radius:17px;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import "bootstrap";
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import $ from "jquery"
|
||||
import "xterm/css/xterm.css"
|
||||
import { Terminal } from "xterm"
|
||||
var ws;
|
||||
var xterm =new Terminal()
|
||||
const DEFAULT_SCROLL_BACK = 1000
|
||||
const MAX_SCROLL_BACK = 9999999
|
||||
const MIN_SCROLL_BACK = 1
|
||||
|
||||
$(function () {
|
||||
var url = window.location.href;
|
||||
var ip = getUrlParam('ip');
|
||||
var port = getUrlParam('port');
|
||||
|
||||
if (ip != '' && ip != null) {
|
||||
$('#ip').val(ip);
|
||||
} else {
|
||||
$('#ip').val(window.location.hostname);
|
||||
}
|
||||
if (port != '' && port != null) {
|
||||
$('#port').val(port);
|
||||
}
|
||||
if (port == null && location.port == "8563") {
|
||||
$('#port').val(8563);
|
||||
}
|
||||
|
||||
var iframe = getUrlParam('iframe');
|
||||
if (iframe != null && iframe != 'false') {
|
||||
$("nav").hide()
|
||||
}
|
||||
|
||||
startConnect(true);
|
||||
});
|
||||
|
||||
/** get params in url **/
|
||||
function getUrlParam(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, '\\$&');
|
||||
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
function getCharSize() {
|
||||
var tempDiv = $('<div />').attr({ 'role': 'listitem' });
|
||||
var tempSpan = $('<div />').html('qwertyuiopasdfghjklzxcvbnm');
|
||||
tempDiv.append(tempSpan);
|
||||
$("html body").append(tempDiv);
|
||||
var size = {
|
||||
width: tempSpan.outerWidth() / 26,
|
||||
height: tempSpan.outerHeight(),
|
||||
left: tempDiv.outerWidth() - tempSpan.outerWidth(),
|
||||
top: tempDiv.outerHeight() - tempSpan.outerHeight(),
|
||||
};
|
||||
tempDiv.remove();
|
||||
return size;
|
||||
}
|
||||
|
||||
function getWindowSize() {
|
||||
var e = window;
|
||||
var a = 'inner';
|
||||
if (!('innerWidth' in window)) {
|
||||
a = 'client';
|
||||
e = document.documentElement || document.body;
|
||||
}
|
||||
var terminalDiv = document.getElementById("terminal-card");
|
||||
var terminalDivRect = terminalDiv.getBoundingClientRect();
|
||||
return {
|
||||
width: terminalDivRect.width,
|
||||
height: e[a + 'Height'] - terminalDivRect.top
|
||||
};
|
||||
}
|
||||
|
||||
function getTerminalSize() {
|
||||
var charSize = getCharSize();
|
||||
var windowSize = getWindowSize();
|
||||
console.log('charsize');
|
||||
console.log(charSize);
|
||||
console.log('windowSize');
|
||||
console.log(windowSize);
|
||||
return {
|
||||
cols: Math.floor((windowSize.width - charSize.left) / 10),
|
||||
rows: Math.floor((windowSize.height - charSize.top) / 17)
|
||||
};
|
||||
}
|
||||
|
||||
/** init websocket **/
|
||||
function initWs(ip, port) {
|
||||
var path = 'ws://' + ip + ':' + port + '/ws';
|
||||
ws = new WebSocket(path);
|
||||
}
|
||||
|
||||
/** init xterm **/
|
||||
function initXterm(cols, rows, scrollback) {
|
||||
let scrollNumber = parseInt(scrollback, 10)
|
||||
xterm = new Terminal({
|
||||
cols: cols,
|
||||
rows: rows,
|
||||
screenReaderMode: false,
|
||||
rendererType: 'canvas',
|
||||
convertEol: true,
|
||||
scrollback: isValidNumber(scrollNumber) ? scrollNumber : DEFAULT_SCROLL_BACK
|
||||
});
|
||||
}
|
||||
|
||||
function isValidNumber(scrollNumber) {
|
||||
return scrollNumber >= MIN_SCROLL_BACK &&
|
||||
scrollNumber <= MAX_SCROLL_BACK;
|
||||
}
|
||||
|
||||
/** begin connect **/
|
||||
window.startConnect = function startConnect(silent) {
|
||||
var ip = $('#ip').val();
|
||||
var port = $('#port').val();
|
||||
if (ip == '' || port == '') {
|
||||
alert('Ip or port can not be empty');
|
||||
return;
|
||||
}
|
||||
if (ws != null) {
|
||||
alert('Already connected');
|
||||
return;
|
||||
}
|
||||
// init webSocket
|
||||
initWs(ip, port);
|
||||
ws.onerror = function () {
|
||||
ws.close();
|
||||
ws = null;
|
||||
!silent && alert('Connect error');
|
||||
};
|
||||
ws.onopen = function () {
|
||||
console.log('open');
|
||||
$('#fullSc').show();
|
||||
var terminalSize = getTerminalSize()
|
||||
let scrollback = getUrlParam('scrollback');
|
||||
console.log('terminalSize')
|
||||
console.log(terminalSize)
|
||||
// init xterm
|
||||
initXterm(terminalSize.cols, terminalSize.rows, scrollback)
|
||||
ws.onmessage = function (event) {
|
||||
if (event.type === 'message') {
|
||||
var data = event.data;
|
||||
xterm.write(data);
|
||||
}
|
||||
};
|
||||
xterm.open(document.getElementById('terminal'));
|
||||
console.log(xterm)
|
||||
// xterm = new Terminal()
|
||||
xterm.onData(function (data) {
|
||||
ws.send(JSON.stringify({ action: 'read', data: data }))
|
||||
});
|
||||
ws.send(JSON.stringify({ action: 'resize', cols: terminalSize.cols, rows: terminalSize.rows }));
|
||||
window.setInterval(function () {
|
||||
if (ws != null && ws.readyState === 1) {
|
||||
ws.send(JSON.stringify({ action: 'read', data: "" }));
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
|
||||
window.disconnect = function disconnect() {
|
||||
try {
|
||||
ws.close();
|
||||
ws.onmessage = null;
|
||||
ws.onclose = null;
|
||||
ws = null;
|
||||
xterm.dispose();
|
||||
$('#fullSc').hide();
|
||||
alert('Connection was closed successfully!');
|
||||
} catch (e) {
|
||||
alert('No connection, please start connect first.');
|
||||
}
|
||||
}
|
||||
|
||||
/** full screen show **/
|
||||
window.xtermFullScreen = function xtermFullScreen() {
|
||||
var ele = document.getElementById('terminal-card');
|
||||
requestFullScreen(ele);
|
||||
}
|
||||
|
||||
function requestFullScreen(element) {
|
||||
var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullScreen;
|
||||
if (requestMethod) {
|
||||
requestMethod.call(element);
|
||||
} else if (typeof window.ActiveXObject !== "undefined") {
|
||||
var wscript = new ActiveXObject("WScript.Shell");
|
||||
if (wscript !== null) {
|
||||
wscript.SendKeys("{F11}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (ws !== undefined && ws !== null) {
|
||||
let terminalSize = getTerminalSize();
|
||||
ws.send(JSON.stringify({ action: 'resize', cols: terminalSize.cols, rows: terminalSize.rows }));
|
||||
xterm.resize(terminalSize.cols, terminalSize.rows);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./ui/index.html",
|
||||
"./ui/src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
},
|
||||
animation: {
|
||||
'spin-rev-pause':'0.3s linear 0s infinite reverse both pause spin',
|
||||
'spin-rev-running':'0.3s linear 0s infinite reverse both running spin'
|
||||
},
|
||||
|
||||
},
|
||||
daisyui: {
|
||||
themes: ["corporate"],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("daisyui")
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*":["ui/src/*"]
|
||||
},
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["ui/src/**/*.ts", "ui/src/**/*.d.ts", "ui/src/**/*.tsx", "ui/src/**/*.vue","ui/tests/**/*.ts"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="corporate">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="../public/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web UI</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
</div>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import NavAside from "@/components/routeTo/NavAside.vue";
|
||||
import NavHeader from "@/components/NavHeader.vue";
|
||||
import ErrDialog from "@/components/dialog/ErrDialog.vue";
|
||||
import SuccessDialog from "@/components/dialog/SuccessDialog.vue";
|
||||
import { onBeforeUnmount } from "vue";
|
||||
import machine from "./machines/consoleMachine";
|
||||
import { interpret } from "xstate";
|
||||
import { fetchStore } from "./stores/fetch";
|
||||
import InputDialog from "./components/dialog/InputDialog.vue";
|
||||
import { publicStore } from "./stores/public";
|
||||
import WarnDialog from "./components/dialog/WarnDialog.vue";
|
||||
const fetchS = fetchStore()
|
||||
const publicS = publicStore()
|
||||
onBeforeUnmount(() => {
|
||||
const actor = interpret(machine)
|
||||
actor.start()
|
||||
console.log('asdfasdf')
|
||||
actor.send("INIT")
|
||||
actor.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "interrupt_job",
|
||||
} as AsyncReq
|
||||
})
|
||||
})
|
||||
// :class='{"pointer-events-none":fetchS.jobRunning}'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class=" h-screen flex flex-col">
|
||||
<nav-header class="h-[10vh]"></nav-header>
|
||||
<div class=" flex-auto h-[90vh] overflow-auto">
|
||||
<div class="flex flex-row h-full">
|
||||
<nav-aside ></nav-aside>
|
||||
<div class="flex-auto overflow-auto h-[90vh] w-[90vw]">
|
||||
<router-view>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<err-dialog v-if="publicS.isErr" />
|
||||
<success-dialog v-if="publicS.isSuccess" />
|
||||
<input-dialog v-if="publicS.isInput " />
|
||||
<warn-dialog v-if="publicS.isWarn"></warn-dialog>
|
||||
</template>
|
||||
+722
@@ -0,0 +1,722 @@
|
||||
|
||||
type SessionAction =
|
||||
| "join_session"
|
||||
| "init_session"
|
||||
| "close_session";
|
||||
type ResState = "SCHEDULED" | "SUCCEEDED" | "FAILED" | "REFUSED";
|
||||
|
||||
type MergeObj<T extends Record<string, any>, U extends Record<string, any>> =
|
||||
T extends T ? {
|
||||
[k in (keyof T | keyof U)]: k extends keyof T ? T[k]
|
||||
: U[k];
|
||||
}
|
||||
: never;
|
||||
|
||||
type JobId<T extends Record<string, any>> = T extends T
|
||||
? MergeObj<T, Record<"jobId", number>>
|
||||
: never;
|
||||
type SessionId<T extends Record<string, any>> = T extends T
|
||||
? MergeObj<T, { sessionId?: string }>
|
||||
: never;
|
||||
// type Command<T extends Record<string|"results", any>> = T extends T
|
||||
// ? T["results"][0] extends JobId<CommandResult>
|
||||
// ? "command" extends keyof T["results"][0]
|
||||
// ? MergeObj<T, {command: T["results"][0]["command"]}>
|
||||
// : never
|
||||
// : MergeObj<T, { command: never }>
|
||||
// : never;
|
||||
type Command<T extends Record<string | "results", any>> = T extends T
|
||||
? MergeObj<T, { command: string }>
|
||||
: never;
|
||||
|
||||
type CommonAction<T> = T extends T ? MergeObj<
|
||||
T,
|
||||
{ action: "exec" }
|
||||
>
|
||||
: never;
|
||||
type unionExclude<T, U> = T extends T ? Exclude<T, U> : T;
|
||||
|
||||
// 数值计算
|
||||
type BuildArray<
|
||||
Length extends number,
|
||||
Ele = unknown,
|
||||
Arr extends unknown[] = [],
|
||||
> = Arr["length"] extends Length ? Arr
|
||||
: BuildArray<Length, Ele, [...Arr, Ele]>;
|
||||
|
||||
type Sub1<N extends number> = BuildArray<N> extends
|
||||
[arr1: unknown, ...arr2: infer Rest] ? Rest["length"]
|
||||
: never;
|
||||
|
||||
// 命令T 可以添加N个参数
|
||||
type StringInclude<T extends string, N extends number, P = string> = T extends T
|
||||
? N extends 0 ? T
|
||||
: T | StringInclude<`${T} ${P}`, Sub1<N>>
|
||||
: never;
|
||||
|
||||
type SessionReq =
|
||||
| {
|
||||
action: "init_session";
|
||||
}
|
||||
| SessionId<{
|
||||
action: "join_session" | "close_session";
|
||||
}>;
|
||||
|
||||
type AsyncReq = SessionId<
|
||||
| {
|
||||
action: "interrupt_job";
|
||||
}
|
||||
| MergeObj<
|
||||
{
|
||||
action: "async_exec";
|
||||
},
|
||||
{
|
||||
command: "dashboard";
|
||||
} | {
|
||||
command: StringInclude<"stack", 3>;
|
||||
} | {
|
||||
command: `monitor -c ${number} ${string} ${string}`;
|
||||
} | {
|
||||
command: `trace ${string} ${string}`;
|
||||
} | {
|
||||
command: `tt -t ${string} ${string}`;
|
||||
} | {
|
||||
command: `watch ${string} ${string}`;
|
||||
}
|
||||
>
|
||||
>;
|
||||
type PullResults = SessionId<{
|
||||
action: "pull_results";
|
||||
consumerId?: string;
|
||||
}>;
|
||||
type CommandReq = CommonAction<
|
||||
{
|
||||
command:
|
||||
| "sysenv"
|
||||
| "version"
|
||||
| "sysprop"
|
||||
| "pwd"
|
||||
| "jvm"
|
||||
| "memory"
|
||||
| "perfcounter -d"
|
||||
| "classloader"
|
||||
| "classloader -a"
|
||||
| "classloader -t"
|
||||
| "classloader --url-stat"
|
||||
| `classloader ${string}`
|
||||
| `sm -d ${string}`
|
||||
| `sm ${string}`
|
||||
| `jad ${string}`
|
||||
| `dump ${string}`
|
||||
| "retransform -l"
|
||||
| `retransform ${string}`
|
||||
| `retransform --classPattern ${string}`
|
||||
| `mbean`
|
||||
| `mbean ${string}`
|
||||
| `mbean -m ${string}`
|
||||
| `vmtool --action ${"forceGc" | "getInstances"} ${string}`
|
||||
| "tt -l"
|
||||
| `tt -i ${string} -p`
|
||||
| `tt -s ${string}`
|
||||
| `profiler ${"list" | "status" | "stop" | "resume" | "getSamples"}`
|
||||
| `profiler ${string}`
|
||||
| `stop`
|
||||
| `ognl ${string}`;
|
||||
} | {
|
||||
command: StringInclude<"vmoption" | "thread", 2>;
|
||||
} | {
|
||||
command: StringInclude<"sc", 3>;
|
||||
} | {
|
||||
command: StringInclude<"heapdump" | "heapdump --live" | "reset", 1>;
|
||||
}
|
||||
>;
|
||||
|
||||
type ArthasReq = SessionReq | CommandReq | AsyncReq | PullResults;
|
||||
type ThreadStateCount = {
|
||||
"NEW": number;
|
||||
"RUNNABLE": number;
|
||||
"BLOCKED": number;
|
||||
"WAITING": number;
|
||||
"TIMED_WAITING": number;
|
||||
"TERMINATED": number;
|
||||
};
|
||||
type StatusResult = {
|
||||
type: "status";
|
||||
statusCode: 0;
|
||||
} | {
|
||||
type: "status";
|
||||
// 实际上不起效果
|
||||
statusCode: number;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type InputResult = {
|
||||
inputStatus: "ALLOW_INPUT" | "DISABLED" | "ALLOW_INTERRUPT";
|
||||
type: "input_status";
|
||||
};
|
||||
|
||||
type VmOption = MergeObj<
|
||||
Record<"name" | "origin" | "value", string>,
|
||||
Record<"writeable", boolean>
|
||||
>;
|
||||
type ThreadState = keyof ThreadStateCount;
|
||||
type ThreadStats = {
|
||||
cpu: number;
|
||||
daemon: boolean;
|
||||
deltaTime: number;
|
||||
group: "system";
|
||||
id: number;
|
||||
interrupted: boolean;
|
||||
name: string;
|
||||
priority: number;
|
||||
state: ThreadState;
|
||||
time: number;
|
||||
};
|
||||
type StackTrace = {
|
||||
className: string;
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
methodName: number;
|
||||
nativeMethod: boolean;
|
||||
};
|
||||
type ThreadInfo = {
|
||||
blockedCount: number;
|
||||
blockedTime: number;
|
||||
inNative: true;
|
||||
lockOwnerId: number;
|
||||
lockedMonitors: [];
|
||||
lockedSynchronizers: [];
|
||||
stackTrace: StackTrace[];
|
||||
suspended: boolean;
|
||||
threadId: number;
|
||||
threadName: string;
|
||||
threadState: ThreadState;
|
||||
waitedCount: number;
|
||||
waitedTime: number;
|
||||
};
|
||||
type BusyThread = {
|
||||
blockedCount: number;
|
||||
blockedTime: number;
|
||||
cpu: number;
|
||||
daemon: true;
|
||||
deltaTime: number;
|
||||
group: string;
|
||||
id: number;
|
||||
inNative: boolean;
|
||||
interrupted: boolean;
|
||||
lockInfo: {
|
||||
className: string;
|
||||
identityHashCode: boolean;
|
||||
};
|
||||
lockName: string;
|
||||
lockOwnerId: number;
|
||||
lockedMonitors: any[];
|
||||
lockedSynchronizers: any[];
|
||||
name: string;
|
||||
priority: 10;
|
||||
stackTrace: {
|
||||
className: string;
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
methodName: string;
|
||||
nativeMethod: boolean;
|
||||
}[];
|
||||
state: "WAITING" | "TIMED_WAITING" | "RUNNABLE";
|
||||
suspended: string;
|
||||
time: number;
|
||||
waitedCount: number;
|
||||
waitedTime: number;
|
||||
};
|
||||
type JvmInfo = {
|
||||
RUNTIME: Record<"name" | "value", string>[];
|
||||
"CLASS-LOADING": { name: string; value: number | boolean }[];
|
||||
COMPILATION: { name: string; value: number | string; desc: string }[];
|
||||
"GARBAGE-COLLECTORS": {
|
||||
name: string;
|
||||
value: { name: string; collectionCount: number; collectionTime: number };
|
||||
desc: string;
|
||||
}[];
|
||||
"MEMORY-MANAGERS": { name: string; value: string[] }[];
|
||||
MEMORY: {
|
||||
desc: string;
|
||||
name: string;
|
||||
value: {
|
||||
name: string;
|
||||
init: number;
|
||||
used: number;
|
||||
committed: number;
|
||||
max: number;
|
||||
} | number;
|
||||
}[];
|
||||
"OPERATING-SYSTEM": Record<"name" | "value", string>[];
|
||||
THREAD: {
|
||||
name: string;
|
||||
value: number;
|
||||
}[];
|
||||
"FILE-DESCRIPTOR": {
|
||||
name: string;
|
||||
value: number;
|
||||
}[];
|
||||
};
|
||||
type MemoryInfo = Record<"heap" | "nonheap" | "buffer_pool", {
|
||||
max: number;
|
||||
name: string;
|
||||
total: number;
|
||||
type: string;
|
||||
used: number;
|
||||
usage?: number;
|
||||
}[]>;
|
||||
type RuntimeInfo = {
|
||||
javaHome: string;
|
||||
javaVersion: string;
|
||||
osName: string;
|
||||
osVersion: string;
|
||||
processors: number;
|
||||
systemLoadAverage: number;
|
||||
timestamp: number;
|
||||
uptime: number;
|
||||
};
|
||||
type ClassDetailInfo = {
|
||||
annotation: boolean;
|
||||
annotations: string[];
|
||||
anonymousClass: boolean;
|
||||
array: boolean;
|
||||
classInfo: string;
|
||||
classLoaderHash: string;
|
||||
classloader: string[];
|
||||
codeSource: string;
|
||||
enum: string;
|
||||
interface: string;
|
||||
interfaces: string[];
|
||||
localClass: string;
|
||||
memberClass: string;
|
||||
modifier: string;
|
||||
name: string;
|
||||
primitive: boolean;
|
||||
simpleName: string;
|
||||
superClass: string[];
|
||||
synthetic: boolean;
|
||||
};
|
||||
type ClassField = {
|
||||
annotations: string[];
|
||||
modifier: string;
|
||||
name: string;
|
||||
static: boolean;
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
type MethodInfo = {
|
||||
classLoaderHash: string;
|
||||
constructor: boolean;
|
||||
declaringClass: string;
|
||||
descriptor: string;
|
||||
exceptions: string[];
|
||||
parameters: string[];
|
||||
annotations: string[];
|
||||
methodName: string;
|
||||
modifier: string;
|
||||
returnType: string;
|
||||
};
|
||||
type ClassLoaderNode = {
|
||||
"hash": string;
|
||||
"loadedCount": number;
|
||||
"name": string;
|
||||
"children": ClassLoaderNode[];
|
||||
"parent": string;
|
||||
};
|
||||
type ClassInfo = MergeObj<ClassDetailInfo, { fields: ClassField[] }>;
|
||||
type TraceNode = {
|
||||
children?: TraceNode[];
|
||||
className: string;
|
||||
cost: number;
|
||||
invoking: boolean;
|
||||
lineNumber: number;
|
||||
maxCost: number;
|
||||
methodName: string;
|
||||
minCost: number;
|
||||
times: number;
|
||||
totalCost: number;
|
||||
type: "method";
|
||||
} | {
|
||||
children: never;
|
||||
exception: string;
|
||||
lineNumber: number;
|
||||
message: string;
|
||||
type: "throw";
|
||||
} | {
|
||||
children?: TraceNode[];
|
||||
classloader: string;
|
||||
daemon: boolean;
|
||||
priority: number;
|
||||
threadId: number;
|
||||
threadName: string;
|
||||
timestamp: string;
|
||||
type: "thread";
|
||||
};
|
||||
type TimeFragment = {
|
||||
"className": string;
|
||||
"cost": number;
|
||||
"index": number;
|
||||
"methodName": string;
|
||||
"object": string;
|
||||
"params": {
|
||||
"expand": number;
|
||||
"object": number;
|
||||
}[];
|
||||
"return": boolean;
|
||||
"returnObj": string;
|
||||
"throw": boolean;
|
||||
"throwExp": string;
|
||||
"timestamp": string;
|
||||
};
|
||||
type MonitorData = {
|
||||
className: string;
|
||||
cost: number;
|
||||
failed: number;
|
||||
methodName: number;
|
||||
success: number;
|
||||
total: number;
|
||||
};
|
||||
type CommandResult = {
|
||||
type: "command";
|
||||
state: ResState;
|
||||
command: string;
|
||||
} | {
|
||||
type: "version";
|
||||
version: string;
|
||||
} | {
|
||||
type: "sysenv";
|
||||
env: Record<string, string>;
|
||||
} | {
|
||||
type: "sysprop";
|
||||
props: Record<string, string>;
|
||||
} | {
|
||||
type: "vmoption";
|
||||
vmOptions: vmOption[];
|
||||
} | {
|
||||
type: "pwd";
|
||||
workingDir: string;
|
||||
} | {
|
||||
all: boolean;
|
||||
threadStateCount: ThreadStateCount;
|
||||
threadStats: ThreadStats[];
|
||||
threadInfo: never;
|
||||
busyThreads: never;
|
||||
type: "thread";
|
||||
} | {
|
||||
threadStateCount: never;
|
||||
threadInfo: ThreadInfo;
|
||||
threadStats: never;
|
||||
busyThreads: never;
|
||||
type: "thread";
|
||||
} | {
|
||||
all: boolean;
|
||||
threadStateCount: never;
|
||||
busyThreads: BusyThread[];
|
||||
threadInfo: never;
|
||||
threadStats: never;
|
||||
type: "thread";
|
||||
} | {
|
||||
jvmInfo: JvmInfo;
|
||||
type: "jvm";
|
||||
} | {
|
||||
memoryInfo: MemoryInfo;
|
||||
type: "memory";
|
||||
} | {
|
||||
perfCounters: {
|
||||
name: string;
|
||||
units: string;
|
||||
value: string | number;
|
||||
variability: string;
|
||||
}[];
|
||||
type: "perfcounter";
|
||||
} | {
|
||||
"classLoaderStats": Record<string,Record<"loadedCount"|"numberOfInstance",number>>
|
||||
urlStats: never;
|
||||
urls:never;
|
||||
tree:never;
|
||||
type: "classloader";
|
||||
} | {
|
||||
type: "classloader";
|
||||
urlStats: {
|
||||
[x: `{hash":${string},"name:${string}}`]: {
|
||||
unUsedUrls: string[];
|
||||
usedUrls: string[];
|
||||
};
|
||||
};
|
||||
"classLoaderStats":never;
|
||||
urls: never;
|
||||
tree: never;
|
||||
} | {
|
||||
type: "classloader";
|
||||
urls: never;
|
||||
"classLoaderStats";
|
||||
urlStats:never;
|
||||
classLoaders: ClassLoaderNode[];
|
||||
tree: boolean;
|
||||
} | {
|
||||
type: "classloader";
|
||||
urls: string[];
|
||||
urlStats:never;
|
||||
"classLoaderStats":never;
|
||||
tree: never;
|
||||
} | {
|
||||
classInfo: ClassInfo;
|
||||
detailed: true;
|
||||
type: "sc";
|
||||
segment: 0;
|
||||
withField: true;
|
||||
} | {
|
||||
classNames: string[];
|
||||
detailed: false;
|
||||
segment: number;
|
||||
type: "sc";
|
||||
withField: false;
|
||||
} | {
|
||||
classInfo: ClassDetailInfo;
|
||||
detailed: true;
|
||||
jobId: 31365;
|
||||
segment: 0;
|
||||
type: "sc";
|
||||
withField: false;
|
||||
} | {
|
||||
detail: true;
|
||||
methodInfo: MethodInfo;
|
||||
type: "sm";
|
||||
} | {
|
||||
classInfo: {
|
||||
classLoaderHash: string;
|
||||
classloader: string[];
|
||||
name: string;
|
||||
};
|
||||
location: string;
|
||||
mappings: Record<string, number>;
|
||||
source: string;
|
||||
type: "jad";
|
||||
} | {
|
||||
retransformCount: number;
|
||||
retransformEntries: {
|
||||
bytes: string;
|
||||
className: string;
|
||||
id: number;
|
||||
transformCount: number;
|
||||
}[];
|
||||
retransformClasses: never;
|
||||
type: "retransform";
|
||||
} | {
|
||||
retransformCount: number;
|
||||
retransformEntries: never;
|
||||
retransformClasses: string[];
|
||||
type: "retransform";
|
||||
} | {
|
||||
dumpedClasses: {
|
||||
classLoaderHash: string;
|
||||
classloader: string[];
|
||||
location: string;
|
||||
name: string;
|
||||
}[];
|
||||
type: "dump";
|
||||
} | {
|
||||
gcInfos: {
|
||||
collectionCount: number;
|
||||
collectionTime: number;
|
||||
name: string;
|
||||
}[];
|
||||
memoryInfo: MemoryInfo;
|
||||
runtimeInfo: RuntimeInfo;
|
||||
threads: ThreadStats[];
|
||||
type: "dashboard";
|
||||
} | {
|
||||
mbeanNames: string[];
|
||||
mbeanMetadata: never;
|
||||
mbeanAttribute: never;
|
||||
type: "mbean";
|
||||
} | {
|
||||
mbeanNames: never;
|
||||
mbeanMetadata: {
|
||||
[x: string]: {
|
||||
attributes: {
|
||||
description: string;
|
||||
is: boolean;
|
||||
name: string;
|
||||
readable: boolean;
|
||||
type: string;
|
||||
writable: boolean;
|
||||
openType: Record<string, string>;
|
||||
}[];
|
||||
className: string;
|
||||
constructors: {
|
||||
description: string;
|
||||
name: string;
|
||||
signature: {
|
||||
description: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}[];
|
||||
}[];
|
||||
description: string;
|
||||
notifications: any[];
|
||||
operations: {
|
||||
description: string;
|
||||
impact: number;
|
||||
name: string;
|
||||
returnType: string;
|
||||
signature: {
|
||||
description: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
mbeanAttribute: never;
|
||||
type: "mbean";
|
||||
} | {
|
||||
mbeanNames: never;
|
||||
mbeanMetadata: never;
|
||||
mbeanAttribute: {
|
||||
[x: string]: {
|
||||
name: string;
|
||||
value: string | number | boolean | (number[]);
|
||||
}[];
|
||||
};
|
||||
type: "mbean";
|
||||
} | {
|
||||
dumpFile: string;
|
||||
live: boolean;
|
||||
type: "heapdump";
|
||||
} | {
|
||||
type: "vmtool";
|
||||
value: string;
|
||||
} | {
|
||||
affect: {
|
||||
classCount: number;
|
||||
cost: number;
|
||||
listenerId: number;
|
||||
methodCount: number;
|
||||
};
|
||||
type: "reset";
|
||||
} | {
|
||||
classloader: string;
|
||||
cost: number;
|
||||
daemon: boolean;
|
||||
priority: number;
|
||||
stackTrace: StackTrace[];
|
||||
threadId: string;
|
||||
threadName: string;
|
||||
// date clock
|
||||
ts: `${string} ${string}`;
|
||||
type: "stack";
|
||||
} | {
|
||||
monitorDataList: MonitorData[];
|
||||
type: "monitor";
|
||||
} | {
|
||||
nodeCount: number;
|
||||
root: TraceNode;
|
||||
type: "trace";
|
||||
} | {
|
||||
"expand": never;
|
||||
"replayNo": never;
|
||||
first: boolean;
|
||||
timeFragmentList: TimeFragment[];
|
||||
replayResult: never;
|
||||
sizeLimit: never;
|
||||
type: "tt";
|
||||
} | {
|
||||
"expand": number;
|
||||
"replayNo": number;
|
||||
first: never;
|
||||
"replayResult": TimeFragment;
|
||||
timeFragmentList: never;
|
||||
"sizeLimit": number;
|
||||
"type": "tt";
|
||||
} | {
|
||||
accessPoint: "AtExceptionExit" | "AtEnter" | "AtExit";
|
||||
className: string;
|
||||
cost: number;
|
||||
methodName: string;
|
||||
sizeLimit: number;
|
||||
ts: string;
|
||||
type: "watch";
|
||||
value: string;
|
||||
} | {
|
||||
"action": "list" | "status" | "stop" | "resume" | "getSamples";
|
||||
"executeResult": string;
|
||||
"outputFile"?: string;
|
||||
"type": "profiler";
|
||||
} | {
|
||||
type: "ognl";
|
||||
value: string;
|
||||
};
|
||||
|
||||
type EnchanceResult = {
|
||||
success: boolean;
|
||||
effect: Record<"listenerId" | "cost" | "classCount" | "methodCount", number>;
|
||||
type: "enhancer";
|
||||
};
|
||||
type MessageResult = {
|
||||
message: string;
|
||||
type: "message";
|
||||
};
|
||||
type ArthasResResult = JobId<
|
||||
| MessageResult
|
||||
| StatusResult
|
||||
| InputResult
|
||||
| CommandResult
|
||||
| EnchanceResult
|
||||
>;
|
||||
|
||||
type ResBody = Command<
|
||||
JobId<{
|
||||
results: ArthasResResult[];
|
||||
timeExpired: boolean;
|
||||
jobStatus: "TERMINATED" | "READY";
|
||||
}>
|
||||
>;
|
||||
|
||||
type CommonRes = {
|
||||
state: ResState;
|
||||
sessionId: string;
|
||||
requestId?: string;
|
||||
body: ResBody;
|
||||
};
|
||||
|
||||
type AsyncRes = {
|
||||
state: ResState;
|
||||
sessionId: string;
|
||||
requestId?: string;
|
||||
// results:never;
|
||||
body: Command<
|
||||
JobId<{
|
||||
jobStatus: "READY" | "TERMINATED";
|
||||
}>
|
||||
>;
|
||||
};
|
||||
|
||||
type SessionRes = {
|
||||
sessionId: string;
|
||||
consumerId: string;
|
||||
body: never;
|
||||
state: Exclude<ResState, "FAILED">;
|
||||
};
|
||||
|
||||
type FailRes = SessionId<{
|
||||
message: string;
|
||||
state: "FAILED" | "REFUSED";
|
||||
body: never;
|
||||
}>;
|
||||
|
||||
type ArthasRes = CommonRes | SessionRes | FailRes | AsyncRes;
|
||||
|
||||
type BindQS =
|
||||
| { req: CommandReq; res: CommonRes }
|
||||
| { req: SessionReq; res: SessionRes }
|
||||
| { req: AsyncReq; res: AsyncRes };
|
||||
// autoComplete
|
||||
type Item = { name: string; value: unknown };
|
||||
|
||||
// Tree
|
||||
interface TreeNode {
|
||||
children: TreeNode[];
|
||||
meta: unknown;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, Ref, ref, watchEffect } from 'vue';
|
||||
import { RefreshIcon, LogoutIcon, LoginIcon, MenuIcon, XCircleIcon } from '@heroicons/vue/outline';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import machine from "@/machines/consoleMachine"
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { interpret } from 'xstate';
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const fetchM = useMachine(machine)
|
||||
const publicS = publicStore()
|
||||
const { send } = fetchM
|
||||
const fetchS = fetchStore()
|
||||
const sessionM = useMachine(machine)
|
||||
const version = ref("N/A")
|
||||
const vCmd: CommandReq = {
|
||||
action: "exec",
|
||||
command: "version"
|
||||
}
|
||||
|
||||
const restBtnclass: Ref<'animate-spin-rev-pause' | 'animate-spin-rev-running'> = ref('animate-spin-rev-pause')
|
||||
publicS.getCommonResEffect(fetchM, body => {
|
||||
const result = body.results[0]
|
||||
if (result.type === "version") version.value = result.version
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (!fetchS.wait) restBtnclass.value = "animate-spin-rev-pause"
|
||||
else restBtnclass.value = "animate-spin-rev-running"
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
send("INIT")
|
||||
send({
|
||||
type: "SUBMIT",
|
||||
value: vCmd
|
||||
})
|
||||
sessionM.send("INIT")
|
||||
})
|
||||
// 手动重来
|
||||
const reset = () => {
|
||||
send({
|
||||
type: "SUBMIT",
|
||||
value: vCmd
|
||||
})
|
||||
}
|
||||
const interruptEvent = () => {
|
||||
fetchS.interruptJob()
|
||||
}
|
||||
const forceGc = () => {
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: "vmtool --action forceGc "
|
||||
}).then(
|
||||
res => publicS.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: "GC success!",
|
||||
})
|
||||
)
|
||||
}
|
||||
const logout = async () => {
|
||||
|
||||
restBtnclass.value = "animate-spin-rev-running"
|
||||
|
||||
interruptEvent()
|
||||
|
||||
sessionM.send("SUBMIT", {
|
||||
value: {
|
||||
action: "close_session",
|
||||
sessionId: undefined
|
||||
}
|
||||
})
|
||||
restBtnclass.value = "animate-spin-rev-pause"
|
||||
}
|
||||
|
||||
const login = async () => {
|
||||
sessionM.send("SUBMIT", {
|
||||
value: {
|
||||
action: "init_session"
|
||||
}
|
||||
})
|
||||
}
|
||||
const shutdown = () => {
|
||||
publicS.warnMessage = "Are you sure to stop the arthas? All the Arthas clients connecting to this server will be disconnected."
|
||||
publicS.warningFn = () => {
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
command: "stop",
|
||||
action: "exec"
|
||||
})
|
||||
fetchS.online = false
|
||||
fetchS.wait = false
|
||||
}
|
||||
publicS.isWarn = true
|
||||
}
|
||||
const resetAllClass = () => {
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `reset`
|
||||
}).then(response => {
|
||||
const result = (response as CommonRes).body.results[0]
|
||||
if (result.type === "reset") {
|
||||
// let res = new Map()
|
||||
// Object.entries(result.affect).forEach(([k, v]) => {
|
||||
// res.set(k, k === "cost" ? [`${v}ms`] : [v])
|
||||
// })
|
||||
// let message = ""
|
||||
// for(const key in result.affect) {
|
||||
// m
|
||||
// }
|
||||
publicS.isSuccess=true
|
||||
publicS.SuccessMessage = JSON.stringify(result.affect)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
const tools:[string,()=>void][] = [
|
||||
["forceGc", forceGc],
|
||||
["shutdown", shutdown],
|
||||
["reset class", resetAllClass]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class=" h-[10vh] flex justify-between items-center min-h-max border-b-2 shadow-orange-300">
|
||||
<a class="w-40 flex items-center justify-center" href="https://arthas.aliyun.com/doc/commands.html" target="_blank">
|
||||
<img src="@/assets/arthas.png" alt="logo" class=" w-3/4" />
|
||||
</a>
|
||||
|
||||
<div class="flex items-center h-20">
|
||||
<div class=" mr-4 bg-info text-info-content h-12 rounded-full flex justify-center items-center font-bold p-2">
|
||||
sessionId: {{fetchS.sessionId}}</div>
|
||||
<div class=" mr-4 bg-info text-info-content h-12 p-2 rounded-full flex justify-center items-center font-bold">
|
||||
version:{{ version }}
|
||||
</div>
|
||||
<button v-if="fetchS.jobRunning" @click.prevent="interruptEvent"
|
||||
class="btn-error btn rounded-full h-1/2 p-2 transition mr-4">interrupt</button>
|
||||
<button class=" rounded-full btn btn-info btn-circle h-12 w-12 flex justify-center items-center mr-4 " @click="reset">
|
||||
<refresh-icon class="h-3/4 w-3/4" :class="restBtnclass" />
|
||||
</button>
|
||||
<button class="hover:opacity-50 h-12 w-12 grid place-items-center rounded-full mr-2 transition-all"
|
||||
:class="{ 'bg-primary': !fetchS.online, 'bg-error': fetchS.online }">
|
||||
<LogoutIcon class="h-1/2 w-1/2 text-error-content" @click="logout" v-if="fetchS.online" />
|
||||
<login-icon class="h-1/2 w-1/2 text-primary-content" @click="login" v-else />
|
||||
</button>
|
||||
<Menu as="div" class="relative mr-4">
|
||||
<MenuButton
|
||||
class="w-12 h-12 input-btn-style grid place-items-center rounded-full bg-primary transition">
|
||||
<MenuIcon class="h-3/4 w-3/4 text-primary-content"></MenuIcon>
|
||||
<!-- <XCirleIcon class="h-3/4 w-3/4"></XCirleIcon> -->
|
||||
</MenuButton>
|
||||
<MenuItems class="absolute right-0 top-full input-btn-style mt-4 bg-white px-0 z-10 w-40">
|
||||
<MenuItem v-slot="{ active }" v-for="(v,i) in tools" :key="i">
|
||||
<div :class='{ "bg-blue-500 text-primary-content": active }' class="px-4 py-2">
|
||||
<button @click.prevent="v[1]">{{v[0]}}</button>
|
||||
</div>
|
||||
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
TransitionChild,
|
||||
TransitionRoot
|
||||
} from '@headlessui/vue'
|
||||
import { ExclamationCircleIcon } from '@heroicons/vue/outline';
|
||||
import { onBeforeMount } from 'vue';
|
||||
const store = publicStore()
|
||||
|
||||
function setIsOpen(value: boolean) {
|
||||
store.isErr = value
|
||||
}
|
||||
onBeforeMount(()=>{fetchStore().curPolling.close()})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot :show="true" as="template">
|
||||
<Dialog @close="setIsOpen" class="min-w-max z-20">
|
||||
<TransitionChild enter="transition-opacity duration-300" enter-from="opacity-0" enter-to="opacity-100"
|
||||
leave="transition-opacity duration-300" leave-from="opacity-100" leave-to="opacity-0">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</TransitionChild>
|
||||
<div class="fixed inset-0 grid place-items-center min-w-max">
|
||||
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95">
|
||||
|
||||
<DialogPanel
|
||||
class=" w-1/3 h-1/2 bg-base-100 p-10 rounded-xl shadow-xl flex flex-col justify-between items-center min-w-max">
|
||||
<DialogTitle>
|
||||
<ExclamationCircleIcon class="w-12 h-12 text-error" />
|
||||
</DialogTitle>
|
||||
<DialogDescription as="section"
|
||||
class="flex-auto self-stretch my-10 rounded p-2 break-all max-w-4xl overflow-auto">
|
||||
reason:
|
||||
{{ store.ErrMessage }}
|
||||
</DialogDescription>
|
||||
|
||||
<button @click="setIsOpen(false)"
|
||||
class="btn btn-primary rounded-xl">OK</button>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { publicStore } from '@/stores/public'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
TransitionChild,
|
||||
TransitionRoot
|
||||
} from '@headlessui/vue'
|
||||
import { onMounted, ref, onBeforeMount } from 'vue';
|
||||
const store = publicStore()
|
||||
const debug = (e: any) => console.log(e)
|
||||
|
||||
const inputV = ref("")
|
||||
onMounted(() => {
|
||||
inputV.value = store.inputVal
|
||||
})
|
||||
|
||||
|
||||
function setIsOpen() {
|
||||
store.inputVal = inputV.value
|
||||
console.log(store.inputVal, inputV.value)
|
||||
store.isInput = false
|
||||
}
|
||||
function setIsOpenCancel() {
|
||||
store.isInput = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="true">
|
||||
<Dialog @close="setIsOpenCancel" class="min-w-max z-20">
|
||||
<TransitionChild enter="transition-opacity duration-300" enter-from="opacity-0" enter-to="opacity-100"
|
||||
leave="transition-opacity duration-300" leave-from="opacity-100" leave-to="opacity-0">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</TransitionChild>
|
||||
<div class="fixed inset-0 grid place-items-center min-w-max">
|
||||
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95">
|
||||
|
||||
<DialogPanel
|
||||
class=" w-1/3 h-1/2 bg-base-100 p-10 rounded-xl shadow-xl flex flex-col justify-between items-center min-w-max">
|
||||
<DialogTitle>
|
||||
input value
|
||||
</DialogTitle>
|
||||
<DialogDescription class=" bg-slate-200 my-10 rounded-full w-full flex justify-center px-4">
|
||||
<input type="text" v-model="inputV" class="bg-slate-200 h-full p-2 w-full focus-visible:outline-none"/>
|
||||
</DialogDescription>
|
||||
<div class="flex justify-evenly w-full">
|
||||
<button @click="setIsOpen"
|
||||
class="btn btn-primary rounded-xl">OK</button>
|
||||
<button @click="setIsOpenCancel"
|
||||
class="btn btn-primary rounded-xl">Cancel</button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { publicStore } from '@/stores/public'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
TransitionChild,
|
||||
TransitionRoot
|
||||
} from '@headlessui/vue'
|
||||
import { CheckCircleIcon } from '@heroicons/vue/outline';
|
||||
const store = publicStore()
|
||||
|
||||
function setIsOpen(value: boolean) {
|
||||
store.isSuccess = value
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot :show="store.isSuccess" as="template">
|
||||
<Dialog @close="setIsOpen" class="min-w-max z-20">
|
||||
<TransitionChild enter="transition-opacity duration-300" enter-from="opacity-0" enter-to="opacity-100"
|
||||
leave="transition-opacity duration-300" leave-from="opacity-100" leave-to="opacity-0">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</TransitionChild>
|
||||
<div class="fixed inset-0 grid place-items-center min-w-max">
|
||||
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95">
|
||||
|
||||
<DialogPanel
|
||||
class=" w-1/3 h-1/2 bg-base-100 p-10 rounded-xl shadow-xl flex flex-col justify-between items-center min-w-max">
|
||||
<DialogTitle>
|
||||
<CheckCircleIcon class="w-12 h-12 text-success" />
|
||||
</DialogTitle>
|
||||
<DialogDescription as="section"
|
||||
class="flex-auto self-stretch my-10 rounded p-2 break-all max-w-4xl">
|
||||
{{ store.SuccessMessage }}
|
||||
</DialogDescription>
|
||||
|
||||
<button @click="setIsOpen(false)"
|
||||
class="btn btn-primary rounded-xl">OK</button>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { publicStore } from '@/stores/public'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
TransitionChild,
|
||||
TransitionRoot
|
||||
} from '@headlessui/vue'
|
||||
import {
|
||||
ExclamationCircleIcon
|
||||
} from "@heroicons/vue/outline"
|
||||
import { onMounted, ref, onBeforeMount } from 'vue';
|
||||
const store = publicStore()
|
||||
|
||||
|
||||
function setIsOpen() {
|
||||
store.warningFn()
|
||||
store.isWarn = false
|
||||
}
|
||||
function setIsOpenCancel() {
|
||||
store.isWarn = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="true">
|
||||
<Dialog @close="setIsOpenCancel" class="min-w-max min-h-max z-20">
|
||||
<TransitionChild enter="transition-opacity duration-300" enter-from="opacity-0" enter-to="opacity-100"
|
||||
leave="transition-opacity duration-300" leave-from="opacity-100" leave-to="opacity-0">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</TransitionChild>
|
||||
<div class="fixed inset-0 grid place-items-center min-w-max">
|
||||
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95">
|
||||
|
||||
<DialogPanel
|
||||
class=" w-1/3 h-1/2 bg-base-100 p-10 rounded-xl shadow-xl flex flex-col justify-between items-center min-w-max">
|
||||
<DialogTitle>
|
||||
<ExclamationCircleIcon class="w-12 h-12 text-warning" />
|
||||
</DialogTitle>
|
||||
<DialogDescription
|
||||
class="flex-auto self-stretch my-10 rounded p-2 break-all max-w-4xl overflow-auto"
|
||||
>
|
||||
{{store.warnMessage}}
|
||||
</DialogDescription>
|
||||
<div class="flex justify-evenly w-full">
|
||||
<button @click="setIsOpen"
|
||||
class="btn btn-primary rounded-xl">OK</button>
|
||||
<button @click="setIsOpenCancel"
|
||||
class="btn btn-primary rounded-xl">Cancel</button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
Combobox, ComboboxButton, ComboboxInput, ComboboxOptions, ComboboxOption, ComboboxLabel,
|
||||
} from "@headlessui/vue"
|
||||
import { SelectorIcon } from "@heroicons/vue/outline"
|
||||
|
||||
/**
|
||||
*
|
||||
* @zh 之后优化的时候可以把autoComplete的input做成伸缩式,可以由组件去
|
||||
*/
|
||||
|
||||
const {
|
||||
optionItems,
|
||||
inputFn,
|
||||
blurFn = _=>{},
|
||||
optionsInit=(_)=>{},
|
||||
filterFn,
|
||||
supportedover=false
|
||||
} = defineProps<{
|
||||
label: string,
|
||||
optionItems: Item[],
|
||||
optionsInit?:(event:FocusEvent)=>void,
|
||||
filterFn?:(query:string,item:Item)=>boolean
|
||||
inputFn?: (value:string) => Promise<unknown>
|
||||
blurFn?:(value:any)=>void
|
||||
supportedover?:boolean
|
||||
}>()
|
||||
|
||||
const query = ref('')
|
||||
const selectedItem = ref({name:"",value:""} as Item)
|
||||
const filterItems = computed(() => {
|
||||
let result:Item[] = []
|
||||
if(query.value === ""){
|
||||
selectedItem.value = {name:"",value:""}
|
||||
result = optionItems
|
||||
} else {
|
||||
result = optionItems.filter(item=>{
|
||||
if(filterFn) return filterFn(query.value,item)
|
||||
else {
|
||||
return item.name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
|
||||
}
|
||||
})
|
||||
}
|
||||
if(supportedover) result.unshift(selectedItem.value)
|
||||
return result
|
||||
})
|
||||
let changeMutex = true
|
||||
const changeF = (event:Event &{target:HTMLInputElement}) => {
|
||||
query.value = event.target.value
|
||||
if(changeMutex) {
|
||||
changeMutex = false
|
||||
if(inputFn) inputFn(query.value).finally(()=>changeMutex = true)
|
||||
else changeMutex = false
|
||||
}
|
||||
}
|
||||
const blurF = (event:Event)=>{
|
||||
blurFn(selectedItem.value.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Combobox v-model="selectedItem" class="flex items-center" as="div">
|
||||
<ComboboxLabel class="p-2">{{ label }}</ComboboxLabel>
|
||||
<div class="relative flex-1">
|
||||
<div
|
||||
class="relative w-full cursor-default
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline
|
||||
outline-2
|
||||
min-w-[15rem]
|
||||
hover:shadow-md transition">
|
||||
<ComboboxInput class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none" @change="changeF" @focus.prevent="optionsInit" @blur="blurF"
|
||||
:displayValue="(item) => (item as Item).name" />
|
||||
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<SelectorIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</ComboboxButton>
|
||||
</div>
|
||||
<ComboboxOptions
|
||||
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div v-if="filterItems.length === 0 && query !== ''"
|
||||
class="relative cursor-default select-none py-2 px-4 text-gray-700">
|
||||
Nothing found.
|
||||
</div>
|
||||
|
||||
<ComboboxOption
|
||||
v-for="(item,i) in filterItems" as="template" :key="i" :value="item"
|
||||
v-slot="{ selected, active }">
|
||||
<li class="relative cursor-default select-none p-2" :class="{
|
||||
'bg-blue-400 text-white': active,
|
||||
'bg-blue-600 text-white': selected,
|
||||
'text-gray-900': !active && !selected,
|
||||
}">
|
||||
<span class="block truncate"
|
||||
:class="{ 'font-medium': selected, 'font-normal': !selected, 'text-white': active, 'text-teal-600': !active && !selected }">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
</ComboboxOptions>
|
||||
</div>
|
||||
<slot :selectItem="selectedItem"></slot>
|
||||
</Combobox>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { ref } from 'vue';
|
||||
import { interpret } from 'xstate';
|
||||
import AutoComplete from './AutoComplete.vue';
|
||||
const { label = "className", supportedover = false, noClassloader=false } = defineProps<{
|
||||
label?: string,
|
||||
submitF: (data: {
|
||||
classItem: Item,
|
||||
loaderItem: Item
|
||||
}) => void
|
||||
supportedover?:boolean
|
||||
noClassloader?:boolean
|
||||
}>()
|
||||
const optionClass = ref([] as { name: string, value: string }[])
|
||||
const optionClassloders = ref([] as { name: string, value: string }[])
|
||||
const fetchS = fetchStore()
|
||||
// const selectedClassItem = ref({ name: "", value: "" } as Item)
|
||||
const changeValue = (value: string) => {
|
||||
const searchClass = interpret(permachine)
|
||||
if (value.length > 2) {
|
||||
return fetchS.baseSubmit(searchClass, {
|
||||
action: "exec",
|
||||
command: `sc *${value}*`
|
||||
}).then(
|
||||
res => {
|
||||
optionClass.value.length = 0
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "sc" && !result.detailed && !result.withField) {
|
||||
console.log(result)
|
||||
result.classNames.forEach(name => {
|
||||
optionClass.value.push({
|
||||
name,
|
||||
value: name
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
const blurF = (value: unknown) => {
|
||||
if (value !== "" && !noClassloader) {
|
||||
const searchClass = interpret(permachine)
|
||||
fetchS.baseSubmit(searchClass, {
|
||||
action: "exec",
|
||||
command: `sc -d *${value}*`
|
||||
}).then(
|
||||
res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "sc" && result.detailed) {
|
||||
|
||||
optionClassloders.value = result.classInfo.classloader.map(v => ({
|
||||
name: v,
|
||||
value: v.split("@")[1]
|
||||
}))
|
||||
optionClassloders.value.unshift({
|
||||
name: "default",
|
||||
value: ""
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
const filterfn = (_: any, item: Item) => true
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoComplete :label="label" :option-items="optionClass" :input-fn="changeValue" :filter-fn="filterfn" v-slot="slotP" :supportedover="supportedover"
|
||||
:blur-fn="blurF" as="form">
|
||||
<template v-if="noClassloader">
|
||||
<slot name="others"></slot>
|
||||
<button @click.prevent="submitF({
|
||||
classItem:slotP.selectItem,
|
||||
loaderItem:slotP.selectItem
|
||||
})" class="btn btn-primary btn-sm btn-outline mx-2 transition">submit</button>
|
||||
</template>
|
||||
<AutoComplete label="classloader" :option-items="optionClassloders" v-slot="slotQ" v-else>
|
||||
<slot name="others"></slot>
|
||||
<button @click.prevent="submitF({
|
||||
classItem:slotP.selectItem,
|
||||
loaderItem:slotQ.selectItem,
|
||||
})" class="btn btn-primary btn-sm btn-outline mx-2 transition">submit</button>
|
||||
</AutoComplete>
|
||||
</AutoComplete>
|
||||
</template>
|
||||
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { ref } from 'vue';
|
||||
import { interpret } from 'xstate';
|
||||
import AutoComplete from './AutoComplete.vue';
|
||||
const { label = "className", ncondition = false, nexpress = false, ncount = false } = defineProps<{
|
||||
label?: string,
|
||||
nexpress?: boolean,
|
||||
ncondition?: boolean,
|
||||
ncount?: boolean,
|
||||
submitF: (data: {
|
||||
classItem: Item, methodItem: Item,
|
||||
conditon: string,
|
||||
express: string,
|
||||
count: number
|
||||
}) => void
|
||||
}>()
|
||||
const fetchS = fetchStore()
|
||||
const optionClass = ref([] as { name: string, value: string }[])
|
||||
const optionMethod = ref([] as { name: string, value: string }[])
|
||||
const conditon = ref("")
|
||||
const express = ref("")
|
||||
const autoStop = ref(0)
|
||||
const changeClass = (value: string) => {
|
||||
const searchClass = interpret(permachine)
|
||||
if (value.length > 2) {
|
||||
return fetchS.baseSubmit(searchClass, {
|
||||
action: "exec",
|
||||
command: `sc *${value}*`
|
||||
}).then(
|
||||
res => {
|
||||
optionClass.value.length = 0
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "sc" && !result.detailed && !result.withField) {
|
||||
result.classNames.forEach(name => {
|
||||
optionClass.value.push({
|
||||
name,
|
||||
value: name
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
const changeMethod = (classV: string, value: string) => {
|
||||
const searchMethod = interpret(permachine)
|
||||
return fetchS.baseSubmit(searchMethod, {
|
||||
action: "exec",
|
||||
command: `sm ${classV} *${value}*`
|
||||
}).then(
|
||||
res => {
|
||||
optionMethod.value.length = 0
|
||||
; (res as CommonRes).body.results.forEach(result => {
|
||||
if (result.type === "sm") {
|
||||
|
||||
const name = result.methodInfo.methodName
|
||||
optionMethod.value.push({
|
||||
name,
|
||||
value: name
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
const setCount = publicStore().inputDialogFactory(autoStop,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 0 : valRaw
|
||||
},
|
||||
(input) => input.value.toString()
|
||||
)
|
||||
const setConditon = publicStore().inputDialogFactory(
|
||||
conditon,
|
||||
raw => raw,
|
||||
_ => _.value
|
||||
)
|
||||
const setExpress = publicStore().inputDialogFactory(
|
||||
express,
|
||||
raw => raw,
|
||||
_ => _.value
|
||||
)
|
||||
|
||||
|
||||
const filterfn = (_: any, item: Item) => true
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoComplete :label="label" :option-items="optionClass" :input-fn="changeClass" :filter-fn="filterfn"
|
||||
v-slot="slotClass">
|
||||
<AutoComplete label="method" :option-items="optionMethod"
|
||||
:input-fn="(value: string) => changeMethod(slotClass.selectItem.value as string, value)" :filter-fn="filterfn"
|
||||
v-slot="slotMethod">
|
||||
<button v-if="nexpress" class="btn btn-sm btn-outline ml-2" @click="setExpress">express:{{express}}</button>
|
||||
<button v-if="ncondition" class="btn btn-sm btn-outline ml-2" @click="setConditon">condition:{{conditon}}</button>
|
||||
<button v-if="ncount" class="btn btn-sm btn-outline ml-2" @click="setCount">count:{{autoStop}}</button>
|
||||
<slot name="others" :methodItem="slotMethod.selectItem" :classItem="slotClass.selectItem"></slot>
|
||||
<button @click.prevent="submitF({
|
||||
classItem: slotClass.selectItem,
|
||||
methodItem: slotMethod.selectItem,
|
||||
conditon,
|
||||
express,
|
||||
count:autoStop
|
||||
})" class="btn btn-primary btn-sm btn-outline mx-2 transition">submit</button>
|
||||
</AutoComplete>
|
||||
|
||||
</AutoComplete>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Switch } from '@headlessui/vue'
|
||||
import { PlayIcon, StopIcon } from "@heroicons/vue/solid"
|
||||
const { playFn = () => { }, stopFn = () => { }, defaultEnabled } = defineProps<{
|
||||
playFn?: Function,
|
||||
stopFn?: Function,
|
||||
defaultEnabled: boolean
|
||||
}>()
|
||||
const enabled = ref(defaultEnabled)
|
||||
const toggle = () => enabled.value ? stopFn() : playFn()
|
||||
</script>
|
||||
<template>
|
||||
<Switch v-model="enabled"
|
||||
class="rounded-full grid place-items-center hover:opacity-50 transition" @click="toggle">
|
||||
<PlayIcon v-if="!enabled" class="w-full h-full text-blue-500" />
|
||||
<StopIcon v-else class="w-full h-full text-red-500" />
|
||||
</Switch>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, reactive, ref, watch, watchEffect } from 'vue';
|
||||
import {
|
||||
Switch
|
||||
} from "@headlessui/vue"
|
||||
const props = defineProps<{
|
||||
data: { key: string, value: boolean | string },
|
||||
send: Function
|
||||
}>()
|
||||
const modelvalue = ref('' as string | boolean)
|
||||
watch(props, () => {
|
||||
modelvalue.value = props.data.value
|
||||
console.log("watch", props.data)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
console.log(props.data)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex justify-between items-center" @submit.prevent="send({ key: data.key, value: modelvalue })">
|
||||
<div class="w-1/5 bg-blue-200 p-2 min-w-max">{{ data.key }}</div>
|
||||
<div class="w-3/5 grid place-items-center">
|
||||
<Switch v-if="typeof data.value === 'boolean'" v-model="(modelvalue as boolean)"
|
||||
:class="modelvalue ? 'bg-blue-500' : 'bg-teal-700'"
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full">
|
||||
<span class="sr-only">Enable notifications</span>
|
||||
<span :class="modelvalue ? 'translate-x-6' : 'translate-x-1'"
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition" />
|
||||
</Switch>
|
||||
<input v-else-if="typeof data.value === 'string'" v-model="(modelvalue as string)" class="rounded-full pl-3 w-1/2"/>
|
||||
</div>
|
||||
<div class="w-1/5">
|
||||
<button
|
||||
class="p-1 w-24 rounded-full bg-blue-300 hover:bg-blue-500 transition text-black">
|
||||
change
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { PlusCircleIcon, MinusCircleIcon } from "@heroicons/vue/outline"
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems
|
||||
} from "@headlessui/vue"
|
||||
import { ref } from "vue";
|
||||
import { publicStore } from "@/stores/public";
|
||||
const { valSet = new Set<string>(), getInput = () => true } = defineProps<{
|
||||
valSet?: Set<string>,
|
||||
getInput?: (raw: string) => boolean,
|
||||
title: string
|
||||
}>()
|
||||
const publicS = publicStore()
|
||||
|
||||
const openInput = publicS.inputDialogFactory(
|
||||
ref(""),
|
||||
(raw) => {
|
||||
if (getInput(raw)) valSet.add(raw)
|
||||
return ""
|
||||
},
|
||||
_ => ""
|
||||
)
|
||||
|
||||
|
||||
const removeValSet = (val: string, valSet: Set<string>) => {
|
||||
valSet.delete(val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Menu as="div" class=" relative flex items-center">
|
||||
<MenuButton class=" w-52 hover:shadow-md btn btn-sm btn-outline">{{title}}</MenuButton>
|
||||
<MenuItems
|
||||
class=" absolute w-52 mt-2 border py-2 rounded-md hover:shadow-xl transition bg-base-100 max-h-80 overflow-y-auto top-[100%]">
|
||||
<MenuItem v-slot="{active}">
|
||||
<li class="flex justify-center " :class='{" bg-neutral text-neutral-content": active}'>
|
||||
<PlusCircleIcon class="w-6 h-6 cursor-pointer" @click="openInput"></PlusCircleIcon>
|
||||
</li>
|
||||
|
||||
</MenuItem>
|
||||
<template v-if="valSet.size > 0">
|
||||
<MenuItem v-slot="{ active, selected }" v-for="(v) in valSet.values()" :key="v">
|
||||
<li class="flex w-full justify-between px-2" :class='{"bg-neutral text-neutral-content":
|
||||
active, "bg-neutral-focus text-neutral-content" : selected,}'>
|
||||
<div>{{v}}</div>
|
||||
<MinusCircleIcon class="w-6 h-6 cursor-pointer" @click="removeValSet(v,valSet)"></MinusCircleIcon>
|
||||
</li>
|
||||
</MenuItem>
|
||||
</template>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { PuzzleIcon, TerminalIcon, ViewGridIcon } from "@heroicons/vue/outline"
|
||||
import { DesktopComputerIcon } from "@heroicons/vue/solid"
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
const tabs = [
|
||||
{
|
||||
name: 'dashboard',
|
||||
url: "/dashboard",
|
||||
icon: DesktopComputerIcon
|
||||
},
|
||||
{
|
||||
name: 'immediacy',
|
||||
url: '/synchronize',
|
||||
icon: ViewGridIcon
|
||||
}, {
|
||||
name: "real time",
|
||||
url: '/asynchronize',
|
||||
icon: ViewGridIcon
|
||||
},
|
||||
{
|
||||
name: 'option',
|
||||
url: '/config',
|
||||
icon: PuzzleIcon
|
||||
},
|
||||
{
|
||||
name: 'console',
|
||||
url: '/console',
|
||||
icon: TerminalIcon
|
||||
},
|
||||
]
|
||||
const router = useRouter()
|
||||
const routePath = computed(() => useRoute().path)
|
||||
const toNext = (url: string) => {
|
||||
router.push(url)
|
||||
}
|
||||
const a: StatusResult = { type: "status", message: "", statusCode: 0 }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <div class=" h-full bg-gray-300">
|
||||
<ul class="flex flex-col justify-start w-40 h-full items-stretch bg-blue-50">
|
||||
<li v-for="(tab, idx) in tabs" :key="idx" class="flex justify-center items-center hover:bg-gray-200 transition"
|
||||
@click="toNext(tab.url)" :class="{ 'bg-gray-200': routePath.includes(tab.url), }">
|
||||
<div class="bg-gray-200 h-10 w-10 grid place-items-center rounded-full">
|
||||
<component :is="tab.icon" class="w-3/4 h-3/4 text-gray-500" />
|
||||
</div>
|
||||
<button class=" outline-none grid place-items-centerh-16 w-20 m-4">{{
|
||||
tab.name
|
||||
}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class=" w-0 overflow-hidden">
|
||||
<slot name="detail">
|
||||
</slot>
|
||||
</ul>
|
||||
</div> -->
|
||||
<ul class="menu bg-base-200 w-[10vw] menu-compact">
|
||||
<li v-for="(tab, idx) in tabs" :key="idx"
|
||||
@click="toNext(tab.url)" class="pl-2" >
|
||||
<a class="break-all" :class="{ 'bg-primary text-primary-content': routePath.includes(tab.url), }">
|
||||
<component :is="tab.icon" class="w-4 h-4" />
|
||||
{{
|
||||
tab.name
|
||||
}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
defineProps<{
|
||||
routes: { cmd: string, url: string }[]
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const routePath = computed(() => route.path)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <ul class=" w-40 border-l bg-gray-200 ">
|
||||
<li v-for="(v, i) in routes" :key="i">
|
||||
<button @click="() => router.push(v.url)"
|
||||
class=" transition w-full h-12 bg-gray-300 hover:bg-slate-400"
|
||||
:class='{"bg-gray-500 text-white":routePath.includes(v.url)}'
|
||||
>
|
||||
{{ v.cmd }}
|
||||
</button>
|
||||
</li>
|
||||
</ul> -->
|
||||
<ul class=" w-[10vw] menu bg-base-300 menu-compact">
|
||||
<li v-for="(v, i) in routes" :key="i">
|
||||
<a @click="() => router.push(v.url)" :class='{"bg-secondary text-secondary-content":routePath.includes(v.url)}'>
|
||||
{{ v.cmd }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
} from "@headlessui/vue"
|
||||
// bug??? data = title就会暴毙
|
||||
const { title, map, buttonWidth = 'w-80', open = false, data = "", buttonAccent=false } = defineProps<{
|
||||
title: string,
|
||||
open?: boolean
|
||||
// reative Proxy
|
||||
map: Map<string, string[]>,
|
||||
buttonWidth?: string,
|
||||
buttonAccent?:boolean
|
||||
data?: any
|
||||
}>()
|
||||
const emit = defineEmits(["myclick"])
|
||||
const disposeClick = (e:Event)=>{
|
||||
emit('myclick',e)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Disclosure as="section" class="w-100 flex flex-col mb-2">
|
||||
<DisclosureButton
|
||||
@click.prevent="disposeClick"
|
||||
class="bg-info py-1 text-info-content rounded truncate"
|
||||
:class="{'bg-accent text-accent-content':buttonAccent,[buttonWidth]:true}"
|
||||
>
|
||||
{{ title }}
|
||||
</DisclosureButton>
|
||||
|
||||
|
||||
<transition enter-active-class="transition duration-75 ease-out" enter-from-class="h-0 opacity-0"
|
||||
enter-to-class="h-auto opacity-100" leave-active-class="transition duration-75 ease-out"
|
||||
leave-from-class="h-auto opacity-100" leave-to-class="h-0 opacity-0">
|
||||
<DisclosurePanel class="text-gray-500 w-full" as="ul" :static="open">
|
||||
<slot name="headerAside" :data="data"></slot>
|
||||
<div v-for="([k, v], i) in map" :key="k" class="flex mt-1">
|
||||
<Disclosure>
|
||||
<DisclosureButton class="bg-base-300 text-base-content w-40 break-all flex-shrink-0">
|
||||
{{ k }}
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel as="ul" static class="flex-auto bg-base-200 text-base-content flex flex-col justify-center">
|
||||
<li v-for="(cv, ci) in v" :key="ci" :class="{ 'border-t-2': (ci > 0), 'border-base-100': (ci > 0) }"
|
||||
class=" pl-2 break-all">
|
||||
{{ cv }}
|
||||
</li>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</div>
|
||||
<slot name="others"></slot>
|
||||
</DisclosurePanel>
|
||||
</transition>
|
||||
</Disclosure>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
const { result } = defineProps<{
|
||||
result: EnchanceResult
|
||||
}>()
|
||||
let enhancer = new Map<string, string>()
|
||||
if (result.type === "enhancer") {
|
||||
enhancer.clear();
|
||||
enhancer.set("success", result.success.toString());
|
||||
for (const k in result.effect) {
|
||||
enhancer.set(k, result.effect[k as "cost"].toString());
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <div class="flex items-center w-full justify-center flex-col"> -->
|
||||
<!-- <h3 class=" text-2xl mb-2">enchance</h3> -->
|
||||
<!-- <ul class="flex">
|
||||
<li
|
||||
v-for="(kv,i) in enhancer.entries()" :key="i"
|
||||
class="flex flex-col mx-1">
|
||||
<div class="bg-orange-300 p-2 rounded-b">{{kv[0]}}</div>
|
||||
<div class=" bg-orange-100 grid place-items-center">{{kv[1]}}</div>
|
||||
</li>
|
||||
</ul> -->
|
||||
<!-- <table class="table table-compact group w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(k,i) in enhancer.keys()" :key="i" class='border border-slate-300'
|
||||
:class="{'group-first:z-0':i==0}">{{k}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td v-for="(v, i) in enhancer.values()" class="border border-slate-300">
|
||||
{{v}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table> -->
|
||||
<div class="stats shadow my-2 mx-auto">
|
||||
|
||||
<div class="stat place-items-center" v-for="(kv,i) in enhancer.entries()" :key="i" >
|
||||
<div class="stat-title">{{kv[0]}}</div>
|
||||
<div class="stat-value">{{kv[1]}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel
|
||||
} from "@headlessui/vue"
|
||||
// import SelectInput from "./SelectInput.vue";
|
||||
|
||||
defineProps<{
|
||||
title: string,
|
||||
list: { [x: string]: any }[],
|
||||
titleKeyName: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Disclosure as="section" class="w-100 flex flex-col mb-2">
|
||||
<DisclosureButton class="text-info-content py-1 bg-info rounded self-start w-80 ">
|
||||
{{ title }}
|
||||
</DisclosureButton>
|
||||
<transition enter-active-class="transition duration-75 ease-out" enter-from-class="h-0 opacity-0"
|
||||
enter-to-class="h-auto opacity-100" leave-active-class="transition duration-75 ease-out"
|
||||
leave-from-class="h-auto opacity-100" leave-to-class="h-0 opacity-0">
|
||||
<DisclosurePanel class=" w-10/12" as="ul">
|
||||
<li v-for="(v, i) in list" :key="i" class="flex mt-1">
|
||||
<Disclosure>
|
||||
<DisclosureButton class="bg-base-300 w-1/4 p2 break-all">
|
||||
{{ v[titleKeyName] }}
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel as="div" static class="flex-auto bg-base-200 flex flex-col justify-center ml-1">
|
||||
<template v-for="(cv, ci) in Object.entries(v).filter(n => n[0] !== titleKeyName)" :key="ci">
|
||||
<slot name="item" :kv="cv" :itemTitle="(v[titleKeyName] as string)" :idx="ci"></slot>
|
||||
</template>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</li>
|
||||
</DisclosurePanel>
|
||||
</transition>
|
||||
</Disclosure>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from 'vue';
|
||||
|
||||
const _props = defineProps<{
|
||||
maplist:Map<string, string>[]
|
||||
}>()
|
||||
const props = toRefs(_props)
|
||||
|
||||
const hlist= ref([] as string[])
|
||||
if(props.maplist.value.length > 0) {
|
||||
console.dir(hlist)
|
||||
hlist.value = Object.keys(props.maplist.value[0])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="border-collapse border border-slate-400 ...">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300 ..." v-for="(v,i) in hlist" :key="i"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(map, i) in maplist" :key="i">
|
||||
<td class="border border-slate-300 ..." v-for="(key,j) in hlist" :key="j">{{map.get(key)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// 忽略文件报错
|
||||
import {
|
||||
getCurrentInstance,
|
||||
onMounted,
|
||||
ref
|
||||
} from "vue"
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosurePanel,
|
||||
DisclosureButton
|
||||
} from "@headlessui/vue"
|
||||
const { root, classList = "", buttonClass = '' } = defineProps<{
|
||||
root: TreeNode
|
||||
classList?: string[] | Record<string, boolean> | string,
|
||||
buttonClass?: string[] | string
|
||||
}>()
|
||||
const btn = ref(null)
|
||||
onMounted(() => {
|
||||
|
||||
(btn.value.el as HTMLButtonElement).dispatchEvent(new Event("click"))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classList" v-if="root">
|
||||
<Disclosure>
|
||||
<div class="flex items-center mb-1 group">
|
||||
<DisclosureButton :class="buttonClass" ref="btn">
|
||||
<slot name="meta" :data="root.meta" :active="root.children.length > 0"></slot>
|
||||
</DisclosureButton>
|
||||
<slot name="others" :data="root.meta" ></slot>
|
||||
</div>
|
||||
<template v-if='root.children !== undefined && root.children.length > 0'>
|
||||
<DisclosurePanel class="pl-4 border-l border-black">
|
||||
<Tree v-for="(child, i) in root.children" :key="i" :root="child" :class-list="classList">
|
||||
<template #meta="{data, active}">
|
||||
<slot name="meta" :data="data" :active="active"></slot>
|
||||
</template>
|
||||
<template #others="{data}">
|
||||
<slot name="others" :data="data" ></slot>
|
||||
</template>
|
||||
</Tree>
|
||||
</DisclosurePanel>
|
||||
</template>
|
||||
</Disclosure>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_TITLE: string
|
||||
// 更多环境变量...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.button-style {
|
||||
@apply bg-blue-400 hover:opacity-50 transition rounded-md p-2
|
||||
}
|
||||
.input-btn-style {
|
||||
@apply border p-2 rounded-xl hover:shadow-md transition border-gray-300
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
import { assign, createMachine, DoneInvokeEvent, spawn } from "xstate";
|
||||
import { fetchStore } from "@/stores/fetch";
|
||||
import { publicStore } from "@/stores/public";
|
||||
import transformMachine from "./transformConfigMachine";
|
||||
|
||||
interface CTX {
|
||||
toObjM: typeof transformMachine | null;
|
||||
inputRaw: any;
|
||||
inputValue?: ArthasReq;
|
||||
request?: Request;
|
||||
response?: ArthasRes;
|
||||
resArr: (ArthasResResult | SessionRes | AsyncRes)[];
|
||||
err: string;
|
||||
// 暂时先anyscript
|
||||
publicStore?: any;
|
||||
fetchStore?: any;
|
||||
}
|
||||
type ET =
|
||||
| {
|
||||
type: "SUBMIT";
|
||||
value: ArthasReq | string;
|
||||
}
|
||||
| {
|
||||
type: "error.platform";
|
||||
data: any;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getCommon";
|
||||
data: CommonRes;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getSession";
|
||||
data: SessionRes;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getAsync";
|
||||
data: AsyncRes;
|
||||
}
|
||||
| {
|
||||
type: "INIT";
|
||||
}
|
||||
// | {
|
||||
// type: "CLEAR_RESARR";
|
||||
// }
|
||||
| {
|
||||
type: "";
|
||||
};
|
||||
|
||||
const machine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGMD2A7WqA2YB0AlhLgMQDKAqgEICyAkgCqKgAOqsBALgRsyAB6IAjAHYALHiEBmAKxiADADYATIoAcytaLEAaEAE9hQgJx5FQtWMUzjU+cvlq1AX2d60mHPmyoAhhAJ0KBIIDHxAgDdUAGt8DyxcPB9-QKgESNRkX24MAG15AF0+Ng4c9D5BBBl5ITNZMREZRUVjeWMtPUMEIXs8Y0UxLRF5KUtBmVd3DATvPwCgkjAAJyXUJbwWbGyAMzWAWzx4ryS51PT0KKyy-KKkEBKuHnK7yuraxXrG5tb2oU7EZSAvBOZrKERCGzKQbGESuNwgdCoCBwPhHRJEXDFdiPXgvRBiZT-bpSKSSeT2KRfKHGMQTeFo2YpIJY0pPCoAyx4OwNCEyERqeQKCFEnoiMzU8RiGFyRpSSYgBl4WAAV2QyDg8DuDzK7IQ6hkeChgLUUmUFkBLSJrT64KkihEgKU9rUsPp02O218BGwyqWYBZOOeoEq+sNBM0pvNqmMRLN8mBtlEIikYiEVhNrqmnkxWuxOrx3TUIrUeHJZfLFddriAA */
|
||||
createMachine({
|
||||
context: {
|
||||
toObjM: null,
|
||||
inputRaw: undefined,
|
||||
inputValue: undefined,
|
||||
request: undefined,
|
||||
response: undefined,
|
||||
publicStore: undefined,
|
||||
fetchStore: undefined,
|
||||
resArr: [],
|
||||
err: "",
|
||||
},
|
||||
schema: {
|
||||
context: {} as CTX,
|
||||
events: {} as ET,
|
||||
services: {} as {
|
||||
requestData: { data: any };
|
||||
stringToObj: { data: any };
|
||||
},
|
||||
},
|
||||
id: "console",
|
||||
initial: "idle",
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
// 延迟pinia的挂载时机
|
||||
INIT: {
|
||||
target: "ready",
|
||||
actions: "initStore",
|
||||
},
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
initial: "stringVal",
|
||||
states: {
|
||||
stringVal: {
|
||||
on: {
|
||||
SUBMIT: [{
|
||||
cond: "notString",
|
||||
actions: "rawInput",
|
||||
target: "objVal",
|
||||
}, {
|
||||
actions: [
|
||||
"rawInput",
|
||||
"toObj",
|
||||
],
|
||||
target: "objVal",
|
||||
}],
|
||||
},
|
||||
},
|
||||
objVal: {
|
||||
entry: assign<CTX, ET>((ctx, e) => {
|
||||
return {
|
||||
inputValue: ctx.inputRaw,
|
||||
};
|
||||
}),
|
||||
always: [
|
||||
{ cond: "notObj", target: "#failure" },
|
||||
{
|
||||
cond: "isAsync",
|
||||
target: "#asyncReq",
|
||||
},
|
||||
{
|
||||
cond: "isCommon",
|
||||
target: "#common",
|
||||
},
|
||||
{
|
||||
cond: "isSession",
|
||||
target: "#session",
|
||||
},
|
||||
{
|
||||
actions: "notReq",
|
||||
target: "#failure",
|
||||
},
|
||||
],
|
||||
exit: "getReq",
|
||||
},
|
||||
},
|
||||
},
|
||||
common: {
|
||||
id: "common",
|
||||
tags: ["loading"],
|
||||
entry: "waitReq",
|
||||
invoke: {
|
||||
id: "getCommon",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
states: {},
|
||||
},
|
||||
session: {
|
||||
id: "session",
|
||||
tags: ["loading"],
|
||||
invoke: {
|
||||
id: "getSession",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformSessionRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
asyncReq: {
|
||||
id: "asyncReq",
|
||||
tags: ["loading"],
|
||||
entry: "waitReq",
|
||||
invoke: {
|
||||
id: "getAsync",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformAsyncRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
success: {
|
||||
entry: ["needReportSuccess", "renderRes"],
|
||||
tags: "result",
|
||||
always: {
|
||||
actions: ["reset"],
|
||||
target: "ready",
|
||||
},
|
||||
},
|
||||
failure: {
|
||||
id: "failure",
|
||||
tags: "result",
|
||||
entry: "outputErr",
|
||||
always: {
|
||||
actions: ["reset"],
|
||||
target: "ready",
|
||||
},
|
||||
},
|
||||
hist: {
|
||||
type: "history",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
services: {
|
||||
requestData: async (context) => {
|
||||
const res = await fetch(context.request as Request);
|
||||
if (!res.ok) return Promise.reject("server error");
|
||||
return res.json();
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
initStore: assign((context, event) => {
|
||||
if (event.type !== "INIT") return {};
|
||||
return {
|
||||
publicStore: publicStore(),
|
||||
fetchStore: fetchStore(),
|
||||
};
|
||||
}),
|
||||
rawInput: assign((context, event) => {
|
||||
if (event.type !== "SUBMIT") return {};
|
||||
return {
|
||||
inputRaw: event.value,
|
||||
};
|
||||
}),
|
||||
waitReq: (context) => {
|
||||
context.fetchStore.onWait();
|
||||
},
|
||||
getReq: assign((context, event) => {
|
||||
if (
|
||||
!context.inputValue || !context.fetchStore ||
|
||||
!("getRequest" in context.fetchStore)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
request: context.fetchStore?.getRequest(context.inputValue),
|
||||
inputValue: context.inputValue,
|
||||
};
|
||||
}),
|
||||
transformRes: assign((context, event) => {
|
||||
if (event.type !== "done.invoke.getCommon") return {};
|
||||
|
||||
return {
|
||||
response: event.data,
|
||||
};
|
||||
}),
|
||||
transformSessionRes: assign((context, event) => {
|
||||
if (event.type !== "done.invoke.getSession") return {};
|
||||
|
||||
return {
|
||||
response: event.data,
|
||||
};
|
||||
}),
|
||||
transformAsyncRes: assign((ctx, e) => {
|
||||
if (e.type !== "done.invoke.getAsync") return {};
|
||||
return {
|
||||
response: e.data,
|
||||
};
|
||||
}),
|
||||
renderRes: assign((context, event) => {
|
||||
let resArr: (ArthasResResult | SessionRes | AsyncRes)[] =
|
||||
context.resArr;
|
||||
const response = context.response;
|
||||
if (!response) {
|
||||
return {};
|
||||
}
|
||||
if (
|
||||
Object.hasOwn(response, "body") &&
|
||||
Object.hasOwn((response as CommonRes).body, "results")
|
||||
) {
|
||||
resArr = resArr.concat((context.response as CommonRes).body.results);
|
||||
} else {
|
||||
resArr = resArr.concat([response] as (SessionRes | AsyncRes)[]);
|
||||
}
|
||||
return { resArr: resArr.filter((v) => v && !Number.isNaN(v)) };
|
||||
}),
|
||||
outputErr: assign({
|
||||
err: (context, event) => {
|
||||
context.publicStore.$patch({
|
||||
isErr: true,
|
||||
ErrMessage: context.err,
|
||||
});
|
||||
return "";
|
||||
},
|
||||
}),
|
||||
// clearResArr: assign((context, event) => ({ resArr: [] })),
|
||||
toObj: assign((ctx) => {
|
||||
const m = spawn(transformMachine, { sync: true });
|
||||
m.send({
|
||||
type: "INPUT",
|
||||
data: ctx.inputRaw as string,
|
||||
});
|
||||
const s = m.getSnapshot();
|
||||
if (s?.matches("failure")) {
|
||||
return {
|
||||
inputRaw: undefined,
|
||||
err: s.context.err,
|
||||
};
|
||||
}
|
||||
let inputRaw:ArthasReq = {
|
||||
sessionId:undefined,
|
||||
...s?.context.output
|
||||
} as ArthasReq
|
||||
return {
|
||||
inputRaw,
|
||||
};
|
||||
}),
|
||||
needReportSuccess: (context, e) => {
|
||||
if (context.inputValue?.action === "close_session") {
|
||||
context.fetchStore.$patch({
|
||||
sessionId: "",
|
||||
consumerId: "",
|
||||
online: false,
|
||||
});
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: `close session success!`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (context.inputValue?.action === "init_session") {
|
||||
const response = (context.response as SessionRes);
|
||||
context.fetchStore.$patch({
|
||||
sessionId: response.sessionId,
|
||||
consumerId: response.consumerId,
|
||||
online: true,
|
||||
});
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: `init_session success!`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
context.inputValue?.action === "exec" &&
|
||||
context.inputValue.command.includes("vmoption") &&
|
||||
context.inputValue.command !== "vmoption"
|
||||
) {
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: JSON.stringify(
|
||||
(context.response as CommonRes).body.results,
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
setErrMessage: assign((_ctx, e) => {
|
||||
if (e.type === "SUBMIT" || e.type === "INIT" || e.type === "") {
|
||||
return {};
|
||||
}
|
||||
return { err: e.data as unknown as string };
|
||||
}),
|
||||
reset: (ctx, e) => {
|
||||
ctx.fetchStore.waitDone();
|
||||
},
|
||||
notReq: assign((context) => {
|
||||
return {
|
||||
err: "not request",
|
||||
};
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
// 判断命令是否有问题
|
||||
cmdSucceeded: (context, event) => {
|
||||
if (
|
||||
event.type !== "done.invoke.getCommon" &&
|
||||
event.type !== "done.invoke.getSession" &&
|
||||
event.type !== "done.invoke.getAsync"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (["SCHEDULED", "SUCCEEDED"].includes(event.data.state)) {
|
||||
if (Object.hasOwn(event.data, "body")) {
|
||||
if (Object.hasOwn(event.data.body, "results")) {
|
||||
return (event.data as CommonRes).body.results.every((result) => {
|
||||
if (result.type === "status" && result.statusCode !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
result.type === "message" &&
|
||||
result.message ===
|
||||
"all consumers are unhealthy, current job was interrupted."
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
return ["READY", "TERMINATED"].includes(
|
||||
(event.data as AsyncRes).body.jobStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
// SessionRes
|
||||
return true;
|
||||
}
|
||||
|
||||
if(context.inputValue && context.inputValue.action=== "interrupt_job") {
|
||||
/**
|
||||
* 永不拦截打断回收的错误
|
||||
*/
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isSession: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["join_session", "init_session", "close_session", "interrupt_job"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isSession");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isCommon: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["exec", "pull_results"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isCommon");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAsync: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["async_exec"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isAsync");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
notObj: (ctx) => {
|
||||
if (ctx.inputValue) return false;
|
||||
return true;
|
||||
},
|
||||
// notReq: (context) => {
|
||||
// if (context.inputValue) return true;
|
||||
// return false;
|
||||
// },
|
||||
notString: (context, event) => {
|
||||
if (event.type !== "SUBMIT") return true;
|
||||
if (typeof event.value !== "string") return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default machine;
|
||||
@@ -0,0 +1,462 @@
|
||||
import { assign, createMachine, DoneInvokeEvent, spawn } from "xstate";
|
||||
import { fetchStore } from "@/stores/fetch";
|
||||
import { publicStore } from "@/stores/public";
|
||||
import transformMachine from "./transformConfigMachine";
|
||||
|
||||
interface CTX {
|
||||
toObjM: typeof transformMachine | null;
|
||||
inputRaw: any;
|
||||
inputValue?: ArthasReq;
|
||||
request?: Request;
|
||||
response?: ArthasRes;
|
||||
err: string;
|
||||
// 暂时先anyscript
|
||||
publicStore?: any;
|
||||
fetchStore?: any;
|
||||
}
|
||||
type ET =
|
||||
| {
|
||||
type: "SUBMIT";
|
||||
value: ArthasReq | string;
|
||||
}
|
||||
| {
|
||||
type: "error.platform";
|
||||
data: any;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getCommon";
|
||||
data: CommonRes;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getSession";
|
||||
data: SessionRes;
|
||||
}
|
||||
| {
|
||||
type: "done.invoke.getAsync";
|
||||
data: AsyncRes;
|
||||
}
|
||||
| {
|
||||
type: "INIT";
|
||||
}
|
||||
| {
|
||||
type: "";
|
||||
};
|
||||
|
||||
const permachine = createMachine({
|
||||
context: {
|
||||
toObjM: null,
|
||||
inputRaw: undefined,
|
||||
inputValue: undefined,
|
||||
request: undefined,
|
||||
response: undefined,
|
||||
publicStore: undefined,
|
||||
fetchStore: undefined,
|
||||
err: "",
|
||||
},
|
||||
schema: {
|
||||
context: {} as CTX,
|
||||
events: {} as ET,
|
||||
services: {} as {
|
||||
requestData: { data: any };
|
||||
stringToObj: { data: any };
|
||||
},
|
||||
},
|
||||
id: "console",
|
||||
initial: "idle",
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
// 延迟pinia的挂载时机
|
||||
INIT: {
|
||||
target: "ready",
|
||||
actions: "initStore",
|
||||
},
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
initial: "stringVal",
|
||||
states: {
|
||||
stringVal: {
|
||||
on: {
|
||||
SUBMIT: [{
|
||||
cond: "notString",
|
||||
actions: "rawInput",
|
||||
target: "objVal",
|
||||
}, {
|
||||
actions: [
|
||||
"rawInput",
|
||||
"toObj",
|
||||
],
|
||||
target: "objVal",
|
||||
}],
|
||||
},
|
||||
},
|
||||
objVal: {
|
||||
entry: assign<CTX, ET>((ctx, e) => {
|
||||
return {
|
||||
inputValue: ctx.inputRaw,
|
||||
};
|
||||
}),
|
||||
always: [
|
||||
{ cond: "notObj", target: "#failure" },
|
||||
{
|
||||
cond: "isAsync",
|
||||
target: "#asyncReq",
|
||||
},
|
||||
{
|
||||
cond: "isCommon",
|
||||
target: "#common",
|
||||
},
|
||||
{
|
||||
cond: "isSession",
|
||||
target: "#session",
|
||||
},
|
||||
{
|
||||
actions: "notReq",
|
||||
target: "#failure",
|
||||
},
|
||||
],
|
||||
exit: "getReq",
|
||||
},
|
||||
},
|
||||
},
|
||||
common: {
|
||||
id: "common",
|
||||
tags: ["loading"],
|
||||
entry: "waitReq",
|
||||
invoke: {
|
||||
id: "getCommon",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
session: {
|
||||
id: "session",
|
||||
tags: ["loading"],
|
||||
invoke: {
|
||||
id: "getSession",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformSessionRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
asyncReq: {
|
||||
id: "asyncReq",
|
||||
tags: ["loading"],
|
||||
entry: "waitReq",
|
||||
invoke: {
|
||||
id: "getAsync",
|
||||
src: "requestData",
|
||||
onDone: [
|
||||
{
|
||||
cond: "cmdSucceeded",
|
||||
actions: ["transformAsyncRes"],
|
||||
target: "success",
|
||||
},
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: ["setErrMessage"],
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
success: {
|
||||
entry: ["needReportSuccess", "reset"],
|
||||
type: "final",
|
||||
tags: "result",
|
||||
},
|
||||
failure: {
|
||||
id: "failure",
|
||||
type: "final",
|
||||
tags: "result",
|
||||
entry: ["outputErr", "reset"],
|
||||
},
|
||||
hist: {
|
||||
type: "history",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
services: {
|
||||
requestData: async (context) => {
|
||||
const res = await fetch(context.request as Request);
|
||||
if (!res.ok) return Promise.reject("server error");
|
||||
return res.json();
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
initStore: assign((context, event) => {
|
||||
if (event.type !== "INIT") return {};
|
||||
return {
|
||||
publicStore: publicStore(),
|
||||
fetchStore: fetchStore(),
|
||||
};
|
||||
}),
|
||||
rawInput: assign((context, event) => {
|
||||
if (event.type !== "SUBMIT") return {};
|
||||
return {
|
||||
inputRaw: event.value,
|
||||
};
|
||||
}),
|
||||
waitReq: (context) => {
|
||||
context.fetchStore.onWait();
|
||||
},
|
||||
getReq: assign((context, event) => {
|
||||
if (
|
||||
!context.inputValue || !context.fetchStore ||
|
||||
!("getRequest" in context.fetchStore)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* session的never和undefinded让fetch.ts来控制
|
||||
*/
|
||||
return {
|
||||
request: context.fetchStore?.getRequest(context.inputValue),
|
||||
inputValue: context.inputValue as ArthasReq,
|
||||
};
|
||||
}),
|
||||
transformRes: assign({
|
||||
response: (context, event) => {
|
||||
if (event.type !== "done.invoke.getCommon") return undefined;
|
||||
return event.data;
|
||||
},
|
||||
}),
|
||||
transformSessionRes: assign((context, event) => {
|
||||
if (event.type !== "done.invoke.getSession") return {};
|
||||
return {
|
||||
response: event.data,
|
||||
};
|
||||
}),
|
||||
transformAsyncRes: assign((ctx, e) => {
|
||||
if (e.type !== "done.invoke.getAsync") return {};
|
||||
return {
|
||||
response: e.data,
|
||||
};
|
||||
}),
|
||||
outputErr: assign((context, event) => {
|
||||
if (!context.publicStore.ignore) {
|
||||
context.publicStore.$patch({
|
||||
isErr: true,
|
||||
ErrMessage: context.err,
|
||||
});
|
||||
} else{
|
||||
console.error(context.err)
|
||||
}
|
||||
return {
|
||||
err: "",
|
||||
};
|
||||
}),
|
||||
// clearResArr: assign((context, event) => ({ resArr: [] })),
|
||||
toObj: assign((ctx) => {
|
||||
const m = spawn(transformMachine, { sync: true });
|
||||
m.send({
|
||||
type: "INPUT",
|
||||
data: ctx.inputRaw as string,
|
||||
});
|
||||
const s = m.getSnapshot();
|
||||
if (s?.matches("failure")) {
|
||||
return {
|
||||
inputRaw: undefined,
|
||||
err: s.context.err,
|
||||
};
|
||||
}
|
||||
return {
|
||||
inputRaw: s?.context.output as ArthasReq,
|
||||
};
|
||||
}),
|
||||
needReportSuccess: (context, e) => {
|
||||
|
||||
if (context.inputValue?.action === "close_session") {
|
||||
context.fetchStore.$patch({
|
||||
sessionId: "",
|
||||
consumerId: "",
|
||||
online: false,
|
||||
});
|
||||
if(context.publicStore.ignore) return;
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: `close session success!`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (context.inputValue?.action === "init_session") {
|
||||
const response = (context.response as SessionRes);
|
||||
context.fetchStore.$patch({
|
||||
sessionId: response.sessionId,
|
||||
consumerId: response.consumerId,
|
||||
online: true,
|
||||
});
|
||||
if(context.publicStore.ignore) return;
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: `init_session success!`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(context.inputValue?.action === "exec" ||
|
||||
context.inputValue?.action === "async_exec") &&
|
||||
context.inputValue.command.search("profiler") >= 0
|
||||
) {
|
||||
let result = (context.response as CommonRes).body.results[0];
|
||||
if (
|
||||
result.type === "profiler" &&
|
||||
["start", "resume", "stop"].includes(result.action)
|
||||
) {
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: result.executeResult,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
context.inputValue?.action === "exec" &&
|
||||
context.inputValue.command.includes("vmoption") &&
|
||||
context.inputValue.command !== "vmoption"
|
||||
) {
|
||||
context.publicStore.$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: JSON.stringify(
|
||||
(context.response as CommonRes).body.results,
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
setErrMessage: assign((_ctx, e) => {
|
||||
if (e.type === "SUBMIT" || e.type === "INIT" || e.type === "") {
|
||||
return {};
|
||||
}
|
||||
return { err: e.data as unknown as string };
|
||||
}),
|
||||
reset: (ctx, e) => {
|
||||
ctx.fetchStore.waitDone();
|
||||
},
|
||||
notReq: assign((context) => {
|
||||
return {
|
||||
err: "not request",
|
||||
};
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
// 判断命令是否有问题
|
||||
cmdSucceeded: (context, event) => {
|
||||
if (
|
||||
event.type !== "done.invoke.getCommon" &&
|
||||
event.type !== "done.invoke.getSession" &&
|
||||
event.type !== "done.invoke.getAsync"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (["SCHEDULED", "SUCCEEDED"].includes(event.data.state)) {
|
||||
if (Object.hasOwn(event.data, "body")) {
|
||||
if (Object.hasOwn(event.data.body, "results")) {
|
||||
return (event.data as CommonRes).body.results.every((result) => {
|
||||
return result.type === "status" ? result.statusCode === 0 : true;
|
||||
});
|
||||
} else {
|
||||
return ["READY", "TERMINATED"].includes(
|
||||
(event.data as AsyncRes).body.jobStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
// SessionRes
|
||||
return true;
|
||||
}
|
||||
if (context.inputValue && context.inputValue.action === "interrupt_job") {
|
||||
/**
|
||||
* 永不拦截打断回收的错误
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isSession: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["join_session", "init_session", "close_session", "interrupt_job"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isSession");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isCommon: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["exec"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isCommon");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAsync: (context) => {
|
||||
if (!context) return false;
|
||||
if (
|
||||
["async_exec", "pull_results"]
|
||||
.includes(context.inputValue!.action)
|
||||
) {
|
||||
console.log("isAsync");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
notObj: (ctx) => {
|
||||
if (ctx.inputValue) return false;
|
||||
return true;
|
||||
},
|
||||
// notReq: (context) => {
|
||||
// if (context.inputValue) return true;
|
||||
// return false;
|
||||
// },
|
||||
notString: (context, event) => {
|
||||
if (event.type !== "SUBMIT") return true;
|
||||
if (typeof event.value !== "string") return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default permachine;
|
||||
@@ -0,0 +1,111 @@
|
||||
import { assign, createMachine } from "xstate";
|
||||
import { respond } from "xstate/lib/actions";
|
||||
// import { publicStore } from "@/stores/public";
|
||||
type Output = object;
|
||||
interface CTX {
|
||||
inputValue: string;
|
||||
output?: Output;
|
||||
notJSON: symbol;
|
||||
err: string;
|
||||
// 暂时先anyscript
|
||||
// publicStore?: any,
|
||||
}
|
||||
type ET =
|
||||
| {
|
||||
type: "INPUT";
|
||||
data: string;
|
||||
}
|
||||
| {
|
||||
type: "FAILURE",
|
||||
data: string
|
||||
}
|
||||
| {
|
||||
type: "SUCCESS";
|
||||
data: Output,
|
||||
};
|
||||
|
||||
const transformMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGMD2A7WqA2YB0AlhLgMQDKAqgEICyAkgCqKgAOqsBALgRsyAB6IAjAHYALHiEBmAKxiADADYATIoAcytaLEAaEAE9hQgJx5FQtWMUzjU+cvlq1AX2d60mHPmyoAhhAJ0KBIIDHxAgDdUAGt8DyxcPB9-QKgESNRkX24MAG15AF0+Ng4c9D5BBBl5ITNZMREZRUVjeWMtPUMEIXs8Y0UxLRF5KUtBmVd3DATvPwCgkjAAJyXUJbwWbGyAMzWAWzx4ryS51PT0KKyy-KKkEBKuHnK7yuraxXrG5tb2oU7EZSAvBOZrKERCGzKQbGESuNwgdCoCBwPhHRJEXDFdiPXgvRBiZT-bpSKSSeT2KRfKHGMQTeFo2YpIJY0pPCoAyx4OwNCEyERqeQKCFEnoiMzU8RiGFyRpSSYgBl4WAAV2QyDg8DuDzK7IQ6hkeChgLUUmUFkBLSJrT64KkihEgKU9rUsPp02O218BGwyqWYBZOOeoEq+sNBM0pvNqmMRLN8mBtlEIikYiEVhNrqmnkxWuxOrx3TUIrUeHJZfLFddriAA */
|
||||
createMachine(
|
||||
{
|
||||
id: "JSON_TO_OBJ",
|
||||
schema: {
|
||||
context: {} as CTX,
|
||||
events: {} as ET,
|
||||
},
|
||||
context: {
|
||||
inputValue: "???",
|
||||
notJSON: Symbol(""),
|
||||
output: undefined,
|
||||
err: "",
|
||||
},
|
||||
initial: "idle",
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
INPUT: [{
|
||||
cond: "isString",
|
||||
actions: "getVal",
|
||||
target: "handle",
|
||||
}, {
|
||||
actions: assign(()=>({err:"not string"})) as any,
|
||||
target: "failure",
|
||||
}],
|
||||
},
|
||||
},
|
||||
handle: {
|
||||
always: [
|
||||
{ cond: "isJSON", actions: "handleEnvJSON", target: "success" },
|
||||
{
|
||||
actions: assign<CTX,ET>({err:"not JSON"}),
|
||||
target: "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
failure: {
|
||||
tags:["result"],
|
||||
entry: respond("FAILURE"),
|
||||
type: "final",
|
||||
},
|
||||
success: {
|
||||
tags:["result"],
|
||||
entry:respond("SUCCESS"),
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
getVal: assign((context, e) => {
|
||||
if (e.type !== "INPUT") return {};
|
||||
return {
|
||||
inputValue: e.data,
|
||||
};
|
||||
}),
|
||||
handleEnvJSON: assign((context) => {
|
||||
const output = JSON.parse(context.inputValue);
|
||||
return {
|
||||
output,
|
||||
};
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
isString: (ctx, e) => {
|
||||
if (e.type !== "INPUT") return true;
|
||||
if (typeof e.data !== "string") return false;
|
||||
return true;
|
||||
},
|
||||
isJSON: (ctx, e) => {
|
||||
// if (e.type !== "INPUT") return true;
|
||||
try {
|
||||
JSON.parse(ctx.inputValue);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export default transformMachine;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createApp } from "vue";
|
||||
import "./index.css";
|
||||
import router from "./router/index";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import "highlight.js/styles/stackoverflow-light.css";
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router)
|
||||
.use(createPinia())
|
||||
.mount("#app");
|
||||
@@ -0,0 +1,19 @@
|
||||
import { fetchStore } from "@/stores/fetch";
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
} from "vue-router";
|
||||
import routes from "./routes";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
router.beforeEach((to, from, next) => {
|
||||
fetchStore()
|
||||
.interruptJob()
|
||||
.finally(() => {
|
||||
next();
|
||||
});
|
||||
});
|
||||
export default router;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/dashboard",
|
||||
},
|
||||
{
|
||||
path: "/config",
|
||||
component: () => import("@/views/Config.vue"),
|
||||
},
|
||||
{
|
||||
path: "/console",
|
||||
component: () => import("@/views/Console.vue"),
|
||||
},
|
||||
{
|
||||
path: "/dashboard",
|
||||
component: () => import("@/views/DashBoard.vue"),
|
||||
},
|
||||
{
|
||||
path: "/synchronize",
|
||||
component: () => import("@/views/Synchronize.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
redirect: "/synchronize/mbean",
|
||||
},
|
||||
{
|
||||
path: "thread",
|
||||
component: () => import("@/views/sync/Thread.vue"),
|
||||
},
|
||||
// {
|
||||
// path: "memory",
|
||||
// component: () => import("@/views/sync/Memory.vue"),
|
||||
// },
|
||||
{
|
||||
path: "jad",
|
||||
component: () => import("@/views/sync/Jad.vue"),
|
||||
},
|
||||
{
|
||||
path: "retransform",
|
||||
component: () => import("@/views/sync/Retransform.vue"),
|
||||
},
|
||||
{
|
||||
path: "mbean",
|
||||
component: () => import("@/views/sync/Mbean.vue"),
|
||||
},
|
||||
{
|
||||
path: "classLoader",
|
||||
component: () => import("@/views/sync/ClassLoader.vue"),
|
||||
},
|
||||
{
|
||||
path: "heapdump",
|
||||
component: () => import("@/views/sync/HeapDump.vue"),
|
||||
},
|
||||
{
|
||||
path: "vmtool",
|
||||
component: () => import("@/views/sync/Vmtool.vue"),
|
||||
},
|
||||
{
|
||||
path: "reset",
|
||||
component: () => import("@/views/sync/Reset.vue"),
|
||||
},
|
||||
{
|
||||
path: "/synchronize/synchronize",
|
||||
redirect: "",
|
||||
},{
|
||||
path:"ognl",
|
||||
component:()=>import("@/views/sync/Ognl.vue")
|
||||
},{
|
||||
path:"classInfo",
|
||||
component:()=>import("@/views/sync/ClassInfo.vue")
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/asynchronize",
|
||||
component: () => import("@/views/Asynchronize.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
redirect: "/asynchronize/stack",
|
||||
},
|
||||
{
|
||||
path: "tt",
|
||||
component: () => import("@/views/async/Tt.vue"),
|
||||
},
|
||||
{
|
||||
path: "ptofiler",
|
||||
component: () => import("@/views/async/Profiler.vue"),
|
||||
},
|
||||
{
|
||||
path: "stack",
|
||||
component: () => import("@/views/async/Stack.vue"),
|
||||
},
|
||||
{
|
||||
path: "monitor",
|
||||
component: () => import("@/views/async/Monitor.vue"),
|
||||
},
|
||||
{
|
||||
path: "trace",
|
||||
component: () => import("@/views/async/Trace.vue"),
|
||||
},
|
||||
{
|
||||
path: "watch",
|
||||
component: () => import("@/views/async/Watch.vue"),
|
||||
},
|
||||
{
|
||||
path: "profiler",
|
||||
component: ()=>import("@/views/async/Profiler.vue")
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
export default routes;
|
||||
@@ -0,0 +1,273 @@
|
||||
import { useInterpret, useMachine } from "@xstate/vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { watchEffect } from "vue";
|
||||
import { publicStore } from "./public";
|
||||
import { waitFor } from "xstate/lib/waitFor";
|
||||
import { interpret } from "xstate";
|
||||
import permachine from "@/machines/perRequestMachine";
|
||||
// 控制fetch的store
|
||||
const getEffect = (
|
||||
M: ReturnType<typeof useMachine>,
|
||||
fn: (res: ArthasRes) => void,
|
||||
) =>
|
||||
watchEffect(() => {
|
||||
// console.dir(M.state.value.context.response)
|
||||
if (M.state.value.context.response) {
|
||||
const response = M.state.value.context.response;
|
||||
//防止触发额外的副作用,因为输入时context会改变,导致多执行一次effect。。。
|
||||
M.state.value.context.response = undefined;
|
||||
fn(response as ArthasRes);
|
||||
}
|
||||
});
|
||||
type Machine = ReturnType<typeof useMachine>;
|
||||
type MachineService = ReturnType<typeof useInterpret>;
|
||||
type PollingLoop = {
|
||||
open(): void;
|
||||
close(): void;
|
||||
isOn(): boolean;
|
||||
invoke(): void;
|
||||
};
|
||||
const nullLoop: PollingLoop = {
|
||||
open() {},
|
||||
close() {},
|
||||
isOn() {
|
||||
return false;
|
||||
},
|
||||
invoke() {},
|
||||
};
|
||||
export const fetchStore = defineStore("fetch", {
|
||||
state: () => ({
|
||||
sessionId: "",
|
||||
consumerId: "",
|
||||
requestId: "",
|
||||
online: false,
|
||||
wait: false,
|
||||
/**
|
||||
* 需要给status计数,不然interupt自动关闭很容易出问题
|
||||
*/
|
||||
statusZeroCount: 0,
|
||||
// 所有用pollingLoop都要
|
||||
jobRunning: false,
|
||||
// 对于 pullresults可能会拉同一个结果很多次
|
||||
jobIdSet: new Set<string>(),
|
||||
//由于轮询只会轮询一个命令,可以直接挂载当前的轮询机
|
||||
curPolling: nullLoop,
|
||||
}),
|
||||
getters: {
|
||||
getRequest: (state) =>
|
||||
(option: ArthasReq) => {
|
||||
/**
|
||||
* 对于never,就直接赋值为"",
|
||||
* 对于undefined, 就使用全局默认值
|
||||
* 对于定义的字符串,则使用定义的值
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
const trans = (key: "sessionId" | "requestId" | "consumerId") => {
|
||||
if (key in option) {
|
||||
//@ts-ignore
|
||||
if (option[key] !== undefined) {
|
||||
//@ts-ignore
|
||||
return option[key];
|
||||
} else {
|
||||
return state[key];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
let sessionId = trans("sessionId");
|
||||
let requestId = trans("requestId");
|
||||
let consumerId = trans("consumerId");
|
||||
const req = new Request("/api", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
...option,
|
||||
sessionId,
|
||||
consumerId,
|
||||
requestId,
|
||||
// 若上面三个属性不传,直接用 as any而不是传undefined
|
||||
}),
|
||||
});
|
||||
return req;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getPollingLoop(
|
||||
hander: Function,
|
||||
options: { step?: number; globalIntrupt?: boolean } = {
|
||||
step: 1000,
|
||||
globalIntrupt: false,
|
||||
},
|
||||
) {
|
||||
let id = -1;
|
||||
const { step, globalIntrupt } = options;
|
||||
const that = this;
|
||||
const pollingLoop = {
|
||||
// 自动轮询的可能会被错误打断
|
||||
open() {
|
||||
if (!this.isOn()) {
|
||||
if (globalIntrupt) that.jobRunning = true;
|
||||
hander();
|
||||
id = setInterval(
|
||||
(() => {
|
||||
if (
|
||||
//isErr是瞬时的,点击了就会变回去。。。
|
||||
publicStore().isErr || (!that.jobRunning && globalIntrupt)
|
||||
) {
|
||||
this.close();
|
||||
} else {
|
||||
hander();
|
||||
}
|
||||
}) as TimerHandler,
|
||||
step,
|
||||
);
|
||||
}
|
||||
},
|
||||
close() {
|
||||
if (this.isOn()) {
|
||||
//用于自动可自动打断,但是要加计时器
|
||||
if (globalIntrupt) that.jobRunning = false;
|
||||
clearInterval(id);
|
||||
id = -1;
|
||||
}
|
||||
},
|
||||
isOn() {
|
||||
return id !== -1;
|
||||
},
|
||||
/**
|
||||
* 无条件调用传入的hander
|
||||
*/
|
||||
invoke() {
|
||||
hander();
|
||||
},
|
||||
};
|
||||
this.curPolling = pollingLoop;
|
||||
return pollingLoop;
|
||||
},
|
||||
pullResultsLoop(pollingM: Machine,globalIntrupt:boolean=true) {
|
||||
return this.getPollingLoop(
|
||||
() => {
|
||||
pollingM.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "pull_results",
|
||||
sessionId: undefined,
|
||||
consumerId: undefined,
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
globalIntrupt,
|
||||
},
|
||||
);
|
||||
},
|
||||
onWait() {
|
||||
if (!this.wait) this.wait = true;
|
||||
},
|
||||
waitDone() {
|
||||
if (this.wait) this.wait = false;
|
||||
},
|
||||
getCommonResEffect(M: Machine, fn: (body: CommonRes["body"]) => void) {
|
||||
return getEffect(M, (res) => {
|
||||
if (Object.hasOwn(res, "body")) {
|
||||
fn((res as CommonRes).body);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 注入enhancer:Proxy<Map<string,string[]>>
|
||||
*/
|
||||
getPullResultsEffect(
|
||||
M: Machine,
|
||||
fn: (result: ArthasResResult) => void,
|
||||
) {
|
||||
return this.getCommonResEffect(M, (body: CommonRes["body"]) => {
|
||||
if (body.results.length > 0) {
|
||||
body.results.forEach((result) => {
|
||||
// 对于既不是状态形状
|
||||
// console.dir(result)
|
||||
// if(result.type !=="status") {
|
||||
// console.log("st",result.jobId)
|
||||
// if(!this.jobIdSet.has(result.jobId.toString())) {
|
||||
// 错误已经在machine那里拦截了
|
||||
fn(result);
|
||||
// this.jobIdSet.add(result.jobId.toString())
|
||||
// }
|
||||
// }
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
interruptJob() {
|
||||
if (this.jobRunning) {
|
||||
// 先不管先后端同步的问题
|
||||
// dashboard可能会寄
|
||||
this.jobRunning = false;
|
||||
return this.baseSubmit(interpret(permachine), {
|
||||
action: "interrupt_job",
|
||||
sessionId: undefined,
|
||||
});
|
||||
}
|
||||
this.jobRunning = false;
|
||||
return Promise.reject("There art not job running");
|
||||
},
|
||||
openJobRun() {
|
||||
this.jobRunning = true;
|
||||
},
|
||||
isResult(m: MachineService) {
|
||||
return waitFor(m, (state) => {
|
||||
return state.hasTag("result");
|
||||
});
|
||||
},
|
||||
tranOgnl(s: string): string[] {
|
||||
return s.replace(/\r\n\tat/g, "\r\n\t@").split("\r\n\t");
|
||||
},
|
||||
/**
|
||||
* @param fetchM 传入的服务
|
||||
* @param value 传入的请求
|
||||
* @returns 待处理的promise
|
||||
* 貌似不支持这样的类型推断,会出现分布式计算...以后想办法处理
|
||||
*/
|
||||
baseSubmit<T extends BindQS>(fetchM: MachineService, value: T["req"]) {
|
||||
//防止在polling时触发其他命令
|
||||
if (this.jobRunning) return Promise.reject("there are jobs on running");
|
||||
fetchM.start();
|
||||
fetchM.send("INIT");
|
||||
fetchM.send({
|
||||
type: "SUBMIT",
|
||||
value,
|
||||
});
|
||||
return this.isResult(fetchM).then(
|
||||
(state) => {
|
||||
if (state.matches("success")) {
|
||||
return Promise.resolve<T["res"]>(state.context.response);
|
||||
} else {
|
||||
return Promise.reject("ERROR");
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
},
|
||||
);
|
||||
},
|
||||
initSession() {
|
||||
return this.baseSubmit(interpret(permachine), {
|
||||
action: "init_session",
|
||||
});
|
||||
},
|
||||
asyncInit() {
|
||||
if (!this.online) {
|
||||
publicStore().ignore = true;
|
||||
return this.initSession().then((res) => {
|
||||
publicStore().ignore = false;
|
||||
return Promise.resolve(res);
|
||||
}, (err) => {
|
||||
publicStore().ignore = false;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
return Promise.resolve("alrealy init");
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { Ref, ref, watch, watchEffect } from "vue";
|
||||
import { useMachine } from "@xstate/vue";
|
||||
|
||||
const getEffect = (
|
||||
M: ReturnType<typeof useMachine>,
|
||||
fn: (res: ArthasRes) => void,
|
||||
) =>
|
||||
watchEffect(() => {
|
||||
if (M.state.value.context.response) {
|
||||
const response = M.state.value.context.response;
|
||||
fn(response as ArthasRes);
|
||||
}
|
||||
});
|
||||
|
||||
export const publicStore = defineStore("public", { // Public项目唯一id
|
||||
state: () => ({
|
||||
userMsg: {},
|
||||
// 当初设计不好,应该写成setter,loop的意外错误关闭得挂到errdialog了。。。
|
||||
isErr: false,
|
||||
/**
|
||||
* isInput 是对input组件的锁,要使用inputVal,调用inputval的组件还要自定义一个锁
|
||||
*/
|
||||
isInput: false,
|
||||
inputVal: "",
|
||||
ErrMessage: "bug!!!",
|
||||
isSuccess: false,
|
||||
SuccessMessage: "bug!!!",
|
||||
isWarn: false,
|
||||
warnMessage: "",
|
||||
warningFn: () => {},
|
||||
/**
|
||||
* 忽略弹窗
|
||||
*/
|
||||
ignore: false,
|
||||
}),
|
||||
getters: {
|
||||
getUserMsg: (state) => {
|
||||
return state.userMsg;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getCommonResEffect: (
|
||||
M: ReturnType<typeof useMachine>,
|
||||
fn: (body: CommonRes["body"]) => void,
|
||||
) => {
|
||||
return getEffect(M, (res) => {
|
||||
if (Object.hasOwn(res, "body")) {
|
||||
fn((res as CommonRes).body);
|
||||
}
|
||||
});
|
||||
},
|
||||
interruptJob(M: ReturnType<typeof useMachine>) {
|
||||
M.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "interrupt_job",
|
||||
} as AsyncReq,
|
||||
});
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param inputRef 组件提供的值的存储区
|
||||
* @param getVal 从inputRef到全局缓冲区的处理流程
|
||||
* @param setVal 冲缓冲区到inputRef的处理流程
|
||||
* @returns 独占缓冲区函数
|
||||
*/
|
||||
inputDialogFactory<T = string>(
|
||||
inputRef: Ref<T>,
|
||||
getVal: (raw: string) => T,
|
||||
setVal: (input: Ref<T>) => string,
|
||||
) {
|
||||
// 初始化一个响应式的锁
|
||||
let mutex = ref(false);
|
||||
// 发布订阅模式,把函数的闭包里的mutex和缓冲区的锁注册到vue上
|
||||
watchEffect(() => {
|
||||
if (mutex.value && !this.isInput) {
|
||||
//先上锁,防止再次触发该副作用
|
||||
mutex.value = false;
|
||||
// 把缓冲区的值输入到需要使用的组件里
|
||||
inputRef.value = getVal(this.inputVal);
|
||||
// 清空缓冲区
|
||||
this.inputVal = "";
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
this.$patch({
|
||||
// 打开缓冲区
|
||||
isInput: true,
|
||||
// 把当前值导入到缓冲区
|
||||
inputVal: setVal(inputRef),
|
||||
});
|
||||
// 解锁
|
||||
mutex.value = true;
|
||||
};
|
||||
},
|
||||
nanoToMillis(nanoSeconds: number): number {
|
||||
return nanoSeconds / 1000000;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const transfromStore = defineStore("transformStore", {
|
||||
actions: {
|
||||
transformStackTrace(trace: StackTrace) {
|
||||
return `${trace.className}.${trace.methodName} (${trace.fileName}: ${trace.lineNumber})`;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import SelectCmd from '@/components/routeTo/SelectCmd.vue';
|
||||
|
||||
const routes: { cmd: string, url: string }[] = [
|
||||
{ cmd: 'tt', url: 'tt' },
|
||||
{
|
||||
cmd: 'stack', url: "stack"
|
||||
}, {
|
||||
cmd: 'monitor',
|
||||
url: 'monitor'
|
||||
}, {
|
||||
cmd: 'trace',
|
||||
url: 'trace'
|
||||
}, {
|
||||
cmd: 'watch',
|
||||
url: 'watch'
|
||||
}, {
|
||||
cmd: 'profiler',
|
||||
url: 'profiler'
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<SelectCmd :routes="routes"></SelectCmd>
|
||||
<div class="p-2 h-[90vh] overflow-auto flex-1 pointer-events-auto">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,179 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, reactive, ref, watchEffect } from 'vue';
|
||||
import OptionConfigMenu from '@/components/show/OptionConfigMenu.vue';
|
||||
import SwitchInput from '@/components/input/SwitchInput.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue"
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const fetchS = fetchStore()
|
||||
const sysEnvMap = reactive(new Map<string, string[]>())
|
||||
const sysPropMap = reactive(new Map<string, string[]>())
|
||||
const perfcounterMap = reactive(new Map<string, string[]>())
|
||||
const vmOptionM = useMachine(machine)
|
||||
const pwd = ref("?")
|
||||
const vmOptionMTree = reactive([] as VmOption[])
|
||||
// 初始化
|
||||
onBeforeMount(() => {
|
||||
fetchS.baseSubmit(useInterpret(permachine), {
|
||||
action: "exec",
|
||||
command: "pwd"
|
||||
}).then(
|
||||
res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "pwd") {
|
||||
pwd.value = result.workingDir
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
vmOptionM.send("INIT")
|
||||
vmOptionM.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: "vmoption"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// 处理展示的树形数据
|
||||
const handleTree = (data: Record<string, string | boolean | number>, map: Map<string, string[]>): string[] => {
|
||||
map.clear()
|
||||
let res: string[] = []
|
||||
Object.entries(data).forEach(([k, v]) => {
|
||||
res.push(k)
|
||||
if (typeof v === "boolean") v = v.toString()
|
||||
if (typeof v === "number") v = v.toString()
|
||||
if (k.toLowerCase().includes("path")) {
|
||||
map.set(k, v.split(":").filter(v => v.trim() !== ''))
|
||||
} else if (v.includes(";")) {
|
||||
map.set(k, v.split(";").filter(v => v.trim() !== ''))
|
||||
} else {
|
||||
map.set(k, [v])
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
const handleEnvTree = (data: Record<string, string>) => handleTree(data, sysEnvMap)
|
||||
|
||||
const handlePropTree = (data: Record<string, string>) => handleTree(data, sysPropMap)
|
||||
|
||||
// 处理可修改参数的树形结构
|
||||
// const handleOptionTree = (data:Record<string>)
|
||||
|
||||
|
||||
|
||||
watchEffect(() => {
|
||||
const response = vmOptionM.state.value.context.response
|
||||
if (response) {
|
||||
if (Object.hasOwn(response, "body")) {
|
||||
console.log(response, "vmoption")
|
||||
const result = (response as CommonRes).body.results[0]
|
||||
|
||||
if (result.type == "vmoption") {
|
||||
// handlePropTree(result.props).forEach(v => {
|
||||
// sysPropTree.push(v)
|
||||
// })
|
||||
// 先clear一下之前的东西
|
||||
vmOptionMTree.length = 0
|
||||
console.log(result.vmOptions, "watchEffect!!!")
|
||||
result.vmOptions.forEach(v => {
|
||||
vmOptionMTree.push(v)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const vmOptionSend = (pre: string) => (v: { key: string, value: boolean | string }) => {
|
||||
if (pre === "HeapDumpPath" && v.value === "") {
|
||||
publicStore().$patch({ ErrMessage: "HeapDumpPath can't be set \"\" ", isErr: true })
|
||||
return
|
||||
} vmOptionM.send({
|
||||
type: 'SUBMIT', value: {
|
||||
action: "exec",
|
||||
command: `vmoption ${pre} ${v.value === "" ? '\"\"' : v.value}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const jvmMap = reactive(new Map<string, string[]>())
|
||||
|
||||
const getJvm = () => fetchS.baseSubmit(useInterpret(permachine), {
|
||||
action: "exec",
|
||||
command: "jvm"
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "jvm") {
|
||||
jvmMap.clear()
|
||||
Object.entries(result.jvmInfo).forEach(([k, v]) => {
|
||||
jvmMap.set(k, v.map(v => `${v.name} : ${v.value}`))
|
||||
})
|
||||
}
|
||||
})
|
||||
const getSysenv = () => fetchS.baseSubmit(useInterpret(permachine), {
|
||||
action: "exec",
|
||||
command: "sysenv",
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "sysenv") {
|
||||
handleEnvTree(result.env)
|
||||
}
|
||||
})
|
||||
const getSysprop = () => fetchS.baseSubmit(useInterpret(permachine), {
|
||||
action: "exec",
|
||||
command: "sysprop",
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "sysprop") {
|
||||
handlePropTree(result.props)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const getPerCounter = () => fetchS.baseSubmit(useInterpret(permachine), { action: "exec", command: "perfcounter -d" }).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "perfcounter") {
|
||||
const perfcounters = result.perfCounters
|
||||
perfcounterMap.clear()
|
||||
perfcounters.forEach(v => {
|
||||
perfcounterMap.set(v.name, Object.entries(v).filter(v => v[0] !== "name").map(([key, value]) => `${key} : ${value}`))
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-2 h-[90vh] overflow-y-scroll">
|
||||
<article>
|
||||
<div class="flex items-center">
|
||||
<div class="btn-info btn my-2 btn-sm normal-case">workingDir</div>
|
||||
<div class="bg-base-200 w-full text-base-content pl-2"> {{ pwd }}</div>
|
||||
</div>
|
||||
<CmdResMenu title="sysenv" :map="sysEnvMap" @click="getSysenv" />
|
||||
<CmdResMenu title="sysprop" :map="sysPropMap" @click="getSysprop" />
|
||||
<option-config-menu title="vmOption" :list="vmOptionMTree" title-key-name="name">
|
||||
<template #item="{ kv, itemTitle, idx }">
|
||||
<switch-input :send="vmOptionSend(itemTitle)" :data="{ key: kv[0], value: kv[1] }" v-if="kv[0] === 'value'"
|
||||
:class="{ 'border-t-4': (idx > 0), 'border-base-100': (idx > 0) }">
|
||||
</switch-input>
|
||||
<div v-else class="flex " :class="{ 'border-t-4': (idx > 0), 'border-base-100': (idx > 0) }">
|
||||
<div class="bg-blue-200 w-1/5 p-1">{{ kv[0] }}</div>
|
||||
<div class="grid place-items-center w-3/5">{{ kv[1] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</option-config-menu>
|
||||
<CmdResMenu title="jvm" :map="jvmMap" class="w-full" @click="getJvm" />
|
||||
<CmdResMenu title="perfcounter" :map="perfcounterMap" @click="getPerCounter" />
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
const fetchM = useMachine(machine)
|
||||
const val = ref(JSON.stringify({
|
||||
action: "exec",
|
||||
command: "version"
|
||||
}));
|
||||
|
||||
|
||||
onBeforeMount(()=>{
|
||||
fetchM.send("INIT")
|
||||
})
|
||||
|
||||
const submitCommand = ()=>{
|
||||
console.log('别报错了')
|
||||
fetchM.send({ type: 'SUBMIT', value: val.value})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col p-2">
|
||||
<form class="h-[10vh] flex items-center border shadow" @submit.prevent="submitCommand">
|
||||
<label for="command-input" class=" m-2 ">command:</label>
|
||||
<div class=" flex-auto grid place-items-start">
|
||||
<input type="text" placeholder="input command" v-model="val" id="command-input"
|
||||
class=" outline-1 focus-visible:outline-gray-600 border rounded hover:shadow h-10 transition w-11/12 box-border">
|
||||
</div>
|
||||
<button class="hover:shadow w-24 h-10 border rounded-md mr-20 "
|
||||
>
|
||||
submit
|
||||
</button>
|
||||
</form>
|
||||
<article class="flex-1 bg-white overflow-auto max-h-[70vh]">
|
||||
<section v-for="(v, i) in fetchM.state.value.context.resArr" :key="i"
|
||||
class="w-full rounded-sm mb-2 p-2 bg-green-200 box-border break-all"
|
||||
:class="{ 'bg-blue-200': v&&!Object.hasOwn(v, 'jobId')}"
|
||||
>
|
||||
{{ JSON.stringify(v) }}
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>·
|
||||
@@ -0,0 +1,497 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
DatasetComponentOption,
|
||||
GridComponentOption,
|
||||
ToolboxComponentOption,
|
||||
GridComponent,
|
||||
ToolboxComponent
|
||||
} from 'echarts/components';
|
||||
import {
|
||||
BarChart,
|
||||
BarSeriesOption,
|
||||
LineChart,
|
||||
LineSeriesOption,
|
||||
PieChart,
|
||||
PieSeriesOption
|
||||
} from 'echarts/charts';
|
||||
import {
|
||||
LabelLayout, UniversalTransition
|
||||
} from 'echarts/features';
|
||||
import {
|
||||
SVGRenderer
|
||||
} from 'echarts/renderers';
|
||||
import { dispose, ECharts } from 'echarts/core';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { onBeforeRouteLeave } from 'vue-router';
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
DatasetComponentOption | PieSeriesOption
|
||||
>
|
||||
type GcEChartsOption = echarts.ComposeOption<
|
||||
ToolboxComponentOption | TooltipComponentOption | GridComponentOption | LegendComponentOption | BarSeriesOption | LineSeriesOption
|
||||
>
|
||||
const fetchS = fetchStore()
|
||||
const { getCommonResEffect } = publicStore()
|
||||
const dashboadM = useInterpret(permachine)
|
||||
const dashboadResM = useMachine(machine)
|
||||
const loop = fetchS.pullResultsLoop(dashboadResM)
|
||||
const toMb = (b: number) => Math.floor(b / 1024 / 1024)
|
||||
const gcInfos = reactive(new Map<string, string[]>())
|
||||
const memoryInfo = reactive(new Map<string, string[]>())
|
||||
const threads = reactive(new Map<string, string[]>())
|
||||
const runtimeInfo = reactive(new Map<keyof RuntimeInfo, string>())
|
||||
const pri = ref(3)
|
||||
const publiC = publicStore()
|
||||
const tableResults = reactive([] as Map<string, string>[])
|
||||
const keyList: (keyof ThreadStats)[] = [
|
||||
"id",
|
||||
"name",
|
||||
"cpu",
|
||||
"daemon",
|
||||
"deltaTime",
|
||||
"group",
|
||||
"interrupted",
|
||||
"priority",
|
||||
"state",
|
||||
"time",
|
||||
]
|
||||
|
||||
let dashboardId = -1
|
||||
let heapChart: ECharts
|
||||
let nonheapChart: ECharts
|
||||
let bufferPoolChart: ECharts
|
||||
let gcChart: ECharts
|
||||
const clearChart = (...charts: ECharts[]) => {
|
||||
charts.forEach(chart => {
|
||||
if (chart !== null && chart !== undefined) chart.dispose()
|
||||
})
|
||||
}
|
||||
const transformMemory = (result: ArthasResResult) => {
|
||||
if (result.type === "dashboard") {
|
||||
|
||||
const heaparr: { value: number, name: string }[] = [
|
||||
]
|
||||
result.memoryInfo.heap.filter(v => v.name !== "heap").forEach(v => {
|
||||
const arr: string[] = []
|
||||
|
||||
arr.push('max : ' + toMb(v.max))
|
||||
arr.push('total : ' + toMb(v.total))
|
||||
arr.push('used : ' + toMb(v.used))
|
||||
|
||||
const usage: number = (v.max > 0 ? (v.used / v.max) : (v.used / v.total)) * 100
|
||||
heaparr.push({ value: toMb(v.used), name: `${v.name}(${usage.toFixed(2)}%)` })
|
||||
|
||||
arr.push(usage + '%')
|
||||
|
||||
memoryInfo.set(v.name, arr)
|
||||
})
|
||||
heaparr.push({
|
||||
value: Math.floor((result.memoryInfo.heap[0].max > 0 ? (result.memoryInfo.heap[0].max - result.memoryInfo.heap[0].used) : (result.memoryInfo.heap[0].total - result.memoryInfo.heap[0].used)) / 1024 / 1024),
|
||||
name: "free",
|
||||
})
|
||||
heapChart && heapChart.setOption({
|
||||
series: {
|
||||
data: heaparr
|
||||
}
|
||||
} as EChartsOption)
|
||||
|
||||
const nonheaparr: {
|
||||
value: number, name: string,
|
||||
}[] = []
|
||||
result.memoryInfo.nonheap.filter(v => v.name !== "nonheap").forEach(v => {
|
||||
const arr: string[] = []
|
||||
|
||||
arr.push('max : ' + toMb(v.max))
|
||||
arr.push('total : ' + toMb(v.total))
|
||||
arr.push('used : ' + toMb(v.used))
|
||||
const usage: number = (v.used / v.total) * 100
|
||||
nonheaparr.push({ value: toMb(v.used), name: `${v.name}(${usage.toFixed(2)}%)` })
|
||||
|
||||
arr.push(usage * 100 + '%')
|
||||
|
||||
memoryInfo.set(v.name, arr)
|
||||
})
|
||||
nonheapChart && nonheapChart.setOption({ series: { data: nonheaparr } } as EChartsOption)
|
||||
|
||||
const bufferPoolarr: {
|
||||
value: number, name: string,
|
||||
}[] = []
|
||||
result.memoryInfo.buffer_pool.filter(v => v.name !== "buffer_pool;").forEach(v => {
|
||||
bufferPoolarr.push({ value: toMb(v.used), name: `${v.name}` })
|
||||
|
||||
})
|
||||
bufferPoolChart && bufferPoolChart.setOption({ series: { data: bufferPoolarr } } as EChartsOption)
|
||||
}
|
||||
}
|
||||
const transformThread = (result: ArthasResResult, end: number) => {
|
||||
if (result.type !== "dashboard") return;
|
||||
// result.threads.filter((v, i) => i < pri.value).forEach(thread => {
|
||||
// // threads.set(v.name, Object.entries(v).filter(([k, v]) => k !== "name").map(([k, v]) => `${k} : ${v}`))
|
||||
// const map = new Map()
|
||||
// Object.entries(thread).map(([k, v]) => map.set(k, v.toString().trim() || "-"))
|
||||
// tableResults.unshift(map)
|
||||
// })
|
||||
|
||||
for (let i = 0; i < end && i < result.threads.length; i++) {
|
||||
const thread = result.threads[i]
|
||||
const map = new Map()
|
||||
Object.entries(thread).forEach(([k, v]) => map.set(k, v.toString().trim() || "-"))
|
||||
tableResults.unshift(map)
|
||||
}
|
||||
}
|
||||
const transformGc = (result: ArthasResResult) => {
|
||||
if (result.type !== "dashboard") return;
|
||||
const gcCountData: number[] = []
|
||||
|
||||
const gcTimeData: number[] = []
|
||||
const gcxdata: string[] = []
|
||||
result.gcInfos.forEach(v => {
|
||||
// gcInfos.set(v.name, [v.collectionCount.toString(), v.collectionTime.toString()])
|
||||
gcxdata.push(v.name)
|
||||
gcCountData.push(v.collectionCount)
|
||||
gcTimeData.push(v.collectionTime)
|
||||
})
|
||||
gcChart.setOption({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
},
|
||||
// prettier-ignore
|
||||
data: gcxdata
|
||||
}, series: [{
|
||||
name: "collectionCount",
|
||||
type: 'bar',
|
||||
data: gcCountData
|
||||
}, {
|
||||
name: "collectionTime",
|
||||
type: 'bar',
|
||||
data: gcTimeData
|
||||
}]
|
||||
} as GcEChartsOption)
|
||||
}
|
||||
const setPri = publiC.inputDialogFactory(
|
||||
pri,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 3 : valRaw
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const transformRuntimeInfo = (result: ArthasResResult) => {
|
||||
if (result.type !== "dashboard") return;
|
||||
for (const key in result.runtimeInfo as RuntimeInfo) {
|
||||
runtimeInfo.set(key as keyof RuntimeInfo, result.runtimeInfo[key as keyof RuntimeInfo].toString())
|
||||
}
|
||||
}
|
||||
getCommonResEffect(dashboadResM, body => {
|
||||
if (body.results.length > 0 && dashboardId >= 0) {
|
||||
const result = body.results.find(v => v.type === "dashboard" && v.jobId === dashboardId)
|
||||
if (result && result.type === "dashboard") {
|
||||
|
||||
memoryInfo.clear()
|
||||
transformMemory(result)
|
||||
|
||||
runtimeInfo.clear()
|
||||
transformRuntimeInfo(result)
|
||||
|
||||
threads.clear()
|
||||
tableResults.length = 0
|
||||
transformThread(result, pri.value)
|
||||
|
||||
gcInfos.clear()
|
||||
transformGc(result)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
// 处理初始化请求
|
||||
onBeforeMount(async () => {
|
||||
dashboadResM.send("INIT")
|
||||
|
||||
fetchS
|
||||
.asyncInit()
|
||||
.finally(
|
||||
() => {
|
||||
fetchS.baseSubmit(dashboadM, {
|
||||
action: "async_exec",
|
||||
command: "dashboard",
|
||||
sessionId: undefined
|
||||
}).then(
|
||||
res => {
|
||||
dashboardId = (res as AsyncRes).body.jobId
|
||||
loop.open()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
// 处理dom
|
||||
onMounted(() => {
|
||||
// init
|
||||
|
||||
const clearDom = (...doms: HTMLElement[]) => {
|
||||
doms.forEach(dom => {
|
||||
dispose(dom)
|
||||
})
|
||||
}
|
||||
clearChart(nonheapChart, heapChart, bufferPoolChart, gcChart)
|
||||
const heapDom = document.getElementById('heapMemory')!
|
||||
const nonheapDom = document.getElementById('nonheapMemory')!
|
||||
const bufferPoolDom = document.getElementById('bufferPoolMemory')!
|
||||
const gcDom = document.getElementById('gc-info')!
|
||||
clearDom(heapDom, nonheapDom, bufferPoolDom, gcDom)
|
||||
echarts.use(
|
||||
[TooltipComponent, LegendComponent, PieChart, SVGRenderer, LabelLayout, ToolboxComponent, GridComponent, BarChart, LineChart, UniversalTransition]
|
||||
);
|
||||
|
||||
|
||||
|
||||
const heapoption: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}:{c}M {d}'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'heap memory',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const nonheapoption: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}:{c}M'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'nonheap memory',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
formatter: '{b}:{c}M'
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const bufferPooloption: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{c}M'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'buffer_pool memory',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'outside',
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 0,
|
||||
name: '',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const colors = ['#5470C6', '#91CC75'];
|
||||
const gcoption: GcEChartsOption = {
|
||||
color: colors,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
right: '20%'
|
||||
},
|
||||
legend: {
|
||||
data: ['collectionCount', 'collectionTime']
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
},
|
||||
// prettier-ignore
|
||||
data: []
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataView: { show: true, readOnly: true },
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: 'collectionCount',
|
||||
position: 'left',
|
||||
alignTicks: true,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: colors[0]
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: 'collectionTime',
|
||||
position: 'right',
|
||||
alignTicks: true,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: colors[1]
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: '{value} ms'
|
||||
}
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'collectionCount',
|
||||
type: 'bar',
|
||||
data: [
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'collectionTime',
|
||||
type: 'bar',
|
||||
yAxisIndex: 1,
|
||||
data: [
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
heapChart = echarts.init(heapDom);
|
||||
heapoption && heapChart.setOption(heapoption);
|
||||
|
||||
nonheapChart = echarts.init(nonheapDom);
|
||||
nonheapoption && nonheapChart.setOption(nonheapoption);
|
||||
|
||||
bufferPoolChart = echarts.init(bufferPoolDom);
|
||||
bufferPooloption && bufferPoolChart.setOption(bufferPooloption);
|
||||
|
||||
// gcInfosChart
|
||||
|
||||
gcChart = echarts.init(gcDom);
|
||||
|
||||
|
||||
gcoption && gcChart.setOption(gcoption);
|
||||
})
|
||||
onBeforeUnmount(async () => {
|
||||
loop.close()
|
||||
clearChart(nonheapChart, heapChart, bufferPoolChart, gcChart)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-2 pointer-events-auto flex flex-col h-full">
|
||||
<div class="input-btn-style mb-4 h-32 flex flex-wrap flex-col items-start overflow-auto min-h-[6rem]">
|
||||
<div v-for="(cv, ci) in runtimeInfo" :key="ci" class="flex mb-1 pr-2">
|
||||
<span class="bg-primary-focus text-primary-content border border-primary-focus w-44 px-2 rounded-l">
|
||||
{{ cv[0] }}
|
||||
</span>
|
||||
<span class="bg-base-200 border border-primary-focus rounded-r px-2 flex-1">
|
||||
{{cv[1]}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <CmdResMenu title="threads" :map="threads" class="w-full flex justify-center" /> -->
|
||||
<div class="flex justify-evenly mb-4 flex-1 h-80">
|
||||
<div id="heapMemory" class="w-80 h-80 flex-1 input-btn-style mr-4"></div>
|
||||
<div id="nonheapMemory" class="w-80 h-80 flex-1 input-btn-style mr-4"></div>
|
||||
<div id="bufferPoolMemory" class="w-80 h-80 flex-1 input-btn-style"></div>
|
||||
</div>
|
||||
<div class="w-full flex justify-start items-start flex-1">
|
||||
<div id="gc-info" class="w-[40rem] h-80 input-btn-style p-2 mr-4"></div>
|
||||
<div class="input-btn-style overflow-auto flex-1 h-80">
|
||||
<div class="flex justify-end mb-2">
|
||||
|
||||
<button class="btn btn-sm btn-outline" @click="setPri">limit:{{pri}}</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-compact w-full group">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-slate-300" v-for="(v,i) in keyList" :key="i" :class="{'group-first:z-0':i==0}">{{v}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
<tr v-for="(map, i) in tableResults" :key="i">
|
||||
<td class="border border-slate-300" v-for="(key,j) in keyList" :key="j">
|
||||
{{map.get(key)}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import SelectCmd from '@/components/routeTo/SelectCmd.vue';
|
||||
const routes: { cmd: string, url: string }[] = [
|
||||
{
|
||||
cmd: "thread",
|
||||
url: "thread"
|
||||
}, {
|
||||
cmd: "classInfo",
|
||||
url: "classInfo"
|
||||
// }, {
|
||||
// cmd: "perfcounter",
|
||||
// url: "perfcounter"
|
||||
}, {
|
||||
cmd: "classLoader",
|
||||
url: "classLoader"
|
||||
}, {
|
||||
cmd: "jad",
|
||||
url: "jad"
|
||||
}, {
|
||||
cmd: "retransform",
|
||||
url: "retransform"
|
||||
}, {
|
||||
cmd: "mbean",
|
||||
url: "mbean"
|
||||
}, {
|
||||
cmd: "heapdump",
|
||||
url: "heapdump"
|
||||
}, {
|
||||
cmd: "vmtool",
|
||||
url: "vmtool"
|
||||
}, {
|
||||
cmd: "reset",
|
||||
url: "reset"
|
||||
},{
|
||||
cmd: "ognl",
|
||||
url: "ognl"
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[90vh] overflow-auto flex">
|
||||
<SelectCmd :routes="routes"></SelectCmd>
|
||||
<div class="p-2 overflow-y-scroll w-full flex flex-col flex-1 pointer-events-auto">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,348 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import {
|
||||
ListboxOption,
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOptions
|
||||
} from "@headlessui/vue"
|
||||
import MethodInput from '@/components/input/MethodInput.vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import Enhancer from '@/components/show/Enhancer.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TitleComponent,
|
||||
TitleComponentOption,
|
||||
ToolboxComponent,
|
||||
ToolboxComponentOption,
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
DataZoomComponent,
|
||||
DataZoomComponentOption
|
||||
} from 'echarts/components';
|
||||
import {
|
||||
BarChart,
|
||||
BarSeriesOption,
|
||||
LineChart,
|
||||
LineSeriesOption
|
||||
} from 'echarts/charts';
|
||||
import {
|
||||
UniversalTransition
|
||||
} from 'echarts/features';
|
||||
import {
|
||||
SVGRenderer
|
||||
} from 'echarts/renderers';
|
||||
import { ECharts, number } from 'echarts/core';
|
||||
|
||||
echarts.use(
|
||||
[TitleComponent, ToolboxComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent, BarChart, LineChart, SVGRenderer, UniversalTransition]
|
||||
);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
TitleComponentOption | ToolboxComponentOption | TooltipComponentOption | GridComponentOption | LegendComponentOption | DataZoomComponentOption | BarSeriesOption | LineSeriesOption
|
||||
>
|
||||
const pollingM = useMachine(machine)
|
||||
const fetchS = fetchStore()
|
||||
const { pullResultsLoop, getCommonResEffect } = fetchS
|
||||
const fetchM = useInterpret(permachine)
|
||||
const loop = pullResultsLoop(pollingM)
|
||||
const enhancer = ref(undefined as undefined | EnchanceResult)
|
||||
const cycleV = ref(120)
|
||||
const publicS = publicStore()
|
||||
type KMD = | keyof MonitorData
|
||||
// const keyList: string[] = [
|
||||
// "className",
|
||||
// "methodName",
|
||||
// "cost",
|
||||
// "success",
|
||||
// "failed",
|
||||
// "fail-rate",
|
||||
// "total",
|
||||
// ]
|
||||
const modelist: { name: string, value: string }[] = [
|
||||
{ name: "before", value: "-b" },
|
||||
{ name: "finish", value: "" }
|
||||
]
|
||||
const mode = ref(modelist[1])
|
||||
|
||||
// const tableResults = reactive([] as Map<string, string[] | string>[])
|
||||
|
||||
const chartContext: {
|
||||
count: number,
|
||||
myChart?: ECharts,
|
||||
costChart?: ECharts,
|
||||
categories: number[],
|
||||
data: number[],
|
||||
cur: number,
|
||||
max: number,
|
||||
successData: number[],
|
||||
failureData: number[]
|
||||
} = {
|
||||
max: 0,
|
||||
cur: 0,
|
||||
count: 40,
|
||||
myChart: undefined,
|
||||
costChart: undefined,
|
||||
categories: [],
|
||||
data: [],
|
||||
successData: [],
|
||||
failureData: [],
|
||||
}
|
||||
for (let i = 0; i < chartContext.count; i++) { chartContext.categories[i] = i + 1 }
|
||||
|
||||
const chartOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#283b56'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataView: { readOnly: false },
|
||||
}
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name: 'count'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'success',
|
||||
type: 'bar',
|
||||
stack: 'count',
|
||||
data: [],
|
||||
// itemStyle: {
|
||||
// color: "#9836cd"
|
||||
// }
|
||||
},
|
||||
{
|
||||
name: 'failure',
|
||||
type: 'bar',
|
||||
stack: "count",
|
||||
data: [],
|
||||
itemStyle: {
|
||||
color: "#ff0000",
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
const costOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#283b56'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataView: { readOnly: false },
|
||||
}
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: 'cost(ms)',
|
||||
min: 0,
|
||||
boundaryGap: [0.2, 0.2]
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'cost',
|
||||
type: 'bar',
|
||||
data: chartContext.data
|
||||
}
|
||||
]
|
||||
}
|
||||
const updateChart = (data: MonitorData) => {
|
||||
while (chartContext.cur > chartContext.count) {
|
||||
chartContext.data.shift()
|
||||
chartContext.successData.shift()
|
||||
chartContext.failureData.shift()
|
||||
chartContext.cur--
|
||||
}
|
||||
chartContext.data.push(data.cost)
|
||||
chartContext.failureData.push(data.failed)
|
||||
chartContext.successData.push(data.success)
|
||||
chartContext.cur++
|
||||
|
||||
chartContext.myChart!.setOption<EChartsOption>({
|
||||
xAxis: [
|
||||
{
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
series: [{
|
||||
data: chartContext.successData
|
||||
}, {
|
||||
data: chartContext.failureData
|
||||
}
|
||||
]
|
||||
})
|
||||
chartContext.costChart!.setOption<EChartsOption>({
|
||||
xAxis: [
|
||||
{
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: chartContext.data
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
const resetChart = () => {
|
||||
chartContext.data.length = 0
|
||||
chartContext.failureData.length = 0
|
||||
chartContext.successData.length = 0
|
||||
chartContext.myChart!.setOption<EChartsOption>({
|
||||
xAxis: [
|
||||
{
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
series: [{
|
||||
data: chartContext.successData
|
||||
}, {
|
||||
data: chartContext.failureData
|
||||
}
|
||||
]
|
||||
})
|
||||
chartContext.costChart!.setOption<EChartsOption>({
|
||||
xAxis: [
|
||||
{
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: chartContext.data
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
const transform = (result: ArthasResResult) => {
|
||||
if (result.type === "monitor") {
|
||||
result.monitorDataList.forEach(data => {
|
||||
updateChart(data)
|
||||
})
|
||||
}
|
||||
if (result.type === "enhancer") {
|
||||
enhancer.value = result
|
||||
}
|
||||
}
|
||||
getCommonResEffect(pollingM, body => {
|
||||
if (body.results.length > 0) {
|
||||
body.results.forEach(result => {
|
||||
transform(result)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const changeCycle = publicS.inputDialogFactory(
|
||||
cycleV,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 120 : valRaw
|
||||
},
|
||||
(input) => input.value.toString()
|
||||
)
|
||||
onBeforeMount(() => {
|
||||
fetchS.asyncInit()
|
||||
pollingM.send("INIT")
|
||||
})
|
||||
onMounted(() => {
|
||||
const chartDom = document.getElementById('monitorchart')!;
|
||||
chartContext.myChart = echarts.init(chartDom);
|
||||
chartOption && chartContext.myChart.setOption(chartOption)
|
||||
chartContext.costChart = echarts.init(document.getElementById('monitorchartcost')!);
|
||||
chartOption && chartContext.costChart.setOption(costOption)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
loop.close()
|
||||
})
|
||||
const submit = async (data: { classItem: Item, methodItem: Item, conditon: string }) => {
|
||||
enhancer.value = undefined
|
||||
// tableResults.length = 0
|
||||
|
||||
let condition = data.conditon.trim() == "" ? "" : `'${data.conditon.trim()}'`
|
||||
let cycle = `-c ${cycleV.value}`
|
||||
fetchS.baseSubmit(fetchM, {
|
||||
action: "async_exec",
|
||||
command: `monitor -c 5 ${data.classItem.value} ${data.methodItem.value} ${condition}`,
|
||||
sessionId: undefined
|
||||
}).then(
|
||||
res => loop.open()
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MethodInput :submit-f="submit" class="mb-4" ncondition>
|
||||
<template #others>
|
||||
<Listbox v-model="mode">
|
||||
<div class=" relative mx-2 ">
|
||||
<ListboxButton class="btn btn-sm btn-outline w-40">{{ mode.name }}</ListboxButton>
|
||||
<ListboxOptions
|
||||
class="absolute w-40 mt-2 border overflow-hidden rounded-md hover:shadow-xl transition bg-white z-10">
|
||||
<ListboxOption v-for="(am,i) in modelist" :key="i" :value="am" v-slot="{active, selected}">
|
||||
<div class=" p-2 transition" :class="{
|
||||
'bg-neutral text-neutral-content': active,
|
||||
'bg-neutral-focus text-neutral-content': selected,
|
||||
}">
|
||||
{{ am.name }}
|
||||
</div>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
<button class="btn btn-sm btn-outline" @click="changeCycle">cycle time:{{cycleV}}</button>
|
||||
</template>
|
||||
</MethodInput>
|
||||
<Enhancer :result="enhancer" v-if="enhancer" class="mb-4"></Enhancer>
|
||||
<div id="monitorchart" class="input-btn-style h-60 w-full pointer-events-auto transition mb-2"></div>
|
||||
<div id="monitorchartcost" class="input-btn-style h-60 w-full pointer-events-auto transition"></div>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.bg {
|
||||
background: #9836cd;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,250 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { useInterpret } from '@xstate/vue';
|
||||
import { nextTick, onBeforeMount, onBeforeUnmount, reactive, Ref, ref, watchEffect } from 'vue';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption,
|
||||
} from "@headlessui/vue"
|
||||
import { publicStore } from '@/stores/public';
|
||||
import TodoList from '@/components/input/TodoList.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { interpret } from 'xstate';
|
||||
const fetchM = useInterpret(permachine)
|
||||
const publicS = publicStore()
|
||||
const fetchS = fetchStore()
|
||||
let eventList = reactive([] as string[]);
|
||||
let selectEvent = ref("cpu")
|
||||
let includesVal = reactive(new Set<string>())
|
||||
let excludesVal = reactive(new Set<string>())
|
||||
let framebuf = ref(1000000)
|
||||
let duration = ref(300)
|
||||
let profilerStatus = ref({
|
||||
is: false,
|
||||
message: ""
|
||||
})
|
||||
let outputPath = ref("")
|
||||
let samples = ref(0)
|
||||
const support = ref(false)
|
||||
let fileformat = ref("%t-%p.html")
|
||||
const getStatusLoop = fetchS.getPollingLoop(() => {
|
||||
const statusM = interpret(permachine)
|
||||
fetchS.baseSubmit(statusM, {
|
||||
command: "profiler status",
|
||||
action: "exec",
|
||||
sessionId: "",
|
||||
}).then(
|
||||
res => {
|
||||
support.value = true
|
||||
if (res) {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "profiler") {
|
||||
if (result.executeResult.search("not") >= 0) {
|
||||
profilerStatus.value.is = false
|
||||
} else profilerStatus.value.is = true
|
||||
profilerStatus.value.message = result.executeResult
|
||||
}
|
||||
}
|
||||
},
|
||||
reject => {
|
||||
getStatusLoop.close()
|
||||
}
|
||||
)
|
||||
}, {
|
||||
step: 2000,
|
||||
})
|
||||
|
||||
const getSampleLoop = fetchS.getPollingLoop(() => {
|
||||
let statusM = interpret(permachine)
|
||||
fetchS.baseSubmit(statusM, {
|
||||
command: "profiler getSamples",
|
||||
action: "exec",
|
||||
// 置空sessionId,使得不与session冲突
|
||||
sessionId: ""
|
||||
}).then(
|
||||
res => {
|
||||
if (res) {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "profiler") {
|
||||
samples.value = parseInt(result.executeResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}, {
|
||||
step: 2000,
|
||||
})
|
||||
const changeFramebuf = publicS.inputDialogFactory(
|
||||
framebuf,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 1000000 : valRaw
|
||||
},
|
||||
(input) => input.value.toString()
|
||||
)
|
||||
const changeFile = publicS.inputDialogFactory(fileformat,
|
||||
(raw) => raw.trim(),
|
||||
(input) => input.value)
|
||||
const changeDuration = publicS.inputDialogFactory(
|
||||
duration,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 300 : valRaw
|
||||
},
|
||||
(input) => input.value.toString()
|
||||
)
|
||||
|
||||
const restartInit = () => {
|
||||
profilerStatus.value.is = true
|
||||
outputPath.value = ""
|
||||
}
|
||||
const transformStartProps = () => {
|
||||
let start = "start"
|
||||
let evenOption = ""
|
||||
let includeOption = ""
|
||||
let excludeOption = ""
|
||||
let file = "--file arthas-output/"
|
||||
if (selectEvent.value !== "all") {
|
||||
evenOption = "--event " + selectEvent.value
|
||||
}
|
||||
for (const v of includesVal) {
|
||||
includeOption += "--include " + v + " "
|
||||
}
|
||||
for (const v of excludesVal) {
|
||||
excludeOption += "--exclude " + v + " "
|
||||
}
|
||||
file += fileformat
|
||||
return {
|
||||
start,
|
||||
evenOption,
|
||||
includeOption,
|
||||
excludeOption,
|
||||
file
|
||||
}
|
||||
}
|
||||
const startSubmit = () => {
|
||||
const { start, evenOption, includeOption, excludeOption, file } = transformStartProps()
|
||||
|
||||
|
||||
fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: `profiler ${start} ${evenOption} ${includeOption} ${excludeOption} ${fileformat}`,
|
||||
sessionId: undefined
|
||||
})
|
||||
.then(restartInit)
|
||||
}
|
||||
const stopProfiler = () => fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: "profiler stop"
|
||||
}).then(
|
||||
res => {
|
||||
profilerStatus.value.is = false
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "profiler" && result.outputFile) {
|
||||
outputPath.value = result.outputFile
|
||||
|
||||
let reg = /arthas-output\/.*/
|
||||
let arr = reg.exec(result.outputFile)
|
||||
if (arr && arr.length > 0) {
|
||||
let url = window.origin + "/" + arr[0]
|
||||
window.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
const resumeProfiler = () => fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: "profiler resume"
|
||||
}).then(
|
||||
restartInit
|
||||
)
|
||||
const toOutputDir = () => window.open(window.location.origin + "/arthas-output/")
|
||||
onBeforeMount(async () => {
|
||||
publicS.inputVal = ""
|
||||
includesVal.clear()
|
||||
excludesVal.clear()
|
||||
getStatusLoop.open()
|
||||
getSampleLoop.open()
|
||||
|
||||
fetchS.asyncInit()
|
||||
await fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: "profiler list"
|
||||
}).then(
|
||||
res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type == "profiler") {
|
||||
result.executeResult.split('\n').forEach(raw => {
|
||||
let cmd = raw.trim();
|
||||
if (!["Basic events:",
|
||||
"Java method calls:",
|
||||
"Perf events:",
|
||||
""
|
||||
].includes(cmd)) eventList.push(cmd)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
onBeforeUnmount(() => {
|
||||
getStatusLoop.close()
|
||||
getSampleLoop.close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="!support">
|
||||
<div class="flex py-2 border-b-2 border-gray-300">
|
||||
<h3 class="text-lg w-40">status: </h3>
|
||||
<div class="mx-2">
|
||||
<div>{{profilerStatus.message}}</div>
|
||||
<div v-if="profilerStatus.is">{{samples}} samples</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex border-b-2 border-gray-300 items-center py-2" v-if="!profilerStatus.is">
|
||||
<h3 class="text-lg w-40">How to start: </h3>
|
||||
<Listbox v-model="selectEvent">
|
||||
<div class=" relative mx-2">
|
||||
<ListboxButton class="btn btn-sm btn-outline w-52 "> even:
|
||||
{{ selectEvent}}
|
||||
</ListboxButton>
|
||||
<ListboxOptions
|
||||
class=" absolute w-52 mt-2 border py-2 rounded-md hover:shadow-xl transition bg-white max-h-80 overflow-y-auto">
|
||||
<ListboxOption v-for="(e,i) in eventList" :key="i" :value="e" v-slot="{active, selected}">
|
||||
<div class=" p-2 transition break-words text-base-100" :class="{
|
||||
'bg-neutral text-neutral-content': active,
|
||||
'bg-neutral-focus text-neutral-content': selected,
|
||||
}">
|
||||
{{ e }}
|
||||
</div>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click="changeDuration">duration :{{duration}}</button>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click="changeFramebuf">framebuf :{{framebuf}}</button>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click="changeFile">file :<span class="normal-case">{{fileformat}}</span></button>
|
||||
<TodoList title="include" :val-set="includesVal" class=" mr-2"></TodoList>
|
||||
<TodoList title="exclude" :val-set="excludesVal" class="mr-2"></TodoList>
|
||||
<button class="btn btn-primary btn-sm btn-outline" @click="startSubmit">start</button>
|
||||
</div>
|
||||
<div class="flex items-center border-b-2 border-gray-300 py-2">
|
||||
<h3 class="text-lg w-40">Resume or stop: </h3>
|
||||
<button class="btn btn-primary btn-sm btn-outline mx-2" @click="resumeProfiler" v-if="!profilerStatus.is">resume</button>
|
||||
<button class="btn btn-primary btn-sm btn-outline" @click="stopProfiler" v-if="profilerStatus.is">stop</button>
|
||||
</div>
|
||||
<div class="flex items-center py-2">
|
||||
<h3 class="text-lg w-40">output file path: </h3>
|
||||
<div class=" ml-2" v-if="outputPath.trim() !== ''">{{ outputPath }}</div>
|
||||
<button class="btn btn-primary btn-sm btn-outline ml-2" @click="toOutputDir">go to the output direction</button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <div v-else>
|
||||
Your system is not supported!
|
||||
</div> -->
|
||||
</template>
|
||||
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import MethodInput from '@/components/input/MethodInput.vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { useMachine, useInterpret } from '@xstate/vue';
|
||||
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import Enhancer from '@/components/show/Enhancer.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import {transfromStore} from "@/stores/resTransform"
|
||||
const fetchM = useInterpret(permachine)
|
||||
const pollingM = useMachine(machine)
|
||||
const fetchS = fetchStore()
|
||||
// const publicS = publicStore()
|
||||
const transS = transfromStore()
|
||||
const { getCommonResEffect } = fetchS
|
||||
// const {getCommonResEffect} = publicStore()
|
||||
|
||||
const loop = fetchS.pullResultsLoop(pollingM)
|
||||
const tableResults = reactive([] as Map<string, string>[])
|
||||
const keyList = [
|
||||
"ts",
|
||||
"cost",
|
||||
"daemon",
|
||||
"priority",
|
||||
"stackTrace",
|
||||
"classloader",
|
||||
"threadId",
|
||||
"threadName",]
|
||||
// const enhancer = reactive(new Map())
|
||||
const enhancer = ref(undefined as EnchanceResult | undefined)
|
||||
getCommonResEffect(pollingM, body => {
|
||||
if (body.results.length > 0) {
|
||||
body.results.forEach(result => {
|
||||
if (result.type === "stack") {
|
||||
const map = new Map()
|
||||
Object
|
||||
.keys(result)
|
||||
.filter((k) => !["jobId", "type"].includes(k))
|
||||
.forEach(k => {
|
||||
let val: string | string[] = ""
|
||||
if (k === "stackTrace") {
|
||||
let stackTrace = result[k]
|
||||
val = stackTrace.map((trace) => transS.transformStackTrace(trace))
|
||||
} else {
|
||||
val = result[k as Exclude<keyof typeof result, "jobId" | "type" | "stackTrace">].toString()
|
||||
}
|
||||
map.set(k, val)
|
||||
})
|
||||
// pollResults.unshift([result.ts, map])
|
||||
tableResults.unshift(map)
|
||||
}
|
||||
|
||||
if (result.type === "enhancer") {
|
||||
enhancer.value = result
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
// fetchM.send("INIT")
|
||||
pollingM.send("INIT")
|
||||
fetchS.asyncInit()
|
||||
// loop.open()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
loop.close()
|
||||
})
|
||||
|
||||
const submit = async (data: { classItem: Item, methodItem: Item, conditon: string, count: number }) => {
|
||||
|
||||
let className = data.classItem.value
|
||||
let methodName = data.methodItem.value
|
||||
let condition = data.conditon.trim() == "" ? "" : `'${data.conditon.trim()}'`
|
||||
let n = data.count > 0 ? `-n ${data.count}` : ""
|
||||
|
||||
// pollResults.length = 0
|
||||
enhancer.value = undefined
|
||||
// tableResults.length = 0
|
||||
fetchS.baseSubmit(fetchM, {
|
||||
action: "async_exec",
|
||||
command: `stack ${className} ${methodName} ${condition} ${n}`,
|
||||
sessionId: undefined
|
||||
}).then(res => {
|
||||
loop.open()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MethodInput :submit-f="submit" ncondition ncount></MethodInput>
|
||||
|
||||
<Enhancer :result="enhancer" v-if="enhancer"></Enhancer>
|
||||
<div class="w-full flex justify-center items-center mt-4">
|
||||
<table class="table w-full table-compact group">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300" v-for="(v,i) in keyList" :key="i" :class="{'group-first:z-0':i===0}">{{v}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
<tr v-for="(map, i) in tableResults" :key="i">
|
||||
<td class="border border-slate-300" v-for="(key,j) in keyList" :key="j">
|
||||
<template v-if="key!== 'stackTrace'">
|
||||
{{map.get(key)}}
|
||||
</template>
|
||||
<div class="flex flex-col items-end" v-else>
|
||||
|
||||
<div v-for="(row, k) in map.get(key)" :key="k">
|
||||
{{row}}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import {
|
||||
Switch,
|
||||
SwitchLabel,
|
||||
SwitchGroup
|
||||
} from "@headlessui/vue"
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import MethodInput from '@/components/input/MethodInput.vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import Tree from '@/components/show/Tree.vue';
|
||||
import Enhancer from '@/components/show/Enhancer.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
const pollingM = useMachine(machine)
|
||||
const fetchS = fetchStore()
|
||||
const { pullResultsLoop, getCommonResEffect } = fetchS
|
||||
const fetchM = useInterpret(permachine)
|
||||
const loop = pullResultsLoop(pollingM)
|
||||
const pollResults = reactive<TreeNode[]>([])
|
||||
const enhancer = ref(undefined as EnchanceResult | undefined)
|
||||
const publiC = publicStore()
|
||||
const excludeClass = ref("")
|
||||
const enabled = ref(true)
|
||||
const trans = (root: TraceNode, parent: TraceNode | null): string[] => {
|
||||
let title: (string)[] = []
|
||||
|
||||
if (root.type === "throw") {
|
||||
const lineNumber = root.lineNumber <= 0 ? "" : `#${root.lineNumber}`
|
||||
title = ["throw:" + root.exception, "lineNumber", lineNumber, `[${root.message}]`]
|
||||
} else if (root.type === "thread") {
|
||||
|
||||
title = [
|
||||
"ts=" + root.timestamp,
|
||||
"thread_name=" + root.threadName,
|
||||
"daemon=" + root.daemon.toString(),
|
||||
"priority=" + root.priority.toString(),
|
||||
"threadId=" + root.threadId.toString(), `TCCL=${root.classloader}`]
|
||||
} else {
|
||||
const lineNumber = root.lineNumber <= 0 ? "" : `#${root.lineNumber}`
|
||||
let percentage = ""
|
||||
|
||||
if (parent && parent.type === "method") percentage = `${(root.totalCost / parent.totalCost * 100).toFixed(2)}%, `
|
||||
|
||||
if (root.times <= 1) {
|
||||
console.log(
|
||||
root.cost,
|
||||
root.totalCost,
|
||||
)
|
||||
if (parent && parent.type === "method") percentage = `${(root.cost / parent.totalCost * 100).toFixed(2)}%, `
|
||||
title = [`[${percentage}${publiC.nanoToMillis(root.cost)}ms]`, lineNumber, `${root.className}:${root.methodName}`]
|
||||
} else {
|
||||
if (parent && parent.type === "method") percentage = `${(root.totalCost / parent.totalCost * 100).toFixed(2)}%, `
|
||||
title = [
|
||||
`[`,
|
||||
percentage,
|
||||
`min=${publiC.nanoToMillis(root.minCost)}ms, max =${publiC.nanoToMillis(root.maxCost)}ms, total=${publiC.nanoToMillis(root.totalCost)}ms, count=${root.times}]`,
|
||||
lineNumber,
|
||||
`${root.className}:${root.methodName}`]
|
||||
}
|
||||
}
|
||||
return title
|
||||
}
|
||||
/**处理Tree */
|
||||
const dfs = (root: TraceNode, parent: TraceNode | null): TreeNode => {
|
||||
return {
|
||||
children: root.children?.map(child => dfs(child, root)) || [],
|
||||
meta: trans(root, parent) as string[]
|
||||
}
|
||||
}
|
||||
getCommonResEffect(pollingM, body => {
|
||||
if (body.results.length > 0) {
|
||||
body.results.forEach(result => {
|
||||
if (result.type === "trace") {
|
||||
|
||||
// const trans = (root: TraceNode): Map<string, string[]> => {
|
||||
// /** 用于cmdRes */
|
||||
// const map = new Map(Object
|
||||
// .entries(root)
|
||||
// .filter(([k, v]) => "children" !== k)
|
||||
// .map(([k, v]) => [k, [v.toString()]]
|
||||
// )
|
||||
// ) as Map<string, string[]>
|
||||
// /**显示简略信息 */
|
||||
// let title = ""
|
||||
// if (root.type === "throw") {
|
||||
// title = "throw"
|
||||
// } else if (root.type === "thread") {
|
||||
// title = `${root.timestamp} ${root.threadName}`
|
||||
// } else {
|
||||
// title = `[${root.totalCost}ms]${root.className}::${root.methodName}`
|
||||
// }
|
||||
// map.set("title", [title])
|
||||
// return map
|
||||
// }
|
||||
|
||||
const root: TreeNode = {
|
||||
children: result.root?.children?.map(ch => dfs(ch, null)) || [],
|
||||
meta: trans(result.root, null)
|
||||
}
|
||||
|
||||
pollResults.unshift(root)
|
||||
}
|
||||
if (result.type === "enhancer") {
|
||||
enhancer.value = result
|
||||
}
|
||||
if (result.type === "status") {
|
||||
console.log(result)
|
||||
// 自动关停,目前有bug,应为interrupt也会出现statusCode,应该计数,目前还没办法解决
|
||||
if (result.statusCode === 0) {
|
||||
// statusCount--
|
||||
console.log("close!!!")
|
||||
// loop.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
pollingM.send("INIT")
|
||||
fetchS.asyncInit()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
loop.close()
|
||||
})
|
||||
|
||||
const setExclude = publicStore().inputDialogFactory(excludeClass,
|
||||
(raw) => raw,
|
||||
(input) => input.value.toString()
|
||||
)
|
||||
const submit = (data: { classItem: Item, methodItem: Item, conditon: string, count: number }) => {
|
||||
let condition = data.conditon.trim() == "" ? "" : `'${data.conditon.trim()}'`
|
||||
// let express = data.express.trim() == "" ? "" : `'${data.express.trim()}'`
|
||||
let n = data.count > 0 ? `-n ${data.count}` : ""
|
||||
let exclude = excludeClass.value == "" ? "" : `--exclude-class-pattern ${excludeClass.value}`
|
||||
let method = data.methodItem.value === "" ? "*" : data.methodItem.value
|
||||
let skipJDKMethod = enabled.value ? "" : "--skipJDKMethod false"
|
||||
return fetchS.baseSubmit(fetchM, {
|
||||
action: "async_exec",
|
||||
command: `trace ${data.classItem.value} ${method} ${skipJDKMethod} ${condition} ${n} ${exclude}`,
|
||||
sessionId: undefined
|
||||
}).then(() => {
|
||||
enhancer.value = undefined
|
||||
pollResults.length = 0
|
||||
loop.open()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MethodInput :submit-f="submit" class="mb-2" ncondition ncount>
|
||||
<template #others>
|
||||
|
||||
<label class="label cursor-pointer btn-sm border border-neutral ml-2">
|
||||
<span class="label-text uppercase font-bold mr-1">skip JDK Method</span>
|
||||
<input v-model="enabled" type="checkbox" class="toggle"/>
|
||||
</label>
|
||||
<button class="btn btn-outline btn-sm ml-2" @click="setExclude">exclude: {{excludeClass}}</button>
|
||||
</template>
|
||||
</MethodInput>
|
||||
<template v-if="pollResults.length > 0 || enhancer">
|
||||
<Enhancer :result="enhancer" v-if="enhancer"></Enhancer>
|
||||
<ul class=" pointer-events-auto mt-2">
|
||||
<template v-for="(result, i) in pollResults" :key="i">
|
||||
<Tree :root="result" class=" border-t-2 mb-4 pt-4">
|
||||
<!-- 具体信息的表达 -->
|
||||
<template #meta="{ data, active }">
|
||||
<div class="bg-info p-1 mb-1 rounded-r rounded-br">
|
||||
{{data.join(" ")}}
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</template>
|
||||
@@ -0,0 +1,367 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import MethodInput from '@/components/input/MethodInput.vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, } from 'vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import Enhancer from '@/components/show/Enhancer.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TitleComponent,
|
||||
TitleComponentOption,
|
||||
ToolboxComponent,
|
||||
ToolboxComponentOption,
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
DataZoomComponent,
|
||||
DataZoomComponentOption
|
||||
} from 'echarts/components';
|
||||
import {
|
||||
BarChart,
|
||||
BarSeriesOption,
|
||||
LineChart,
|
||||
LineSeriesOption
|
||||
} from 'echarts/charts';
|
||||
import {
|
||||
UniversalTransition
|
||||
} from 'echarts/features';
|
||||
import {
|
||||
SVGRenderer
|
||||
} from 'echarts/renderers';
|
||||
import { ECharts, number } from 'echarts/core';
|
||||
|
||||
echarts.use(
|
||||
[TitleComponent, ToolboxComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent, BarChart, LineChart, SVGRenderer, UniversalTransition]
|
||||
);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
TitleComponentOption | ToolboxComponentOption | TooltipComponentOption | GridComponentOption | LegendComponentOption | DataZoomComponentOption | BarSeriesOption | LineSeriesOption
|
||||
>
|
||||
const pollingM = useMachine(machine)
|
||||
const fetchS = fetchStore()
|
||||
const { pullResultsLoop, getPullResultsEffect } = fetchS
|
||||
const fetchM = useInterpret(permachine)
|
||||
const loop = pullResultsLoop(pollingM)
|
||||
const enhancer = ref(undefined as EnchanceResult | undefined)
|
||||
const trigerRes = reactive(new Map<string, string[]>)
|
||||
const cacheIdx = ref("-1")
|
||||
const inputVal = ref("")
|
||||
const keyList: tfkey[] = [
|
||||
"index",
|
||||
"timestamp",
|
||||
"className",
|
||||
"methodName",
|
||||
"cost",
|
||||
"object",
|
||||
"params",
|
||||
"returnObj",
|
||||
"throwExp",
|
||||
// 暂时隐藏这两个属性,不够宽了
|
||||
// "return",
|
||||
// "throw",
|
||||
]
|
||||
const tableResults = reactive([] as Map<string, string>[])
|
||||
// const timeFragmentSet = new Set()
|
||||
type tfkey = keyof TimeFragment
|
||||
|
||||
const chartContext: {
|
||||
count: number,
|
||||
myChart?: ECharts,
|
||||
categories: number[],
|
||||
data: number[],
|
||||
cur: number,
|
||||
max: number,
|
||||
} = {
|
||||
max: 0,
|
||||
cur: 0,
|
||||
count: 20,
|
||||
myChart: undefined,
|
||||
categories: [],
|
||||
data: []
|
||||
}
|
||||
const chartOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#283b56'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataView: { readOnly: false },
|
||||
}
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: 'cost(ms)',
|
||||
max: 0,
|
||||
min: 0,
|
||||
boundaryGap: [0.2, 0.2]
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'cost',
|
||||
type: 'bar',
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
data: chartContext.data
|
||||
}
|
||||
]
|
||||
};
|
||||
const updateChart = (tf: TimeFragment) => {
|
||||
while (chartContext.cur > chartContext.count) {
|
||||
chartContext.data.shift()
|
||||
chartContext.categories.shift()
|
||||
chartContext.cur--
|
||||
}
|
||||
chartContext.data.push(tf.cost)
|
||||
chartContext.categories.push(tf.index)
|
||||
chartContext.cur++
|
||||
chartContext.max = Math.max(...chartContext.data)
|
||||
chartContext.myChart!.setOption<EChartsOption>({
|
||||
xAxis: [
|
||||
{
|
||||
data: chartContext.categories
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
max: chartContext.max,
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: chartContext.data
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
const transform = (tf: TimeFragment) => {
|
||||
const map = new Map()
|
||||
Object.keys(tf).forEach((k) => {
|
||||
let val: string | string[] = []
|
||||
if ((k) === "params") {
|
||||
tf.params.forEach(para => {
|
||||
// 以后可能会有bug
|
||||
for (const key in para) {
|
||||
// @ts-ignore
|
||||
val.push(`${key}:${para[key].toString()}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
val = (tf[k as tfkey].toString())
|
||||
}
|
||||
|
||||
map.set(k, val)
|
||||
})
|
||||
updateChart(tf)
|
||||
return map
|
||||
}
|
||||
getPullResultsEffect(
|
||||
pollingM,
|
||||
result => {
|
||||
if (result.type === "tt") {
|
||||
result.timeFragmentList.forEach(tf => {
|
||||
console.log(tf.index)
|
||||
// if(!timeFragmentSet.has(tf.index)){
|
||||
// timeFragmentSet.add(tf.index)
|
||||
tableResults.unshift(transform(tf))
|
||||
// }
|
||||
|
||||
})
|
||||
}
|
||||
if (result.type == "enhancer") {
|
||||
enhancer.value = result
|
||||
}
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
pollingM.send("INIT")
|
||||
fetchS.asyncInit()
|
||||
})
|
||||
onMounted(() => {
|
||||
const chartDom = document.getElementById('ttchart')!;
|
||||
chartContext.myChart = echarts.init(chartDom);
|
||||
chartContext.myChart.on("click", e => {
|
||||
console.dir(e)
|
||||
})
|
||||
chartOption && chartContext.myChart.setOption(chartOption)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
loop.close()
|
||||
})
|
||||
const submit = async (data: { classItem: Item, methodItem: Item, count: number, conditon: string }) => {
|
||||
let n = data.count > 0 ? `-n ${data.count}` : ""
|
||||
let condition = data.conditon
|
||||
fetchS.baseSubmit(fetchM, {
|
||||
action: "async_exec",
|
||||
command: `tt -t ${data.classItem.value} ${data.methodItem.value} ${n} ${condition}`,
|
||||
sessionId: undefined
|
||||
}).then(res => {
|
||||
enhancer.value = undefined
|
||||
loop.open()
|
||||
})
|
||||
}
|
||||
const alltt = () => fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: `tt -l`
|
||||
}).then((res) => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
trigerRes.clear()
|
||||
tableResults.length = 0
|
||||
if (result.type === "tt") {
|
||||
result.timeFragmentList.forEach(tf => {
|
||||
tableResults.unshift(transform(tf))
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const reTrigger = (idx: string) => fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: `tt -i ${idx} -p`,
|
||||
}).then(
|
||||
res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
|
||||
if (result.type === "tt") {
|
||||
trigerRes.clear()
|
||||
cacheIdx.value = idx
|
||||
let tf = result.replayResult
|
||||
|
||||
Object.keys(tf).forEach((k) => {
|
||||
let val: string[] = []
|
||||
if ((k as keyof TimeFragment) === "params") {
|
||||
tf.params.forEach(para => {
|
||||
val.push(JSON.stringify(para))
|
||||
})
|
||||
} else {
|
||||
val.push(tf[k as keyof TimeFragment].toString())
|
||||
}
|
||||
trigerRes.set(k as tfkey, val)
|
||||
})
|
||||
|
||||
trigerRes.set("sizeLimit", [result.sizeLimit.toString()])
|
||||
trigerRes.set("replayNo", [result.replayNo.toString()])
|
||||
}
|
||||
}, () => {
|
||||
trigerRes.clear()
|
||||
}
|
||||
)
|
||||
|
||||
const searchTt = () => {
|
||||
let condition = inputVal.value.trim() !== "" ? `'${inputVal.value}'` : ''
|
||||
return fetchS.baseSubmit(fetchM, {
|
||||
action: "exec",
|
||||
command: `tt -s ${condition}`
|
||||
}).then(res => {
|
||||
tableResults.length = 0
|
||||
trigerRes.clear()
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "tt") {
|
||||
if (result.timeFragmentList.length === 0) {
|
||||
publicStore().$patch({
|
||||
isErr: true,
|
||||
ErrMessage: "not found"
|
||||
})
|
||||
return
|
||||
}
|
||||
result.timeFragmentList.forEach(tf => {
|
||||
tableResults.unshift(transform(tf))
|
||||
})
|
||||
}
|
||||
}).catch(err=>{
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MethodInput :submit-f="submit" ncount ncondition>
|
||||
</MethodInput>
|
||||
<div class="divider"></div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="mr-2">searching records</div>
|
||||
<div
|
||||
class="flex-1 overflow-hidden rounded-lg bg-white text-left border focus-within:outline outline-2 hover:shadow-md transition">
|
||||
<input type="text" v-model="inputVal"
|
||||
class="w-full border-none py-2 pl-3 pr-10 h-full text-gray-900 focus:outline-none">
|
||||
</div>
|
||||
<button @click="searchTt" class="mx-2 btn btn-primary btn-sm btn-outline">search</button>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button class="btn btn-primary btn-sm btn-outline my-4 mr-2" @click="alltt">
|
||||
all records
|
||||
</button>
|
||||
</div>
|
||||
<div class="pointer-events-auto">
|
||||
<div id="ttchart" class="w-full h-60 input-btn-style mb-4"></div>
|
||||
<div class="text-gray-500">
|
||||
<CmdResMenu title="invoked result" :map="trigerRes" v-if="trigerRes.size > 0">
|
||||
<template #headerAside>
|
||||
<div class="flex mt-2 justify-end mr-1">
|
||||
<button @click="reTrigger(cacheIdx)" class="btn btn-primary btn-outline btn-xs p-1">invoke</button>
|
||||
</div>
|
||||
</template>
|
||||
</CmdResMenu>
|
||||
</div>
|
||||
<!-- <template v-if="enhancer|| tableResults.length > 0"> -->
|
||||
<Enhancer :result="enhancer" v-if="enhancer"></Enhancer>
|
||||
<div class="w-full flex justify-center items-center overflow-auto flex-1">
|
||||
<table class="table table-compact group w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border group-first:z-0" v-for="(v,i) in keyList" :key="i">{{v}}</th>
|
||||
<th class="border">invoke</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
<tr v-for="(map, i) in tableResults" :key="i">
|
||||
<td class="border" v-for="(key,j) in keyList" :key="j">
|
||||
<template v-if=" key !== 'params'">
|
||||
{{map.get(key)}}
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col" v-else>
|
||||
<div v-for="(row, k) in map.get(key)" :key="k">
|
||||
{{row}}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="border">
|
||||
<button class="btn btn-primary btn-sm btn-outline" @click="reTrigger(map.get('index')!)">invoke</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- </template> -->
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,245 @@
|
||||
<script setup lang="ts">
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { useInterpret, useMachine } from '@xstate/vue';
|
||||
import MethodInput from '@/components/input/MethodInput.vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption,
|
||||
Switch,
|
||||
SwitchLabel,
|
||||
SwitchGroup,
|
||||
SwitchDescription
|
||||
} from "@headlessui/vue"
|
||||
import { onBeforeMount, onBeforeUnmount, reactive, Ref, ref, watchEffect } from 'vue';
|
||||
import Tree from '@/components/show/Tree.vue';
|
||||
import Enhancer from '@/components/show/Enhancer.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
const pollingM = useMachine(machine)
|
||||
const fetchS = fetchStore()
|
||||
const publiC = publicStore()
|
||||
const { pullResultsLoop, getPullResultsEffect } = fetchS
|
||||
const fetchM = useInterpret(permachine)
|
||||
const loop = pullResultsLoop(pollingM)
|
||||
const pollResults = reactive([] as [string, Map<string, string[]>, TreeNode][])
|
||||
const enhancer = ref(undefined as EnchanceResult | undefined)
|
||||
const depth = ref(1)
|
||||
const tableResults = reactive([] as Map<string, string | TreeNode>[])
|
||||
const keyList = [
|
||||
"ts",
|
||||
"accessPoint",
|
||||
"className",
|
||||
"methodName",
|
||||
"cost",
|
||||
// "sizeLimit",
|
||||
"value",
|
||||
]
|
||||
const tranOgnl = (s: string): string[] => s.split("\n")
|
||||
// type Mode = "-f" | "-s" | "-e" | "-b"
|
||||
// const modelist: { name: string, value: Mode }[] = [
|
||||
// { name: "before method being invoked", value: "-b" },
|
||||
// { name: "when method encountering exceptions", value: "-e" },
|
||||
// { name: "when method exits normally", value: "-s" },
|
||||
// { name: "when method exits", value: "-f" }
|
||||
// ]
|
||||
const beforeInvoke = ref(false)
|
||||
const successInvoke = ref(false)
|
||||
const failureInvoke = ref(false)
|
||||
const allInvoke = ref(true)
|
||||
const modereflist: { enabled: Ref<boolean>, name: string }[] = [
|
||||
{ enabled: beforeInvoke, name: "before" },
|
||||
{ enabled: successInvoke, name: "success" },
|
||||
{ enabled: failureInvoke, name: "exception" },
|
||||
{ enabled: allInvoke, name: "finish" }
|
||||
]
|
||||
const selectedMode: { enabled: Ref<boolean>, name: string }[] = [
|
||||
{ enabled: beforeInvoke, name: "before" },
|
||||
{ enabled: successInvoke, name: "success" },
|
||||
{ enabled: failureInvoke, name: "exception" },
|
||||
{ enabled: allInvoke, name: "finish" }
|
||||
]
|
||||
// const mode = ref(modelist[3])
|
||||
|
||||
const transform = (result: CommandResult) => {
|
||||
const map = new Map();
|
||||
if (result.type !== "watch") return map
|
||||
|
||||
for (const key in result) {
|
||||
if (key !== "value") {
|
||||
//@ts-ignore
|
||||
map.set(key, result[key])
|
||||
}
|
||||
}
|
||||
let raw = tranOgnl(result.value)
|
||||
const stk: TreeNode[] = []
|
||||
// Tree的构建
|
||||
raw.forEach(v => {
|
||||
let str = v.trim()
|
||||
let match = 0
|
||||
for (let s of str) {
|
||||
if (s === "[") {
|
||||
match++
|
||||
} else if (s === "]") {
|
||||
match--
|
||||
}
|
||||
}
|
||||
const root = {
|
||||
children: [],
|
||||
meta: str.substring(0, str.length - 1)
|
||||
} as TreeNode
|
||||
|
||||
if (match > 0) {
|
||||
stk.push(root)
|
||||
} else if (match === 0) {
|
||||
let cur = stk.pop()
|
||||
if (cur) {
|
||||
cur.children!.push(root)
|
||||
stk.push(cur)
|
||||
} else {
|
||||
stk.push(root)
|
||||
}
|
||||
|
||||
} else {
|
||||
/// 默认每行只会一个]
|
||||
//!可能会有bug
|
||||
let cur = stk.pop()!
|
||||
if (stk.length > 0) {
|
||||
let parent = stk.pop()!
|
||||
parent.children!.push(cur)
|
||||
stk.push(parent)
|
||||
} else {
|
||||
// 构建结束
|
||||
stk.push(cur)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
map.set("value", stk[0])
|
||||
return map
|
||||
}
|
||||
getPullResultsEffect(
|
||||
pollingM,
|
||||
result => {
|
||||
console.log(result)
|
||||
if (result.type === "watch") {
|
||||
tableResults.unshift(transform(result))
|
||||
}
|
||||
if (result.type === "enhancer") {
|
||||
enhancer.value = result
|
||||
}
|
||||
})
|
||||
const setDepth = publiC.inputDialogFactory(
|
||||
depth,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 1 : valRaw
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
onBeforeMount(() => {
|
||||
pollingM.send("INIT")
|
||||
fetchS.asyncInit()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
loop.close()
|
||||
})
|
||||
|
||||
const submit = async (data: { classItem: Item, methodItem: Item, conditon: string, express: string }) => {
|
||||
let conditon = data.conditon.trim() == "" ? "" : `'${data.conditon.trim()}'`
|
||||
let express = data.express.trim() == "" ? "" : `'${data.express.trim()}'`
|
||||
let mode = ""
|
||||
if (beforeInvoke.value) mode += " -b"
|
||||
if (failureInvoke.value) mode += " -e"
|
||||
if (successInvoke.value) mode += " -s"
|
||||
if (allInvoke.value) mode += " -f"
|
||||
tableResults.length = 0
|
||||
fetchS.baseSubmit(fetchM, {
|
||||
action: "async_exec",
|
||||
command: `watch ${mode} ${data.classItem.value} ${data.methodItem.value} -x ${depth.value} ${conditon} ${express}`,
|
||||
sessionId: undefined
|
||||
}).finally(() => {
|
||||
pollResults.length = 0
|
||||
enhancer.value = undefined
|
||||
loop.open()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MethodInput :submit-f="submit" nexpress ncondition>
|
||||
<template #others>
|
||||
<div class="relative group ml-2">
|
||||
<div class="btn btn-sm btn-outline">watching point</div>
|
||||
<div class="h-0 group-hover:h-auto group-focus-within:h-auto absolute overflow-clip transition z-10 top-full pt-2">
|
||||
<!-- <SwitchGroup v-for="(mode,i) in modereflist" :key="i">
|
||||
<div class="flex input-btn-style ml-2 focus-within:outline outline-1 justify-between m-2 bg-white">
|
||||
<SwitchLabel class="mr-2">{{mode.name}}:</SwitchLabel>
|
||||
<Switch v-model="mode.enabled.value" :class="mode.enabled.value ? 'bg-blue-400' : 'bg-gray-500'"
|
||||
class="relative items-center inline-flex h-6 w-12 shrink-0 cursor-pointer rounded-full border-transparent transition-colors ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 mr-2">
|
||||
<span aria-hidden="true" :class="mode.enabled.value ? 'translate-x-6' : '-translate-x-1'"
|
||||
class="pointer-events-none inline-block h-6 w-6 transform rounded-full bg-white shadow-md shadow-gray-500 ring-0 transition ease-in-out" />
|
||||
</Switch>
|
||||
</div>
|
||||
</SwitchGroup> -->
|
||||
|
||||
<label class="label cursor-pointer btn-sm border border-neutral ml-2 bg-base-100"
|
||||
v-for="(mode,i) in modereflist" :key="i">
|
||||
<span class="label-text uppercase font-bold mr-1">{{mode.name}}</span>
|
||||
<input v-model="mode.enabled.value" type="checkbox" class="toggle" />
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- <Listbox v-model="modereflist" multiple class="relative" as="div">
|
||||
<ListboxButton as="button" class="btn btn-sm btn-outline ml-2">
|
||||
watching point
|
||||
</ListboxButton>
|
||||
<ListboxOptions class="absolute top-full z-10">
|
||||
<ListboxOption v-for="mode in modereflist" :key="mode.name" :value="mode" v-slot="{active}">
|
||||
<label class="label cursor-pointer bg-base-100">
|
||||
<span class="label-text">{{mode.name}}</span>
|
||||
<input v-model="mode.enabled.value" type="checkbox" class="toggle" />
|
||||
</label>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</Listbox> -->
|
||||
<button class="btn btn-sm btn-outline ml-2" @click="setDepth">depth:{{depth}}</button>
|
||||
</template>
|
||||
</MethodInput>
|
||||
<Enhancer :result="enhancer" v-if="enhancer"></Enhancer>
|
||||
<div class="flex justify-center mt-4 overflow-auto">
|
||||
<table class="table w-full group">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300" v-for="(v,i) in keyList" :key="i" :class="{'group-first:z-0':i===0}">{{v}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(map, i) in tableResults" :key="i">
|
||||
<td class="border border-slate-300" v-for="(key,j) in keyList" :key="j">
|
||||
<div v-if=" key !== 'value'">
|
||||
{{map.get(key)}}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col" v-else>
|
||||
<Tree :root="(map.get('value') as TreeNode)" class="mt-2" button-class=" ">
|
||||
<template #meta="{ data, active }">
|
||||
<div class="bg-blue-200 p-2 mb-2 rounded-r rounded-br"
|
||||
:class='{"hover:bg-blue-300 bg-blue-400":active}'>
|
||||
{{data}}
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,175 @@
|
||||
<script setup lang="ts">import machine from '@/machines/consoleMachine';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import { reactive } from 'vue';
|
||||
import ClassInput from '@/components/input/ClassInput.vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { interpret } from 'xstate';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const classInfoM = useMachine(machine)
|
||||
const classMethodInfoM = useMachine(machine)
|
||||
const dumpM = useMachine(machine)
|
||||
const classDetailMap = reactive(new Map<string, string[]>())
|
||||
const classFields = reactive(new Map<string, string[]>())
|
||||
const classMethodMap = reactive(new Map<string, string[]>())
|
||||
const dumpMap = reactive(new Map<string, string[]>())
|
||||
const { getCommonResEffect } = publicStore()
|
||||
const publicS = publicStore()
|
||||
const fetchS = fetchStore()
|
||||
// getCommonResEffect(classInfoM, body => {
|
||||
// const result = body.results[0]
|
||||
// if (result.type === "sc" && result.detailed === true && result.withField === true) {
|
||||
|
||||
// classDetailMap.clear()
|
||||
// classFields.clear()
|
||||
|
||||
// Object.entries(result.classInfo).filter(([k, v]) => k !== "fields").forEach(([k, v]) => {
|
||||
// let value: string[] = []
|
||||
// if (!["interfaces", "annotations", "classloader", "superClass"].includes(k)) value.push(v.toString())
|
||||
// else value = v as string[]
|
||||
// classDetailMap.set(k, value)
|
||||
// })
|
||||
|
||||
// result.classInfo.fields.forEach(field => {
|
||||
// classFields.set(field.name, Object.entries(field).filter(([k, v]) => k !== "name").map(([k, v]) => {
|
||||
// if (k === "value") v = JSON.stringify(v)
|
||||
// return `${k}: ${v}`
|
||||
// }))
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
getCommonResEffect(classMethodInfoM, body => {
|
||||
classMethodMap.clear()
|
||||
body.results.forEach(result => {
|
||||
if (result.type === "sm" && result.detail == true) {
|
||||
classMethodMap.set(result.methodInfo.methodName, Object.entries(result.methodInfo).filter(([k, v]) => k !== "methodName").map(([k, v]) => {
|
||||
let res = k + ' : '
|
||||
if (!["exceptions", "parameters", "annotations"].includes(k)) res += v.toString()
|
||||
else res += JSON.stringify(v)
|
||||
return res
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
getCommonResEffect(dumpM, body => {
|
||||
dumpMap.clear()
|
||||
body.results.forEach(result => {
|
||||
if (result.type === "dump") {
|
||||
result.dumpedClasses.forEach(obj => {
|
||||
dumpMap.set(obj.name, Object.entries(obj).filter(([k, v]) => k !== "name").map(([k, v]) => {
|
||||
let res = k + ' : '
|
||||
if (k === "classloader") res += JSON.stringify(v)
|
||||
else res += v
|
||||
return res
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
const getClassInfo = (data: { classItem: Item; loaderItem: Item }) => {
|
||||
let item = data.classItem
|
||||
let classLoader = data.loaderItem.value === "" ? "" : `-c ${data.loaderItem.value}`
|
||||
|
||||
classDetailMap.clear()
|
||||
classFields.clear()
|
||||
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `sc -d -f ${item.value} ${classLoader}`
|
||||
}).then(
|
||||
res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "sc" && result.detailed === true && result.withField === true) {
|
||||
Object.entries(result.classInfo).filter(([k, v]) => k !== "fields").forEach(([k, v]) => {
|
||||
let value: string[] = []
|
||||
if (!["interfaces", "annotations", "classloader", "superClass"].includes(k)) value.push(v.toString())
|
||||
else value = v as string[]
|
||||
classDetailMap.set(k, value)
|
||||
})
|
||||
|
||||
result.classInfo.fields.forEach(field => {
|
||||
classFields.set(field.name, Object.entries(field).filter(([k, v]) => k !== "name").map(([k, v]) => {
|
||||
if (k === "value") v = JSON.stringify(v)
|
||||
return `${k}: ${v}`
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `sm -d ${item.value} ${classLoader}`
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "sm" && result.detail == true) {
|
||||
classMethodMap.set(result.methodInfo.methodName, Object.entries(result.methodInfo).filter(([k, v]) => k !== "methodName").map(([k, v]) => {
|
||||
let res = k + ' : '
|
||||
if (!["exceptions", "parameters", "annotations"].includes(k)) res += v.toString()
|
||||
else res += JSON.stringify(v)
|
||||
return res
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `dump ${item.value} ${classLoader}`
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "dump") {
|
||||
result.dumpedClasses.forEach(obj => {
|
||||
dumpMap.set(obj.name, Object.entries(obj).filter(([k, v]) => k !== "name").map(([k, v]) => {
|
||||
let res = k + ' : '
|
||||
if (k === "classloader") res += JSON.stringify(v)
|
||||
else res += v
|
||||
return res
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
// classInfoM.send({
|
||||
// type: "SUBMIT",
|
||||
// value: {
|
||||
// action: "exec",
|
||||
// command: `sc -d -f ${item.value} ${classLoader}`
|
||||
// }
|
||||
// })
|
||||
// classMethodInfoM.send({
|
||||
// type: "SUBMIT",
|
||||
// value: {
|
||||
// action: "exec",
|
||||
// command: `sm -d ${item.value} ${classLoader}`
|
||||
// }
|
||||
// })
|
||||
// dumpM.send({
|
||||
// type: "SUBMIT",
|
||||
// value: {
|
||||
// action: "exec",
|
||||
// command: `dump ${item.value} ${classLoader}`
|
||||
// }
|
||||
// })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClassInput :submit-f="getClassInfo"></ClassInput>
|
||||
<div>
|
||||
<template v-if="classDetailMap.size !== 0">
|
||||
<h4 class="grid place-content-center mb-2 text-3xl mt-4">classInfo</h4>
|
||||
<CmdResMenu :map="classFields" title="fields"></CmdResMenu>
|
||||
<CmdResMenu :map="classDetailMap" title="detail"></CmdResMenu>
|
||||
</template>
|
||||
<template v-if="classMethodMap.size !== 0">
|
||||
<CmdResMenu :map="classMethodMap" title="methods"></CmdResMenu>
|
||||
</template>
|
||||
<template v-if="dumpMap.size !== 0">
|
||||
<CmdResMenu :map="dumpMap" title="dump"></CmdResMenu>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,422 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, onUnmounted, reactive, ref } from 'vue';
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue';
|
||||
import { publicStore } from "@/stores/public"
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { interpret } from 'xstate';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import transformMachine from '@/machines/transformConfigMachine';
|
||||
// import ClassInput from '@/components/input/ClassInput.vue';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import Tree from '@/components/show/Tree.vue';
|
||||
const fetchS = fetchStore()
|
||||
|
||||
const urlStats = ref([] as [
|
||||
string,
|
||||
Map<"hash" | "unUsedUrls" | "usedUrls" | "parent", string[]>,
|
||||
string
|
||||
][])
|
||||
// const tablelResults = reactive([] as Map<string, string | number>[])
|
||||
const tableResults = reactive([] as Map<string, string | number>[])
|
||||
const loaderCache = ref({ name: "", hash: "", count: "" } as Record<"name" | "hash" | "count", string>)
|
||||
const classLoaderTree = reactive([] as TreeNode[])
|
||||
|
||||
const hashCode = computed(() => {
|
||||
let res = loaderCache.value.hash.trim() === "" ? "" : "-c " + loaderCache.value.hash.trim()
|
||||
if (loaderCache.value.hash.trim() === "null") {
|
||||
res = `--classLoaderClass ${loaderCache.value.name}`
|
||||
}
|
||||
return res
|
||||
})
|
||||
const selectedClassLoadersUrlStats = ref([] as string[])
|
||||
const classVal = ref("")
|
||||
const resourceVal = ref("")
|
||||
// const keylList = [
|
||||
// "name", "loadedCount", "hash", "parent"
|
||||
// ]
|
||||
const keyList = [
|
||||
"name", "numberOfInstance", "loadedCount"
|
||||
]
|
||||
const trans = (root: ClassLoaderNode, parent: ClassLoaderNode | null): string[] => {
|
||||
let title: (string)[] = []
|
||||
|
||||
let count = root.loadedCount.toString()
|
||||
let name = root.name.split('@')[0]
|
||||
let hash = root.hash
|
||||
title = [count, name, hash]
|
||||
|
||||
return title
|
||||
}
|
||||
/**处理Tree */
|
||||
const dfs = (root: ClassLoaderNode, parent: ClassLoaderNode | null): TreeNode => {
|
||||
let children: TreeNode[] = []
|
||||
|
||||
if ("children" in root) {
|
||||
if (root.children) {
|
||||
children = root.children.map(child => dfs(child, root))
|
||||
}
|
||||
}
|
||||
return {
|
||||
children,
|
||||
meta: trans(root, parent) as string[]
|
||||
}
|
||||
}
|
||||
|
||||
const json_to_obj = (str: string) => {
|
||||
const actor = interpret(transformMachine)
|
||||
actor.start()
|
||||
|
||||
actor.send("INPUT", {
|
||||
data: str
|
||||
})
|
||||
|
||||
return fetchS.isResult(actor).then(
|
||||
state => {
|
||||
if (state.matches("success")) {
|
||||
return Promise.resolve(state.context.output)
|
||||
} else {
|
||||
publicStore().$patch({
|
||||
isErr: true,
|
||||
ErrMessage: actor.state.context.err
|
||||
})
|
||||
return Promise.reject(1)
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
err => {
|
||||
return Promise.reject(2)
|
||||
}
|
||||
)
|
||||
}
|
||||
const getAllUrlStats = () => fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: "classloader --url-stat"
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "classloader" && Object.hasOwn(result, "urlStats")) {
|
||||
urlStats.value.length = 0
|
||||
Object.entries(result.urlStats).forEach(([k, v]) => {
|
||||
json_to_obj(k).then(
|
||||
obj => {
|
||||
urlStats.value.push([
|
||||
obj.name.split("@")[0],
|
||||
new Map([
|
||||
["parent", [obj.parent]],
|
||||
["hash", [obj.hash]],
|
||||
["unUsedUrls", v.unUsedUrls],
|
||||
["usedUrls", v.usedUrls]
|
||||
]),
|
||||
obj.hash
|
||||
])
|
||||
}
|
||||
).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getClassLoaderTree = () => fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: "classloader -t"
|
||||
}).then(res => {
|
||||
const results = (res as CommonRes).body.results
|
||||
classLoaderTree.length = 0
|
||||
results.forEach(result => {
|
||||
if (result.type === "classloader" && result.tree) {
|
||||
result.classLoaders.forEach(classloader => {
|
||||
classLoaderTree.push(dfs(classloader, null))
|
||||
})
|
||||
}
|
||||
})
|
||||
}, err => {
|
||||
console.error(err)
|
||||
})
|
||||
// const getCategorizedByLoaded = () => {
|
||||
// tablelResults.length = 0
|
||||
// fetchS.baseSubmit(interpret(permachine), {
|
||||
// action: "exec",
|
||||
// command: "classloader -l"
|
||||
// }).then(res => {
|
||||
// const result = (res as CommonRes).body.results[0]
|
||||
// if (result.type === "classloader" && !result.tree) {
|
||||
// result.classLoaders.forEach(loader => {
|
||||
// const map = new Map()
|
||||
// for (const key in loader) {
|
||||
// //@ts-ignore
|
||||
// if(key == "name") map.set(key, loader[key].split("@")[0])
|
||||
// else map.set(key, loader[key])
|
||||
// }
|
||||
// tablelResults.push(map)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
const getCategorizedByClassType = () => {
|
||||
tableResults.length = 0
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: "classloader"
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "classloader") {
|
||||
|
||||
for (const name in result.classLoaderStats) {
|
||||
const map = new Map()
|
||||
for (const key in result.classLoaderStats[name]) {
|
||||
map.set(key, result.classLoaderStats[name][key])
|
||||
}
|
||||
map.set("name", name)
|
||||
tableResults.push(map)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
getAllUrlStats()
|
||||
getClassLoaderTree()
|
||||
// getCategorizedByLoaded()
|
||||
getCategorizedByClassType()
|
||||
})
|
||||
|
||||
const loadClass = () => {
|
||||
let classItem = classVal.value.trim() === "" ? "" : `--load ${classVal.value.trim()}`
|
||||
if (classItem === "") return
|
||||
return fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `classloader ${hashCode.value} ${classItem}`
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
publicStore().$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: JSON.stringify(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
const loadResource = () => {
|
||||
let resourceItem = resourceVal.value.trim() === "" ? "" : `-r ${resourceVal.value.trim()}`
|
||||
if (resourceItem === "") return
|
||||
return fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `classloader ${hashCode.value} ${resourceItem}`
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
publicStore().$patch({
|
||||
isSuccess: true,
|
||||
SuccessMessage: JSON.stringify(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
const getUrlStats = () => {
|
||||
selectedClassLoadersUrlStats.value = []
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `classloader ${hashCode.value}`
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "classloader") {
|
||||
selectedClassLoadersUrlStats.value = result.urls
|
||||
}
|
||||
})
|
||||
}
|
||||
const selectClassLoader = (data: { hash: string, name: string, count: string }) => {
|
||||
loaderCache.value = data
|
||||
getUrlStats()
|
||||
}
|
||||
const resetClassloader = () => {
|
||||
selectClassLoader({ hash: "", name: "", count: "" })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full justify-between">
|
||||
<div class="flex h-[40vh]">
|
||||
<div class="input-btn-style h-full p-4 mb-2 flex flex-col transition-all duration-500" :class='{
|
||||
"w-full":loaderCache.hash === "",
|
||||
"w-2/3":loaderCache.hash !== ""
|
||||
}'>
|
||||
<!-- 后置为了让用户能注意到右上角的refreshicon -->
|
||||
<div class="h-[5vh] mb-4 justify-end flex">
|
||||
<button @click="resetClassloader" class="btn btn-primary btn-sm mr-1">reset</button>
|
||||
<button @click="getClassLoaderTree" class="btn btn-primary btn-sm">refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto w-full flex-1">
|
||||
<div v-for="(tree,i) in classLoaderTree" :key="i">
|
||||
<Tree :root="tree">
|
||||
<template #meta="{ data, active }">
|
||||
<!-- <div class="flex items-center"> -->
|
||||
<div class="bg-info px-2 rounded-r rounded-br mr-2 text-info-content" :class='{
|
||||
"hover:opacity-50":active,
|
||||
"bg-success text-success-content":loaderCache.hash=== data[2]
|
||||
}'>
|
||||
{{data[1]}}
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</template>
|
||||
<template #others="{data}">
|
||||
<div class="items-center flex">
|
||||
<div class="mr-2">
|
||||
<span class="bg-primary-focus text-primary-content border border-primary-focus px-2 rounded-l ">
|
||||
count :
|
||||
</span>
|
||||
<span class="bg-base-200 border border-primary-focus rounded-r px-1 ">
|
||||
{{data[0]}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<span class="bg-primary-focus px-2 rounded-l text-primary-content border border-primary-focus">
|
||||
hash :
|
||||
</span>
|
||||
<span class="bg-base-200 rounded-r flex-1 px-1 border border-primary-focus">
|
||||
{{data[2]}}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="">count:{{data[0]}}</div> -->
|
||||
<button @click="selectClassLoader({name:data[1],hash:data[2],count:data[0]})" class="btn btn-primary btn-xs btn-outline opacity-0 group-hover:opacity-100"
|
||||
v-if="data[2]!== 'null'">
|
||||
select classloader
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-1/3 ml-2 overflow-y-scroll transition-all duration-500" :class='{
|
||||
"w-0":loaderCache.hash === "",
|
||||
"input-btn-style":loaderCache.hash !==""
|
||||
}'>
|
||||
|
||||
<!-- <div class="overflow-auto h-full"> -->
|
||||
<div class="mb-2">
|
||||
<div class="overflow-auto">
|
||||
<span class="bg-primary-focus px-2 rounded-l text-primary-content border border-primary-focus">
|
||||
selected classLoader:
|
||||
</span>
|
||||
<span class="bg-base-200 rounded-r px-1 border border-primary-focus">
|
||||
{{loaderCache.name}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<span class="bg-primary-focus px-2 rounded-l text-primary-content border border-primary-focus">
|
||||
loadedcount :
|
||||
</span>
|
||||
<span class="bg-base-200 rounded-r px-1 border border-primary-focus">
|
||||
{{loaderCache.count}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<span class="bg-primary-focus px-2 rounded-l text-primary-content border border-primary-focus">
|
||||
hash :
|
||||
</span>
|
||||
<span class="bg-base-200 rounded-r px-1 border border-primary-focus">
|
||||
{{loaderCache.hash}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="loaderCache.hash.trim() !== ''">
|
||||
<div class="flex mb-2 w-full">
|
||||
<div class=" cursor-default
|
||||
flex-auto
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline outline-2
|
||||
hover:shadow-md transition mr-2">
|
||||
<input class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none"
|
||||
v-model="classVal" />
|
||||
</div>
|
||||
<button @click="loadClass" class="btn btn-primary btn-sm btn-outline">load class</button>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class=" cursor-default
|
||||
flex-auto
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline outline-2
|
||||
hover:shadow-md transition mr-2">
|
||||
<input class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none"
|
||||
v-model="resourceVal" />
|
||||
</div>
|
||||
<button @click="loadResource" class="btn btn-primary btn-sm btn-outline">load resource</button>
|
||||
</div>
|
||||
<div class="h-0 border my-2"></div>
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-xl flex-1 flex justify-center">urls</h3><button class="btn btn-primary btn-sm"
|
||||
@click="getUrlStats">refresh</button>
|
||||
</div>
|
||||
<ul class="mt-2 w-full flex flex-col">
|
||||
<li v-for="(url,i) in selectedClassLoadersUrlStats" :key="i" class="bg-blue-200 mb-2 p-2 break-all w-full">
|
||||
{{url}}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 下面的3格 -->
|
||||
<div class="w-full flex-auto flex h-[40vh] mt-2">
|
||||
<div class="input-btn-style w-1/3 mr-2 h-full flex">
|
||||
<div class="overflow-y-scroll h-ful w-full">
|
||||
<div class=" mb-2 flex items-center justify-end">
|
||||
<h3 class="text-xl flex-1 flex justify-center">urlStats</h3>
|
||||
<button class="btn btn-primary btn-sm" @click="getAllUrlStats">refresh</button>
|
||||
</div>
|
||||
<div v-for="v in urlStats" :key="v[0]" class="flex flex-col">
|
||||
<CmdResMenu :title="v[0]" :map="v[1]" button-width="w-full" :button-accent="v[2] === loaderCache.hash">
|
||||
</CmdResMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col h-full w-2/3">
|
||||
<!-- <div class="input-btn-style w-full mr-2 h-1/2">
|
||||
<div class="overflow-auto flex-1 h-full">
|
||||
<div class="flex justify-end mb-2">
|
||||
<button class="button-style" @click="getCategorizedByLoaded">refresh</button>
|
||||
</div>
|
||||
<table class="border-collapse border border-slate-400 mx-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300 p-2" v-for="(v,i) in keylList" :key="i">{{v}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
<tr v-for="(map, i) in tablelResults" :key="i">
|
||||
<td class="border border-slate-300 p-2" v-for="(key,j) in keylList" :key="j">
|
||||
{{map.get(key)}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="input-btn-style w-full mr-2 h-full">
|
||||
<div class="overflow-auto flex-1 h-full">
|
||||
<div class="flex justify-end mb-2">
|
||||
<button class="btn btn-primary btn-sm" @click="getCategorizedByClassType">refresh</button>
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<table class="table w-full group table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300 p-2" v-for="(v,i) in keyList" :key="i" :class="{'group-first:z-0':i==0}">{{v}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
<tr v-for="(map, i) in tableResults" :key="i">
|
||||
<td class="border border-slate-300 p-2" v-for="(key,j) in keyList" :key="j">
|
||||
{{map.get(key)}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, reactive, ref } from 'vue';
|
||||
import { Switch } from "@headlessui/vue"
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
|
||||
const { getCommonResEffect } = publicStore()
|
||||
const fetchM = useMachine(machine)
|
||||
const path = ref("")
|
||||
const enabled = ref(false)
|
||||
const map = reactive(new Map())
|
||||
onBeforeMount(() => {
|
||||
fetchM.send("INIT")
|
||||
})
|
||||
getCommonResEffect(fetchM, body => {
|
||||
const result = body.results.filter(result => result.type === "heapdump")[0]
|
||||
if (result.type === "heapdump") {
|
||||
map.clear()
|
||||
map.set("filePath", [result.dumpFile])
|
||||
map.set("live", [result.live])
|
||||
}
|
||||
})
|
||||
const submitCommand = (e: Event) => {
|
||||
fetchM.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: `heapdump ${enabled.value ? "--live" : ''}${path.value}`
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="mb-4 flex items-center justify-between">
|
||||
<label class="flex flex-1 items-center"> path
|
||||
<div class="flex-1
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline
|
||||
outline-2
|
||||
min-w-[15rem]
|
||||
mx-2
|
||||
hover:shadow-md transition">
|
||||
<input type="text" v-model="path"
|
||||
class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none">
|
||||
</div>
|
||||
<!-- <div class="flex input-btn-style mr-2 focus-within:outline outline-2">
|
||||
<div class="mx-2">only live object : </div>
|
||||
<Switch v-model="enabled" :class="enabled ? 'bg-blue-400' : 'bg-gray-500'"
|
||||
class="relative items-center inline-flex h-6 w-12 shrink-0 cursor-pointer rounded-full border-transparent transition-colors ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 mr-2">
|
||||
<span aria-hidden="true" :class="enabled ? 'translate-x-6' : '-translate-x-1'"
|
||||
class="pointer-events-none inline-block h-6 w-6 transform rounded-full bg-white shadow-md shadow-gray-500 ring-0 transition ease-in-out" />
|
||||
</Switch>
|
||||
</div> -->
|
||||
<label class="label cursor-pointer btn-sm border border-neutral mr-2">
|
||||
<span class="label-text uppercase font-bold mr-1">only live object</span>
|
||||
<input v-model="enabled" type="checkbox" class="toggle" />
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<button @click.prevent="submitCommand"
|
||||
class="btn btn-primary btn-sm btn-outline transition-all truncate p-2">dump</button>
|
||||
</form>
|
||||
<CmdResMenu title="dumpRes" open :map="map" v-if="map.size > 0" class="mt-4"></CmdResMenu>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import 'highlight.js/lib/common';
|
||||
import highlightjsP from "@highlightjs/vue-plugin";
|
||||
import ClassInput from '@/components/input/ClassInput.vue';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { onBeforeMount, reactive, ref } from 'vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
const { getCommonResEffect } = publicStore()
|
||||
const highlightjs = highlightjsP.component
|
||||
const sourceM = useMachine(machine)
|
||||
const code = ref('')
|
||||
const locationMap = reactive(new Map<string, string[]>())
|
||||
const getSource = (data: { classItem: Item; }) => {
|
||||
sourceM.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: `jad ${data.classItem.value}`
|
||||
}
|
||||
})
|
||||
}
|
||||
getCommonResEffect(sourceM, body => {
|
||||
const result = body.results[0]
|
||||
|
||||
if (result.type === "jad") {
|
||||
code.value = result.source
|
||||
locationMap.clear()
|
||||
|
||||
locationMap.set('location', [result.location])
|
||||
Object.entries(result.classInfo).forEach(([k, v]) => {
|
||||
let value: string[] = []
|
||||
if (k === "classloader") value = v as string[]
|
||||
else value.push(v.toString())
|
||||
locationMap.set(k, value)
|
||||
})
|
||||
}
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
sourceM.send("INIT")
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<!-- <div class="flex flex-col items-center w-full"> -->
|
||||
<div class="mb-4">
|
||||
<ClassInput :submit-f="getSource"></ClassInput>
|
||||
</div>
|
||||
<div v-if="code !== ''">
|
||||
<CmdResMenu title="classInfo" :map="locationMap" class="mb-4"></CmdResMenu>
|
||||
<div class="w-10/12 rounded-xl border p-4 bg-[#f6f6f6] hover:shadow-gray-400 mx-auto shadow-lg transition mb-4">
|
||||
<highlightjs language="Java" :code="code" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, reactive, ref } from 'vue';
|
||||
import AutoComplete from "@/components/input/AutoComplete.vue";
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { waitFor } from 'xstate/lib/waitFor';
|
||||
import { interpret } from 'xstate';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const { getCommonResEffect } = publicStore()
|
||||
const searchMbean = useMachine(machine)
|
||||
const allMbean = useMachine(machine)
|
||||
const optionItems = ref([] as { name: string, value: string }[])
|
||||
const attributesMap = reactive(new Map<string, string[]>())
|
||||
const constructorsMap = reactive(new Map<string, string[]>())
|
||||
const operationMap = reactive(new Map<string, string[]>())
|
||||
const className = ref('')
|
||||
const description = ref('')
|
||||
|
||||
let mbeanName = ''
|
||||
onBeforeMount(() => {
|
||||
searchMbean.send("INIT")
|
||||
allMbean.send("INIT")
|
||||
// allLoop.open()
|
||||
})
|
||||
getCommonResEffect(searchMbean, body => {
|
||||
optionItems.value.length = 0
|
||||
const result = body.results[0]
|
||||
|
||||
if (result.type === "mbean") {
|
||||
if (Object.hasOwn(result, "mbeanMetadata") && Object.hasOwn(result.mbeanMetadata, mbeanName)) {
|
||||
const res = result.mbeanMetadata[mbeanName]
|
||||
attributesMap.clear()
|
||||
constructorsMap.clear()
|
||||
operationMap.clear()
|
||||
className.value = ''
|
||||
description.value = ''
|
||||
res.attributes
|
||||
.forEach(v => {
|
||||
attributesMap.set(
|
||||
v.name,
|
||||
Object
|
||||
.entries(v)
|
||||
.filter(([k, v]) => k !== "name")
|
||||
.map(([k, v]) => {
|
||||
if (k === "openType") return `${k} : ${JSON.stringify(v)}`
|
||||
return `${k} : ${v.toString()}`
|
||||
})
|
||||
)
|
||||
})
|
||||
res.constructors
|
||||
.forEach(v => {
|
||||
constructorsMap.set(
|
||||
v.name,
|
||||
Object
|
||||
.entries(v)
|
||||
.filter(([k, v]) => k !== "name")
|
||||
.map(([k, v]) => {
|
||||
if (k === "signature") return `${k} : ${JSON.stringify(v)}`
|
||||
return `${k} : ${v.toString()}`
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
res.operations
|
||||
.forEach(operation => {
|
||||
operationMap.set(
|
||||
operation.name,
|
||||
Object
|
||||
.entries(operation)
|
||||
.filter(([k, v]) => k !== "name")
|
||||
.map(([k, v]) => {
|
||||
if (k === "signature") return `${k} : ${JSON.stringify(v)}`
|
||||
return `${k} : ${v.toString()}`
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
className.value = res.className
|
||||
description.value = res.description
|
||||
}
|
||||
if (Object.hasOwn(result, "mbeanAttribute") && Object.hasOwn(result.mbeanAttribute, mbeanName)) {
|
||||
const res = result.mbeanAttribute[mbeanName]
|
||||
res.forEach(({ name, value }) => {
|
||||
let format = "value : "
|
||||
if (["string", "number", "boolean"].includes(typeof value)) {
|
||||
format += value.toString()
|
||||
} else {
|
||||
format += JSON.stringify(value)
|
||||
}
|
||||
const v = attributesMap.get(name)
|
||||
if (v) v.push(format)
|
||||
else attributesMap.set(name, [format])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
const getMbeanInfo = async (item: Item) => {
|
||||
searchMbean.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: `mbean -m ${item.value}`
|
||||
}
|
||||
})
|
||||
mbeanName = item.value as string
|
||||
await waitFor(searchMbean.service, state => state.matches("ready"))
|
||||
searchMbean.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: `mbean ${item.value}`
|
||||
}
|
||||
})
|
||||
}
|
||||
const getAll = () => fetchStore().baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `mbean`
|
||||
}).then(res=>{
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
optionItems.value.length = 0
|
||||
if (result.type === "mbean" && Object.hasOwn(result, "mbeanNames")) {
|
||||
result.mbeanNames.forEach(name => {
|
||||
optionItems.value.push({
|
||||
name,
|
||||
value: name
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoComplete label="mbeanInfo" :option-items="optionItems" :input-fn="getAll" v-slot="slotP" as="form">
|
||||
<button @click.prevent="getMbeanInfo(slotP.selectItem)"
|
||||
class="btn btn-primary btn-sm btn-outline mx-2 transition">submit</button>
|
||||
</AutoComplete>
|
||||
<div v-if="className !== ''" class="mt-4">
|
||||
<h2 class="flex justify-center my-4 text-xl">{{ className }}</h2>
|
||||
<div class="flex my-4 pl-10">description : {{ description }}</div>
|
||||
<CmdResMenu title="arrtibute" :map="attributesMap"></CmdResMenu>
|
||||
<CmdResMenu title="constructors" :map="constructorsMap"></CmdResMenu>
|
||||
<CmdResMenu title="operations" :map="operationMap"></CmdResMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { useMachine } from '@xstate/vue';
|
||||
import { onBeforeMount, onUnmounted, reactive, watchEffect } from 'vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { number } from 'echarts/core';
|
||||
const fetchM = useMachine(machine)
|
||||
const { getPollingLoop } = fetchStore()
|
||||
const { getCommonResEffect } = publicStore()
|
||||
|
||||
const map = reactive(new Map<string, string[]>())
|
||||
const loop = getPollingLoop(() => {
|
||||
fetchM.send({
|
||||
type: "SUBMIT",
|
||||
value: {
|
||||
action: "exec",
|
||||
command: "memory"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchM.send("INIT")
|
||||
|
||||
loop.open()
|
||||
})
|
||||
onUnmounted(()=>loop.close())
|
||||
getCommonResEffect(fetchM, body => {
|
||||
const result = body.results[0]
|
||||
if (result.type === "memory") {
|
||||
const memoryInfo = result.memoryInfo
|
||||
map.clear()
|
||||
Object.entries(memoryInfo).reduce((pre, cur) => {
|
||||
cur[1].forEach(v => pre.push(v))
|
||||
return pre
|
||||
}, [] as any[]).forEach(v => {
|
||||
map.set(v.name,
|
||||
Object.entries(v).filter(([k, v]) => k !== "name").map((k) => {
|
||||
return `${k[0]} : ${typeof k[1] === "number" && k[1] > 0 ?
|
||||
(Math.floor(k[1] / 1024 / 1024) + 'M'):k[1] }`
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CmdResMenu title="memory" :map="map" class="w-full" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import 'highlight.js/lib/common';
|
||||
import highlightjsP from "@highlightjs/vue-plugin";
|
||||
import { useInterpret } from '@xstate/vue';
|
||||
import { ref } from 'vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { publicStore } from '@/stores/public';
|
||||
const publiC = publicStore()
|
||||
const express = ref("")
|
||||
const highlightjs = highlightjsP.component
|
||||
const sourceM = useInterpret(permachine)
|
||||
const code = ref('')
|
||||
const depth = ref(1)
|
||||
const classloaderName = ref("")
|
||||
const hashcode = ref("")
|
||||
const setDepth = publiC.inputDialogFactory(
|
||||
depth,
|
||||
(raw) => {
|
||||
const valRaw = parseInt(raw)
|
||||
const realVal = Number.isNaN(valRaw) ? 1 : valRaw
|
||||
return realVal
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const setHash = publiC.inputDialogFactory(
|
||||
hashcode,
|
||||
(raw) => raw,
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const setClassLoader = publiC.inputDialogFactory(
|
||||
classloaderName,
|
||||
(raw) => raw,
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const getSource = () => {
|
||||
let nhash = ""
|
||||
let nclassLoader = ""
|
||||
let ndepth = `-x ${depth.value}`
|
||||
if(classloaderName.value !== "") nclassLoader += `--classLoaderClass ${classloaderName.value}`
|
||||
if(hashcode.value !== "") nhash += `-c ${hashcode.value}}`
|
||||
|
||||
fetchStore().baseSubmit(sourceM, {
|
||||
action: "exec",
|
||||
command: `ognl ${express.value} ${nclassLoader} ${nhash} ${ndepth}`
|
||||
}).then(
|
||||
res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "ognl") {
|
||||
code.value = result.value
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<form class="mb-4 flex items-center justify-between">
|
||||
<label class="flex flex-1 items-center"> ognl
|
||||
<div class="w-full cursor-default
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline
|
||||
outline-2
|
||||
min-w-[15rem]
|
||||
mx-2
|
||||
hover:shadow-md transition">
|
||||
<input type="text" v-model="express"
|
||||
class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click.prevent="setDepth">depth:{{depth}}</button>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click.prevent="setClassLoader" v-if="hashcode === ''">ClassLoaderClass:{{classloaderName}}</button>
|
||||
<button class="btn btn-sm btn-outline mr-2" @click.prevent="setHash" v-if="classloaderName === ''">hashcode:{{hashcode}}</button>
|
||||
</label>
|
||||
|
||||
<button @click.prevent="getSource"
|
||||
class="btn btn-primary btn-sm btn-outline truncate p-2">submit</button>
|
||||
</form>
|
||||
<div v-if="code !== ''">
|
||||
<!-- <CmdResMenu title="classInfo" :map="locationMap" class="mb-4"></CmdResMenu> -->
|
||||
<div class="w-10/12 rounded-xl border p-4 bg-[#f6f6f6] hover:shadow-gray-400 mx-auto shadow-lg transition mb-4">
|
||||
<highlightjs language="bash" :code="code" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @zh reset 功能比较常用,之后应该装载到header上进行操作
|
||||
*/
|
||||
import ClassInput from '@/components/input/ClassInput.vue';
|
||||
import { reactive } from 'vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { interpret } from 'xstate';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const fetchS = fetchStore()
|
||||
const res = reactive(new Map())
|
||||
|
||||
const resetClass = (data: { classItem: Item }) => {
|
||||
fetchS.baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `reset ${data.classItem.value as string}`
|
||||
}).then(response => {
|
||||
const result = (response as CommonRes).body.results[0]
|
||||
if (result.type === "reset") {
|
||||
Object.entries(result.affect).forEach(([k, v]) => {
|
||||
res.set(k, k === "cost" ? [`${v}ms`] : [v])
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClassInput :submit-f="resetClass"></ClassInput>
|
||||
<CmdResMenu title="reset affect" open :map="res" v-if="res.size > 0"></CmdResMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import machine from '@/machines/consoleMachine';
|
||||
import { useInterpret } from '@xstate/vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { Switch, } from '@headlessui/vue';
|
||||
import { interpret } from 'xstate';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
const retransformListM = useInterpret(machine)
|
||||
const { getPollingLoop } = fetchStore()
|
||||
const retransformListMap = reactive(new Map<string, string[]>())
|
||||
const fetchS = fetchStore()
|
||||
const retransformRes = reactive(new Map<string, string[]>())
|
||||
const retransformPath = ref('')
|
||||
const enabled = ref(false)
|
||||
const listLoop = getPollingLoop(() => {
|
||||
fetchS.baseSubmit(retransformListM, {
|
||||
action: "exec",
|
||||
command: "retransform -l"
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
retransformListMap.clear()
|
||||
if (result.type === "retransform") {
|
||||
result.retransformEntries.forEach(v => {
|
||||
console.log(111)
|
||||
retransformListMap.set(v.className, Object.entries(v).filter(([k, v]) => k !== "className").map(([k, v]) => `${k} : ${v.toString()}`))
|
||||
})
|
||||
}
|
||||
})
|
||||
}, {
|
||||
step: 2000,
|
||||
globalIntrupt: true
|
||||
})
|
||||
|
||||
|
||||
const onSubmit = () => {
|
||||
let classPattern = ""
|
||||
if (enabled.value) classPattern += `--classPattern`
|
||||
return fetchStore().baseSubmit(interpret(permachine), {
|
||||
action: "exec",
|
||||
command: `retransform ${retransformPath.value} ${classPattern}`
|
||||
}).then(res => {
|
||||
let result = (res as CommonRes).body.results[0]
|
||||
|
||||
if (result.type === "retransform") {
|
||||
retransformRes.clear()
|
||||
retransformRes.set("retransformClass", result.retransformClasses)
|
||||
retransformRes.set("retransformCount", [result.retransformCount.toString()])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openList = () => {
|
||||
listLoop.invoke()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class=" flex items-center justify-between mb-2">
|
||||
<label class="flex flex-1 items-center"> retransform
|
||||
<div class="w-full cursor-default
|
||||
overflow-hidden rounded-lg bg-white text-left border
|
||||
focus-within:outline
|
||||
outline-2
|
||||
min-w-[15rem]
|
||||
mx-2
|
||||
hover:shadow-md transition">
|
||||
<input type="text" v-model="retransformPath"
|
||||
class="w-full border-none py-2 pl-3 pr-10 leading-5 text-gray-900 focus-visible:outline-none">
|
||||
</div>
|
||||
</label>
|
||||
<!-- <div class="flex input-btn-style mr-2 focus-within:outline outline-2">
|
||||
<div class="mx-2">explicitly trigger</div>
|
||||
<Switch v-model="enabled" :class="enabled ? 'bg-blue-400' : 'bg-gray-500'"
|
||||
class="relative items-center inline-flex h-6 w-12 shrink-0 cursor-pointer rounded-full border-transparent transition-colors ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 mr-2">
|
||||
<span aria-hidden="true" :class="enabled ? 'translate-x-6' : '-translate-x-1'"
|
||||
class="pointer-events-none inline-block h-6 w-6 transform rounded-full bg-white shadow-md shadow-gray-500 ring-0 transition ease-in-out" />
|
||||
</Switch>
|
||||
</div> -->
|
||||
<label class="label cursor-pointer btn-sm border border-neutral mr-2">
|
||||
<span class="label-text uppercase font-bold mr-1">explicitly trigger</span>
|
||||
<input v-model="enabled" type="checkbox" class="toggle" />
|
||||
</label>
|
||||
<button @click.prevent="onSubmit" class="btn btn-primary btn-sm btn-outline mr-4 truncate">submit</button>
|
||||
</form>
|
||||
|
||||
<CmdResMenu title="response" :map="retransformRes" open v-if="retransformRes.size !== 0"></CmdResMenu>
|
||||
|
||||
<CmdResMenu title="entries" :map="retransformListMap" @myclick="openList"></CmdResMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,292 @@
|
||||
<script setup lang="ts">
|
||||
// import machine from '@/machines/consoleMachine';
|
||||
import { useInterpret, } from '@xstate/vue';
|
||||
import { onBeforeMount, reactive, ref, onUnmounted, watchEffect } from 'vue';
|
||||
import CmdResMenu from '@/components/show/CmdResMenu.vue';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import TodoList from '@/components/input/TodoList.vue';
|
||||
import {
|
||||
Switch, SwitchLabel, SwitchGroup,
|
||||
Listbox, ListboxButton, ListboxOptions, ListboxOption
|
||||
} from '@headlessui/vue';
|
||||
import { transfromStore } from "@/stores/resTransform"
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { computed } from '@vue/reactivity';
|
||||
|
||||
const fetchS = fetchStore()
|
||||
const FetchService = useInterpret(permachine)
|
||||
const stackTrace = reactive([] as string[])
|
||||
const transformS = transfromStore()
|
||||
const count = ref(0)
|
||||
const leastTime = ref(200)
|
||||
const isBlock = ref(false)
|
||||
const publiC = publicStore()
|
||||
type thkey = keyof BusyThread
|
||||
const keyList: thkey[] = [
|
||||
"id",
|
||||
"name",
|
||||
"cpu",
|
||||
"daemon",
|
||||
"deltaTime",
|
||||
"group",
|
||||
"interrupted",
|
||||
"priority",
|
||||
"state",
|
||||
"time",
|
||||
|
||||
"inNative",
|
||||
"suspended",
|
||||
"waitedCount",
|
||||
"waitedTime",
|
||||
"lockOwnerId",
|
||||
"lockedMonitors",
|
||||
"lockedSynchronizers",
|
||||
"blockedCount",
|
||||
"blockedTime",
|
||||
]
|
||||
const statsList: (keyof ThreadStats)[] = ["id",
|
||||
"name",
|
||||
"cpu",
|
||||
"daemon",
|
||||
"deltaTime",
|
||||
"group",
|
||||
"interrupted",
|
||||
"priority",
|
||||
"state",
|
||||
"time"]
|
||||
const infoCount = ref({
|
||||
NEW: 0,
|
||||
RUNNABLE: 0,
|
||||
BLOCKED: 0,
|
||||
WAITING: 0,
|
||||
TIMED_WAITING: 0,
|
||||
TERMINATED: 0
|
||||
} as ThreadStateCount)
|
||||
const tableResults = reactive([] as Map<string, string>[])
|
||||
const tableFilter = computed(() => {
|
||||
// 原本的数组
|
||||
let res = tableResults
|
||||
if (includesVal.size === 0) return res;
|
||||
// 导入过滤条件
|
||||
includesVal.forEach((v1) => {
|
||||
let [key, vals] = v1.split(":")
|
||||
//@ts-ignore
|
||||
if (keyList.includes(key)) {
|
||||
const raw = vals.split("")
|
||||
let incudes: string[] = []
|
||||
if (raw[0] === "[" && raw[raw.length - 1] === "]") {
|
||||
raw.pop()
|
||||
raw.shift()
|
||||
}
|
||||
incudes = raw.join("").split(',')
|
||||
|
||||
if (key === "name" || key === "group") {
|
||||
// 字符串是包含
|
||||
res = res.filter((map) => {
|
||||
let val = map.get(key.trim())!
|
||||
// 取对于多个子字符串, 取交集
|
||||
return incudes.every(reg => val.includes(reg))
|
||||
})
|
||||
} else res = res.filter((map) => incudes.includes(map.get(key.trim())!))
|
||||
}
|
||||
|
||||
})
|
||||
return res
|
||||
})
|
||||
const statelist: { name: string, value: ThreadState | "" }[] = [
|
||||
{ name: "WAITING", value: "WAITING" },
|
||||
{ name: "RUNNABLE", value: "RUNNABLE" },
|
||||
{ name: "TIMED_WAITING", value: "TIMED_WAITING" },
|
||||
{ name: "BLOCKED", value: "BLOCKED" },
|
||||
{ name: "all", value: "" }
|
||||
]
|
||||
const threadState = ref(statelist[4])
|
||||
const includesVal = reactive(new Set<string>())
|
||||
onBeforeMount(() => {
|
||||
getThreads()
|
||||
})
|
||||
const getThreads = () => {
|
||||
let i = leastTime.value > 0 ? "-i " + leastTime.value : ""
|
||||
let n = count.value > 0 ? "-n " + count.value : ""
|
||||
const b = isBlock.value ? "-b" : ""
|
||||
let state = threadState.value.value === "" ? "" : `--state ${threadState.value.value}`
|
||||
tableResults.length = 0
|
||||
for (const key in infoCount.value) {
|
||||
//@ts-ignore
|
||||
infoCount.value[key] = 0
|
||||
}
|
||||
// thread [--all] [-b] [--lockedMonitors] [--lockedSynchronizers] [-i <value>] [--state <value>] [-n <value>] [id]
|
||||
fetchS.baseSubmit(FetchService, {
|
||||
action: "exec",
|
||||
command: `thread --all ${b} ${i} ${n} ${state}`
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
|
||||
// threadInfo.value = result.threadInfo
|
||||
if (result.type === "thread") {
|
||||
|
||||
stackTrace.length = 0
|
||||
|
||||
if (n === "") {
|
||||
// result.threadStateCount.
|
||||
result.threadStats.forEach(thread => {
|
||||
const map = new Map()
|
||||
// Object.entries(thread).forEach(([k, v]) => map.set(k, v.toString().trim() || "-"))
|
||||
for (const key in thread) {
|
||||
// if(key === 'id') map.set(key, thread[key as thkey])
|
||||
map.set(key, thread[key as keyof ThreadStats].toString().trim() || "-")
|
||||
}
|
||||
tableResults.unshift(map)
|
||||
|
||||
})
|
||||
|
||||
for (const key in result.threadStateCount) {
|
||||
if (Object.hasOwn(result.threadStateCount, key)) {
|
||||
//@ts-ignore
|
||||
infoCount.value[key] = result.threadStateCount[key];
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.busyThreads.forEach((thread) => {
|
||||
// allMap.set(v.name, Object.entries(v).filter(([k, v]) => k !== "name").map(([k, v]) => `${k} : ${v}`))
|
||||
// if (v.id > 0) optionThread.push({ name: v.name, value: v.id })
|
||||
const map = new Map()
|
||||
// Object.entries(thread).forEach(([k, v]) => map.set(k, v.toString().trim() || "-"))
|
||||
for (const key in thread) {
|
||||
// if(key === 'id') map.set(key, thread[key as thkey])
|
||||
map.set(key, thread[key as thkey].toString().trim() || "-")
|
||||
}
|
||||
tableResults.unshift(map)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
tableResults.sort((m1, m2) => parseFloat(m2.get("cpu")!) - parseFloat(m1.get("cpu")!))
|
||||
}
|
||||
})
|
||||
}
|
||||
const setlimit = publiC.inputDialogFactory(
|
||||
count,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 3 : valRaw
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const setleast = publiC.inputDialogFactory(
|
||||
leastTime,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 200 : valRaw
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
|
||||
const getSpecialThreads = (threadid: number = -1) => {
|
||||
let threadName = threadid > 0 ? `${threadid}` : ""
|
||||
// let i = leastTime.value > 0 ? "-i " + leastTime.value : ""
|
||||
// let n = count.value > 0 ? "-n " + count.value : ""
|
||||
// const b = isBlock.value ? "-b" : ""
|
||||
// let state = threadState.value.value === "" ? "" : `--state ${threadState.value.value}`
|
||||
fetchS.baseSubmit(FetchService, {
|
||||
action: "exec",
|
||||
command: `thread ${threadName}`
|
||||
}).then(res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "thread") {
|
||||
// threadInfo.value = result.threadInfo
|
||||
stackTrace.length = 0
|
||||
result.threadInfo.stackTrace.forEach(stack => stackTrace.unshift(transformS.transformStackTrace(stack)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex justify-end items-center h-[10vh]">
|
||||
|
||||
<TodoList title="filter" :val-set="includesVal" class=" mr-2"></TodoList>
|
||||
<label class="label cursor-pointer btn-sm border border-neutral ">
|
||||
<span class="label-text uppercase font-bold mr-1">is blocking:</span>
|
||||
<input v-model="isBlock" type="checkbox" class="toggle"/>
|
||||
</label>
|
||||
<button class="btn ml-2 btn-sm btn-outline" @click="setleast">sample interval:{{leastTime}}</button>
|
||||
<button v-show="!isBlock" class="btn ml-2 btn-sm btn-outline" @click="setlimit"> top n threads:{{count}}</button>
|
||||
<Listbox v-model="threadState">
|
||||
<div class=" relative mx-2 ">
|
||||
<ListboxButton class="btn w-40 btn-sm btn-outline">state {{ threadState.name }}</ListboxButton>
|
||||
<ListboxOptions
|
||||
class=" z-10 absolute w-40 mt-2 border overflow-hidden rounded-md hover:shadow-xl transition bg-base-100">
|
||||
<ListboxOption v-for="(am,i) in statelist" :key="i" :value="am" v-slot="{active, selected}">
|
||||
<div class=" p-2 transition " :class="{
|
||||
'bg-neutral text-neutral-content': active,
|
||||
'bg-neutral-focus text-neutral-content': selected,
|
||||
}">
|
||||
{{ am.name }}
|
||||
</div>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
<button class="btn btn-primary btn-sm btn-outline" @click="getThreads"> get threads</button>
|
||||
</div>
|
||||
<div class="w-full h-[50vh] input-btn-style my-2 p-4 flex flex-col">
|
||||
<div class="flex h-[8vh] flex-wrap flex-auto">
|
||||
<div v-for="(v, i) in Object.entries(infoCount)" :key="i" class="mr-2">
|
||||
<span class="text-primary-content border border-primary-focus bg-primary-focus w-44 px-2 rounded-l">
|
||||
{{ v[0] }}
|
||||
</span>
|
||||
<span class="border border-primary-focus bg-base-200 rounded-r flex-1 px-1">
|
||||
{{v[1]}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto h-[40vh] w-full">
|
||||
<table class="table w-full group">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-slate-300 p-2 group-first:z-0">get stackTrace</th>
|
||||
<template v-if="count===0">
|
||||
<th class="border border-slate-300 p-2" v-for="(v,i) in keyList" :key="i">{{v}}</th>
|
||||
</template>
|
||||
<template v-else>
|
||||
<th class="border border-slate-300 p-2" v-for="(v,i) in statsList" :key="i">{{v}}</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(map, i) in tableFilter" :key="i">
|
||||
<td class="border border-slate-300 p-2"><button class="btn-outline btn-primary btn btn-sm"
|
||||
@click="getSpecialThreads(parseInt(map.get('id')!))" v-if="map.get('id')!=='-1'">
|
||||
get stackTrace
|
||||
</button></td>
|
||||
<template v-if="count === 0">
|
||||
<td class="border border-slate-300 p-2" v-for="(key,j) in keyList" :key="j">
|
||||
{{map.get(key)}}
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td class="border border-slate-300 p-2" v-for="(key,j) in statsList" :key="j">
|
||||
{{map.get(key)}}
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-btn-style flex-auto overflow-auto">
|
||||
<h2 class="text-lg">stackTrace</h2>
|
||||
<div v-for="(stack, i) in stackTrace" class="mb-2" :key="i">{{stack}} </div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import ClassInput from '@/components/input/ClassInput.vue';
|
||||
import Tree from '@/components/show/Tree.vue';
|
||||
import permachine from '@/machines/perRequestMachine';
|
||||
import { fetchStore } from '@/stores/fetch';
|
||||
import { publicStore } from '@/stores/public';
|
||||
import { useInterpret } from '@xstate/vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
const pollResults = reactive<TreeNode[]>([])
|
||||
const gcMachine = useInterpret(permachine)
|
||||
const fetchS = fetchStore()
|
||||
const publicS = publicStore()
|
||||
const depth = ref(1)
|
||||
const setDepth = publicS.inputDialogFactory(
|
||||
depth,
|
||||
(raw) => {
|
||||
let valRaw = parseInt(raw)
|
||||
return Number.isNaN(valRaw) ? 1 : valRaw
|
||||
},
|
||||
(input) => input.value.toString(),
|
||||
)
|
||||
const getInstance = (data:{classItem:Item}) => {
|
||||
pollResults.length = 0
|
||||
fetchS.baseSubmit(gcMachine, {
|
||||
action: "exec",
|
||||
command: `vmtool --action getInstances --className ${data.classItem.value} -x ${depth.value}`
|
||||
}).then(
|
||||
res => {
|
||||
const result = (res as CommonRes).body.results[0]
|
||||
if (result.type === "vmtool") {
|
||||
let raw = result.value.split("\n")
|
||||
const stk: TreeNode[] = []
|
||||
|
||||
// Tree的构建
|
||||
raw.forEach(v => {
|
||||
let str = v.trim()
|
||||
let match = 0
|
||||
for (let s of str) {
|
||||
if (s === "[") {
|
||||
match++
|
||||
} else if (s === "]") {
|
||||
match--
|
||||
}
|
||||
}
|
||||
const root = {
|
||||
children: [],
|
||||
meta: str.substring(0, str.length - 1)
|
||||
} as TreeNode
|
||||
|
||||
if (match > 0) {
|
||||
stk.push(root)
|
||||
} else if (match === 0) {
|
||||
let cur = stk.pop()
|
||||
if (cur) {
|
||||
cur.children!.push(root)
|
||||
stk.push(cur)
|
||||
} else {
|
||||
stk.push(root)
|
||||
}
|
||||
|
||||
} else {
|
||||
/// 默认每行只会一个]
|
||||
//!可能会有bug
|
||||
let cur = stk.pop()!
|
||||
if (stk.length > 0) {
|
||||
let parent = stk.pop()!
|
||||
parent.children!.push(cur)
|
||||
stk.push(parent)
|
||||
} else {
|
||||
// 构建结束
|
||||
stk.push(cur)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(stk))
|
||||
})
|
||||
|
||||
pollResults.unshift(stk[0])
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<ClassInput :submit-f="getInstance" class="mb-4" >
|
||||
<template #others>
|
||||
<button class="ml-2 btn btn-outline btn-sm" @click="setDepth">depth:{{depth}}</button>
|
||||
</template>
|
||||
</ClassInput>
|
||||
<template v-if="pollResults.length > 0">
|
||||
<ul class=" pointer-events-auto mt-10">
|
||||
<template v-for="(result, i) in pollResults" :key="i">
|
||||
<Tree :root="result" class="mt-2">
|
||||
<template #meta="{ data }">
|
||||
<div class="bg-info p-1 mb-1 rounded-r rounded-br">
|
||||
{{data}}
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import * as path from "path";
|
||||
import { resolve } from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue({
|
||||
reactivityTransform: path.resolve(__dirname, "./ui"),
|
||||
})],
|
||||
// root:path.resolve(__dirname,"src"),
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./ui/src/")
|
||||
},
|
||||
},
|
||||
build: {
|
||||
emptyOutDir:true,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: path.resolve(__dirname, "index.html"),
|
||||
ui: path.resolve(__dirname, "ui/index.html"),
|
||||
},
|
||||
|
||||
output:{
|
||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||
entryFileNames:'static/js/[name]-[hash].js',
|
||||
assetFileNames:'static/[ext]/[name]-[hash].[ext]'
|
||||
}
|
||||
},
|
||||
},
|
||||
base: "/",
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://127.0.0.1:8563",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
+131
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>arthas-all</artifactId>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>web-ui</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>web-ui</name>
|
||||
<!-- FIXME change it to the project's website -->
|
||||
<!-- <url>http://www.example.com</url>-->
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<yarn.registry.url>https://registry.npmmirror.com/</yarn.registry.url>
|
||||
<yarn.download.url>http://npmmirror.com</yarn.download.url>
|
||||
<node.download.url>https://npmmirror.com/mirrors/node/</node.download.url>
|
||||
<node.version>v16.16.0</node.version>
|
||||
<yarn.version>v1.22.19</yarn.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<finalName>web-ui</finalName>
|
||||
|
||||
<!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<!-- Use the latest released version:
|
||||
https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ -->
|
||||
<version>1.12.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- optional: you don't really need execution ids, but it looks nice in your build log. -->
|
||||
<id>install node and yarn</id>
|
||||
<goals>
|
||||
<goal>install-node-and-yarn</goal>
|
||||
</goals>
|
||||
<!-- optional: default phase is "generate-resources" -->
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>set registry</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<arguments>config set registry ${yarn.registry.url}</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>yarn install</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- optional: The default argument is actually
|
||||
"install", so unless you need to run some other yarn command,
|
||||
you can remove this whole <configuration> section.
|
||||
-->
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>vite build</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>vite build</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<nodeVersion>${node.version}</nodeVersion>
|
||||
<yarnVersion>${yarn.version}</yarnVersion>
|
||||
|
||||
<!-- optional: where to download node from. Defaults to https://nodejs.org/dist/ -->
|
||||
<nodeDownloadRoot>${node.download.url}</nodeDownloadRoot>
|
||||
<!-- optional: where to download yarn from. Defaults to https://github.com/yarnpkg/yarn/releases/download/ -->
|
||||
<!-- <yarnDownloadRoot>${yarn.registry.url}</yarnDownloadRoot>-->
|
||||
<workingDirectory>arthasWebConsole</workingDirectory>
|
||||
|
||||
<installDirectory>target</installDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy dist</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>arthasWebConsole/dist</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<outputDirectory>${project.build.directory}/static</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- <plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin> -->
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Reference in New Issue
Block a user