mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-04 12:59:52 +08:00
feat: 进程管理增加 tcp udp 网络列表 (#1548)
This commit is contained in:
parent
12eeb6503c
commit
759382bcfb
@ -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
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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: '選擇本地文件',
|
||||
|
@ -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 {
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
230
frontend/src/views/host/process/network/index.vue
Normal file
230
frontend/src/views/host/process/network/index.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user