添加服务器详细信息展示模块

This commit is contained in:
柏码の讲师 2023-12-02 17:23:34 +08:00
parent 9b66fb2473
commit 53cac30550
12 changed files with 534 additions and 31 deletions

View File

@ -2,6 +2,7 @@ package com.example.controller;
import com.example.entity.RestBean; import com.example.entity.RestBean;
import com.example.entity.vo.request.RenameClientVO; import com.example.entity.vo.request.RenameClientVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO; import com.example.entity.vo.response.ClientPreviewVO;
import com.example.service.ClientService; import com.example.service.ClientService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -27,4 +28,9 @@ public class MonitorController {
service.renameClient(vo); service.renameClient(vo);
return RestBean.success(); return RestBean.success();
} }
@GetMapping("/details")
public RestBean<ClientDetailsVO> clientDetails(int clientId) {
return RestBean.success(service.clientDetails(clientId));
}
} }

View File

@ -0,0 +1,18 @@
package com.example.entity.vo.response;
import lombok.Data;
@Data
public class ClientDetailsVO {
int id;
String name;
boolean online;
String location;
String ip;
String cpuName;
String osName;
String osVersion;
double memory;
int cpuCore;
double disk;
}

View File

@ -5,6 +5,7 @@ import com.example.entity.dto.Client;
import com.example.entity.vo.request.ClientDetailVO; import com.example.entity.vo.request.ClientDetailVO;
import com.example.entity.vo.request.RenameClientVO; import com.example.entity.vo.request.RenameClientVO;
import com.example.entity.vo.request.RuntimeDetailVO; import com.example.entity.vo.request.RuntimeDetailVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO; import com.example.entity.vo.response.ClientPreviewVO;
import java.util.List; import java.util.List;
@ -18,4 +19,5 @@ public interface ClientService extends IService<Client> {
void updateRuntimeDetail(RuntimeDetailVO vo, Client client); void updateRuntimeDetail(RuntimeDetailVO vo, Client client);
List<ClientPreviewVO> listClients(); List<ClientPreviewVO> listClients();
void renameClient(RenameClientVO vo); void renameClient(RenameClientVO vo);
ClientDetailsVO clientDetails(int clientId);
} }

View File

@ -7,6 +7,7 @@ import com.example.entity.dto.ClientDetail;
import com.example.entity.vo.request.ClientDetailVO; import com.example.entity.vo.request.ClientDetailVO;
import com.example.entity.vo.request.RenameClientVO; import com.example.entity.vo.request.RenameClientVO;
import com.example.entity.vo.request.RuntimeDetailVO; import com.example.entity.vo.request.RuntimeDetailVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO; import com.example.entity.vo.response.ClientPreviewVO;
import com.example.mapper.ClientDetailMapper; import com.example.mapper.ClientDetailMapper;
import com.example.mapper.ClientMapper; import com.example.mapper.ClientMapper;
@ -98,7 +99,7 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
ClientPreviewVO vo = client.asViewObject(ClientPreviewVO.class); ClientPreviewVO vo = client.asViewObject(ClientPreviewVO.class);
BeanUtils.copyProperties(detailMapper.selectById(vo.getId()), vo); BeanUtils.copyProperties(detailMapper.selectById(vo.getId()), vo);
RuntimeDetailVO runtime = lastRuntime.get(client.getId()); RuntimeDetailVO runtime = lastRuntime.get(client.getId());
if(runtime != null && System.currentTimeMillis() - runtime.getTimestamp() < 60 * 1000) { if(this.isOnline(runtime)) {
BeanUtils.copyProperties(runtime, vo); BeanUtils.copyProperties(runtime, vo);
vo.setOnline(true); vo.setOnline(true);
} }
@ -112,6 +113,18 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
this.initClientCache(); this.initClientCache();
} }
@Override
public ClientDetailsVO clientDetails(int clientId) {
ClientDetailsVO vo = this.getById(clientId).asViewObject(ClientDetailsVO.class);
BeanUtils.copyProperties(detailMapper.selectById(clientId), vo);
vo.setOnline(this.isOnline(lastRuntime.get(clientId)));
return vo;
}
private boolean isOnline(RuntimeDetailVO runtime) {
return runtime != null && System.currentTimeMillis() - runtime.getTimestamp() < 60 * 1000;
}
private void addClientCache(Client client) { private void addClientCache(Client client) {
clientIdCache.put(client.getId(), client); clientIdCache.put(client.getId(), client);
clientTokenCache.put(client.getToken(), client); clientTokenCache.put(client.getToken(), client);

View File

@ -18,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.2.3",
"less": "^4.2.0",
"unplugin-auto-import": "^0.15.2", "unplugin-auto-import": "^0.15.2",
"unplugin-vue-components": "^0.24.1", "unplugin-vue-components": "^0.24.1",
"vite": "^4.4.6" "vite": "^4.4.6"
@ -823,6 +824,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"dev": true,
"dependencies": {
"is-what": "^3.14.1"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz",
@ -956,6 +966,19 @@
} }
} }
}, },
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"optional": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.18.17", "version": "0.18.17",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.17.tgz", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.17.tgz",
@ -1113,6 +1136,13 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"optional": true
},
"node_modules/has": { "node_modules/has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
@ -1125,6 +1155,32 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"dev": true,
"optional": true,
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -1176,12 +1232,44 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"dev": true
},
"node_modules/jsonc-parser": { "node_modules/jsonc-parser": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true "dev": true
}, },
"node_modules/less": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz",
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
"dev": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=6"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/local-pkg": { "node_modules/local-pkg": {
"version": "0.4.3", "version": "0.4.3",
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz", "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz",
@ -1222,6 +1310,20 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"optional": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/memoize-one": { "node_modules/memoize-one": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
@ -1249,6 +1351,19 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"optional": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
@ -1309,6 +1424,34 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/needle": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz",
"integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==",
"dev": true,
"optional": true,
"dependencies": {
"debug": "^3.2.6",
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/needle/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"optional": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
@ -1323,6 +1466,15 @@
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
}, },
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/path-parse": { "node_modules/path-parse": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
@ -1349,6 +1501,16 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz",
@ -1378,6 +1540,13 @@
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
}, },
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"dev": true,
"optional": true
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -1445,12 +1614,46 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"optional": true
},
"node_modules/sax": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
"dev": true,
"optional": true
},
"node_modules/scule": { "node_modules/scule": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/scule/-/scule-1.0.0.tgz",
"integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==", "integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==",
"dev": true "dev": true
}, },
"node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
@ -1489,6 +1692,12 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true
},
"node_modules/ufo": { "node_modules/ufo": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.2.0.tgz",

View File

@ -18,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.2.3",
"less": "^4.2.0",
"unplugin-auto-import": "^0.15.2", "unplugin-auto-import": "^0.15.2",
"unplugin-vue-components": "^0.24.1", "unplugin-vue-components": "^0.24.1",
"vite": "^4.4.6" "vite": "^4.4.6"

View File

@ -0,0 +1,54 @@
.is-success {
.el-progress-bar__outer {
background-color: #18cb1822;
}
.el-progress-circle__track {
stroke: #18cb1822;
}
.el-progress-circle__path {
stroke: #18cb18 !important;
}
.el-progress-bar__inner {
background-color: #18cb18 !important;
}
}
.is-warring {
.el-progress-bar__outer {
background-color: #ffa04622;
}
.el-progress-circle__track {
stroke: #ffa04622;
}
.el-progress-circle__path {
stroke: #ffa046 !important;
}
.el-progress-bar__inner {
background-color: #ffa046 !important;
}
}
.is-exception {
.el-progress-bar__outer {
background-color: #ef4e4e22;
}
.el-progress-circle__track {
stroke: #ef4e4e22;
}
.el-progress-circle__path {
stroke: #ef4e4e !important;
}
.el-progress-bar__inner {
background-color: #ef4e4e !important;
}
}

View File

@ -0,0 +1,175 @@
<script setup>
import {reactive, watch} from "vue";
import {get} from "@/net";
import {useClipboard} from "@vueuse/core";
import {ElMessage} from "element-plus";
const props = defineProps({
id: Number
})
const details = reactive({
base: {},
runtime: {}
})
const { copy } = useClipboard()
const copyIp = () => {
copy(details.base.ip).then(() => ElMessage.success('成功复制IP地址到剪贴板'))
}
watch(() => props.id, value => {
details.base = {}
get(`/api/monitor/details?clientId=${value}`, data => Object.assign(details.base, data))
}, { immediate: true })
</script>
<template>
<div class="client-details"
v-loading="Object.keys(details.base).length === 0">
<div v-if="Object.keys(details.base).length">
<div class="title">
<i class="fa-solid fa-server"></i>
服务器信息
</div>
<el-divider style="margin: 10px 0"/>
<div class="details-list">
<div>
<span>服务器ID</span>
<span>{{details.base.id}}</span>
</div>
<div>
<span>服务器名称</span>
<span>{{details.base.name}}</span>
</div>
<div>
<span>运行状态</span>
<span>
<i style="color: #18cb18" class="fa-solid fa-circle-play" v-if="details.base.online"></i>
<i style="color: #18cb18" class="fa-solid fa-circle-stop" v-else></i>
{{details.base.online ? '运行中' : '离线'}}
</span>
</div>
<div>
<span>服务器节点</span>
<span :class="`flag-icon flag-icon-${details.base.location}`"></span>&nbsp;
<span>未命名节点</span>
</div>
<div>
<span>公网IP地址</span>
<span>
{{details.base.ip}}
<i class="fa-solid fa-copy interact-item" style="color: dodgerblue" @click.stop="copyIp"></i>
</span>
</div>
<div>
<span>处理器</span>
<span>{{details.base.cpuName}}</span>
</div>
<div>
<span>硬件配置信息</span>
<span>
<i class="fa-solid fa-microchip"></i>
<span style="margin-right: 10px">{{` ${details.base.cpuCore} CPU 核心数 /`}}</span>
<i class="fa-solid fa-memory"></i>
<span>{{` ${details.base.memory.toFixed(1)} GB 内存容量`}}</span>
</span>
</div>
<div>
<span>操作系统</span>
<span>{{`${details.base.osName} ${details.base.osVersion}`}}</span>
</div>
</div>
<div class="title" style="margin-top: 20px">
<i class="fa-solid fa-gauge-high"></i>
实时监控
</div>
<el-divider style="margin: 10px 0"/>
<div style="display: flex">
<el-progress type="dashboard" :width="100" :percentage="20" status="success">
<div style="font-size: 17px;font-weight: bold;color: initial">CPU</div>
<div style="font-size: 13px;color: grey;margin-top: 5px">20%</div>
</el-progress>
<el-progress style="margin-left: 20px"
type="dashboard" :width="100" :percentage="60" status="success">
<div style="font-size: 16px;font-weight: bold;color: initial">内存</div>
<div style="font-size: 13px;color: grey;margin-top: 5px">28.6 GB</div>
</el-progress>
<div style="flex: 1;margin-left: 30px;display: flex;flex-direction: column;height: 80px">
<div style="flex: 1;font-size: 14px">
<div>实时网络速度</div>
<div>
<i style="color: orange" class="fa-solid fa-arrow-up"></i>
<span>{{` 20KB/s`}}</span>
<el-divider direction="vertical"/>
<i style="color: dodgerblue" class="fa-solid fa-arrow-down"></i>
<span>{{` 0KB/s`}}</span>
</div>
</div>
<div>
<div style="font-size: 13px;display: flex;justify-content: space-between">
<div>
<i class="fa-solid fa-hard-drive"></i>
<span> 磁盘总容量</span>
</div>
<div>6.6 GB / 40.0 GB</div>
</div>
<el-progress type="line" status="success" :percentage="24" :show-text="false"/>
</div>
</div>
</div>
<el-divider style="margin: 10px 0"/>
</div>
</div>
</template>
<style scoped>
.client-details {
height: 100%;
padding: 20px;
.title {
color: dodgerblue;
font-size: 18px;
font-weight: bold;
}
.details-list {
font-size: 14px;
.interact-item {
transition: .3s;
&:hover {
cursor: pointer;
scale: 1.1;
opacity: 0.8;
}
}
& div {
margin-bottom: 10px;
& span:first-child {
color: grey;
font-size: 13px;
font-weight: normal;
width: 120px;
display: inline-block;
}
& span {
font-weight: bold;
}
}
}
}
span {
color: #5e5e5e;
}
.dark span{
color: #d9d9d9;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import {fitToRightByteUnit} from "@/tools"; import {fitToRightByteUnit, percentageToStatus} from "@/tools";
import {useClipboard} from "@vueuse/core"; import {useClipboard} from "@vueuse/core";
import {ElMessage, ElMessageBox} from "element-plus"; import {ElMessage, ElMessageBox} from "element-plus";
import {post} from "@/net"; import {post} from "@/net";
@ -28,7 +28,7 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
ElMessage.success('主机名称已更新') ElMessage.success('主机名称已更新')
props.update() props.update()
}) })
) ).catch(() => {})
</script> </script>
<template> <template>
@ -38,7 +38,7 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
<div class="title"> <div class="title">
<span :class="`flag-icon flag-icon-${data.location}`"></span> <span :class="`flag-icon flag-icon-${data.location}`"></span>
<span style="margin: 0 10px">{{ data.name }}</span> <span style="margin: 0 10px">{{ data.name }}</span>
<i @click="rename" class="fa-solid fa-pen-to-square interact-item"></i> <i @click.stop="rename" class="fa-solid fa-pen-to-square interact-item"></i>
</div> </div>
<div class="os"> <div class="os">
操作系统: {{`${data.osName} ${data.osVersion}`}} 操作系统: {{`${data.osName} ${data.osVersion}`}}
@ -56,7 +56,7 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
<el-divider style="margin: 10px 0"/> <el-divider style="margin: 10px 0"/>
<div class="network"> <div class="network">
<span style="margin-right: 10px">公网IP: {{data.ip}}</span> <span style="margin-right: 10px">公网IP: {{data.ip}}</span>
<i class="fa-solid fa-copy interact-item" style="color: dodgerblue" @click="copyIp"></i> <i class="fa-solid fa-copy interact-item" style="color: dodgerblue" @click.stop="copyIp"></i>
</div> </div>
<div class="cpu"> <div class="cpu">
<span style="margin-right: 10px">处理器: {{data.cpuName}}</span> <span style="margin-right: 10px">处理器: {{data.cpuName}}</span>
@ -70,12 +70,12 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
<div class="progress"> <div class="progress">
<span>{{ `CPU: ${(data.cpuUsage * 100).toFixed(1)} %` }}</span> <span>{{ `CPU: ${(data.cpuUsage * 100).toFixed(1)} %` }}</span>
<el-progress :percentage="data.cpuUsage * 100" :stroke-width="5" :show-text="false" <el-progress :percentage="data.cpuUsage * 100" :stroke-width="5" :show-text="false"
:status="data.cpuUsage * 100 > 80 ? 'exception' : 'success'"/> :status="percentageToStatus(data.cpuUsage * 100)"/>
</div> </div>
<div class="progress" style="margin-top: 7px"> <div class="progress" style="margin-top: 7px">
<span>内存: <b>{{ data.memoryUsage.toFixed(1) }}</b> GB</span> <span>内存: <b>{{ data.memoryUsage.toFixed(1) }}</b> GB</span>
<el-progress :percentage="data.memoryUsage/data.memory * 100" :stroke-width="5" :show-text="false" <el-progress :percentage="data.memoryUsage/data.memory * 100" :stroke-width="5" :show-text="false"
:status="data.memoryUsage/data.memory * 100 > 80 ? 'exception' : 'success'"/> :status="percentageToStatus(data.memoryUsage/data.memory * 100)"/>
</div> </div>
<div class="network-flow"> <div class="network-flow">
<div>网络流量</div> <div>网络流量</div>
@ -91,22 +91,6 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
</template> </template>
<style scoped> <style scoped>
:deep(.is-success .el-progress-bar__outer) {
background-color: #18cb1822;
}
:deep(.is-exception .el-progress-bar__outer) {
background-color: #ef4e4e22;
}
:deep(.is-success .el-progress-bar__inner) {
background-color: #18cb18;
}
:deep(.is-exception .el-progress-bar__inner) {
background-color: #ef4e4e;
}
.dark .instance-card { color: #d9d9d9 .dark .instance-card { color: #d9d9d9
} }
.instance-card { .instance-card {
@ -116,6 +100,12 @@ const rename = () => ElMessageBox.prompt('请输入新的服务器主机名称',
padding: 15px; padding: 15px;
box-sizing: border-box; box-sizing: border-box;
color: #6b6b6b; color: #6b6b6b;
transition: scale .3s;
&:hover {
cursor: pointer;
scale: 1.02;
}
.interact-item { .interact-item {
transition: .3s; transition: .3s;

View File

@ -5,6 +5,7 @@ import axios from "axios";
import 'flag-icon-css/css/flag-icons.min.css' import 'flag-icon-css/css/flag-icons.min.css'
import 'element-plus/theme-chalk/dark/css-vars.css' import 'element-plus/theme-chalk/dark/css-vars.css'
import '@/assets/css/element.less'
axios.defaults.baseURL = 'http://localhost:8080' axios.defaults.baseURL = 'http://localhost:8080'

View File

@ -1,16 +1,25 @@
function fitToRightByteUnit(value, unit) { function fitToRightByteUnit(value, unit) {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
let index = units.indexOf(unit) let index = units.indexOf(unit)
while ((value < 0 || value >= 1024) && (index >= 0 || index < units.length)) { while (((value < 1 && value !== 0) || value >= 1024) && (index >= 0 || index < units.length)) {
if(value < 0) { if(value >= 1024) {
value = value * 1024
index = index - 1
} else {
value = value / 1024 value = value / 1024
index = index + 1 index = index + 1
} else {
value = value * 1024
index = index - 1
} }
} }
return `${parseInt(value)} ${units[index]}` return `${parseInt(value)} ${units[index]}`
} }
export { fitToRightByteUnit } function percentageToStatus(percentage) {
if(percentage < 50)
return 'success'
else if(percentage < 80)
return 'warring'
else
return 'exception'
}
export { fitToRightByteUnit, percentageToStatus }

View File

@ -1,10 +1,20 @@
<script setup> <script setup>
import PreviewCard from "@/component/PreviewCard.vue"; import PreviewCard from "@/component/PreviewCard.vue";
import {get} from "@/net"; import {get} from "@/net";
import {ref} from "vue"; import {reactive, ref} from "vue";
import ClientDetails from "@/component/ClientDetails.vue";
const list = ref([]) const list = ref([])
const detail = reactive({
show: false,
id: -1
})
const displayClientDetails = (id) => {
detail.show = true
detail.id = id
}
const updateList = () => get('/api/monitor/list', data => list.value = data) const updateList = () => get('/api/monitor/list', data => list.value = data)
setInterval(updateList, 10000) setInterval(updateList, 10000)
updateList() updateList()
@ -16,12 +26,27 @@ updateList()
<div class="description">在这里管理所有已经注册的主机实例实时监控主机运行状态快速进行管理和操作</div> <div class="description">在这里管理所有已经注册的主机实例实时监控主机运行状态快速进行管理和操作</div>
<el-divider style="margin: 10px 0"/> <el-divider style="margin: 10px 0"/>
<div class="card-list"> <div class="card-list">
<preview-card :update="updateList" :data="item" v-for="item in list"/> <preview-card :update="updateList" :data="item" v-for="item in list"
@click="displayClientDetails(item.id)"/>
</div> </div>
<el-drawer size="520" :show-close="false" v-model="detail.show"
:with-header="false" v-if="list.length">
<client-details :id="detail.id"/>
</el-drawer>
</div> </div>
</template> </template>
<style scoped> <style scoped>
:deep(.el-drawer) {
margin: 10px;
height: calc(100% - 20px);
border-radius: 10px;
}
:deep(.el-drawer__body) {
padding: 0;
}
.manage-main { .manage-main {
margin: 0 50px; margin: 0 50px;