feat: 进程管理增加 tcp udp 网络列表 (#1548)

This commit is contained in:
zhengkunwang223 2023-07-05 17:08:19 +08:00 committed by GitHub
parent 12eeb6503c
commit 759382bcfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 306 additions and 4 deletions

View File

@ -17,6 +17,7 @@ type WsInput struct {
DownloadProgress
PsProcessConfig
SSHSessionConfig
NetConfig
}
type DownloadProgress struct {
@ -34,6 +35,12 @@ type SSHSessionConfig struct {
LoginIP string `json:"loginIP"`
}
type NetConfig struct {
Port uint32 `json:"port"`
ProcessName string `json:"processName"`
ProcessID int32 `json:"processID"`
}
type PsProcessData struct {
PID int32 `json:"PID"`
Name string `json:"name"`
@ -71,7 +78,8 @@ type processConnect struct {
Status string `json:"status"`
Laddr net.Addr `json:"localaddr"`
Raddr net.Addr `json:"remoteaddr"`
PID string `json:"PID"`
PID int32 `json:"PID"`
Name string `json:"name"`
}
type sshSession struct {
@ -108,6 +116,12 @@ func ProcessData(c *Client, inputMsg []byte) {
return
}
c.Msg <- res
case "net":
res, err := getNetConnections(wsInput.NetConfig)
if err != nil {
return
}
c.Msg <- res
}
}
@ -295,3 +309,43 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
res, err = json.Marshal(result)
return
}
var netTypes = [...]string{"tcp", "udp"}
func getNetConnections(config NetConfig) (res []byte, err error) {
var (
result []processConnect
proc *process.Process
)
for _, netType := range netTypes {
connections, _ := net.Connections(netType)
if err == nil {
for _, conn := range connections {
if config.ProcessID > 0 && config.ProcessID != conn.Pid {
continue
}
proc, err = process.NewProcess(conn.Pid)
if err == nil {
name, _ := proc.Name()
if name != "" && config.ProcessName != "" && !strings.Contains(name, config.ProcessName) {
continue
}
if config.Port > 0 && config.Port != conn.Laddr.Port && config.Port != conn.Raddr.Port {
continue
}
result = append(result, processConnect{
Type: netType,
Status: conn.Status,
Laddr: conn.Laddr,
Raddr: conn.Raddr,
PID: conn.Pid,
Name: name,
})
}
}
}
}
res, err = json.Marshal(result)
return
}

View File

@ -249,6 +249,7 @@ const message = {
runtime: 'Runtime',
processManage: 'Process',
process: 'Process',
network: 'Network',
},
home: {
overview: 'Overview',
@ -1417,7 +1418,7 @@ const message = {
ipv6: 'Listen IPV6',
leechReturnError: 'Please fill in the HTTP status code',
selectAcme: 'Select Acme account',
imported: 'Imported',
imported: 'Manually Created',
importType: 'Import Type',
pasteSSL: 'Paste code',
localSSL: 'Select local file',

View File

@ -247,6 +247,7 @@ const message = {
runtime: '運行環境',
processManage: '進程管理',
process: '進程',
network: '網絡',
},
home: {
overview: '概覽',
@ -1348,7 +1349,7 @@ const message = {
ipv6: '監聽 IPV6 端口',
leechReturnError: '請填寫 HTTP 狀態碼',
selectAcme: '選擇 Acme 賬號',
imported: '已導入',
imported: '手動創建',
importType: '導入方式',
pasteSSL: '粘貼代碼',
localSSL: '選擇本地文件',

View File

@ -247,6 +247,7 @@ const message = {
runtime: '运行环境',
processManage: '进程管理',
process: '进程',
network: '网络',
},
home: {
overview: '概览',
@ -1354,7 +1355,7 @@ const message = {
ipv6: '监听 IPV6 端口',
leechReturnError: '请填写 HTTP 状态码',
selectAcme: '选择 acme 账号',
imported: '已导入',
imported: '手动创建',
importType: '导入方式',
pasteSSL: '粘贴代码',
localSSL: '选择本地文件',
@ -1527,6 +1528,7 @@ const message = {
raddr: '目标地址/端口',
stopProcess: '结束',
stopProcessWarn: '是否确定结束此进程 (PID:{0})此操作不可回滚',
processName: '进程名称',
},
};
export default {

View File

@ -80,6 +80,16 @@ const hostRouter = {
requiresAuth: false,
},
},
{
path: '/hosts/process/network',
name: 'ProcessNetwork',
hidden: true,
component: () => import('@/views/host/process/network/index.vue'),
meta: {
activeMenu: '/hosts/process/process',
requiresAuth: false,
},
},
{
path: '/hosts/ssh/ssh',
name: 'SSH',

View File

@ -16,5 +16,9 @@ const buttons = [
label: i18n.global.t('menu.process'),
path: '/hosts/process/process',
},
{
label: i18n.global.t('menu.network'),
path: '/hosts/process/network',
},
];
</script>

View File

@ -0,0 +1,230 @@
<template>
<div>
<FireRouter />
<LayoutContent :title="$t('menu.network')" v-loading="loading">
<template #toolbar>
<el-row>
<el-col :span="24">
<div style="width: 100%">
<el-form-item style="float: right">
<el-row :gutter="20">
<el-col :span="8">
<div class="search-button">
<el-input
typpe="number"
v-model.number="netSearch.processID"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('process.pid')"
></el-input>
</div>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
v-model.trim="netSearch.processName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('process.processName')"
></el-input>
</div>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
type="number"
v-model.number="netSearch.port"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.table.port')"
></el-input>
</div>
</el-col>
</el-row>
</el-form-item>
</div>
</el-col>
</el-row>
</template>
<template #main>
<ComplexTable :data="data" @sort-change="changeSort" @filter-change="changeFilter" ref="tableRef">
<el-table-column :label="$t('commons.table.type')" fix prop="type"></el-table-column>
<el-table-column :label="'PID'" fix prop="PID" max-width="60px" sortable></el-table-column>
<el-table-column
:label="$t('process.processName')"
fix
prop="name"
min-width="120px"
></el-table-column>
<el-table-column prop="localaddr" :label="$t('process.laddr')">
<template #default="{ row }">
<span>{{ row.localaddr.ip }}</span>
<span v-if="row.localaddr.port > 0">:{{ row.localaddr.port }}</span>
</template>
</el-table-column>
<el-table-column prop="remoteaddr" :label="$t('process.raddr')">
<template #default="{ row }">
<span>{{ row.remoteaddr.ip }}</span>
<span v-if="row.remoteaddr.port > 0">:{{ row.remoteaddr.port }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
column-key="status"
:label="$t('app.status')"
:filters="[
{ text: 'LISTEN', value: 'LISTEN' },
{ text: 'ESTABLISHED', value: 'ESTABLISHED' },
{ text: 'TIME_WAIT', value: 'TIME_WAIT' },
{ text: 'CLOSE_WAIT', value: 'CLOSE_WAIT' },
{ text: 'NONE', value: 'NONE' },
]"
:filter-method="filterStatus"
:filtered-value="sortConfig.filters"
></el-table-column>
</ComplexTable>
</template>
</LayoutContent>
</div>
</template>
<script setup lang="ts">
import FireRouter from '@/views/host/process/index.vue';
import { ref, onMounted, onUnmounted, nextTick, reactive } from 'vue';
interface SortStatus {
prop: '';
order: '';
filters: [];
}
const sortConfig: SortStatus = {
prop: '',
order: '',
filters: [],
};
const netSearch = reactive({
type: 'net',
processID: undefined,
processName: '',
port: undefined,
});
let processSocket = ref(null) as unknown as WebSocket;
const data = ref([]);
const loading = ref(false);
const tableRef = ref();
const oldData = ref([]);
const changeSort = ({ prop, order }) => {
sortConfig.prop = prop;
sortConfig.order = order;
};
const changeFilter = (filters: any) => {
if (filters.status && filters.status.length > 0) {
sortConfig.filters = filters.status;
data.value = filterByStatus();
sortTable();
} else {
data.value = oldData.value;
sortConfig.filters = [];
sortTable();
}
};
const isWsOpen = () => {
const readyState = processSocket && processSocket.readyState;
return readyState === 1;
};
const closeSocket = () => {
if (isWsOpen()) {
processSocket && processSocket.close();
}
};
const filterStatus = (value: string, row: any) => {
return row.status === value;
};
const onOpenProcess = () => {};
const onMessage = (message: any) => {
let result: any[] = JSON.parse(message.data);
oldData.value = result;
data.value = filterByStatus();
sortTable();
loading.value = false;
};
const filterByStatus = () => {
console.log(sortConfig.filters);
if (sortConfig.filters.length > 0) {
const newData = oldData.value.filter((re: any) => {
return (sortConfig.filters as string[]).indexOf(re.status) > -1;
});
return newData;
} else {
return oldData.value;
}
};
const sortTable = () => {
if (sortConfig.prop != '' && sortConfig.order != '') {
nextTick(() => {
tableRef.value?.sort(sortConfig.prop, sortConfig.order);
});
}
};
const onerror = () => {};
const onClose = () => {};
const initProcess = () => {
let href = window.location.href;
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
let ipLocal = href.split('//')[1].split('/')[0];
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/process/ws`);
processSocket.onopen = onOpenProcess;
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;
processSocket.onclose = onClose;
loading.value = true;
search();
sendMsg();
};
const sendMsg = () => {
setInterval(() => {
search();
}, 3000);
};
const search = () => {
if (isWsOpen()) {
if (typeof netSearch.processID === 'string') {
netSearch.processID = undefined;
}
if (typeof netSearch.port === 'string') {
netSearch.port = undefined;
}
processSocket.send(JSON.stringify(netSearch));
}
};
onMounted(() => {
initProcess();
});
onUnmounted(() => {
closeSocket();
});
</script>