Skip to content

Commit 38ab0d9

Browse files
authored
feat: Implement process management and network data handling (#11270)
- Introduced ProcessStore for managing process and network data via WebSocket. - Enhanced TableSearch component to synchronize search parameters with props. - Updated network and process views to utilize ProcessStore for data fetching and state management. - Improved data filtering and sorting in network and process views. - Added WebSocket connection management with polling for real-time updates.
1 parent 3898567 commit 38ab0d9

File tree

5 files changed

+441
-174
lines changed

5 files changed

+441
-174
lines changed

frontend/src/components/table-search/index.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</template>
1616

1717
<script setup lang="ts">
18-
import { ref } from 'vue';
18+
import { ref, watch } from 'vue';
1919
defineOptions({ name: 'TableSearch' });
2020
2121
const emit = defineEmits(['search', 'update:searchName']);
@@ -26,8 +26,22 @@ const props = defineProps({
2626
type: Boolean,
2727
default: false,
2828
},
29+
searchName: {
30+
type: [String, Number],
31+
default: undefined,
32+
},
2933
});
3034
35+
watch(
36+
() => props.searchName,
37+
(newVal) => {
38+
if (searchInfo.value !== newVal) {
39+
searchInfo.value = newVal;
40+
}
41+
},
42+
{ immediate: true },
43+
);
44+
3145
const search = () => {
3246
emit('update:searchName', searchInfo.value);
3347
emit('search');

frontend/src/store/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import GlobalStore from './modules/global';
44
import MenuStore from './modules/menu';
55
import TabsStore from './modules/tabs';
66
import TerminalStore from './modules/terminal';
7+
import ProcessStore from './modules/process';
78

89
const pinia = createPinia();
910
pinia.use(piniaPluginPersistedstate);
1011

11-
export { GlobalStore, MenuStore, TabsStore, TerminalStore };
12+
export { GlobalStore, MenuStore, TabsStore, TerminalStore, ProcessStore };
1213

1314
export default pinia;
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
import { defineStore } from 'pinia';
2+
import { ref, reactive } from 'vue';
3+
4+
export interface PsSearch {
5+
type: 'ps';
6+
pid: number | undefined;
7+
username: string;
8+
name: string;
9+
}
10+
11+
export interface NetSearch {
12+
type: 'net';
13+
processID: number | undefined;
14+
processName: string;
15+
port: number | undefined;
16+
}
17+
18+
export const ProcessStore = defineStore('ProcessStore', () => {
19+
let websocket: WebSocket | null = null;
20+
let pollingTimer: ReturnType<typeof setInterval> | null = null;
21+
let disconnectTimer: ReturnType<typeof setTimeout> | null = null;
22+
23+
let connectionRefCount = 0;
24+
25+
const isConnected = ref(false);
26+
const isConnecting = ref(false);
27+
28+
const psData = ref<any[]>([]);
29+
const psLoading = ref(false);
30+
const psSearch = reactive<PsSearch>({
31+
type: 'ps',
32+
pid: undefined,
33+
username: '',
34+
name: '',
35+
});
36+
37+
const netData = ref<any[]>([]);
38+
const netLoading = ref(false);
39+
const netSearch = reactive<NetSearch>({
40+
type: 'net',
41+
processID: undefined,
42+
processName: '',
43+
port: undefined,
44+
});
45+
46+
let pendingRequestType: 'ps' | 'net' | null = null;
47+
48+
let queuedRequestType: 'ps' | 'net' | null = null;
49+
50+
const isPsFetching = ref(false);
51+
const isNetFetching = ref(false);
52+
53+
const activePollingType = ref<'ps' | 'net' | null>(null);
54+
55+
const isWsOpen = () => {
56+
return websocket && websocket.readyState === WebSocket.OPEN;
57+
};
58+
59+
const onOpen = () => {
60+
isConnected.value = true;
61+
isConnecting.value = false;
62+
};
63+
64+
const doSendMessage = (type: 'ps' | 'net') => {
65+
pendingRequestType = type;
66+
67+
if (type === 'ps') {
68+
isPsFetching.value = true;
69+
psLoading.value = psData.value.length === 0;
70+
71+
const searchParams = { ...psSearch };
72+
if (typeof searchParams.pid === 'string') {
73+
searchParams.pid = Number(searchParams.pid);
74+
}
75+
websocket!.send(JSON.stringify(searchParams));
76+
} else {
77+
isNetFetching.value = true;
78+
netLoading.value = netData.value.length === 0;
79+
80+
const searchParams = { ...netSearch };
81+
if (typeof searchParams.processID === 'string') {
82+
searchParams.processID = Number(searchParams.processID);
83+
}
84+
if (typeof searchParams.port === 'string') {
85+
searchParams.port = Number(searchParams.port);
86+
}
87+
websocket!.send(JSON.stringify(searchParams));
88+
}
89+
};
90+
91+
const onMessage = (event: MessageEvent) => {
92+
try {
93+
const data = JSON.parse(event.data);
94+
const responseType = pendingRequestType;
95+
96+
if (pendingRequestType === 'ps') {
97+
isPsFetching.value = false;
98+
} else if (pendingRequestType === 'net') {
99+
isNetFetching.value = false;
100+
}
101+
pendingRequestType = null;
102+
103+
if (responseType === activePollingType.value) {
104+
if (responseType === 'ps') {
105+
psData.value = data || [];
106+
psLoading.value = false;
107+
} else if (responseType === 'net') {
108+
netData.value = data || [];
109+
netLoading.value = false;
110+
}
111+
}
112+
113+
if (queuedRequestType && isWsOpen()) {
114+
const typeToSend = queuedRequestType;
115+
queuedRequestType = null;
116+
doSendMessage(typeToSend);
117+
}
118+
} catch (e) {
119+
console.error('Failed to parse WebSocket message:', e);
120+
}
121+
};
122+
123+
const onError = () => {
124+
console.error('WebSocket error');
125+
};
126+
127+
const onClose = () => {
128+
isConnected.value = false;
129+
isConnecting.value = false;
130+
websocket = null;
131+
};
132+
133+
const initWebSocket = (currentNode: string) => {
134+
if (websocket || isConnecting.value) {
135+
return;
136+
}
137+
138+
isConnecting.value = true;
139+
140+
const href = window.location.href;
141+
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
142+
const ipLocal = href.split('//')[1].split('/')[0];
143+
144+
websocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/process/ws?operateNode=${currentNode}`);
145+
websocket.onopen = onOpen;
146+
websocket.onmessage = onMessage;
147+
websocket.onerror = onError;
148+
websocket.onclose = onClose;
149+
};
150+
151+
const closeWebSocket = () => {
152+
stopPolling();
153+
154+
if (websocket) {
155+
websocket.close();
156+
websocket = null;
157+
}
158+
159+
isConnected.value = false;
160+
isConnecting.value = false;
161+
};
162+
163+
const connect = (currentNode: string) => {
164+
if (disconnectTimer) {
165+
clearTimeout(disconnectTimer);
166+
disconnectTimer = null;
167+
}
168+
169+
connectionRefCount++;
170+
171+
if (!websocket && !isConnecting.value) {
172+
initWebSocket(currentNode);
173+
}
174+
};
175+
176+
const disconnect = () => {
177+
connectionRefCount = Math.max(0, connectionRefCount - 1);
178+
179+
if (connectionRefCount === 0) {
180+
disconnectTimer = setTimeout(() => {
181+
if (connectionRefCount === 0) {
182+
closeWebSocket();
183+
}
184+
}, 500);
185+
}
186+
};
187+
188+
const sendPsMessage = () => {
189+
if (!isWsOpen()) {
190+
return;
191+
}
192+
193+
if (pendingRequestType !== null) {
194+
queuedRequestType = 'ps';
195+
return;
196+
}
197+
198+
if (isPsFetching.value) {
199+
return;
200+
}
201+
202+
doSendMessage('ps');
203+
};
204+
205+
const sendNetMessage = () => {
206+
if (!isWsOpen()) {
207+
return;
208+
}
209+
210+
if (pendingRequestType !== null) {
211+
queuedRequestType = 'net';
212+
return;
213+
}
214+
215+
if (isNetFetching.value) {
216+
return;
217+
}
218+
219+
doSendMessage('net');
220+
};
221+
222+
const startPolling = (type: 'ps' | 'net', interval = 3000, initialDelay = 0) => {
223+
stopPolling();
224+
activePollingType.value = type;
225+
226+
const sendInitial = () => {
227+
if (type === 'ps') {
228+
sendPsMessage();
229+
} else {
230+
sendNetMessage();
231+
}
232+
};
233+
234+
const scheduleInitialFetch = () => {
235+
if (initialDelay > 0) {
236+
setTimeout(sendInitial, initialDelay);
237+
} else {
238+
sendInitial();
239+
}
240+
};
241+
242+
if (isWsOpen()) {
243+
scheduleInitialFetch();
244+
} else {
245+
const checkConnection = setInterval(() => {
246+
if (isWsOpen()) {
247+
clearInterval(checkConnection);
248+
scheduleInitialFetch();
249+
}
250+
}, 100);
251+
setTimeout(() => clearInterval(checkConnection), 5000);
252+
}
253+
254+
pollingTimer = setInterval(() => {
255+
if (type === 'ps') {
256+
sendPsMessage();
257+
} else {
258+
sendNetMessage();
259+
}
260+
}, interval);
261+
};
262+
263+
const stopPolling = () => {
264+
if (pollingTimer) {
265+
clearInterval(pollingTimer);
266+
pollingTimer = null;
267+
}
268+
activePollingType.value = null;
269+
};
270+
271+
const updatePsSearch = (params: Partial<Omit<PsSearch, 'type'>>) => {
272+
Object.assign(psSearch, params);
273+
};
274+
275+
const updateNetSearch = (params: Partial<Omit<NetSearch, 'type'>>) => {
276+
Object.assign(netSearch, params);
277+
};
278+
279+
const resetPsSearch = () => {
280+
psSearch.pid = undefined;
281+
psSearch.username = '';
282+
psSearch.name = '';
283+
};
284+
285+
const resetNetSearch = () => {
286+
netSearch.processID = undefined;
287+
netSearch.processName = '';
288+
netSearch.port = undefined;
289+
};
290+
291+
return {
292+
isConnected,
293+
isConnecting,
294+
psData,
295+
psLoading,
296+
psSearch,
297+
netData,
298+
netLoading,
299+
netSearch,
300+
isPsFetching,
301+
isNetFetching,
302+
activePollingType,
303+
304+
isWsOpen,
305+
connect,
306+
disconnect,
307+
initWebSocket,
308+
closeWebSocket,
309+
sendPsMessage,
310+
sendNetMessage,
311+
startPolling,
312+
stopPolling,
313+
updatePsSearch,
314+
updateNetSearch,
315+
resetPsSearch,
316+
resetNetSearch,
317+
};
318+
});
319+
320+
export default ProcessStore;

0 commit comments

Comments
 (0)