From 4d26310666dd749b78a8a362caacc039a3916044 Mon Sep 17 00:00:00 2001 From: zhanglei <383094403@qq.com> Date: Sun, 6 Sep 2026 09:54:35 +0800 Subject: [PATCH] =?UTF-8?q?Update=20plugin=20=E6=9D=80=E8=BF=9B=E7=A8=8B/?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=AE=A1=E7=90=86=20v1.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - init - 添加端口查询 - 修改版本号 --- plugins/ztools-kill-process/app.js | 137 +++++++++++++++++++---- plugins/ztools-kill-process/index.css | 47 +++++++- plugins/ztools-kill-process/index.html | 6 +- plugins/ztools-kill-process/plugin.json | 2 +- plugins/ztools-kill-process/preload.js | 140 +++++++++++++++++++++++- 5 files changed, 304 insertions(+), 28 deletions(-) diff --git a/plugins/ztools-kill-process/app.js b/plugins/ztools-kill-process/app.js index 8dbe3ead7..d873ee7fe 100644 --- a/plugins/ztools-kill-process/app.js +++ b/plugins/ztools-kill-process/app.js @@ -15,6 +15,7 @@ document.addEventListener('DOMContentLoaded', () => { // Header click sort handlers const thName = document.getElementById('thName'); const thPID = document.getElementById('thPID'); + const thPort = document.getElementById('thPort'); const thMem = document.getElementById('thMem'); // Modal Elements @@ -88,11 +89,11 @@ document.addEventListener('DOMContentLoaded', () => { // Resolution of process service from preload const processService = window.services || window.processService || { getProcesses: async () => [ - { name: 'chrome.exe', pid: 12100, memoryStr: '132.3 MB', memoryKB: 135480 }, - { name: 'Antigravity.exe', pid: 65176, memoryStr: '252.4 MB', memoryKB: 258488 }, - { name: 'ZTools.exe', pid: 56044, memoryStr: '91.9 MB', memoryKB: 94092 }, - { name: 'WeChatAppEx.exe', pid: 54964, memoryStr: '133.1 MB', memoryKB: 136308 }, - { name: 'Unity.exe', pid: 28976, memoryStr: '0.96 GB', memoryKB: 1002752 } + { name: 'chrome.exe', pid: 12100, memoryStr: '132.3 MB', memoryKB: 135480, listeningPorts: [8080, 8081], allPorts: [8080, 8081], primaryPort: 8080, portsStr: ':8080, :8081' }, + { name: 'Antigravity.exe', pid: 65176, memoryStr: '252.4 MB', memoryKB: 258488, listeningPorts: [], allPorts: [54321], primaryPort: 54321, portsStr: ':54321' }, + { name: 'ZTools.exe', pid: 56044, memoryStr: '91.9 MB', memoryKB: 94092, listeningPorts: [], allPorts: [], primaryPort: Infinity, portsStr: '' }, + { name: 'WeChatAppEx.exe', pid: 54964, memoryStr: '133.1 MB', memoryKB: 136308, listeningPorts: [], allPorts: [], primaryPort: Infinity, portsStr: '' }, + { name: 'Unity.exe', pid: 28976, memoryStr: '0.96 GB', memoryKB: 1002752, listeningPorts: [3000], allPorts: [3000], primaryPort: 3000, portsStr: ':3000' } ], killProcess: async (pid) => `Mock killed process PID ${pid}` }; @@ -120,12 +121,20 @@ document.addEventListener('DOMContentLoaded', () => { }, 3000); } + let isFetchingProcesses = false; + let searchDebounceTimer = null; + // Load and refresh process list - async function loadProcesses(keepIndex = true) { + async function loadProcesses(keepIndex = true, silent = false) { + if (isFetchingProcesses) return; + isFetchingProcesses = true; + adjustPluginHeight(580); - loadingState.style.display = 'flex'; - emptyState.style.display = 'none'; - processListEl.style.display = 'none'; + if (!silent) { + loadingState.style.display = 'flex'; + emptyState.style.display = 'none'; + processListEl.style.display = 'none'; + } try { allProcesses = await processService.getProcesses(); @@ -141,21 +150,44 @@ document.addEventListener('DOMContentLoaded', () => { applyFilterAndSort(keepIndex); } catch (err) { console.error('Failed to load processes:', err); - showToast('获取进程列表失败: ' + (err.message || '未知错误'), 'error'); + if (!silent) { + showToast('获取进程列表失败: ' + (err.message || '未知错误'), 'error'); + } } finally { - loadingState.style.display = 'none'; - processListEl.style.display = 'block'; - if (searchInput && !isModalOpen) { + isFetchingProcesses = false; + if (!silent) { + loadingState.style.display = 'none'; + processListEl.style.display = 'block'; + } + if (searchInput && !isModalOpen && document.activeElement !== searchInput) { searchInput.focus(); } } } + // Window Focus & Plugin Enter Lifecycle Events for Auto-Refresh + window.onPluginEnter = function(action) { + loadProcesses(true, allProcesses.length > 0); + }; + + window.addEventListener('focus', () => { + if (!isModalOpen && !isFetchingProcesses) { + loadProcesses(true, true); + } + }); + + document.addEventListener('visibilitychange', () => { + if (!document.hidden && !isModalOpen && !isFetchingProcesses) { + loadProcesses(true, true); + } + }); + // Update header sort direction icons function updateHeaderSortIcons() { const headers = [ { el: thName, field: 'name' }, { el: thPID, field: 'pid' }, + { el: thPort, field: 'primaryPort' }, { el: thMem, field: 'memoryKB' } ]; @@ -209,18 +241,36 @@ document.addEventListener('DOMContentLoaded', () => { // Filter and Sort logic function applyFilterAndSort(keepIndex = false) { - const query = searchInput.value.trim().toLowerCase(); + const rawQuery = searchInput.value.trim().toLowerCase(); // Filter - if (!query) { + if (!rawQuery) { filteredProcesses = [...allProcesses]; searchClear.style.display = 'none'; } else { searchClear.style.display = 'flex'; + + // Clean query for port matching (e.g. ":8080", "port:8080", "port=8080" -> "8080") + const cleanPortQuery = rawQuery.replace(/^port\s*[:=]?\s*/i, '').replace(/^[:]/, '').trim(); + filteredProcesses = allProcesses.filter(p => { - const nameMatch = p.name.toLowerCase().includes(query); - const pidMatch = p.pid.toString().includes(query); - return nameMatch || pidMatch; + const nameMatch = p.name.toLowerCase().includes(rawQuery); + const pidMatch = p.pid.toString().includes(rawQuery); + + let portMatch = false; + if (cleanPortQuery) { + const listeningPorts = p.listeningPorts || []; + const allPorts = p.allPorts || []; + const portsStr = p.portsStr || ''; + + const hasListeningMatch = listeningPorts.some(port => port.toString().includes(cleanPortQuery)); + const hasAllMatch = allPorts.some(port => port.toString().includes(cleanPortQuery)); + const hasStrMatch = portsStr.toLowerCase().includes(rawQuery) || portsStr.toLowerCase().includes(cleanPortQuery); + + portMatch = hasListeningMatch || hasAllMatch || hasStrMatch; + } + + return nameMatch || pidMatch || portMatch; }); } @@ -229,6 +279,9 @@ document.addEventListener('DOMContentLoaded', () => { let valA = a[currentSort.field]; let valB = b[currentSort.field]; + if (valA === undefined || valA === null) valA = currentSort.field === 'primaryPort' ? Infinity : ''; + if (valB === undefined || valB === null) valB = currentSort.field === 'primaryPort' ? Infinity : ''; + if (typeof valA === 'string') valA = valA.toLowerCase(); if (typeof valB === 'string') valB = valB.toLowerCase(); @@ -251,6 +304,36 @@ document.addEventListener('DOMContentLoaded', () => { updateBatchKillState(); } + // Render Port Badges Helper + function renderPortBadges(proc) { + const listening = proc.listeningPorts || []; + const all = proc.allPorts || []; + + if (listening.length > 0) { + const showPorts = listening.slice(0, 2); + const extraCount = listening.length - showPorts.length; + const fullTooltip = `监听端口: ${listening.map(p => ':' + p).join(', ')}`; + + let html = showPorts.map(p => `:${p}`).join(' '); + if (extraCount > 0) { + html += ` +${extraCount}`; + } + return html; + } else if (all.length > 0) { + const showPorts = all.slice(0, 2); + const extraCount = all.length - showPorts.length; + const fullTooltip = `活跃连接端口: ${all.map(p => ':' + p).join(', ')}`; + + let html = showPorts.map(p => `:${p}`).join(' '); + if (extraCount > 0) { + html += ` +${extraCount}`; + } + return html; + } + + return `-`; + } + // Render Process Items function renderList() { processListEl.innerHTML = ''; @@ -268,6 +351,8 @@ document.addEventListener('DOMContentLoaded', () => { item.className = `process-item ${index === selectedIndex ? 'selected' : ''} ${isChecked ? 'checked-row' : ''}`; item.setAttribute('data-index', index); + const portHtml = renderPortBadges(proc); + item.innerHTML = `
@@ -288,6 +373,7 @@ document.addEventListener('DOMContentLoaded', () => { ${escapeHtml(proc.name)}
${proc.pid}
+
${portHtml}
${escapeHtml(proc.memoryStr)}
+
+ 占用端口: + ${escapeHtml(proc.portsStr || '无')} +
内存占用: ${escapeHtml(proc.memoryStr)} @@ -459,7 +549,7 @@ document.addEventListener('DOMContentLoaded', () => { let html = selectedProcs.map(p => ` `).join(''); @@ -647,12 +737,20 @@ document.addEventListener('DOMContentLoaded', () => { // Search Input Event searchInput.addEventListener('input', () => { + // 1. Instantly filter existing cached processes for fast response applyFilterAndSort(false); + + // 2. Debounce (300ms) background refresh from OS to catch newly launched processes/ports + if (searchDebounceTimer) clearTimeout(searchDebounceTimer); + searchDebounceTimer = setTimeout(() => { + loadProcesses(true, true); + }, 300); }); searchClear.addEventListener('click', () => { searchInput.value = ''; applyFilterAndSort(false); + loadProcesses(true, true); searchInput.focus(); }); @@ -674,6 +772,7 @@ document.addEventListener('DOMContentLoaded', () => { thName.addEventListener('click', () => toggleSort('name', false)); thPID.addEventListener('click', () => toggleSort('pid', false)); + if (thPort) thPort.addEventListener('click', () => toggleSort('primaryPort', false)); thMem.addEventListener('click', () => toggleSort('memoryKB', true)); // Modal Mouse Click Handlers diff --git a/plugins/ztools-kill-process/index.css b/plugins/ztools-kill-process/index.css index 831dab583..2119824d1 100644 --- a/plugins/ztools-kill-process/index.css +++ b/plugins/ztools-kill-process/index.css @@ -434,7 +434,7 @@ input[type="checkbox"]:indeterminate::before { /* List Table Header */ .table-header { display: grid; - grid-template-columns: 36px minmax(180px, 3fr) 100px 140px 90px; + grid-template-columns: 36px minmax(140px, 3fr) 75px 130px 90px 65px; padding: 10px 16px; background-color: var(--bg-header); border-bottom: 1px solid var(--border-color); @@ -504,7 +504,7 @@ input[type="checkbox"]:indeterminate::before { .process-item { display: grid; - grid-template-columns: 36px minmax(180px, 3fr) 100px 140px 90px; + grid-template-columns: 36px minmax(140px, 3fr) 75px 130px 90px 65px; align-items: center; padding: 9px 12px; margin-bottom: 4px; @@ -556,6 +556,49 @@ input[type="checkbox"]:indeterminate::before { font-size: 12px; } +.port-cell { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: nowrap; + overflow: hidden; +} + +.port-badge { + background-color: rgba(16, 185, 129, 0.12); + color: var(--accent-green); + border: 1px solid rgba(16, 185, 129, 0.25); + border-radius: var(--radius-sm); + padding: 1px 5px; + font-family: var(--font-mono); + font-size: 11px; + font-weight: 600; + white-space: nowrap; +} + +.port-badge-active { + background-color: rgba(59, 130, 246, 0.12); + color: var(--accent-blue); + border-color: rgba(59, 130, 246, 0.25); +} + +.port-badge-more { + background-color: var(--bg-surface-hover); + color: var(--text-muted); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + padding: 1px 4px; + font-family: var(--font-mono); + font-size: 10px; +} + +.no-port { + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 12px; + opacity: 0.5; +} + .mem-cell { font-family: var(--font-mono); font-weight: 600; diff --git a/plugins/ztools-kill-process/index.html b/plugins/ztools-kill-process/index.html index 75b42e3e3..186b66df3 100644 --- a/plugins/ztools-kill-process/index.html +++ b/plugins/ztools-kill-process/index.html @@ -29,7 +29,7 @@

进程管理与结束工具

- +
@@ -70,6 +70,10 @@

进程管理与结束工具

PID
+
+ 占用端口 + +
内存占用 diff --git a/plugins/ztools-kill-process/plugin.json b/plugins/ztools-kill-process/plugin.json index 0e22555b6..9e41d938a 100644 --- a/plugins/ztools-kill-process/plugin.json +++ b/plugins/ztools-kill-process/plugin.json @@ -3,7 +3,7 @@ "title": "杀进程/进程管理", "pluginName": "杀进程/进程管理", "description": "显示当前系统的进程列表,快速搜索与一键结束指定进程", - "version": "1.0.0", + "version": "1.0.1", "author": "zhanglei", "main": "index.html", "preload": "preload.js", diff --git a/plugins/ztools-kill-process/preload.js b/plugins/ztools-kill-process/preload.js index e4494ddcc..e761108b4 100644 --- a/plugins/ztools-kill-process/preload.js +++ b/plugins/ztools-kill-process/preload.js @@ -2,20 +2,37 @@ console.log('ztools-kill-process preload.js loaded!'); const ztools = window.ztools || window.utools || {}; +function notifyPluginEnter(action) { + if (typeof window.onPluginEnter === 'function') { + try { + window.onPluginEnter(action); + } catch (e) { + console.error('Error in onPluginEnter:', e); + } + } +} + window.exports = { 'kill-process': { mode: 'none', args: { - enter() { + enter(action) { if (ztools.setExpendHeight) ztools.setExpendHeight(580); if (ztools.setExploresHeight) ztools.setExploresHeight(580); if (ztools.showMainWindow) ztools.showMainWindow(); + notifyPluginEnter(action); }, leave() {} } } }; +if (ztools && typeof ztools.onPluginEnter === 'function') { + ztools.onPluginEnter((action) => { + notifyPluginEnter(action); + }); +} + // Memory formatter helper function formatMemory(memKB) { if (!memKB || isNaN(memKB) || memKB <= 0) return '0 KB'; @@ -28,6 +45,93 @@ function formatMemory(memKB) { } } +// Port scanner helper for Windows using netstat -ano +function getPortsWin32() { + return new Promise((resolve) => { + const { exec } = require('child_process'); + exec('netstat -ano', { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 5000 }, (err, stdout) => { + if (err || !stdout) return resolve({}); + const lines = stdout.split('\r\n'); + const portMap = {}; // pid -> { listening: Set, all: Set } + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length >= 4 && (parts[0] === 'TCP' || parts[0] === 'UDP')) { + const proto = parts[0]; + const localAddr = parts[1]; + const pidStr = parts[parts.length - 1]; + const pid = parseInt(pidStr, 10); + if (!isNaN(pid) && pid > 0) { + const lastColon = localAddr.lastIndexOf(':'); + if (lastColon !== -1) { + const port = parseInt(localAddr.substring(lastColon + 1), 10); + if (!isNaN(port) && port > 0) { + if (!portMap[pid]) { + portMap[pid] = { listening: new Set(), all: new Set() }; + } + portMap[pid].all.add(port); + const isListening = (proto === 'TCP' && parts.includes('LISTENING')) || proto === 'UDP'; + if (isListening) { + portMap[pid].listening.add(port); + } + } + } + } + } + } + const result = {}; + for (const pid in portMap) { + result[pid] = { + listening: Array.from(portMap[pid].listening).sort((a, b) => a - b), + all: Array.from(portMap[pid].all).sort((a, b) => a - b) + }; + } + resolve(result); + }); + }); +} + +// Port scanner helper for macOS / Linux using lsof +function getPortsPosix() { + return new Promise((resolve) => { + const { exec } = require('child_process'); + exec('lsof -i -P -n', { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 5000 }, (err, stdout) => { + if (err || !stdout) return resolve({}); + const lines = stdout.split('\n'); + const portMap = {}; + for (let i = 1; i < lines.length; i++) { + const parts = lines[i].trim().split(/\s+/); + if (parts.length >= 9) { + const pid = parseInt(parts[1], 10); + const nameField = parts[8] || parts[parts.length - 1]; + if (!isNaN(pid) && nameField) { + const match = nameField.match(/:(\d+)(?:->|$)/); + if (match) { + const port = parseInt(match[1], 10); + if (!isNaN(port) && port > 0) { + if (!portMap[pid]) { + portMap[pid] = { listening: new Set(), all: new Set() }; + } + portMap[pid].all.add(port); + if (lines[i].includes('LISTEN') || lines[i].includes('(LISTEN)')) { + portMap[pid].listening.add(port); + } + } + } + } + } + } + const result = {}; + for (const pid in portMap) { + result[pid] = { + listening: Array.from(portMap[pid].listening).sort((a, b) => a - b), + all: Array.from(portMap[pid].all).sort((a, b) => a - b) + }; + } + resolve(result); + }); + }); +} + window.services = { getProcesses() { return new Promise((resolve, reject) => { @@ -35,6 +139,8 @@ window.services = { const os = require('os'); const platform = os.platform(); + const portsPromise = platform === 'win32' ? getPortsWin32() : getPortsPosix(); + if (platform === 'win32') { const psScript = ` Get-Process | ForEach-Object { @@ -48,12 +154,13 @@ Get-Process | ForEach-Object { const encoded = Buffer.from(psScript, 'utf16le').toString('base64'); - execFile('powershell.exe', ['-NoProfile', '-EncodedCommand', encoded], { encoding: 'utf8', timeout: 5000, maxBuffer: 20 * 1024 * 1024 }, (err, stdout) => { + execFile('powershell.exe', ['-NoProfile', '-EncodedCommand', encoded], { encoding: 'utf8', timeout: 5000, maxBuffer: 20 * 1024 * 1024 }, async (err, stdout) => { if (err) return reject(err); try { const rawItems = JSON.parse(stdout || '[]'); const items = Array.isArray(rawItems) ? rawItems : [rawItems]; + const portMap = await portsPromise.catch(() => ({})); const list = []; for (const item of items) { @@ -70,11 +177,22 @@ Get-Process | ForEach-Object { const name = rawName ? `${rawName}.exe` : `PID-${pid}`; const memoryKB = Math.round((item.WorkingSet64 || 0) / 1024); + const portsInfo = portMap[pid] || { listening: [], all: [] }; + const listeningPorts = portsInfo.listening || []; + const allPorts = portsInfo.all || []; + const displayPorts = listeningPorts.length > 0 ? listeningPorts : allPorts; + const primaryPort = displayPorts.length > 0 ? displayPorts[0] : 999999; + const portsStr = displayPorts.map(p => `:${p}`).join(', '); + list.push({ name, pid, memoryStr: formatMemory(memoryKB), - memoryKB + memoryKB, + listeningPorts, + allPorts, + primaryPort, + portsStr }); } @@ -85,9 +203,10 @@ Get-Process | ForEach-Object { }); } else { // macOS / Linux - execFile('ps', ['-ax', '-o', 'pid,rss,comm'], { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024, timeout: 5000 }, (err, stdout) => { + execFile('ps', ['-ax', '-o', 'pid,rss,comm'], { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024, timeout: 5000 }, async (err, stdout) => { if (err) return reject(err); const lines = stdout.split('\n').slice(1); + const portMap = await portsPromise.catch(() => ({})); const list = []; for (const line of lines) { const parts = line.trim().split(/\s+/); @@ -98,11 +217,22 @@ Get-Process | ForEach-Object { if (!isNaN(pid) && comm) { const name = comm.split('/').pop(); if (name.toLowerCase().includes('memory compression')) continue; + const portsInfo = portMap[pid] || { listening: [], all: [] }; + const listeningPorts = portsInfo.listening || []; + const allPorts = portsInfo.all || []; + const displayPorts = listeningPorts.length > 0 ? listeningPorts : allPorts; + const primaryPort = displayPorts.length > 0 ? displayPorts[0] : 999999; + const portsStr = displayPorts.map(p => `:${p}`).join(', '); + list.push({ name, pid, memoryStr: formatMemory(rssKB), - memoryKB: rssKB + memoryKB: rssKB, + listeningPorts, + allPorts, + primaryPort, + portsStr }); } }