perf: Optimize Process-Network Page Performance (#10456)

Refs https://github.com/1Panel-dev/1Panel/issues/6900
This commit is contained in:
CityFun 2025-09-23 18:32:05 +08:00 committed by GitHub
parent 657b2ddc8a
commit 563ea985a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 170 additions and 131 deletions

View file

@ -44,7 +44,7 @@
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
"md-editor-v3": "^2.11.3",
"monaco-editor": "^0.50.0",
"monaco-editor": "^0.53.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^1.6.1",

View file

@ -3,77 +3,58 @@
<FireRouter />
<LayoutContent :title="$t('menu.network', 2)" v-loading="loading">
<template #rightToolBar>
<div class="w-full">
<el-form-item class="float-right">
<el-row :gutter="20">
<el-col :span="8">
<TableSearch
@search="search()"
:placeholder="$t('process.pid')"
v-model:searchName="netSearch.processID"
/>
</el-col>
<el-col :span="8">
<TableSearch
@search="search()"
:placeholder="$t('process.processName')"
v-model:searchName="netSearch.processName"
/>
</el-col>
<el-col :span="8">
<TableSearch
@search="search()"
:placeholder="$t('commons.table.port')"
v-model:searchName="netSearch.port"
/>
</el-col>
</el-row>
</el-form-item>
<div class="w-full flex justify-end items-center gap-5">
<el-select
v-model="filters"
:placeholder="$t('commons.table.status')"
clearable
multiple
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="2"
@change="search()"
class="p-w-400"
>
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
<TableSearch
@search="search()"
:placeholder="$t('process.pid')"
v-model:searchName="netSearch.processID"
/>
<TableSearch
@search="search()"
:placeholder="$t('process.processName')"
v-model:searchName="netSearch.processName"
/>
<TableSearch
@search="search()"
:placeholder="$t('commons.table.port')"
v-model:searchName="netSearch.port"
/>
</div>
</template>
<template #main>
<ComplexTable
:data="data"
@sort-change="changeSort"
@filter-change="changeFilter"
ref="tableRef"
:heightDiff="220"
>
<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>
<div class="!h-[900px]">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
:sort-by="sortState"
@column-sort="changeSort"
/>
</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('commons.table.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>
</el-auto-resizer>
</div>
</template>
</LayoutContent>
</div>
@ -81,20 +62,20 @@
<script setup lang="ts">
import FireRouter from '@/views/host/process/index.vue';
import { ref, onMounted, onUnmounted, nextTick, reactive } from 'vue';
import { ref, onMounted, onUnmounted, reactive, watch } from 'vue';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
import { SortBy, TableV2SortOrder } from 'element-plus';
import i18n from '@/lang';
interface SortStatus {
prop: '';
order: '';
filters: [];
}
const sortConfig: SortStatus = {
prop: '',
order: '',
filters: [],
};
const statusOptions = [
{ text: 'LISTEN', value: 'LISTEN' },
{ text: 'ESTABLISHED', value: 'ESTABLISHED' },
{ text: 'TIME_WAIT', value: 'TIME_WAIT' },
{ text: 'CLOSE_WAIT', value: 'CLOSE_WAIT' },
{ text: 'NONE', value: 'NONE' },
];
const globalStore = GlobalStore();
const netSearch = reactive({
type: 'net',
@ -104,70 +85,129 @@ const netSearch = reactive({
});
let processSocket = ref(null) as unknown as WebSocket;
const data = ref([]);
const data = ref<any[]>([]);
const oldData = ref<any[]>([]);
const loading = ref(false);
const tableRef = ref();
const oldData = ref([]);
const changeSort = ({ prop, order }) => {
sortConfig.prop = prop;
sortConfig.order = order;
const sortState = ref<SortBy>({
key: 'PID',
order: TableV2SortOrder.ASC,
});
const filters = ref<string[]>([]);
const sortByNum = (a: any, b: any, prop: string): number => {
const aVal = parseFloat(a[prop]) || 0;
const bVal = parseFloat(b[prop]) || 0;
return aVal - bVal;
};
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 columns = ref([
{
key: 'type',
title: i18n.global.t('commons.table.type'),
dataKey: 'type',
width: 220,
},
{
key: 'PID',
title: 'PID',
dataKey: 'PID',
width: 220,
sortable: true,
sortMethod: sortByNum,
},
{
key: 'name',
title: i18n.global.t('process.processName'),
dataKey: 'name',
width: 300,
},
{
key: 'localaddr',
title: i18n.global.t('process.laddr'),
dataKey: 'localaddr',
width: 350,
cellRenderer: ({ rowData }) => {
const addr = rowData.localaddr;
return addr?.ip ? `${addr.ip}${addr.port > 0 ? ':' + addr.port : ''}` : '';
},
},
{
key: 'remoteaddr',
title: i18n.global.t('process.raddr'),
dataKey: 'remoteaddr',
width: 350,
cellRenderer: ({ rowData }) => {
const addr = rowData.remoteaddr;
return addr?.ip ? `${addr.ip}${addr.port > 0 ? ':' + addr.port : ''}` : '';
},
},
{
key: 'status',
title: i18n.global.t('commons.table.status'),
dataKey: 'status',
width: 380,
cellRenderer: ({ rowData }) => rowData.status,
},
]);
const isWsOpen = () => {
const readyState = processSocket && processSocket.readyState;
return readyState === 1;
};
const closeSocket = () => {
if (isWsOpen()) {
processSocket && processSocket.close();
}
};
watch(
[sortState, oldData, filters],
([newState, newData, newFilters]) => {
if (!newData?.length) return;
const filterStatus = (value: string, row: any) => {
return row.status === value;
};
let filtered = newData;
if (newFilters.length > 0) {
filtered = filtered.filter((row) => newFilters.includes(row.status));
}
const onOpenProcess = () => {};
const onMessage = (message: any) => {
let result: any[] = JSON.parse(message.data);
oldData.value = result;
data.value = filterByStatus();
sortTable();
loading.value = false;
const { key, order } = newState ?? {};
if (!key || !order) {
data.value = filtered;
return;
}
const currCol = columns.value.find((c) => c.key === key);
if (!currCol) {
data.value = filtered;
return;
}
const sortMethod = currCol.sortMethod ?? sortByNum;
data.value = filtered.slice().sort((a, b) => {
const res = (sortMethod as any)(a, b, currCol.dataKey);
return order === TableV2SortOrder.ASC ? res : -res;
});
},
{ immediate: true },
);
const changeSort = ({ key, order }) => {
if (!order) order = TableV2SortOrder.ASC;
sortState.value = { key, order };
};
const filterByStatus = () => {
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;
if (filters.value.length > 0) {
return oldData.value.filter((row) => filters.value.includes(row.status));
}
return oldData.value;
};
const sortTable = () => {
if (sortConfig.prop != '' && sortConfig.order != '') {
nextTick(() => {
tableRef.value?.sort(sortConfig.prop, sortConfig.order);
});
}
const isWsOpen = () => processSocket && processSocket.readyState === 1;
const closeSocket = () => {
if (isWsOpen()) processSocket.close();
};
const onOpenProcess = () => {
loading.value = true;
processSocket.send(JSON.stringify(netSearch));
};
const onMessage = (message: any) => {
oldData.value = JSON.parse(message.data);
data.value = filterByStatus();
loading.value = false;
};
const onerror = () => {};
const onClose = () => {};
@ -181,7 +221,7 @@ const initProcess = () => {
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;
processSocket.onclose = onClose;
loading.value = true;
search();
sendMsg();
};
@ -207,7 +247,6 @@ const search = () => {
onMounted(() => {
initProcess();
});
onUnmounted(() => {
closeSocket();
});