mirror of
https://github.com/snltty/linker.git
synced 2025-12-19 18:06:47 +08:00
init
This commit is contained in:
Submodule cmonitor.web deleted from b5a3da842b
23
cmonitor.web/.gitignore
vendored
Normal file
23
cmonitor.web/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
19
cmonitor.web/README.md
Normal file
19
cmonitor.web/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# cmonitor.web
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
cmonitor.web/babel.config.js
Normal file
5
cmonitor.web/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
19
cmonitor.web/jsconfig.json
Normal file
19
cmonitor.web/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
18146
cmonitor.web/package-lock.json
generated
Normal file
18146
cmonitor.web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
cmonitor.web/package.json
Normal file
30
cmonitor.web/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "cmonitor.web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"stylus": "^0.55.0",
|
||||
"stylus-loader": "^6.1.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
BIN
cmonitor.web/public/favicon.ico
Normal file
BIN
cmonitor.web/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
23
cmonitor.web/public/index.html
Normal file
23
cmonitor.web/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>
|
||||
<%= htmlWebpackPlugin.options.title %>
|
||||
</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
cmonitor.web/src/App.vue
Normal file
5
cmonitor.web/src/App.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style lang="stylus"></style>
|
||||
22
cmonitor.web/src/apis/active.js
Normal file
22
cmonitor.web/src/apis/active.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const getActiveTimes = (name) => {
|
||||
return sendWebsocketMsg('active/get', name);
|
||||
}
|
||||
export const activeTimesClear = (name) => {
|
||||
return sendWebsocketMsg('active/clear', name);
|
||||
}
|
||||
export const activeDisallow = (names, filenames) => {
|
||||
return sendWebsocketMsg('active/disallow', {
|
||||
names, filenames
|
||||
});
|
||||
}
|
||||
|
||||
export const activeAddExe = (data) => {
|
||||
return sendWebsocketMsg('active/add', data);
|
||||
}
|
||||
export const activeDelExe = (username, id) => {
|
||||
return sendWebsocketMsg('active/del', {
|
||||
username, id
|
||||
});
|
||||
}
|
||||
7
cmonitor.web/src/apis/command.js
Normal file
7
cmonitor.web/src/apis/command.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const exec = (names, commands) => {
|
||||
return sendWebsocketMsg('command/exec', {
|
||||
names, commands
|
||||
});
|
||||
}
|
||||
36
cmonitor.web/src/apis/hijack.js
Normal file
36
cmonitor.web/src/apis/hijack.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const getRules = () => {
|
||||
return sendWebsocketMsg('hijack/info');
|
||||
}
|
||||
export const addName = (data) => {
|
||||
return sendWebsocketMsg('hijack/addName', data);
|
||||
}
|
||||
|
||||
export const addProcessGroup = (data) => {
|
||||
return sendWebsocketMsg('hijack/addProcessGroup', data);
|
||||
}
|
||||
export const deleteProcessGroup = (data) => {
|
||||
return sendWebsocketMsg('hijack/deleteProcessGroup', data);
|
||||
}
|
||||
export const addProcess = (data) => {
|
||||
return sendWebsocketMsg('hijack/addProcess', data);
|
||||
}
|
||||
export const deleteProcess = (data) => {
|
||||
return sendWebsocketMsg('hijack/deleteProcess', data);
|
||||
}
|
||||
|
||||
export const addRule = (data) => {
|
||||
return sendWebsocketMsg('hijack/AddRule', data);
|
||||
}
|
||||
export const deleteRule = (data) => {
|
||||
return sendWebsocketMsg('hijack/deleteRule', data);
|
||||
}
|
||||
|
||||
export const updateDevices = (data) => {
|
||||
return sendWebsocketMsg('hijack/UpdateDevices', data);
|
||||
}
|
||||
|
||||
export const setRules = (data) => {
|
||||
return sendWebsocketMsg('hijack/setRules', data);
|
||||
}
|
||||
8
cmonitor.web/src/apis/light.js
Normal file
8
cmonitor.web/src/apis/light.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
|
||||
export const setLight = (names, value) => {
|
||||
return sendWebsocketMsg('light/update', {
|
||||
names, value
|
||||
});
|
||||
}
|
||||
8
cmonitor.web/src/apis/llock.js
Normal file
8
cmonitor.web/src/apis/llock.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
|
||||
export const llockUpdate = (names, value) => {
|
||||
return sendWebsocketMsg('llock/update', {
|
||||
names, value
|
||||
});
|
||||
}
|
||||
8
cmonitor.web/src/apis/report.js
Normal file
8
cmonitor.web/src/apis/report.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const reportUpdate = (names) => {
|
||||
return sendWebsocketMsg('report/update', names);
|
||||
}
|
||||
export const reportPing = (names) => {
|
||||
return sendWebsocketMsg('report/ping', names);
|
||||
}
|
||||
143
cmonitor.web/src/apis/request.js
Normal file
143
cmonitor.web/src/apis/request.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
let requestId = 0, ws = null, wsUrl = '';
|
||||
//请求缓存,等待回调
|
||||
const requests = {};
|
||||
const queues = [];
|
||||
export const websocketState = { connected: false };
|
||||
|
||||
const sendQueueMsg = () => {
|
||||
if (queues.length > 0 && websocketState.connected) {
|
||||
ws.send(queues.shift());
|
||||
}
|
||||
setTimeout(sendQueueMsg, 1000 / 60);
|
||||
}
|
||||
sendQueueMsg();
|
||||
|
||||
//发布订阅
|
||||
export const pushListener = {
|
||||
subs: {
|
||||
},
|
||||
add: function (type, callback) {
|
||||
if (typeof callback == 'function') {
|
||||
if (!this.subs[type]) {
|
||||
this.subs[type] = [];
|
||||
}
|
||||
this.subs[type].push(callback);
|
||||
}
|
||||
},
|
||||
remove(type, callback) {
|
||||
let funcs = this.subs[type] || [];
|
||||
for (let i = funcs.length - 1; i >= 0; i--) {
|
||||
if (funcs[i] == callback) {
|
||||
funcs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
push(type, data) {
|
||||
let funcs = this.subs[type] || [];
|
||||
for (let i = funcs.length - 1; i >= 0; i--) {
|
||||
funcs[i](data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//消息处理
|
||||
const onWebsocketOpen = () => {
|
||||
websocketState.connected = true;
|
||||
pushListener.push(websocketStateChangeKey, websocketState.connected);
|
||||
}
|
||||
const onWebsocketClose = (e) => {
|
||||
websocketState.connected = false;
|
||||
pushListener.push(websocketStateChangeKey, websocketState.connected);
|
||||
initWebsocket();
|
||||
}
|
||||
export const onWebsocketMsg = (msg) => {
|
||||
if (typeof msg.data != 'string') {
|
||||
msg.data.arrayBuffer().then((res) => {
|
||||
const length = new DataView(res).getInt8();
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(msg.data.slice(4, 4 + length), 'utf8');
|
||||
reader.onload = () => {
|
||||
let json = JSON.parse(reader.result);
|
||||
json.Content = {
|
||||
Name: json.Content,
|
||||
Img: msg.data.slice(4 + length, msg.data.length)
|
||||
};
|
||||
pushMessage(json);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
let json = JSON.parse(msg.data);
|
||||
pushMessage(json);
|
||||
}
|
||||
const pushMessage = (json) => {
|
||||
let callback = requests[json.RequestId];
|
||||
if (callback) {
|
||||
if (json.Code == 0) {
|
||||
callback.resolve(json.Content);
|
||||
} else if (json.Code == 1) {
|
||||
callback.reject(json.Content);
|
||||
}
|
||||
else if (json.Code == 255) {
|
||||
callback.reject(json.Content);
|
||||
if (!callback.errHandle) {
|
||||
ElMessage.error(`${callback.path}:${json.Content}`);
|
||||
}
|
||||
} else {
|
||||
pushListener.push(json.Path, json.Content);
|
||||
}
|
||||
delete requests[json.RequestId];
|
||||
} else {
|
||||
pushListener.push(json.Path, json.Content);
|
||||
}
|
||||
}
|
||||
|
||||
export const initWebsocket = (url = wsUrl) => {
|
||||
if (ws != null) {
|
||||
ws.close();
|
||||
}
|
||||
wsUrl = url;
|
||||
ws = new WebSocket(wsUrl);
|
||||
ws.onopen = onWebsocketOpen;
|
||||
ws.onclose = onWebsocketClose
|
||||
ws.onmessage = onWebsocketMsg
|
||||
}
|
||||
|
||||
|
||||
//发送消息
|
||||
export const sendWebsocketMsg = (path, msg = {}, errHandle = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let id = ++requestId;
|
||||
try {
|
||||
requests[id] = { resolve, reject, errHandle, path };
|
||||
let str = JSON.stringify({
|
||||
Path: path,
|
||||
RequestId: id,
|
||||
Content: typeof msg == 'string' ? msg : JSON.stringify(msg)
|
||||
});
|
||||
if (websocketState.connected) {
|
||||
ws.send(str);
|
||||
} else {
|
||||
queues.push(str);
|
||||
}
|
||||
} catch (e) {
|
||||
reject('网络错误~');
|
||||
ElMessage.error('网络错误~');
|
||||
delete requests[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const websocketStateChangeKey = Symbol();
|
||||
export const subWebsocketState = (callback) => {
|
||||
pushListener.add(websocketStateChangeKey, callback);
|
||||
}
|
||||
export const subNotifyMsg = (path, callback) => {
|
||||
pushListener.add(path, callback);
|
||||
}
|
||||
export const unsubNotifyMsg = (path, callback) => {
|
||||
pushListener.remove(path, callback);
|
||||
}
|
||||
5
cmonitor.web/src/apis/screen.js
Normal file
5
cmonitor.web/src/apis/screen.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const screenUpdate = (names) => {
|
||||
return sendWebsocketMsg('screen/update', names);
|
||||
}
|
||||
11
cmonitor.web/src/apis/signin.js
Normal file
11
cmonitor.web/src/apis/signin.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const getList = () => {
|
||||
return sendWebsocketMsg('signin/list');
|
||||
}
|
||||
export const getConfig = () => {
|
||||
return sendWebsocketMsg('signin/config');
|
||||
}
|
||||
export const delDevice = (name) => {
|
||||
return sendWebsocketMsg('signin/del', name);
|
||||
}
|
||||
8
cmonitor.web/src/apis/times.js
Normal file
8
cmonitor.web/src/apis/times.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const activeUpdate = (name) => {
|
||||
return sendWebsocketMsg('active/update', name);
|
||||
}
|
||||
export const activeClear = (name) => {
|
||||
return sendWebsocketMsg('active/clear', name);
|
||||
}
|
||||
8
cmonitor.web/src/apis/usb.js
Normal file
8
cmonitor.web/src/apis/usb.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
|
||||
export const usbUpdate = (names, value) => {
|
||||
return sendWebsocketMsg('usb/update', {
|
||||
names, value
|
||||
});
|
||||
}
|
||||
13
cmonitor.web/src/apis/volume.js
Normal file
13
cmonitor.web/src/apis/volume.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
|
||||
export const setVolume = (names, value) => {
|
||||
return sendWebsocketMsg('volume/update', {
|
||||
names, value
|
||||
});
|
||||
}
|
||||
export const setVolumeMute = (names, value) => {
|
||||
return sendWebsocketMsg('volume/mute', {
|
||||
names, value
|
||||
});
|
||||
}
|
||||
7
cmonitor.web/src/apis/wallpaper.js
Normal file
7
cmonitor.web/src/apis/wallpaper.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sendWebsocketMsg } from './request'
|
||||
|
||||
export const wallpaperUpdate = (names, value, url = '') => {
|
||||
return sendWebsocketMsg('wallpaper/update', {
|
||||
names, value, url
|
||||
});
|
||||
}
|
||||
BIN
cmonitor.web/src/assets/bg.webp
Normal file
BIN
cmonitor.web/src/assets/bg.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
cmonitor.web/src/assets/logo.png
Normal file
BIN
cmonitor.web/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
339
cmonitor.web/src/assets/style.css
Normal file
339
cmonitor.web/src/assets/style.css
Normal file
@@ -0,0 +1,339 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #6f9ccd;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flex;
|
||||
display: -o-flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.t-c {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.t-r {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.t-l {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
|
||||
.m-r-1 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
/* background-color: #282c34; */
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
span.split {
|
||||
width: 0.6rem;
|
||||
}
|
||||
|
||||
span.split-pad {
|
||||
padding: 0 0.3rem;
|
||||
}
|
||||
|
||||
span.split-pad10 {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.scrollbar,
|
||||
.scrollbar-4,
|
||||
.scrollbar-10 {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-4::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.scrollbar-4::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-10::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.scrollbar-10::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar {
|
||||
background: #f5f5f5
|
||||
}
|
||||
|
||||
.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar-thumb {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.el-collapse-item__header {
|
||||
background-color: #fafafa !important;
|
||||
border-left: 1px solid #EBEEF5;
|
||||
border-right: 1px solid #EBEEF5;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding: 1rem;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.el-input.w-search .el-input__inner,
|
||||
.el-input.w-search,
|
||||
.el-select.w-search {
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.el-form-item.w-search .el-form-item__label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.table-search .el-form--inline .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item a {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.el-input__inner:focus {
|
||||
border-color: var(--main-color);
|
||||
}
|
||||
|
||||
.el-date-editor.el-input.w-auto,
|
||||
.el-date-editor.el-input__inner.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.el-table .active-row {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-table .table-green-row {
|
||||
background: rgba(0, 255, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-table .table-red-row {
|
||||
background: rgba(255, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-table .table-green-row td {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-table .table-red-row td {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-date-editor.el-input,
|
||||
.el-date-editor.el-input__inner {
|
||||
width: auto
|
||||
}
|
||||
|
||||
.el-table .active-row td {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-table--border th {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-table--border,
|
||||
.el-table--group,
|
||||
.el-table-filter,
|
||||
.el-table td,
|
||||
.el-table th.is-leaf {
|
||||
border-color: var(--main-border-color);
|
||||
}
|
||||
|
||||
.el-pagination.is-background .el-pager li:not(.disabled).active {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
.el-pagination.is-background .el-pager li:not(.disabled):hover {
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.el-pagination .btn-next .el-icon,
|
||||
.el-pagination .btn-prev .el-icon {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
max-width: 96%;
|
||||
}
|
||||
|
||||
.el-dialog__body .el-form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-input-group__append,
|
||||
.el-input-group__prepend {
|
||||
padding: 0 4px !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.el-checkbox__label .el-icon {
|
||||
vertical-align: middle;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
/* .el-input__inner:read-only {
|
||||
background-color: #fafafa;
|
||||
} */
|
||||
|
||||
.el-color-picker {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-color-picker__trigger {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.el-color-picker__color {
|
||||
border: 0 !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.el-color-picker__color-inner {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
min-width: 10rem !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
||||
.forward-wrap .el-table--small.el-table .el-table__expanded-cell[class*=cell] {
|
||||
padding: 20px 50px 20px 50px;
|
||||
}
|
||||
|
||||
|
||||
h3.title {
|
||||
font-size: 1.6rem;
|
||||
padding-bottom: .6rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item {
|
||||
padding-right: 2rem !important;
|
||||
}
|
||||
|
||||
.el-form-item--default {
|
||||
--font-size: 13px !important;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-dialog--center .el-dialog__body {
|
||||
padding-top: 1rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
45
cmonitor.web/src/main.js
Normal file
45
cmonitor.web/src/main.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
import './assets/style.css'
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/display.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
|
||||
import {
|
||||
ChromeFilled, Promotion, Grid, ArrowDown, Upload, Download, EditPen, Delete, Refresh, BellFilled, Microphone
|
||||
, Position, Message, Bell, Mute, SwitchButton, Lock, DataLine, CirclePlus, QuestionFilled, Monitor, Sunny, Warning, Umbrella
|
||||
} from '@element-plus/icons-vue'
|
||||
app.component(ChromeFilled.name, ChromeFilled);
|
||||
app.component(Promotion.name, Promotion);
|
||||
app.component(Grid.name, Grid);
|
||||
app.component(ArrowDown.name, ArrowDown);
|
||||
app.component(Upload.name, Upload);
|
||||
app.component(Download.name, Download);
|
||||
app.component(EditPen.name, EditPen);
|
||||
app.component(Delete.name, Delete);
|
||||
app.component(Refresh.name, Refresh);
|
||||
app.component(BellFilled.name, BellFilled);
|
||||
app.component(Microphone.name, Microphone);
|
||||
|
||||
app.component(Position.name, Position);
|
||||
app.component(Message.name, Message);
|
||||
app.component(Bell.name, Bell);
|
||||
app.component(Mute.name, Mute);
|
||||
app.component(SwitchButton.name, SwitchButton);
|
||||
app.component(Lock.name, Lock);
|
||||
app.component(DataLine.name, DataLine);
|
||||
app.component(CirclePlus.name, CirclePlus);
|
||||
app.component(QuestionFilled.name, QuestionFilled);
|
||||
app.component(Monitor.name, Monitor);
|
||||
app.component(Sunny.name, Sunny);
|
||||
app.component(Warning.name, Warning);
|
||||
app.component(Umbrella.name, Umbrella);
|
||||
|
||||
|
||||
app.use(ElementPlus, { size: 'default' }).use(router).mount('#app');
|
||||
14
cmonitor.web/src/router/index.js
Normal file
14
cmonitor.web/src/router/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../views/Index.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
export default router
|
||||
111
cmonitor.web/src/views/Head.vue
Normal file
111
cmonitor.web/src/views/Head.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="选择角色" destroy-on-close v-model="showSelectUsername" center :show-close="false" :close-on-click-modal="false" align-center width="70%">
|
||||
<div class="username-wrap t-c">
|
||||
<el-select filterable allow-create default-first-option v-model="state.username" @change="handleChange" placeholder="选择角色" size="large">
|
||||
<el-option v-for="item in state.usernames" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleUsername">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog title="管理端口" destroy-on-close v-model="showPort" center :show-close="false" :close-on-click-modal="false" align-center width="70%">
|
||||
<div class="port-wrap t-c">
|
||||
<el-input v-model="state.port" style="width:auto"></el-input>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleConnect">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, onMounted, reactive, watch } from 'vue';
|
||||
import { initWebsocket } from '../apis/request'
|
||||
import { getRules, addName } from '../apis/hijack'
|
||||
import { useRoute } from 'vue-router';
|
||||
import { injectGlobalData } from './provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const route = useRoute();
|
||||
const port = +(route.query.api || localStorage.getItem('port') || 1801);
|
||||
localStorage.setItem('port', port);
|
||||
|
||||
globalData.value.username = globalData.value.username || localStorage.getItem('username') || '';
|
||||
const state = reactive({
|
||||
port: port,
|
||||
usernames: [],
|
||||
username: globalData.value.username,
|
||||
showPort: false
|
||||
});
|
||||
const showSelectUsername = computed(() => !!!globalData.value.username && globalData.value.connected);
|
||||
const showPort = computed(() => globalData.value.connected == false && state.showPort);
|
||||
|
||||
watch(() => globalData.value.updateFlag, () => {
|
||||
_getRules();
|
||||
});
|
||||
|
||||
const _getRules = () => {
|
||||
getRules().then((res) => {
|
||||
globalData.value.usernames = res;
|
||||
state.usernames = Object.keys(res);
|
||||
}).catch(() => { });
|
||||
}
|
||||
onMounted(() => {
|
||||
handleUsername();
|
||||
handleConnect();
|
||||
_getRules();
|
||||
|
||||
setTimeout(() => {
|
||||
state.showPort = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const handleConnect = () => {
|
||||
initWebsocket(`ws://${window.location.hostname}:${state.port}`);
|
||||
localStorage.setItem('port', state.port);
|
||||
}
|
||||
const handleUsername = () => {
|
||||
globalData.value.username = state.username || '';
|
||||
localStorage.setItem('username', globalData.value.username);
|
||||
document.title = `班长-${globalData.value.username}`
|
||||
}
|
||||
const handleChange = (value) => {
|
||||
addName(value).then(() => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}).catch(() => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
state, showSelectUsername, showPort, handleUsername, handleConnect, handleChange
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.head-wrap {
|
||||
text-align: center;
|
||||
padding: 0.5rem 0;
|
||||
line-height: 4rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f0f0f0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
z-index: 999;
|
||||
position: relative;
|
||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
img {
|
||||
height: 4rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
</style>
|
||||
59
cmonitor.web/src/views/Index.vue
Normal file
59
cmonitor.web/src/views/Index.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="main-wrap flex flex-column flex-nowrap">
|
||||
<div class="head">
|
||||
|
||||
<Head></Head>
|
||||
</div>
|
||||
<div class="body flex-1 scrollbar" v-if="showList">
|
||||
<Device></Device>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Head from './Head.vue'
|
||||
import Device from './device/Index.vue'
|
||||
import { subWebsocketState } from '../apis/request'
|
||||
import { computed } from 'vue'
|
||||
import { provideGlobalData } from './provide'
|
||||
export default {
|
||||
components: { Head, Device },
|
||||
setup() {
|
||||
|
||||
const globalData = provideGlobalData();
|
||||
subWebsocketState((state) => {
|
||||
globalData.value.connected = state;
|
||||
})
|
||||
const showList = computed(() => !!globalData.value.username && globalData.value.connected);
|
||||
|
||||
return {
|
||||
showList
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@media (min-width: 768px) {
|
||||
.main-wrap {
|
||||
border: 2px solid #d0d7de;
|
||||
height: 90% !important;
|
||||
width: 390px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
|
||||
.body {
|
||||
position: relative;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
cmonitor.web/src/views/device/Index.vue
Normal file
93
cmonitor.web/src/views/device/Index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="device-list-wrap absolute flex flex-column" id="device-list-wrap">
|
||||
<div class="items flex-1 relative scrollbar">
|
||||
<Items></Items>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<div class="foot-options">
|
||||
<FootOptions></FootOptions>
|
||||
</div>
|
||||
<div class="foot-menu">
|
||||
<FootMenu></FootMenu>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(item,index) in indexModules" :key="index">
|
||||
<component :is="item"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FootMenu from './wraps/FootMenu.vue'
|
||||
import FootOptions from './wraps/FootOptions.vue'
|
||||
import Items from './wraps/Items.vue'
|
||||
import { providePluginState } from './provide'
|
||||
import { provide, ref, watch } from 'vue'
|
||||
export default {
|
||||
components: { Items, FootMenu, FootOptions },
|
||||
setup() {
|
||||
|
||||
const files = require.context('./plugins/', true, /index\.js/);
|
||||
const pluginSettings = files.keys().map(c => files(c).default);
|
||||
const pluginState = pluginSettings.reduce((data, item, index) => {
|
||||
if (item.state) {
|
||||
data = Object.assign(data, item.state);
|
||||
}
|
||||
return data;
|
||||
}, {});
|
||||
const state = providePluginState(pluginState);
|
||||
|
||||
const indexFiles = require.context('./plugins/', true, /Index\.vue/);
|
||||
const indexModules = indexFiles.keys().map(c => indexFiles(c).default);
|
||||
|
||||
return {
|
||||
indexModules
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.device-list-wrap {
|
||||
.head {
|
||||
padding: 2rem 1rem 1rem 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f0f0f0;
|
||||
z-index: 999;
|
||||
position: relative;
|
||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.foot {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 4px;
|
||||
background-color: rgba(186, 217, 255, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
padding: 1rem;
|
||||
transform-style: preserve-3d;
|
||||
perspective: 600px;
|
||||
background-color: #333;
|
||||
background-image: url('../../assets/bg.webp');
|
||||
background-size: cover;
|
||||
padding-bottom: 13rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
cmonitor.web/src/views/device/boxs/CheckBoxWrap.vue
Normal file
108
cmonitor.web/src/views/device/boxs/CheckBoxWrap.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="checkbox-wrap absolute flex flex-column">
|
||||
<div class="head flex">
|
||||
<span>
|
||||
<el-checkbox :indeterminate="state.isIndeterminate" v-model="state.checkAll" @change="handleCheckAllChange" :label="state.title" size="large" />
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<div class="body flex-1 scrollbar">
|
||||
<el-checkbox-group v-model="state.checkList" @change="handleChange">
|
||||
<ul>
|
||||
<template v-for="(item,index) in state.data" :key="index">
|
||||
<li class="flex">
|
||||
<div class="flex-1">
|
||||
<el-checkbox :label="item[state.label]" size="large">
|
||||
<slot name="name" :item="item">
|
||||
{{item[state.text]}}
|
||||
</slot>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<slot name="oper" :item="item"></slot>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, onMounted, reactive, watch } from 'vue'
|
||||
export default {
|
||||
props: ['title', 'items', 'data', 'label', 'text'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const state = reactive({
|
||||
title: props.title,
|
||||
label: props.label,
|
||||
text: props.text || props.label,
|
||||
data: computed(() => props.data),
|
||||
checkList: props.items.map(c => c[props.label]),
|
||||
checkAll: false,
|
||||
isIndeterminate: false
|
||||
});
|
||||
watch(() => props.items, () => {
|
||||
state.checkList = props.items.map(c => c[props.label]);
|
||||
updateCheckAll(state.checkList);
|
||||
})
|
||||
|
||||
const handleCheckAllChange = (value) => {
|
||||
if (value) {
|
||||
state.checkList = state.data.map(c => c[state.label]);
|
||||
} else {
|
||||
state.checkList = [];
|
||||
}
|
||||
updateCheckAll(state.checkList);
|
||||
}
|
||||
const handleChange = (values) => {
|
||||
updateCheckAll(values);
|
||||
}
|
||||
const updateCheckAll = (values) => {
|
||||
const checkedCount = values.length;
|
||||
state.isIndeterminate = checkedCount > 0 && checkedCount < state.data.length;
|
||||
state.checkAll = checkedCount > 0 && checkedCount == state.data.length;
|
||||
}
|
||||
onMounted(() => {
|
||||
updateCheckAll(state.checkList);
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
return state.checkList;
|
||||
}
|
||||
|
||||
|
||||
return { state, handleCheckAllChange, handleChange, getData }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.checkbox-wrap {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.head {
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 4rem;
|
||||
padding: 0 1rem;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.body {
|
||||
ul {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0 1rem;
|
||||
|
||||
.el-checkbox {
|
||||
width: 100%;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
76
cmonitor.web/src/views/device/boxs/PrevBoxWrap.vue
Normal file
76
cmonitor.web/src/views/device/boxs/PrevBoxWrap.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="checkbox-wrap absolute flex flex-column">
|
||||
<div class="head flex">
|
||||
<span>
|
||||
{{state.title}}
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
</div>
|
||||
<div class="body flex-1 scrollbar">
|
||||
<slot name="wrap">
|
||||
<ul>
|
||||
<template v-for="(item,index) in state.data" :key="index">
|
||||
<li class="flex">
|
||||
<slot :item="item">
|
||||
<div class="default" @click="handleClick(item)">
|
||||
{{item}}
|
||||
</div>
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, reactive } from 'vue'
|
||||
export default {
|
||||
props: ['title', 'data'],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
title: props.title,
|
||||
data: computed(() => props.data),
|
||||
});
|
||||
const handleClick = (item) => {
|
||||
emit('prev', item);
|
||||
}
|
||||
return { state, handleClick }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.checkbox-wrap {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.head {
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 4rem;
|
||||
padding: 0 1rem;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.body {
|
||||
ul {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
li {
|
||||
.el-checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&>div.default {
|
||||
padding: 0.6rem 1rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
147
cmonitor.web/src/views/device/plugins/active/ActiveTimes.vue
Normal file
147
cmonitor.web/src/views/device/plugins/active/ActiveTimes.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="窗口使用时间统计" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="wrap flex flex-column">
|
||||
<h4>{{state.startTime}} - 至今({{(state.totalTime/1000).toFixed(2)}}s)</h4>
|
||||
<div class="inner flex-1 scrollbar">
|
||||
<ul>
|
||||
<template v-for="(item,index) in state.list" :key="index">
|
||||
<li>
|
||||
<dl>
|
||||
<dt>{{item.Desc}} <el-button @click="showTitles(item)" size="small">{{item.titleLength}}</el-button></dt>
|
||||
<dd>
|
||||
<el-progress :percentage="(item.Time/state.totalTime*100).toFixed(2)" />
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleCancel">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog class="options" title="详细标题" destroy-on-close v-model="state.showTitles" center align-center width="94%">
|
||||
<div class="wrap flex flex-column">
|
||||
<h5>{{state.currentTime/1000}}s</h5>
|
||||
<div class="inner flex-1 scrollbar">
|
||||
<ul>
|
||||
<template v-for="(item,index) in state.currentTitles" :key="index">
|
||||
<li>
|
||||
<dl>
|
||||
<dt>{{item.t}}</dt>
|
||||
<dd>
|
||||
<el-progress :percentage="(item.v/state.currentTime*100).toFixed(2)" />
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleCancel">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { onMounted, watch } from '@vue/runtime-core';
|
||||
import { getActiveTimes } from '../../../../apis/active'
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: {},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
loading: false,
|
||||
startTime: new Date(),
|
||||
totalTime: 1,
|
||||
list: [],
|
||||
showTitles: false,
|
||||
currentTitles: [],
|
||||
currentTime: 1,
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
const getData = () => {
|
||||
getActiveTimes(pluginState.value.activeWindow.items[0].MachineName).then((res) => {
|
||||
state.startTime = res.StartTime;
|
||||
state.totalTime = res.List.reduce((val, item, index) => {
|
||||
item.titleLength = Object.keys(item.Titles).length;
|
||||
val += item.Time;
|
||||
return val;
|
||||
}, 0);
|
||||
state.list = res.List.sort((a, b) => b.Time - a.Time);
|
||||
}).catch((e) => {
|
||||
})
|
||||
}
|
||||
const showTitles = (item) => {
|
||||
state.showTitles = true;
|
||||
let res = [];
|
||||
let time = 0;
|
||||
for (let j in item.Titles) {
|
||||
res.push({
|
||||
t: j,
|
||||
v: item.Titles[j]
|
||||
});
|
||||
time += item.Titles[j];
|
||||
}
|
||||
state.currentTitles = res.sort((a, b) => b.v - a.v);
|
||||
state.currentTime = time;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
return {
|
||||
state, handleCancel, showTitles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.wrap {
|
||||
height: 60vh;
|
||||
|
||||
.inner {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 1rem 0.6rem 1rem 1rem;
|
||||
|
||||
li {
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.6rem;
|
||||
margin-bottom: 0.6rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
dt {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
dd {
|
||||
.time {
|
||||
height: 1rem;
|
||||
background-color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
cmonitor.web/src/views/device/plugins/active/BtnLeft.vue
Normal file
27
cmonitor.web/src/views/device/plugins/active/BtnLeft.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleTimes">
|
||||
<el-icon>
|
||||
<DataLine />
|
||||
</el-icon>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleTimes = () => {
|
||||
pluginState.value.activeWindow.items = [props.data];
|
||||
pluginState.value.activeWindow.showTimes = true;
|
||||
}
|
||||
return {
|
||||
handleTimes
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
147
cmonitor.web/src/views/device/plugins/active/ChooseDig.vue
Normal file
147
cmonitor.web/src/views/device/plugins/active/ChooseDig.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="阻止窗口" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="rule-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="devices" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="Exes flex flex-column">
|
||||
<div class="private">
|
||||
<CheckBoxWrap ref="privateExes" :data="state.privateExes" :items="[]" label="FileName" text="Desc" title="私有窗口">
|
||||
<template #name="scoped">
|
||||
{{scoped.item.Desc || scoped.item.FileName }}
|
||||
</template>
|
||||
</CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="public">
|
||||
<CheckBoxWrap ref="publicExes" :data="state.publicExes" :items="[]" label="FileName" text="Desc" title="公共窗口">
|
||||
<template #name="scoped">
|
||||
{{scoped.item.Desc || scoped.item.FileName }}
|
||||
</template>
|
||||
</CheckBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, onMounted, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { activeDisallow } from '@/apis/active'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue', 'items'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const user = computed(() => globalData.value.usernames[globalData.value.username]);
|
||||
const publicUserName = globalData.value.publicUserName;
|
||||
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
|
||||
const usePublic = publicUser.value && globalData.value.username != publicUserName;
|
||||
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.activeWindow.devices),
|
||||
privateExes: computed(() => user.value ? user.value.FileNames : []),
|
||||
publicExes: computed(() => usePublic ? publicUser.value.FileNames : []),
|
||||
loading: false
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
});
|
||||
|
||||
const devices = ref(null);
|
||||
const privateExes = ref(null);
|
||||
const publicExes = ref(null);
|
||||
const parseRule = () => {
|
||||
const _privateExes = privateExes.value.getData();
|
||||
const _publicExes = publicExes.value.getData();
|
||||
return _privateExes.concat(_publicExes).reduce((data, item, index) => {
|
||||
let arr = item.split(',');
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (data.indexOf(arr[i]) == -1) {
|
||||
data.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}, []);
|
||||
}
|
||||
const handleSubmit = () => {
|
||||
const _devices = devices.value.getData();
|
||||
if (_devices.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('如果未选择程序,则视为清空程序,是否确定应用?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
state.loading = true;
|
||||
const exes = parseRule();
|
||||
activeDisallow(_devices, exes).then((res) => {
|
||||
state.loading = false;
|
||||
ElMessage.success('操作成功!');
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, globalData, devices, privateExes, publicExes, handleSubmit, handleCancel
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.rule-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Exes {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
|
||||
.private, .public {
|
||||
height: 49%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
cmonitor.web/src/views/device/plugins/active/FileNames.vue
Normal file
148
cmonitor.web/src/views/device/plugins/active/FileNames.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="配置窗口列表" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="filenames-items-wrap flex flex-nowrap flex-column">
|
||||
<div class="head t-c flex">
|
||||
<el-button @click="handleAdd()">添加项</el-button>
|
||||
<span class="flex-1"></span>
|
||||
<span style="line-height:3.2rem">不允许打开哪些窗口</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="prevs-wrap">
|
||||
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="60vh">
|
||||
<el-table-column prop="Desc" label="名称" />
|
||||
<el-table-column prop="FileName" label="文件" />
|
||||
<el-table-column label="操作" width="110">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleAdd(scope.row)">
|
||||
<el-icon>
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
|
||||
<div>
|
||||
<p><el-input v-model="state.currentItem.Desc" size="large" placeholder="名称" /></p>
|
||||
<p style="padding-top:1rem"><el-input v-model="state.currentItem.FileName" size="large" placeholder="文件,多个逗号间隔,无后缀则按标题处理" /></p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleEditCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { activeAddExe, activeDelExe } from '@/apis/active';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
loading: false,
|
||||
currentItem: { ID: 0, FileName: '', Desc: '' },
|
||||
showEdit: false,
|
||||
list: computed(() => {
|
||||
let user = globalData.value.usernames[globalData.value.username];
|
||||
if (user) {
|
||||
return user.FileNames;
|
||||
}
|
||||
return [];
|
||||
})
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const handleAdd = (item) => {
|
||||
item = item || { ID: 0, FileName: '', Desc: '' };
|
||||
state.currentItem.FileName = item.FileName;
|
||||
state.currentItem.Desc = item.Desc;
|
||||
state.currentItem.ID = item.ID;
|
||||
state.showEdit = true;
|
||||
}
|
||||
const handleDel = (item) => {
|
||||
state.loading = true;
|
||||
activeDelExe(globalData.value.username, item.ID).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
const handleEditCancel = () => {
|
||||
state.showEdit = false;
|
||||
}
|
||||
const handleEditSubmit = () => {
|
||||
state.currentItem.Desc = state.currentItem.Desc.replace(/^\s|\s$/g, '');
|
||||
state.currentItem.FileName = state.currentItem.FileName.replace(/^\s|\s$/g, '');
|
||||
if (!state.currentItem.FileName || !state.currentItem.Desc) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
activeAddExe({
|
||||
UserName: globalData.value.username,
|
||||
FileName: state.currentItem
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
state.showEdit = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.filenames-items-wrap {
|
||||
.head {
|
||||
width: 100%;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.prevs-wrap {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
36
cmonitor.web/src/views/device/plugins/active/FootMenu.vue
Normal file
36
cmonitor.web/src/views/device/plugins/active/FootMenu.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleFileNames">
|
||||
<span>
|
||||
<el-icon>
|
||||
<Monitor />
|
||||
</el-icon>窗口
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
sort: 1,
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleFileNames = () => {
|
||||
pluginState.value.activeWindow.showFileNames = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleFileNames
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
span {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleMessage">窗口<el-icon>
|
||||
<Monitor />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleMessage = () => {
|
||||
pluginState.value.activeWindow.devices = globalData.value.devices;
|
||||
pluginState.value.activeWindow.showChoose = true;
|
||||
}
|
||||
|
||||
return { handleMessage }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
25
cmonitor.web/src/views/device/plugins/active/Index.vue
Normal file
25
cmonitor.web/src/views/device/plugins/active/Index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="active">
|
||||
<ActiveTimes v-if="pluginState.activeWindow.showTimes" v-model="pluginState.activeWindow.showTimes"></ActiveTimes>
|
||||
<FileNames v-if="pluginState.activeWindow.showFileNames" v-model="pluginState.activeWindow.showFileNames"></FileNames>
|
||||
<ChooseDig v-if="pluginState.activeWindow.showChoose" v-model="pluginState.activeWindow.showChoose"></ChooseDig>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActiveTimes from './ActiveTimes.vue'
|
||||
import FileNames from './FileNames.vue'
|
||||
import ChooseDig from './ChooseDig.vue'
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
components: { ActiveTimes, FileNames, ChooseDig },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
return {
|
||||
pluginState
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
135
cmonitor.web/src/views/device/plugins/active/Screen.vue
Normal file
135
cmonitor.web/src/views/device/plugins/active/Screen.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<a v-if="data.ActiveWindow.Pid>0" class="process flex" href="javascript:;" @click="handleCloseActive">
|
||||
<span class="title flex-1">{{data.ActiveWindow.Title}}</span>
|
||||
<p class="btn">
|
||||
<a href="javascript:;" @click.stop="handleChoose">
|
||||
<el-icon>
|
||||
<Warning />
|
||||
</el-icon>
|
||||
<span class="num">{{data.ActiveWindow.Count}}</span>
|
||||
</a>
|
||||
<a href="javascript:;" @click.stop="handleAddExe">
|
||||
<el-icon>
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</a>
|
||||
</p>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { exec } from '@/apis/command';
|
||||
import { activeAddExe } from '@/apis/active';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const handleCloseActive = () => {
|
||||
ElMessageBox.confirm('是否确定关闭焦点窗口?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
exec([props.data.MachineName], [`taskkill /f /pid ${props.data.ActiveWindow.Pid}`]).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
const handleChoose = () => {
|
||||
pluginState.value.activeWindow.devices = [props.data];
|
||||
pluginState.value.activeWindow.showChoose = true;
|
||||
}
|
||||
const handleAddExe = () => {
|
||||
const arr = props.data.ActiveWindow.FileName.split('\\');
|
||||
const fileName = arr[arr.length - 1];
|
||||
const desc = props.data.ActiveWindow.Desc;
|
||||
const title = props.data.ActiveWindow.Title;
|
||||
let html = `</p>是否确定添加到待选列表?</p>`;
|
||||
html += `<p>标题:【${title}】</p>`;
|
||||
html += `<p>描述:【${desc}】</p>`;
|
||||
html += `<p>文件:【${fileName}】</p>`;
|
||||
html += `<p>windows商店应用,可能无法阻止,需要手动添加例如【计算器】,以侦测程序关闭</p>`;
|
||||
ElMessageBox.confirm(html, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
dangerouslyUseHTMLString: true,
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
activeAddExe({
|
||||
username: globalData.value.username,
|
||||
FileName: {
|
||||
ID: 0,
|
||||
FileName: fileName,
|
||||
Desc: desc
|
||||
}
|
||||
}).then((error) => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
if (!error) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error(`操作失败:${error}`);
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
return {
|
||||
data: props.data,
|
||||
handleCloseActive, handleChoose, handleAddExe
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.process {
|
||||
position: absolute;
|
||||
left: 0rem;
|
||||
right: 0rem;
|
||||
bottom: 0.2rem;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
padding: 0.6rem;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font-size: 1.4rem;
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
border-radius: 4px;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
a {
|
||||
font-size: 1.6rem;
|
||||
color: #fff;
|
||||
margin-left: 0.6rem;
|
||||
position: relative;
|
||||
|
||||
span.num {
|
||||
font-size: 1.3rem;
|
||||
position: absolute;
|
||||
right: 110%;
|
||||
top: -20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
cmonitor.web/src/views/device/plugins/active/index.js
Normal file
31
cmonitor.web/src/views/device/plugins/active/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
ActiveWindow: {
|
||||
Title: '',
|
||||
FileName: '',
|
||||
Desc: '',
|
||||
Pid: 0,
|
||||
Count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
state: {
|
||||
activeWindow: [
|
||||
{
|
||||
showTimes: false,
|
||||
items: [],
|
||||
showFileNames: false,
|
||||
showChoose: true,
|
||||
devices: []
|
||||
}
|
||||
]
|
||||
},
|
||||
update(item, report) {
|
||||
item.ActiveWindow.Title = report.ActiveWindow.Title;
|
||||
item.ActiveWindow.FileName = report.ActiveWindow.FileName;
|
||||
item.ActiveWindow.Desc = report.ActiveWindow.Desc;
|
||||
item.ActiveWindow.Pid = report.ActiveWindow.Pid;
|
||||
item.ActiveWindow.Count = report.ActiveWindow.Count;
|
||||
}
|
||||
}
|
||||
28
cmonitor.web/src/views/device/plugins/command/BtnLeft.vue
Normal file
28
cmonitor.web/src/views/device/plugins/command/BtnLeft.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleCommand">
|
||||
<el-icon>
|
||||
<Position />
|
||||
</el-icon>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleCommand = () => {
|
||||
pluginState.value.command.items = [props.data];
|
||||
pluginState.value.command.showCommand = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
74
cmonitor.web/src/views/device/plugins/command/BtnRight.vue
Normal file
74
cmonitor.web/src/views/device/plugins/command/BtnRight.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<a href="javascript:;" @click="handleRebotSystem">
|
||||
<el-icon>
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
</a>
|
||||
<a href="javascript:;" @click="handleCloseSystem">
|
||||
<el-icon>
|
||||
<SwitchButton />
|
||||
</el-icon>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { exec } from '@/apis/command';
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const handleRebotSystem = () => {
|
||||
handleSendCommand('确定重启系统吗?', `shutdown -r -f -t 00`);
|
||||
}
|
||||
const handleCloseSystem = () => {
|
||||
handleSendCommand('确定关闭系统吗?', `shutdown -s -f -t 00`);
|
||||
}
|
||||
|
||||
const handleSendCommand = (desc, command, value) => {
|
||||
ElMessageBox.confirm(desc, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
exec([props.data.MachineName], [command]).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
return { handleRebotSystem, handleCloseSystem }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
a {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
text-align: center;
|
||||
line-height: 2.8rem;
|
||||
margin-bottom: 0.6rem;
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #3e5a6e;
|
||||
box-shadow: 0 0 4px rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
color: #3e5a6e;
|
||||
transition: 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 4px 2px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
138
cmonitor.web/src/views/device/plugins/command/ChooseDig.vue
Normal file
138
cmonitor.web/src/views/device/plugins/command/ChooseDig.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="执行命令" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="command-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="commands">
|
||||
<PrevBoxWrap ref="commands" :data="state.commands" title="命令多发">
|
||||
<template #default="scope">
|
||||
<div class="btn">
|
||||
<el-button :loading="state.loading" @click="handleCommand(scope.item)">
|
||||
{{scope.item.label}}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</PrevBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleCancel">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, inject, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { exec } from '../../../../apis/command'
|
||||
import { llockUpdate } from '../../../../apis/llock'
|
||||
import { wallpaperUpdate } from '../../../../apis/wallpaper'
|
||||
import { usbUpdate } from '../../../../apis/usb'
|
||||
import { setVolumeMute } from '../../../../apis/volume'
|
||||
import { injectPluginState } from '../../provide';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap, PrevBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
|
||||
const wallpaperFunc = (names, value) => {
|
||||
wallpaperUpdate(names, value, `http://${window.location.hostname}:${window.location.port}/bg.jpg`);
|
||||
}
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.command.items),
|
||||
commands: [
|
||||
{ label: '强制关机', value: 'shutdown -s -f -t 00' },
|
||||
{ label: '强制重启', value: 'shutdown -r -f -t 00' },
|
||||
{ label: '打开锁屏', func: llockUpdate, value: true },
|
||||
{ label: '关闭锁屏', func: llockUpdate, value: false },
|
||||
{ label: '打开壁纸', func: wallpaperFunc, value: true },
|
||||
{ label: '关闭壁纸', func: wallpaperFunc, value: false },
|
||||
{ label: '禁用U盘', func: usbUpdate, value: true },
|
||||
{ label: '启用U盘', func: usbUpdate, value: false },
|
||||
{ label: '设置静音', func: setVolumeMute, value: true },
|
||||
{ label: '取消静音', func: setVolumeMute, value: false },
|
||||
],
|
||||
loading: false
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const items = ref(null);
|
||||
const handleCommand = (commandItem) => {
|
||||
let _items = items.value.getData();
|
||||
if (_items.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('是否确定执行命令?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
state.loading = true;
|
||||
const func = commandItem.func ? commandItem.func(_items, commandItem.value) : exec(_items, [commandItem.value]);
|
||||
func.then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
state.loading = false;
|
||||
}).catch(() => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
return {
|
||||
state, globalData, items, handleCancel, handleCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.command-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.commands {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn {
|
||||
text-align: center;
|
||||
padding: 0.6rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleCommand">命令<el-icon>
|
||||
<Position />
|
||||
</el-icon></el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleCommand = () => {
|
||||
pluginState.value.command.items = globalData.value.devices;
|
||||
pluginState.value.command.showCommand = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
19
cmonitor.web/src/views/device/plugins/command/Index.vue
Normal file
19
cmonitor.web/src/views/device/plugins/command/Index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<ChooseDig v-if="pluginState.command.showCommand" v-model="pluginState.command.showCommand"></ChooseDig>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
import ChooseDig from './ChooseDig.vue'
|
||||
export default {
|
||||
components: { ChooseDig },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
15
cmonitor.web/src/views/device/plugins/command/index.js
Normal file
15
cmonitor.web/src/views/device/plugins/command/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { commandPing } from '../../../../apis/command'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
field() {
|
||||
return {}
|
||||
},
|
||||
state: {
|
||||
command: {
|
||||
showCommand: false,
|
||||
items: []
|
||||
}
|
||||
},
|
||||
init() {
|
||||
}
|
||||
}
|
||||
23
cmonitor.web/src/views/device/plugins/demo.js
Normal file
23
cmonitor.web/src/views/device/plugins/demo.js
Normal file
@@ -0,0 +1,23 @@
|
||||
//Index.vue 主入口,用于放置一些内容,弹窗什么的
|
||||
//Option.vue 下方快捷选项
|
||||
//BtnLeft.vue 左按钮
|
||||
//BtnRight.vue 右按钮
|
||||
//TitleRight.vue //标题右边
|
||||
//Screen.vue //屏幕
|
||||
//FootOptionTop.vue
|
||||
//FootOptionBottom.vue
|
||||
//FootMenu.vue
|
||||
|
||||
|
||||
|
||||
//动态扩展功能配置示例
|
||||
export default {
|
||||
//字段
|
||||
field: {},
|
||||
//状态数据
|
||||
state: {},
|
||||
//报告更新
|
||||
update(item, report) { },
|
||||
//初始化
|
||||
init() { }
|
||||
}
|
||||
112
cmonitor.web/src/views/device/plugins/device/ChooseDig.vue
Normal file
112
cmonitor.web/src/views/device/plugins/device/ChooseDig.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="选择你的设备" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
|
||||
<div class="devices-wrap">
|
||||
<CheckBoxWrap ref="devices" :data="state.list" :items="state.items" label="MachineName" text="MachineName" title="选择设备">
|
||||
<template #oper="scope">
|
||||
<div>
|
||||
<el-popconfirm title="删除不可逆,是否确认?" @confirm="handleDel(scope.item.MachineName)">
|
||||
<template #reference>
|
||||
<span class="del-btn">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</CheckBoxWrap>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, onMounted, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import { updateDevices } from '../../../../apis/hijack'
|
||||
import { delDevice } from '../../../../apis/signin'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
loading: false,
|
||||
list: computed(() => globalData.value.allDevices),
|
||||
items: computed(() => globalData.value.devices),
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
const handleDel = (name) => {
|
||||
state.loading = true;
|
||||
delDevice(name).then(() => {
|
||||
state.loading = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}).catch(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
const devices = ref(null);
|
||||
const handleSubmit = () => {
|
||||
const _devices = devices.value.getData();
|
||||
|
||||
state.loading = true;
|
||||
updateDevices({
|
||||
username: globalData.value.username,
|
||||
devices: _devices
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
}
|
||||
}).catch(() => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
});
|
||||
|
||||
return {
|
||||
state, devices, handleCancel, handleSubmit, handleDel
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.devices-wrap {
|
||||
height: 60vh;
|
||||
position: relative;
|
||||
|
||||
.del-btn {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
cmonitor.web/src/views/device/plugins/device/FootMenu.vue
Normal file
32
cmonitor.web/src/views/device/plugins/device/FootMenu.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleDevices">
|
||||
<span><el-icon>
|
||||
<Grid />
|
||||
</el-icon>设备</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
sort: 0,
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleDevices = () => {
|
||||
pluginState.value.device.showDevices = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleDevices
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
span {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
19
cmonitor.web/src/views/device/plugins/device/Index.vue
Normal file
19
cmonitor.web/src/views/device/plugins/device/Index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<ChooseDig v-if="pluginState.device.showDevices" v-model="pluginState.device.showDevices"></ChooseDig>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide';
|
||||
import ChooseDig from './ChooseDig.vue'
|
||||
export default {
|
||||
components: { ChooseDig },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
7
cmonitor.web/src/views/device/plugins/device/index.js
Normal file
7
cmonitor.web/src/views/device/plugins/device/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
state: {
|
||||
device: {
|
||||
showDevices: false
|
||||
}
|
||||
}
|
||||
}
|
||||
42
cmonitor.web/src/views/device/plugins/hijack/BtnLeft.vue
Normal file
42
cmonitor.web/src/views/device/plugins/hijack/BtnLeft.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleRule">
|
||||
<el-icon>
|
||||
<Umbrella />
|
||||
</el-icon>
|
||||
<span class="value">{{data.Hijack.Count}}</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleRule = () => {
|
||||
|
||||
pluginState.value.hijack.showRulesItems = [props.data];
|
||||
pluginState.value.hijack.showRules = true;
|
||||
}
|
||||
|
||||
return { data: props.data, handleRule }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
a {
|
||||
position: relative;
|
||||
|
||||
span.value {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
164
cmonitor.web/src/views/device/plugins/hijack/ChooseDig.vue
Normal file
164
cmonitor.web/src/views/device/plugins/hijack/ChooseDig.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="网络限制" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="rule-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="devices" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="rules flex flex-column">
|
||||
<div class="private">
|
||||
<CheckBoxWrap ref="privateRules" :data="state.privateRules" :items="[]" label="ID" text="Name" title="私有限制"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="public">
|
||||
<CheckBoxWrap ref="publicRules" :data="state.publicRules" :items="[]" label="ID" text="Name" title="公共限制"></CheckBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, inject, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { setRules } from '../../../../apis/hijack'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue', 'items'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const user = computed(() => globalData.value.usernames[globalData.value.username]);
|
||||
const publicUserName = globalData.value.publicUserName;
|
||||
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
|
||||
const usePublic = publicUser.value && globalData.value.username != publicUserName;
|
||||
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.hijack.showRulesItems),
|
||||
privateRules: computed(() => user.value ? user.value.Rules : []),
|
||||
publicRules: computed(() => usePublic ? publicUser.value.Rules : []),
|
||||
loading: false
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const devices = ref(null);
|
||||
const privateRules = ref(null);
|
||||
const publicRules = ref(null);
|
||||
const parseRule = () => {
|
||||
const _privateRules = privateRules.value.getData();
|
||||
const _publicRules = publicRules.value.getData();
|
||||
const _user = user.value;
|
||||
const _publicUser = publicUser.value;
|
||||
|
||||
const publicList = _user.Rules.filter(c => _privateRules.indexOf(c.ID) >= 0).map(rule => {
|
||||
return _user.Processs.filter(c => rule.PrivateProcesss.indexOf(c.ID) >= 0);
|
||||
});
|
||||
const privateList = _publicUser.Rules.filter(c => _publicRules.indexOf(c.ID) >= 0).map(rule => {
|
||||
return _publicUser.Processs.filter(c => rule.PublicProcesss.indexOf(c.ID) >= 0);
|
||||
});
|
||||
|
||||
const origin = publicList.concat(privateList).reduce((arr, value, index) => {
|
||||
arr = arr.concat(value.reduce((arr, value, index) => {
|
||||
arr = arr.concat(value.List);
|
||||
return arr;
|
||||
}, []));
|
||||
return arr;
|
||||
}, []);
|
||||
const res = [];
|
||||
origin.forEach(element => {
|
||||
if (res.filter(c => c.Name == element.Name && c.DataType == element.DataType && c.AllowType == element.AllowType).length == 0) {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
AllowProcesss: res.filter(c => c.DataType == 0 && c.AllowType == 0).map(c => c.Name),
|
||||
DeniedProcesss: res.filter(c => c.DataType == 0 && c.AllowType == 1).map(c => c.Name),
|
||||
AllowDomains: res.filter(c => c.DataType == 1 && c.AllowType == 0).map(c => c.Name),
|
||||
DeniedDomains: res.filter(c => c.DataType == 1 && c.AllowType == 1).map(c => c.Name),
|
||||
AllowIPs: res.filter(c => c.DataType == 2 && c.AllowType == 0).map(c => c.Name),
|
||||
DeniedIPs: res.filter(c => c.DataType == 2 && c.AllowType == 1).map(c => c.Name),
|
||||
}
|
||||
}
|
||||
const handleSubmit = () => {
|
||||
const _devices = devices.value.getData();
|
||||
if (_devices.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ElMessageBox.confirm('如果未选择任何限制,则视为清空限制,是否确定应用限制?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
|
||||
state.loading = true;
|
||||
const rules = parseRule();
|
||||
setRules({
|
||||
Devices: _devices,
|
||||
Rules: rules
|
||||
}).then((errorDevices) => {
|
||||
state.loading = false;
|
||||
if (errorDevices && errorDevices.length > 0) {
|
||||
ElMessage.error(`操作失败,失败设备:${errorDevices.join(',')}`);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
return {
|
||||
state, globalData, devices, privateRules, publicRules, handleSubmit, handleCancel
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.rule-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rules {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
|
||||
.private, .public {
|
||||
height: 49%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
cmonitor.web/src/views/device/plugins/hijack/FootMenu.vue
Normal file
56
cmonitor.web/src/views/device/plugins/hijack/FootMenu.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<a href="javascript:;">
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
<el-icon>
|
||||
<Umbrella />
|
||||
</el-icon>网络
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="handleProcess">程序配置</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="handleRule">分组配置</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '@/views/device/provide'
|
||||
export default {
|
||||
sort: 2,
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleProcess = () => {
|
||||
pluginState.value.hijack.showProcessSetting = true;
|
||||
}
|
||||
const handleRule = () => {
|
||||
pluginState.value.hijack.showRuleSetting = true;
|
||||
}
|
||||
return {
|
||||
handleProcess, handleRule
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.el-icon {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
color: #ebffef;
|
||||
font-size: 1.6rem;
|
||||
|
||||
.el-dropdown-link {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
cmonitor.web/src/views/device/plugins/hijack/Index.vue
Normal file
24
cmonitor.web/src/views/device/plugins/hijack/Index.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<ChooseDig v-if="pluginState.hijack.showRules" v-model="pluginState.hijack.showRules"></ChooseDig>
|
||||
<RuleSetting v-if="pluginState.hijack.showRuleSetting" v-model="pluginState.hijack.showRuleSetting"></RuleSetting>
|
||||
<ProcessSetting v-if="pluginState.hijack.showProcessSetting" v-model="pluginState.hijack.showProcessSetting"></ProcessSetting>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
import ChooseDig from './ChooseDig.vue'
|
||||
import RuleSetting from './rules/Index.vue'
|
||||
import ProcessSetting from './process/Index.vue'
|
||||
export default {
|
||||
components: { ChooseDig, RuleSetting, ProcessSetting },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
45
cmonitor.web/src/views/device/plugins/hijack/TitleRight.vue
Normal file
45
cmonitor.web/src/views/device/plugins/hijack/TitleRight.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<span class="speed">
|
||||
<span>
|
||||
<el-icon>
|
||||
<Upload />
|
||||
</el-icon>
|
||||
{{data.Hijack.UploadText}}
|
||||
</span>
|
||||
<span>
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
{{data.Hijack.DownloadText}}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
return {
|
||||
data: props.data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.speed {
|
||||
font-size: 1.3rem;
|
||||
padding-top: 0.1rem;
|
||||
color: #333;
|
||||
|
||||
span:nth-child(1) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
vertical-align: middle;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
cmonitor.web/src/views/device/plugins/hijack/index.js
Normal file
68
cmonitor.web/src/views/device/plugins/hijack/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { injectGlobalData } from "@/views/provide";
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Hijack: {
|
||||
Upload: 0,
|
||||
UploadText: '',
|
||||
Download: 0,
|
||||
DownloadText: '',
|
||||
Count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
state: {
|
||||
hijack: {
|
||||
showRules: false,
|
||||
showRulesItems: [],
|
||||
showRuleSetting: false,
|
||||
showProcessSetting: false,
|
||||
}
|
||||
},
|
||||
timer: 0,
|
||||
speedCaches: {},
|
||||
sizeFormat(size) {
|
||||
let unites = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let unit = unites[0];
|
||||
while ((unit = unites.shift()) && size.toFixed(2) >= 1024) {
|
||||
size /= 1024;
|
||||
}
|
||||
return unit == 'B' ? [size, unit] : [size.toFixed(2), unit];
|
||||
},
|
||||
globalData: null,
|
||||
init() {
|
||||
this.globalData = injectGlobalData();;
|
||||
const speedCaches = this.speedCaches;
|
||||
const sizeFormat = this.sizeFormat;
|
||||
this.timer = setInterval(() => {
|
||||
this.globalData.value.devices.forEach(item => {
|
||||
|
||||
let cache = speedCaches[item.MachineName] || { up: 0, down: 0 };
|
||||
|
||||
if (isNaN(cache.up)) cache.up = 0;
|
||||
if (isNaN(cache.down)) cache.down = 0;
|
||||
|
||||
item.Hijack.Upload = item.Hijack.Upload || 0;
|
||||
item.Hijack.Download = item.Hijack.Download || 0;
|
||||
|
||||
let bytes = item.Hijack.Upload - cache.up;
|
||||
cache.up = item.Hijack.Upload;
|
||||
let format = sizeFormat(bytes);
|
||||
item.Hijack.UploadText = `${format[0]}${format[1]}/s`;
|
||||
|
||||
bytes = item.Hijack.Download - cache.down;
|
||||
cache.down = item.Hijack.Download;
|
||||
format = sizeFormat(bytes);
|
||||
item.Hijack.DownloadText = `${format[0]}${format[1]}/s`;
|
||||
|
||||
speedCaches[item.MachineName] = cache;
|
||||
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
update(item, report) {
|
||||
item.Hijack.Upload = report.Hijack.Upload;
|
||||
item.Hijack.Download = report.Hijack.Download;
|
||||
item.Hijack.Count = report.Hijack.Count;
|
||||
}
|
||||
}
|
||||
138
cmonitor.web/src/views/device/plugins/hijack/process/Groups.vue
Normal file
138
cmonitor.web/src/views/device/plugins/hijack/process/Groups.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="process-items-wrap flex flex-nowrap flex-column">
|
||||
<div class="head t-c flex">
|
||||
<el-button @click="handleAdd()">添加项</el-button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="prevs-wrap">
|
||||
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
|
||||
<el-table-column prop="Name" label="名称" />
|
||||
<el-table-column label="操作" width="110">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.ID > 1">
|
||||
<el-button size="small" @click="handleAdd(scope.row)">
|
||||
<el-icon>
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
|
||||
<div>
|
||||
<el-input v-model="state.currentItem.Name" size="large" placeholder="分组名称" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleEditCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed } from '@vue/runtime-core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addProcessGroup, deleteProcessGroup } from '../../../../../apis/hijack'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
currentItem: { ID: 0, Name: '' },
|
||||
showEdit: false,
|
||||
list: computed(() => {
|
||||
let user = globalData.value.usernames[globalData.value.username];
|
||||
if (user) {
|
||||
if (state.group == 0 && user.Processs.length > 0) {
|
||||
state.group = user.Processs[0].ID;
|
||||
}
|
||||
return user.Processs;
|
||||
}
|
||||
return [];
|
||||
})
|
||||
});
|
||||
|
||||
const handleAdd = (item) => {
|
||||
item = item || { Name: '', ID: 0 };
|
||||
state.currentItem.Name = item.Name;
|
||||
state.currentItem.ID = item.ID;
|
||||
state.showEdit = true;
|
||||
}
|
||||
const handleDel = (item) => {
|
||||
state.loading = true;
|
||||
deleteProcessGroup({
|
||||
UserName: globalData.value.username,
|
||||
ID: item.ID
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
const handleEditCancel = () => {
|
||||
state.showEdit = false;
|
||||
}
|
||||
const handleEditSubmit = () => {
|
||||
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
|
||||
if (!state.currentItem.Name) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
addProcessGroup({
|
||||
UserName: globalData.value.username,
|
||||
Group: state.currentItem
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
state.showEdit = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.process-items-wrap {
|
||||
.head {
|
||||
width: 100%;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.prevs-wrap {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="限制程序配置" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="程序分组">
|
||||
<Groups></Groups>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="程序列表">
|
||||
<Items></Items>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { onMounted, watch } from '@vue/runtime-core';
|
||||
import Groups from './Groups.vue'
|
||||
import Items from './Items.vue'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { Groups, Items },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
loading: false,
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
onMounted(() => {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, handleCancel
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped></style>
|
||||
196
cmonitor.web/src/views/device/plugins/hijack/process/Items.vue
Normal file
196
cmonitor.web/src/views/device/plugins/hijack/process/Items.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div class="process-items-wrap flex flex-nowrap flex-column">
|
||||
<div class="head t-c flex">
|
||||
<el-select v-model="state.group" placeholder="选择一个分组" style="width:13rem">
|
||||
<el-option v-for="item in state.groups" :key="item.ID" :label="item.Name" :value="item.ID" />
|
||||
</el-select>
|
||||
<span class="flex-1"></span>
|
||||
<el-button @click="handleAdd()">添加项</el-button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="prevs-wrap">
|
||||
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
|
||||
<el-table-column prop="Name" label="名称">
|
||||
<template #default="scope">
|
||||
<strong :class="`allow-type-${scope.row.AllowType}`">{{scope.row.Name}}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleAdd(scope.row)">
|
||||
<el-icon>
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
|
||||
<div>
|
||||
<div class="alert">
|
||||
<p>1、黑名单优先</p>
|
||||
<p>2、支持进程名,域名,ip(支持掩码)</p>
|
||||
<p>3、进程,域名,后序截取判断</p>
|
||||
</div>
|
||||
<div style="padding-bottom:1rem">
|
||||
<el-input v-model="state.currentItem.Name" size="large" placeholder="进程 | 域名 | ip(支持掩码/32)" />
|
||||
</div>
|
||||
<div class="t-c" style="padding-bottom:1rem">
|
||||
<el-radio-group v-model="state.currentItem.DataType">
|
||||
<el-radio :label="0">进程</el-radio>
|
||||
<el-radio :label="1">域名</el-radio>
|
||||
<el-radio :label="2">IP</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="t-c">
|
||||
<el-switch v-model="state.currentItem.AllowType" size="large" active-text="允许" inactive-text="阻止" :active-value="0" :inactive-value="1" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleEditCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addProcess, deleteProcess } from '../../../../../apis/hijack'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
setup() {
|
||||
const globalData = injectGlobalData();;
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
group: 0,
|
||||
currentItem: { ID: 0, Name: '', AllowType: 1, DataType: 0 },
|
||||
showEdit: false,
|
||||
groups: computed(() => {
|
||||
let user = globalData.value.usernames[globalData.value.username];
|
||||
if (user) {
|
||||
if (state.group == 0 && user.Processs.length > 0) {
|
||||
state.group = user.Processs[0].ID;
|
||||
}
|
||||
return user.Processs;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
list: computed(() => {
|
||||
let group = state.groups.filter(c => c.ID == state.group)[0];
|
||||
if (group) return group.List;
|
||||
return [];
|
||||
})
|
||||
});
|
||||
watch(() => state.currentItem.Name, () => {
|
||||
handleNameChange(state.currentItem.Name);
|
||||
})
|
||||
|
||||
const handleNameChange = (value) => {
|
||||
const isExe = /^.{0,}(\.exe)$/.test(value);
|
||||
const isip = /^((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))(\/\d{1,})?$/.test(value);
|
||||
state.currentItem.DataType = isExe ? 0 : (isip ? 2 : 1);
|
||||
if (isip && value.indexOf('/') < 0) {
|
||||
state.currentItem.Name = state.currentItem.Name + '/32';
|
||||
}
|
||||
}
|
||||
const handleAdd = (item) => {
|
||||
item = item || { Name: '', ID: 0, AllowType: 1, DataType: 0 };
|
||||
state.currentItem.Name = item.Name;
|
||||
state.currentItem.ID = item.ID;
|
||||
state.currentItem.AllowType = item.AllowType;
|
||||
state.showEdit = true;
|
||||
}
|
||||
const handleDel = (item) => {
|
||||
state.loading = true;
|
||||
deleteProcess({
|
||||
UserName: globalData.value.username,
|
||||
GroupID: state.group,
|
||||
ID: item.ID
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
const handleEditCancel = () => {
|
||||
state.showEdit = false;
|
||||
}
|
||||
const handleEditSubmit = () => {
|
||||
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
|
||||
if (!state.currentItem.Name) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
addProcess({
|
||||
UserName: globalData.value.username,
|
||||
GroupID: state.group,
|
||||
Item: state.currentItem
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
state.showEdit = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
return { state, handleNameChange, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.process-items-wrap {
|
||||
.head {
|
||||
width: 100%;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.prevs-wrap {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.allow-type-0 {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.allow-type-1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background-color: rgba(255, 136, 0, 0.2);
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleRule">网络<el-icon>
|
||||
<Umbrella />
|
||||
</el-icon></el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '@/views/device/provide';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleRule = () => {
|
||||
pluginState.value.hijack.showRulesItems = globalData.value.devices;
|
||||
pluginState.value.hijack.showRules = true;
|
||||
}
|
||||
return {
|
||||
handleRule
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
138
cmonitor.web/src/views/device/plugins/hijack/rules/Groups.vue
Normal file
138
cmonitor.web/src/views/device/plugins/hijack/rules/Groups.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="rule-items-wrap flex flex-nowrap flex-column">
|
||||
<div class="head t-c flex">
|
||||
<el-button @click="handleAdd()">添加项</el-button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="prevs-wrap">
|
||||
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
|
||||
<el-table-column prop="Name" label="名称" />
|
||||
<el-table-column label="操作" width="110">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.ID > 1">
|
||||
<el-button size="small" @click="handleAdd(scope.row)">
|
||||
<el-icon>
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
|
||||
<div>
|
||||
<el-input v-model="state.currentItem.Name" size="large" placeholder="分组名称" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleEditCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed } from '@vue/runtime-core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addRule, deleteRule } from '../../../../../apis/hijack'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
currentItem: { ID: 0, Name: '' },
|
||||
showEdit: false,
|
||||
list: computed(() => {
|
||||
let user = globalData.value.usernames[globalData.value.username];
|
||||
if (user) {
|
||||
if (state.group == 0 && user.Rules.length > 0) {
|
||||
state.group = user.Rules[0].ID;
|
||||
}
|
||||
return user.Rules;
|
||||
}
|
||||
return [];
|
||||
})
|
||||
});
|
||||
|
||||
const handleAdd = (item) => {
|
||||
item = item || { Name: '', ID: 0 };
|
||||
state.currentItem.Name = item.Name;
|
||||
state.currentItem.ID = item.ID;
|
||||
state.showEdit = true;
|
||||
}
|
||||
const handleDel = (item) => {
|
||||
state.loading = true;
|
||||
deleteRule({
|
||||
UserName: globalData.value.username,
|
||||
ID: item.ID
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
const handleEditCancel = () => {
|
||||
state.showEdit = false;
|
||||
}
|
||||
const handleEditSubmit = () => {
|
||||
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
|
||||
if (!state.currentItem.Name) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
addRule({
|
||||
UserName: globalData.value.username,
|
||||
Rule: state.currentItem
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
ElMessage.success('操作成功!');
|
||||
state.showEdit = false;
|
||||
globalData.value.updateFlag = Date.now();
|
||||
}
|
||||
}).catch((e) => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败!');
|
||||
})
|
||||
}
|
||||
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.rule-items-wrap {
|
||||
.head {
|
||||
width: 100%;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.prevs-wrap {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
cmonitor.web/src/views/device/plugins/hijack/rules/Index.vue
Normal file
49
cmonitor.web/src/views/device/plugins/hijack/rules/Index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="限制组配置" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="限制组">
|
||||
<Groups></Groups>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="限制内容">
|
||||
<Rule></Rule>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { watch } from '@vue/runtime-core';
|
||||
import Groups from './Groups.vue'
|
||||
import Rule from './Rule.vue'
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { Groups, Rule },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const state = reactive({
|
||||
show: props.modelValue
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
return {
|
||||
state, handleCancel
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped></style>
|
||||
139
cmonitor.web/src/views/device/plugins/hijack/rules/Rule.vue
Normal file
139
cmonitor.web/src/views/device/plugins/hijack/rules/Rule.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="command-wrap flex flex-column">
|
||||
<div class="head t-c flex">
|
||||
<div>
|
||||
<el-select v-model="state.group" placeholder="选择一个分组" style="width:13rem">
|
||||
<el-option v-for="item in state.groups" :key="item.ID" :label="item.Name" :value="item.ID" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div>
|
||||
<el-button @click="handleSave()" :loading="state.loading">保存选择</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body flex flex-1">
|
||||
<div class="private">
|
||||
<CheckBoxWrap ref="privateProcess" :data="state.privateProcess" :items="state.privateProcessItems" label="ID" text="Name" title="私有程序组"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="public">
|
||||
<CheckBoxWrap ref="publicProcess" :data="state.publicProcess" :items="state.publicProcessItems" label="ID" text="Name" title="公共程序组"></CheckBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import CheckBoxWrap from '../../../boxs/CheckBoxWrap.vue'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addRule } from '../../../../../apis/hijack'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
components: { CheckBoxWrap },
|
||||
setup() {
|
||||
|
||||
const globalData = injectGlobalData();;
|
||||
const user = computed(() => globalData.value.usernames[globalData.value.username]);
|
||||
const publicUserName = globalData.value.publicUserName;
|
||||
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
|
||||
const usePublic = publicUser.value && globalData.value.username != publicUserName;
|
||||
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
group: 0,
|
||||
groups: computed(() => {
|
||||
if (user.value) {
|
||||
if (state.group == 0 && user.value.Rules.length > 0) {
|
||||
state.group = user.value.Rules[0].ID;
|
||||
}
|
||||
return user.value.Rules;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
rule: computed(() => {
|
||||
if (user) {
|
||||
let rule = user.value.Rules.filter(c => c.ID == state.group)[0];
|
||||
if (rule) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
return {
|
||||
ID: 0,
|
||||
Name: '',
|
||||
PrivateProcesss: [],
|
||||
PublicProcesss: [],
|
||||
}
|
||||
}),
|
||||
privateProcess: computed(() => user.value ? user.value.Processs : []),
|
||||
privateProcessItems: computed(() => user.value ? user.value.Processs.filter(c => state.rule.PrivateProcesss.indexOf(c.ID) >= 0) : []),
|
||||
publicProcess: computed(() => usePublic ? publicUser.value.Processs : []),
|
||||
publicProcessItems: computed(() => usePublic ? publicUser.value.Processs.filter(c => state.rule.PublicProcesss.indexOf(c.ID) >= 0) : []),
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const privateProcess = ref(null);
|
||||
const publicProcess = ref(null);
|
||||
|
||||
const handleSave = () => {
|
||||
let rule = user.value.Rules.filter(c => c.ID == state.group)[0];
|
||||
if (!rule) {
|
||||
ElMessage.error('未选择任何限制分组');
|
||||
return;
|
||||
}
|
||||
rule.PrivateProcesss = privateProcess.value.getData();
|
||||
rule.PublicProcesss = publicProcess.value.getData();
|
||||
|
||||
state.loading = true;
|
||||
addRule({
|
||||
UserName: globalData.value.username,
|
||||
Rule: rule
|
||||
}).then((error) => {
|
||||
state.loading = false;
|
||||
if (error) {
|
||||
ElMessage.error(error);
|
||||
} else {
|
||||
globalData.value.updateFlag = Date.now();
|
||||
ElMessage.success('操作成功');
|
||||
}
|
||||
}).catch(() => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败');
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
state, handleSave, privateProcess, publicProcess
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.command-wrap {
|
||||
height: 55vh;
|
||||
|
||||
.head {
|
||||
width: 100%;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.private, .public {
|
||||
width: 49%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.process {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
cmonitor.web/src/views/device/plugins/light/BtnRight.vue
Normal file
78
cmonitor.web/src/views/device/plugins/light/BtnRight.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<a href="javascript:;">
|
||||
<span class="light" @click="handleLight">
|
||||
<el-icon>
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
<div class="light-bg" :style="{height:`${data.Light.Value}%`}">
|
||||
<el-icon class="value">
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
</div>
|
||||
</span>
|
||||
<p class="light-value">{{data.Light.Value}}</p>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleLight = () => {
|
||||
pluginState.value.light.items = [props.data];
|
||||
pluginState.value.light.showLightSingle = true;
|
||||
}
|
||||
|
||||
return {
|
||||
data: props.data,
|
||||
handleLight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.light-value {
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
span.light {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: block;
|
||||
|
||||
.el-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.light-bg {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
height: 0%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.el-icon {
|
||||
color: green;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleLight">亮度 <el-icon>
|
||||
<Sunny />
|
||||
</el-icon></el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleLight = () => {
|
||||
pluginState.value.light.items = globalData.value.devices;
|
||||
pluginState.value.light.showLight = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleLight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
21
cmonitor.web/src/views/device/plugins/light/Index.vue
Normal file
21
cmonitor.web/src/views/device/plugins/light/Index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<Light v-if="pluginState.light.showLight" v-model="pluginState.light.showLight"></Light>
|
||||
<LightSingle v-if="pluginState.light.showLightSingle" v-model="pluginState.light.showLightSingle"></LightSingle>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide';
|
||||
import Light from './Light.vue'
|
||||
import LightSingle from './LightSingle.vue'
|
||||
export default {
|
||||
components: { Light, LightSingle },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
137
cmonitor.web/src/views/device/plugins/light/Light.vue
Normal file
137
cmonitor.web/src/views/device/plugins/light/Light.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="调节亮度" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="command-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="全选">
|
||||
<template #name="scope">
|
||||
<span>
|
||||
<span class="name">
|
||||
{{scope.item.MachineName}}
|
||||
</span>
|
||||
<strong class="light">
|
||||
<el-icon>
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
<strong class="value">{{scope.item.Light.Value?Math.floor(scope.item.Light.Value):scope.item.Light.Value}}%</strong>
|
||||
</strong>
|
||||
</span>
|
||||
</template>
|
||||
</CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="commands">
|
||||
<PrevBoxWrap ref="commands" title="调节亮度">
|
||||
<template #wrap>
|
||||
<div class="slider-wrap flex flex-column">
|
||||
<div class="silder flex flex-1">
|
||||
<div class="flex-1">
|
||||
<el-slider @change="handleChangeLight" v-model="state.light" vertical height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PrevBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleCancel">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { setLight } from '../../../../apis/light'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap, PrevBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.light.items),
|
||||
mute: false,
|
||||
loading: false,
|
||||
light: 0
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
const items = ref(null);
|
||||
const handleChangeLight = () => {
|
||||
let _items = items.value.getData();
|
||||
if (_items.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
setLight(_items, state.light);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, globalData, items, handleCancel, handleChangeLight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.command-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.commands {
|
||||
height: 100%;
|
||||
width: 38%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.light {
|
||||
font-size: 2rem;
|
||||
padding-left: 1rem;
|
||||
|
||||
.value {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrap {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
.silder {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn+.btn {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
cmonitor.web/src/views/device/plugins/light/LightSingle.vue
Normal file
81
cmonitor.web/src/views/device/plugins/light/LightSingle.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<el-dialog title="调节亮度" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="slider-wrap flex flex-column">
|
||||
<div class="silder flex flex-1">
|
||||
<div class="flex-1">
|
||||
<el-slider @change="handleChangeLight" v-model="state.light" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import { setLight } from '../../../../apis/light'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue', 'items'],
|
||||
emits: ['update:modelValue'],
|
||||
components: {},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.light.items),
|
||||
loading: false,
|
||||
light: pluginState.value.light.items[0].Light.Value
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
const handleChangeLight = () => {
|
||||
setLight(state.items.map(c => c.MachineName), state.light);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, globalData, handleCancel, handleChangeLight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.light {
|
||||
font-size: 2rem;
|
||||
padding-left: 1rem;
|
||||
|
||||
.value {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrap {
|
||||
text-align: center;
|
||||
|
||||
// height: 10rem;
|
||||
.silder {
|
||||
padding: 2rem 4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn+.btn {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
cmonitor.web/src/views/device/plugins/light/index.js
Normal file
28
cmonitor.web/src/views/device/plugins/light/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { injectGlobalData } from "@/views/provide";
|
||||
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Light: {
|
||||
Value: 0
|
||||
},
|
||||
}
|
||||
},
|
||||
state: {
|
||||
light: {
|
||||
showLight: false,
|
||||
showLightSingle: false,
|
||||
items: []
|
||||
}
|
||||
},
|
||||
globalData: null,
|
||||
init() {
|
||||
this.globalData = injectGlobalData();
|
||||
},
|
||||
update(item, report) {
|
||||
if (report.Light) {
|
||||
item.Light.Value = Math.floor(+report.Light.Value);
|
||||
if (isNaN(item.Light.Value)) item.Light.Value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
cmonitor.web/src/views/device/plugins/llock/Option.vue
Normal file
44
cmonitor.web/src/views/device/plugins/llock/Option.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-col :span="4">
|
||||
<el-switch size="small" @click="handleLock" :model-value="data.LLock.Value" inline-prompt active-text="锁屏" inactive-text="锁屏" />
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { llockUpdate } from '@/apis/llock';
|
||||
import { injectPluginState } from '../../provide'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
export default {
|
||||
sort: 2,
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleLock = () => {
|
||||
let desc = props.data.LLock.Value ? '确定解除锁屏吗?' : '确定开启锁屏吗?';
|
||||
ElMessageBox.confirm(desc, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
llockUpdate([props.data.MachineName], !props.data.LLock.Value).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
return {
|
||||
data: props.data, handleLock
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
12
cmonitor.web/src/views/device/plugins/llock/index.js
Normal file
12
cmonitor.web/src/views/device/plugins/llock/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
LLock: {
|
||||
Value: false
|
||||
},
|
||||
}
|
||||
},
|
||||
update(item, report) {
|
||||
item.LLock.Value = report.LLock.Value;
|
||||
}
|
||||
}
|
||||
26
cmonitor.web/src/views/device/plugins/message/BtnLeft.vue
Normal file
26
cmonitor.web/src/views/device/plugins/message/BtnLeft.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<a href="javascript:;" @click="handleMessage">
|
||||
<el-icon>
|
||||
<Bell />
|
||||
</el-icon>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
const pluginState = injectPluginState();
|
||||
const handleMessage = () => {
|
||||
pluginState.value.message.items = [props.data];
|
||||
pluginState.value.message.showMessage = true;
|
||||
}
|
||||
return {
|
||||
handleMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
148
cmonitor.web/src/views/device/plugins/message/ChooseDig.vue
Normal file
148
cmonitor.web/src/views/device/plugins/message/ChooseDig.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="发送提醒" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="command-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="prevs-wrap flex flex-column flex-nowrap">
|
||||
<div class="prevs">
|
||||
<PrevBoxWrap ref="prevs" :data="state.prevs" @prev="handlePrev" title="快捷短语"></PrevBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div>
|
||||
<div class="times">
|
||||
<el-input v-model="state.sec" size="large">
|
||||
<template #append>秒钟</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="prev">
|
||||
<el-input v-model="state.prev" type="textarea" resize="none" placeholder="输入提醒消息"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.loading" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { exec } from '../../../../apis/command'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap, PrevBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.message.items),
|
||||
prevs: [
|
||||
'请注意上课纪律!',
|
||||
'请勿玩游戏!',
|
||||
'请勿大声喧哗!'
|
||||
],
|
||||
sec: 10,
|
||||
prev: '',
|
||||
loading: false
|
||||
});
|
||||
try {
|
||||
if (pluginState.value.message.items.length == 1 && pluginState.value.message.items[0].Screen.UserName) {
|
||||
state.prevs.push(`【${pluginState.value.message.items[0].Screen.UserName}】请注意上课纪律!`);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const handlePrev = (item) => {
|
||||
state.prev = item;
|
||||
}
|
||||
|
||||
const items = ref(null);
|
||||
const prevs = ref(null);
|
||||
const handleSubmit = () => {
|
||||
let _items = items.value.getData();
|
||||
if (_items.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
if (state.prev.length == 0) {
|
||||
ElMessage.error('未填写消息');
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('是否确定发送消息?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
state.loading = true;
|
||||
exec(_items, [`start message.win.exe "${state.prev}" ${state.sec}`]).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
state.loading = false;
|
||||
}).catch(() => {
|
||||
state.loading = false;
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
return {
|
||||
state, globalData, items, prevs, handleSubmit, handleCancel, handlePrev
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.command-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.prevs-wrap {
|
||||
height: 100%;
|
||||
width: 48%;
|
||||
position: relative;
|
||||
|
||||
.times {
|
||||
margin: 0.6rem 0;
|
||||
}
|
||||
|
||||
.prevs {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleMessage">提醒<el-icon>
|
||||
<Bell />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleMessage = () => {
|
||||
pluginState.value.message.items = globalData.value.devices;
|
||||
pluginState.value.message.showMessage = true;
|
||||
}
|
||||
|
||||
return { handleMessage }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
21
cmonitor.web/src/views/device/plugins/message/Index.vue
Normal file
21
cmonitor.web/src/views/device/plugins/message/Index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<ChooseDig v-if="pluginState.message.showMessage" v-model="pluginState.message.showMessage"></ChooseDig>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
import ChooseDig from './ChooseDig.vue'
|
||||
export default {
|
||||
components: { ChooseDig },
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
8
cmonitor.web/src/views/device/plugins/message/index.js
Normal file
8
cmonitor.web/src/views/device/plugins/message/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
state: {
|
||||
message: {
|
||||
showMessage: false,
|
||||
items: []
|
||||
}
|
||||
}
|
||||
}
|
||||
29
cmonitor.web/src/views/device/plugins/report/Option.vue
Normal file
29
cmonitor.web/src/views/device/plugins/report/Option.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<el-col :span="8" class="time">
|
||||
<span>fps : {{data.Report.fps}} 、 {{data.Report.ping}}ms</span>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
sort: -1,
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
return {
|
||||
data: props.data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.time {
|
||||
text-align: left !important;
|
||||
color: #666;
|
||||
line-height: 2rem;
|
||||
|
||||
&>span {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
cmonitor.web/src/views/device/plugins/report/index.js
Normal file
93
cmonitor.web/src/views/device/plugins/report/index.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { subNotifyMsg } from '@/apis/request';
|
||||
import { reportPing, reportUpdate } from '../../../../apis/report'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Report: {
|
||||
fps: 0,
|
||||
fpsTimes: 0,
|
||||
ping: 0
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
globalData: null,
|
||||
init() {
|
||||
this.globalData = injectGlobalData();
|
||||
this.reportInterval();
|
||||
this.reportPingInterval();
|
||||
subNotifyMsg('/notify/report/pong', (res) => {
|
||||
let item = this.globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
|
||||
if (item) {
|
||||
item.Connected = true;
|
||||
item.Report.ping = res.Time;
|
||||
}
|
||||
});
|
||||
this.fpsInterval();
|
||||
},
|
||||
|
||||
reportTimer: 0,
|
||||
reported: true,
|
||||
reportInterval() {
|
||||
if (this.reported) {
|
||||
this.reported = false;
|
||||
reportUpdate(this.globalData.value.reportNames).then(() => {
|
||||
this.reported = true;
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
}).catch(() => {
|
||||
this.reported = true;
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
});
|
||||
} else {
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
}
|
||||
|
||||
},
|
||||
reportPingTimer: 0,
|
||||
reportedPing: true,
|
||||
reportPingInterval() {
|
||||
if (this.reportedPing) {
|
||||
this.reportedPing = false;
|
||||
let start = Date.now();
|
||||
reportPing(this.globalData.value.reportNames).then(() => {
|
||||
this.reportedPing = true;
|
||||
let lastTime = 1000 - (Date.now() - start);
|
||||
if (lastTime < 10) {
|
||||
lastTime = 10;
|
||||
}
|
||||
this.reportPingTimer = setTimeout(() => {
|
||||
this.reportPingInterval();
|
||||
}, lastTime);
|
||||
}).catch(() => {
|
||||
this.reportedPing = true;
|
||||
this.reportPingTimer = setTimeout(() => {
|
||||
this.reportPingInterval();
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
this.reportPingTimer = setTimeout(() => {
|
||||
this.reportPingInterval();
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
fpsInterval() {
|
||||
this.globalData.value.devices.forEach(item => {
|
||||
item.Report.fps = item.Report.fpsTimes;
|
||||
item.Report.fpsTimes = 0;
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.fpsInterval();
|
||||
}, 1000)
|
||||
},
|
||||
update(item, report) {
|
||||
item.Report.fpsTimes++;
|
||||
}
|
||||
}
|
||||
139
cmonitor.web/src/views/device/plugins/screen/index.js
Normal file
139
cmonitor.web/src/views/device/plugins/screen/index.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { screenUpdate } from '../../../../apis/screen'
|
||||
import { subNotifyMsg } from '@/apis/request';
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Screen: {
|
||||
fps: 0,
|
||||
fpsTimes: 0,
|
||||
img: null,
|
||||
LastInput: 0,
|
||||
UserName: '',
|
||||
KeyBoard: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
reportTimer: 0,
|
||||
globalData: null,
|
||||
reported: true,
|
||||
init() {
|
||||
this.globalData = injectGlobalData();;
|
||||
this.reportInterval();
|
||||
this.subMessage();
|
||||
this.fpsInterval();
|
||||
this.draw();
|
||||
},
|
||||
draw() {
|
||||
const devices = this.globalData.value.devices.filter(c => this.globalData.value.reportNames.indexOf(c.MachineName) >= 0);
|
||||
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
const item = devices[i];
|
||||
if (!item.canvas) {
|
||||
item.canvas = document.getElementById(`canvas-${item.MachineName}`);
|
||||
if (item.canvas) {
|
||||
item.ctx = item.canvas.getContext('2d');
|
||||
}
|
||||
}
|
||||
if (item.ctx) {
|
||||
const img = item.Screen.img;
|
||||
if (img) {
|
||||
item.ctx.clearRect(0, 0, item.canvas.width, item.canvas.height);
|
||||
item.ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, item.canvas.width, item.canvas.height);
|
||||
item.ctx.lineWidth = 3;
|
||||
item.ctx.font = 'bold 50px Arial';
|
||||
item.ctx.fillStyle = '#000';
|
||||
item.ctx.fillText(`FPS : ${item.Screen.fps} 、LT : ${item.Screen.LastInput}ms`, 50, 70);
|
||||
item.ctx.strokeStyle = '#0f0';
|
||||
item.ctx.strokeText(`FPS : ${item.Screen.fps} 、LT : ${item.Screen.LastInput}ms`, 50, 70);
|
||||
|
||||
item.ctx.lineWidth = 10;
|
||||
let top = (item.canvas.height - 100) / 2;
|
||||
let left = (item.canvas.width - 50 * item.Screen.KeyBoard.length) / 2;
|
||||
item.ctx.font = 'bold 100px Arial';
|
||||
item.ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
||||
item.ctx.fillText(`${item.Screen.KeyBoard}`, left, top);
|
||||
item.ctx.lineWidth = 2;
|
||||
item.ctx.strokeStyle = '#000';
|
||||
item.ctx.strokeText(`${item.Screen.KeyBoard}`, left, top);
|
||||
}
|
||||
for (let j in item) {
|
||||
try {
|
||||
if (item[j] && item[j].draw) {
|
||||
item[j].draw(item.canvas, item.ctx);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(item);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//item.Screen.img = null;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
this.draw();
|
||||
});
|
||||
},
|
||||
subMessage() {
|
||||
subNotifyMsg('/notify/report/screen', (res, param) => {
|
||||
if (this.globalData.value.reportNames.indexOf(res.Name) == -1) return;
|
||||
let item = this.globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
|
||||
if (item) {
|
||||
item.Screen.fpsTimes++;
|
||||
const img = new Image();
|
||||
if (typeof res.Img == 'string') {
|
||||
img.src = `data:image/jpg;base64,${res.Img}`;
|
||||
img.onload = function () {
|
||||
item.Screen.img = img;
|
||||
};
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(res.Img);
|
||||
reader.onload = function (e) {
|
||||
img.src = e.target.result;
|
||||
img.onload = function () {
|
||||
item.Screen.img = img;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
fpsInterval() {
|
||||
this.globalData.value.devices.forEach(item => {
|
||||
item.Screen.fps = item.Screen.fpsTimes;
|
||||
item.Screen.fpsTimes = 0;
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.fpsInterval();
|
||||
}, 1000)
|
||||
},
|
||||
reported: true,
|
||||
reportInterval() {
|
||||
if (this.reported) {
|
||||
this.reported = false;
|
||||
screenUpdate(this.globalData.value.reportNames).then(() => {
|
||||
this.reported = true;
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
}).catch(() => {
|
||||
this.reported = true;
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
});
|
||||
} else {
|
||||
this.reportTimer = setTimeout(() => {
|
||||
this.reportInterval();
|
||||
}, 30);
|
||||
}
|
||||
},
|
||||
update(item, report) {
|
||||
if (report.Screen) {
|
||||
item.Screen.LastInput = report.Screen.LastInput || '0';
|
||||
item.Screen.UserName = report.Screen.UserName || '';
|
||||
item.Screen.KeyBoard = report.Screen.KeyBoard || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
44
cmonitor.web/src/views/device/plugins/usb/Option.vue
Normal file
44
cmonitor.web/src/views/device/plugins/usb/Option.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-col :span="4">
|
||||
<el-switch size="small" @click="handleUSB" :model-value="data.Usb.Value" inline-prompt active-color="#ff0000" active-text="U盘" inactive-text="U盘" />
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { usbUpdate } from '@/apis/usb';
|
||||
import { injectPluginState } from '../../provide'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
export default {
|
||||
sort: 4,
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleUSB = () => {
|
||||
let desc = props.data.Usb.Value ? '确定启用USB吗?' : '确定禁用USB吗?';
|
||||
ElMessageBox.confirm(desc, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
usbUpdate([props.data.MachineName], !props.data.Usb.Value).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
return {
|
||||
data: props.data, handleUSB
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
12
cmonitor.web/src/views/device/plugins/usb/index.js
Normal file
12
cmonitor.web/src/views/device/plugins/usb/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Usb: {
|
||||
Value: false
|
||||
},
|
||||
}
|
||||
},
|
||||
update(item, report) {
|
||||
item.Usb.Value = report.Usb.Value;
|
||||
}
|
||||
}
|
||||
85
cmonitor.web/src/views/device/plugins/volume/BtnRight.vue
Normal file
85
cmonitor.web/src/views/device/plugins/volume/BtnRight.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<a href="javascript:;">
|
||||
<span class="volume" @click="handleVolume">
|
||||
<template v-if="data.Volume.Mute">
|
||||
<el-icon>
|
||||
<Mute />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
<div class="volume-bg" :style="{height:`${data.Volume.Value}%`}">
|
||||
<el-icon class="value">
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</span>
|
||||
<p class="volume-value">{{Math.floor(data.Volume.Value)}}</p>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide'
|
||||
export default {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleVolume = () => {
|
||||
pluginState.value.volume.items = [props.data];
|
||||
pluginState.value.volume.showVolumeSingle = true;
|
||||
}
|
||||
|
||||
return {
|
||||
data: props.data,
|
||||
handleVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.volume-value {
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
span.volume {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: block;
|
||||
|
||||
.el-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.volume-bg {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
height: 0%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.el-icon {
|
||||
color: green;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-button size="small" plain dark @click="handleVolume">音量<el-icon>
|
||||
<Microphone />
|
||||
</el-icon></el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const globalData = injectGlobalData();
|
||||
const handleVolume = () => {
|
||||
pluginState.value.volume.items = globalData.value.devices;
|
||||
pluginState.value.volume.showVolume = true;
|
||||
}
|
||||
|
||||
return {
|
||||
handleVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
21
cmonitor.web/src/views/device/plugins/volume/Index.vue
Normal file
21
cmonitor.web/src/views/device/plugins/volume/Index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<Volume v-if="pluginState.volume.showVolume" v-model="pluginState.volume.showVolume"></Volume>
|
||||
<VolumeSingle v-if="pluginState.volume.showVolumeSingle" v-model="pluginState.volume.showVolumeSingle"></VolumeSingle>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { injectPluginState } from '../../provide';
|
||||
import Volume from './Volume.vue'
|
||||
import VolumeSingle from './VolumeSingle.vue'
|
||||
export default {
|
||||
components: { Volume, VolumeSingle },
|
||||
setup() {
|
||||
const pluginState = injectPluginState();
|
||||
return { pluginState }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
45
cmonitor.web/src/views/device/plugins/volume/Option.vue
Normal file
45
cmonitor.web/src/views/device/plugins/volume/Option.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<el-col :span="4">
|
||||
<el-switch size="small" @click="handleVolumeMute" :model-value="data.Volume.Mute" inline-prompt active-text="静音" inactive-text="静音" />
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setVolumeMute } from '@/apis/volume';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
sort: 0,
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleVolumeMute = () => {
|
||||
let desc = props.data.Volume.Mute ? '确定取消静音吗?' : '确定静音吗?';
|
||||
ElMessageBox.confirm(desc, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
setVolumeMute([props.data.MachineName], !props.data.Volume.Mute).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
data: props.data, handleVolumeMute
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
169
cmonitor.web/src/views/device/plugins/volume/Volume.vue
Normal file
169
cmonitor.web/src/views/device/plugins/volume/Volume.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<el-dialog class="options" title="调节音量" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="command-wrap flex">
|
||||
<div class="items">
|
||||
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="全选">
|
||||
<template #title>
|
||||
<div>
|
||||
<el-button size="small" @click="handleSelectMute">状态选择</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="scope">
|
||||
<span>
|
||||
<span class="name">
|
||||
{{scope.item.MachineName}}
|
||||
</span>
|
||||
<strong class="volume">
|
||||
<template v-if="scope.item.VolumeMute">
|
||||
<el-icon>
|
||||
<Mute />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</template>
|
||||
<strong class="value">{{scope.item.Volume.Value?Math.floor(scope.item.Volume.Value):scope.item.Volume.Value}}%</strong>
|
||||
</strong>
|
||||
</span>
|
||||
</template>
|
||||
</CheckBoxWrap>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="commands">
|
||||
<PrevBoxWrap ref="commands" title="调节音量">
|
||||
<template #wrap>
|
||||
<div class="slider-wrap flex flex-column">
|
||||
<div class="silder flex flex-1">
|
||||
<div class="flex-1">
|
||||
<el-slider @change="handleChangeVolume" v-model="state.volume" vertical height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<el-button @click="handleMute(true)">静音</el-button>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<el-button @click="handleMute(false)">取消静音</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PrevBoxWrap>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleCancel">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
|
||||
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { setVolumeMute, setVolume } from '../../../../apis/volume'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { CheckBoxWrap, PrevBoxWrap },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.volume.items),
|
||||
mute: false,
|
||||
loading: false,
|
||||
volume: 0
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
const handleSelectMute = () => {
|
||||
state.items = globalData.value.devices.filter(c => c.VolumeMute == state.mute);
|
||||
ElMessage.success(`已选中${state.mute ? '静音' : '未静音'}设备`)
|
||||
state.mute = !state.mute;
|
||||
}
|
||||
|
||||
const items = ref(null);
|
||||
const handleMute = (mute) => {
|
||||
let _items = items.value.getData();
|
||||
if (_items.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
setVolumeMute(_items, mute);
|
||||
}
|
||||
const handleChangeVolume = () => {
|
||||
let _items = items.value.getData();
|
||||
if (_items.length == 0) {
|
||||
ElMessage.error('未选择任何设备');
|
||||
return;
|
||||
}
|
||||
setVolume(_items, state.volume / 100);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, globalData, items, handleCancel, handleSelectMute, handleMute, handleChangeVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.command-wrap {
|
||||
height: 60vh;
|
||||
|
||||
.items {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.commands {
|
||||
height: 100%;
|
||||
width: 38%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.volume {
|
||||
font-size: 2rem;
|
||||
padding-left: 1rem;
|
||||
|
||||
.value {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrap {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
.silder {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn+.btn {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<el-dialog title="调节音量" destroy-on-close v-model="state.show" center align-center width="94%">
|
||||
<div class="slider-wrap flex flex-column">
|
||||
<div class="silder flex flex-1">
|
||||
<div class="flex-1">
|
||||
<el-slider @change="handleChangeVolume" v-model="state.volume" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { computed, watch } from '@vue/runtime-core';
|
||||
import { setVolume } from '../../../../apis/volume'
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
props: ['modelValue', 'items'],
|
||||
emits: ['update:modelValue'],
|
||||
components: {},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const pluginState = injectPluginState();
|
||||
const state = reactive({
|
||||
show: props.modelValue,
|
||||
items: computed(() => pluginState.value.volume.items),
|
||||
loading: false,
|
||||
volume: pluginState.value.volume.items[0].Volume.Value
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
const handleCancel = () => {
|
||||
state.show = false;
|
||||
}
|
||||
|
||||
const handleChangeVolume = () => {
|
||||
setVolume(state.items.map(c => c.MachineName), state.volume / 100);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state, globalData, handleCancel, handleChangeVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
.volume {
|
||||
font-size: 2rem;
|
||||
padding-left: 1rem;
|
||||
|
||||
.value {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrap {
|
||||
text-align: center;
|
||||
|
||||
// height: 10rem;
|
||||
.silder {
|
||||
padding: 2rem 4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn+.btn {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
cmonitor.web/src/views/device/plugins/volume/index.js
Normal file
38
cmonitor.web/src/views/device/plugins/volume/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Volume: {
|
||||
Value: 0,
|
||||
Mute: false,
|
||||
MasterPeak: 0,
|
||||
draw(canvas, ctx) {
|
||||
this.MasterPeak -= 1;
|
||||
if (this.MasterPeak < 0) {
|
||||
this.MasterPeak = 0;
|
||||
return;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(0, canvas.height - 10, this.MasterPeak / 100 * canvas.width, 10);
|
||||
ctx.closePath();
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
state: {
|
||||
volume: {
|
||||
showVolume: false,
|
||||
showVolumeSingle: false,
|
||||
items: []
|
||||
}
|
||||
},
|
||||
init() {
|
||||
},
|
||||
update(item, report) {
|
||||
item.Volume.Value = report.Volume.Value;
|
||||
item.Volume.Mute = report.Volume.Mute;
|
||||
if (report.Volume.MasterPeak > item.Volume.MasterPeak) {
|
||||
item.Volume.MasterPeak = report.Volume.MasterPeak;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
cmonitor.web/src/views/device/plugins/wallpaper/Option.vue
Normal file
41
cmonitor.web/src/views/device/plugins/wallpaper/Option.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<el-col :span="4">
|
||||
<el-switch size="small" @click="handleWallpaper" :model-value="data.Wallpaper.Value" inline-prompt active-text="壁纸" inactive-text="壁纸" />
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { wallpaperUpdate } from '@/apis/wallpaper';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { injectPluginState } from '../../provide';
|
||||
export default {
|
||||
sort: 3,
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
|
||||
const pluginState = injectPluginState();
|
||||
const handleWallpaper = () => {
|
||||
let desc = props.data.Wallpaper.Value ? '确定关闭壁纸吗?' : '确定开启壁纸吗?';
|
||||
ElMessageBox.confirm(desc, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
wallpaperUpdate([props.data.MachineName], !props.data.Wallpaper.Value, `${window.location.origin}/bg.jpg`).then((res) => {
|
||||
if (res) {
|
||||
ElMessage.success('操作成功');
|
||||
} else {
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('操作失败');
|
||||
});
|
||||
|
||||
}).catch(() => { });
|
||||
}
|
||||
return { data: props.data, handleWallpaper }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
12
cmonitor.web/src/views/device/plugins/wallpaper/index.js
Normal file
12
cmonitor.web/src/views/device/plugins/wallpaper/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
field() {
|
||||
return {
|
||||
Wallpaper: {
|
||||
Value: false
|
||||
},
|
||||
}
|
||||
},
|
||||
update(item, report) {
|
||||
item.Wallpaper.Value = report.Wallpaper.Value;
|
||||
}
|
||||
}
|
||||
11
cmonitor.web/src/views/device/provide.js
Normal file
11
cmonitor.web/src/views/device/provide.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { inject, provide, ref } from "vue"
|
||||
|
||||
const pluginStateSymbol = Symbol();
|
||||
export const providePluginState = (data) => {
|
||||
const state = ref(data);
|
||||
provide(pluginStateSymbol, state);
|
||||
return state;
|
||||
}
|
||||
export const injectPluginState = () => {
|
||||
return inject(pluginStateSymbol);
|
||||
}
|
||||
53
cmonitor.web/src/views/device/wraps/FootMenu.vue
Normal file
53
cmonitor.web/src/views/device/wraps/FootMenu.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="foot-wrap">
|
||||
<ul class="flex">
|
||||
<template v-for="(item,index) in footMenuModules" :key="index">
|
||||
<li>
|
||||
<component :is="item"></component>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
setup() {
|
||||
const footMenuFiles = require.context('../plugins/', true, /FootMenu\.vue/);
|
||||
const footMenuModules = footMenuFiles.keys().map(c => footMenuFiles(c).default).sort((a, b) => a.sort - b.sort);
|
||||
return { footMenuModules }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.el-dialog.is-align-center.options {
|
||||
margin-top: 3vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.foot-wrap {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
ul li {
|
||||
width: 33.333333%;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
padding: 1.7rem 0;
|
||||
font-size: 1.6rem;
|
||||
display: block;
|
||||
color: #ebffef;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
cmonitor.web/src/views/device/wraps/FootOptions.vue
Normal file
91
cmonitor.web/src/views/device/wraps/FootOptions.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="foot-options-wrap flex">
|
||||
<el-button size="default" plain dark @click="handleRefresh">
|
||||
<el-icon>
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<span class="flex-1"></span>
|
||||
<div class="options-btn">
|
||||
<p>
|
||||
<template v-for="(item,index) in footOptionTopModules" :key="index">
|
||||
<component :is="item"></component>
|
||||
</template>
|
||||
</p>
|
||||
<p>
|
||||
<template v-for="(item,index) in footOptionBottomModules" :key="index">
|
||||
<component :is="item"></component>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<span class="flex-1"></span>
|
||||
<el-button size="default" plain dark @click="handleUpdate">{{username}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { injectGlobalData } from '@/views/provide';
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const footOptionTopFiles = require.context('../plugins/', true, /FootOptionTop\.vue/);
|
||||
const footOptionTopModules = footOptionTopFiles.keys().map(c => footOptionTopFiles(c).default);
|
||||
|
||||
const footOptionBottomFiles = require.context('../plugins/', true, /FootOptionBottom\.vue/);
|
||||
const footOptionBottomModules = footOptionBottomFiles.keys().map(c => footOptionBottomFiles(c).default);
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const username = computed(() => globalData.value.username);
|
||||
const handleRefresh = () => {
|
||||
window.location.reload();
|
||||
}
|
||||
const handleUpdate = () => {
|
||||
ElMessageBox.confirm('是否确定重选角色?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
globalData.value.username = '';
|
||||
localStorage.setItem('username', '');
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
return {
|
||||
footOptionTopModules, footOptionBottomModules, username, handleRefresh, handleUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.foot-options-wrap {
|
||||
text-align: center;
|
||||
padding: 0.6rem;
|
||||
|
||||
.el-button {
|
||||
background-color: rgba(58, 74, 102, 0.5);
|
||||
border-color: rgba(58, 74, 102, 0.2);
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
margin: 0 0.6rem;
|
||||
}
|
||||
|
||||
.options-btn {
|
||||
.el-button+.el-button {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-top: 0.6rem;
|
||||
|
||||
&:nth-child(1) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
214
cmonitor.web/src/views/device/wraps/Item.vue
Normal file
214
cmonitor.web/src/views/device/wraps/Item.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="device-item" :style="data.style">
|
||||
<dl>
|
||||
<dt>
|
||||
<div class="bg"></div>
|
||||
<div class="value flex">
|
||||
<span class="name" :class="{connected:data.Connected}">
|
||||
<span class="machine-mame">{{data.MachineName}}</span>
|
||||
<i class="user-name" v-if="data.Screen.UserName"> - {{data.Screen.UserName}}</i>
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
<template v-for="(item,index) in titleRightModules" :key="index">
|
||||
<component :is="item" :data="data"></component>
|
||||
</template>
|
||||
</div>
|
||||
</dt>
|
||||
<dd class="img">
|
||||
<div class="inner">
|
||||
<canvas v-if="data.Connected" width="1920" height="1080" :id="`canvas-${data.MachineName}`"></canvas>
|
||||
<template v-for="(item,index) in screenModules" :key="index">
|
||||
<component :is="item" :data="data"></component>
|
||||
</template>
|
||||
<div class="btns flex">
|
||||
<div class="left">
|
||||
<template v-for="(item,index) in btnLeftModules" :key="index">
|
||||
<component :is="item" :data="data"></component>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="right">
|
||||
<template v-for="(item,index) in btnRightModules" :key="index">
|
||||
<component :is="item" :data="data"></component>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
<dd class="options">
|
||||
<el-row>
|
||||
<template v-for="(item,index) in optionModules" :key="index">
|
||||
<component :is="item" :data="data"></component>
|
||||
</template>
|
||||
</el-row>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const data = props.data;
|
||||
|
||||
|
||||
const titleRightFiles = require.context('../plugins/', true, /TitleRight\.vue/);
|
||||
const titleRightModules = titleRightFiles.keys().map(c => titleRightFiles(c).default);
|
||||
|
||||
const screenFiles = require.context('../plugins/', true, /Screen\.vue/);
|
||||
const screenModules = screenFiles.keys().map(c => screenFiles(c).default);
|
||||
|
||||
const btnLeftFiles = require.context('../plugins/', true, /BtnLeft\.vue/);
|
||||
const btnLeftModules = btnLeftFiles.keys().map(c => btnLeftFiles(c).default);
|
||||
|
||||
const btnRightFiles = require.context('../plugins/', true, /BtnRight\.vue/);
|
||||
const btnRightModules = btnRightFiles.keys().map(c => btnRightFiles(c).default);
|
||||
|
||||
const optionFiles = require.context('../plugins/', true, /\/Option\.vue/);
|
||||
const optionModules = optionFiles.keys().map(c => optionFiles(c).default).sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
|
||||
return {
|
||||
data, titleRightModules, screenModules, btnLeftModules, btnRightModules, optionModules
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.device-item {
|
||||
border: 1px solid #ddd;
|
||||
font-size: 1.6rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
width: 98%;
|
||||
margin: 0 auto 1rem auto;
|
||||
position: relative;
|
||||
transition: 0.3s;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
dt {
|
||||
padding: 0.6rem 0.6rem 0 0.6rem;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
span.name {
|
||||
line-height: 2rem;
|
||||
|
||||
&.connected {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.machine-mame {
|
||||
}
|
||||
|
||||
i.user-name {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dd.img {
|
||||
padding: 0.6rem;
|
||||
position: relative;
|
||||
font-size: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
padding-bottom: 56.25%;
|
||||
width: 0.1px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: absolute;
|
||||
left: 0.6rem;
|
||||
top: 0.6rem;
|
||||
right: 0.6rem;
|
||||
bottom: 0.6rem;
|
||||
overflow: hidden;
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btns {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 10%;
|
||||
|
||||
.left {
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
|
||||
.right {
|
||||
padding-right: 0.6rem;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
a {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
text-align: center;
|
||||
line-height: 2.8rem;
|
||||
margin-bottom: 0.6rem;
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #3e5a6e;
|
||||
box-shadow: 0 0 4px rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
color: #3e5a6e;
|
||||
transition: 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 4px 2px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dd.options {
|
||||
padding: 0 0.6rem 0.6rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
|
||||
.el-col {
|
||||
text-align: right;
|
||||
|
||||
.el-switch {
|
||||
--el-switch-off-color: #ccc;
|
||||
--el-switch-on-color: #69b56c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
137
cmonitor.web/src/views/device/wraps/Items.vue
Normal file
137
cmonitor.web/src/views/device/wraps/Items.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<template v-if="devices.length > 0">
|
||||
<template v-for="(item) in devices" :key="item.MachineName">
|
||||
<Item :data="item"></Item>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-empty description="或许你应该先去选择管理设备"></el-empty>
|
||||
</template>
|
||||
</template>
|
||||
<script>
|
||||
import Item from './Item.vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { getList } from '../../../apis/signin.js'
|
||||
import { nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import { injectGlobalData } from '@/views/provide'
|
||||
import { subNotifyMsg } from '@/apis/request'
|
||||
export default {
|
||||
components: { Item },
|
||||
setup() {
|
||||
|
||||
const files = require.context('../plugins/', true, /index\.js/);
|
||||
const pluginSettings = files.keys().map(c => files(c).default);
|
||||
pluginSettings.forEach((item) => {
|
||||
item.init && item.init();
|
||||
});
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
|
||||
const getData = () => {
|
||||
getList().then((res) => {
|
||||
globalData.value.allDevices = res.map(c => {
|
||||
return pluginSettings.reduce((result, item, index) => {
|
||||
if (item.field) {
|
||||
result = Object.assign(result, item.field());
|
||||
}
|
||||
return result;
|
||||
}, c);
|
||||
}).sort((a, b) => a.MachineName < b.MachineName ? -1 : 1);
|
||||
nextTick(() => {
|
||||
updateVisibleItems();
|
||||
});
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const devices = computed(() => {
|
||||
nextTick(() => {
|
||||
updateVisibleItems();
|
||||
});
|
||||
return globalData.value.devices;
|
||||
});
|
||||
|
||||
const subMessage = () => {
|
||||
subNotifyMsg('/notify/report/report', (res) => {
|
||||
if (globalData.value.reportNames.indexOf(res.Name) == -1) return;
|
||||
if (typeof res.Report == 'string') {
|
||||
res.Report = JSON.parse(res.Report);
|
||||
}
|
||||
let item = globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
|
||||
if (item) {
|
||||
pluginSettings.forEach(plugin => {
|
||||
plugin.update && plugin.update(item, res.Report);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const listWrapScrollListener = () => {
|
||||
nextTick(() => {
|
||||
document.querySelector('#device-list-wrap').querySelector('.items').addEventListener('scroll', updateVisibleItems);
|
||||
});
|
||||
}
|
||||
const listWrapRemoveScrollListener = () => {
|
||||
try {
|
||||
document.querySelector('#device-list-wrap').querySelector('.items').removeEventListener('scroll', updateVisibleItems);
|
||||
} catch (e) { }
|
||||
}
|
||||
const updateVisibleItems = () => {
|
||||
try {
|
||||
const itemsWrap = document.querySelector('#device-list-wrap').querySelector('.items');
|
||||
const scrollTop = itemsWrap.scrollTop;
|
||||
const items = itemsWrap.querySelectorAll('.device-item');
|
||||
if (items.length == 0) return;
|
||||
const wrapHeight = itemsWrap.offsetHeight;
|
||||
|
||||
const doms = [...items].map((item, index) => {
|
||||
const topLine = item.offsetTop - scrollTop;
|
||||
const middleLine = topLine + item.offsetHeight / 2;
|
||||
const offset = Math.abs(middleLine - wrapHeight / 2);
|
||||
return { dom: item, index: index, offset: offset };
|
||||
});
|
||||
|
||||
//哪个是在最中间的
|
||||
const middleItem = doms.sort((a, b) => a.offset - b.offset)[0];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let style = 'z-index:9;background-color:#fff;';
|
||||
const dist = Math.abs((middleItem.index - i));
|
||||
const opacity = 1 - Math.abs((middleItem.index - i)) / 10 * 2;
|
||||
const translateZ = 100 + (dist * 100);
|
||||
if (i < middleItem.index && middleItem.index > 1) {
|
||||
style = `background-color:rgba(255,255,255,${opacity});z-index:8;transform: translateZ(-${translateZ}px) `;
|
||||
style += `translateY(30px);`;
|
||||
} else if (i > middleItem.index && middleItem.index < items.length - 2) {
|
||||
style = `background-color:rgba(255,255,255,${opacity});z-index:8;transform: translateZ(-${translateZ}px) `;
|
||||
style += `translateY(-30px);`;
|
||||
}
|
||||
items[i].style = style;
|
||||
}
|
||||
|
||||
//有哪些需要报告
|
||||
const reportDoms = doms.filter(item => item.index >= middleItem.index - 2 && item.index <= middleItem.index + 2).map(c => c.index);
|
||||
globalData.value.reportNames = globalData.value.devices
|
||||
.filter((value, index) => reportDoms.indexOf(index) >= 0)
|
||||
.map(c => c.MachineName);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
getData();
|
||||
listWrapScrollListener();
|
||||
updateVisibleItems();
|
||||
subMessage();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
listWrapRemoveScrollListener();
|
||||
});
|
||||
|
||||
return {
|
||||
devices
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped></style>
|
||||
27
cmonitor.web/src/views/provide.js
Normal file
27
cmonitor.web/src/views/provide.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { computed, inject, provide, ref } from "vue";
|
||||
|
||||
const globalDataSymbol = Symbol();
|
||||
|
||||
export const provideGlobalData = () => {
|
||||
const globalData = ref({
|
||||
username: '',
|
||||
publicUserName: 'snltty',
|
||||
usernames: {},
|
||||
connected: false,
|
||||
updateFlag: 0,
|
||||
allDevices: [],
|
||||
devices: computed(() => {
|
||||
const user = globalData.value.usernames[globalData.value.username];
|
||||
if (user) {
|
||||
return globalData.value.allDevices.filter(c => user.Devices.indexOf(c.MachineName) >= 0);
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
reportNames: []
|
||||
});
|
||||
provide(globalDataSymbol, globalData);
|
||||
return globalData;
|
||||
}
|
||||
export const injectGlobalData = () => {
|
||||
return inject(globalDataSymbol);
|
||||
}
|
||||
6
cmonitor.web/vue.config.js
Normal file
6
cmonitor.web/vue.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
productionSourceMap: process.env.NODE_ENV === 'production' ? false : true,
|
||||
outputDir: '../cmonitor/web',
|
||||
transpileDependencies: true
|
||||
})
|
||||
Reference in New Issue
Block a user