docs: add project README and initialize repository
This commit is contained in:
+468
@@ -0,0 +1,468 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PortFlow - 系统进程与网络监视器</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
*, *::before, *::after {
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #020617;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #1e293b;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #334155;
|
||||
}
|
||||
@keyframes radar-pulse {
|
||||
0% { transform: scale(0.95); opacity: 0.8; box-shadow: 0 0 30px rgba(79, 70, 229, 0.15); }
|
||||
50% { transform: scale(1.05); opacity: 0.5; box-shadow: 0 0 50px rgba(79, 70, 229, 0.3); }
|
||||
100% { transform: scale(0.95); opacity: 0.8; box-shadow: 0 0 30px rgba(79, 70, 229, 0.15); }
|
||||
}
|
||||
.radar-animate {
|
||||
animation: radar-pulse 2s infinite ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-50 text-slate-800 dark:bg-slate-950 dark:text-slate-100 h-screen w-screen overflow-hidden font-sans select-none flex flex-col border border-slate-200 dark:border-slate-800 rounded-lg">
|
||||
|
||||
<div id="splash-screen" data-tauri-drag-region class="absolute inset-0 z-50 bg-slate-100 flex flex-col items-center justify-center text-slate-800 transition-all duration-500 ease-in-out">
|
||||
<div class="flex flex-col items-center pointer-events-none">
|
||||
<div class="relative w-20 h-20 bg-white rounded-full flex items-center justify-center border border-slate-200 shadow-md mb-6 radar-animate">
|
||||
<i class="fa-solid fa-network-wired text-3xl text-indigo-500 animate-pulse"></i>
|
||||
<div class="absolute inset-0 border-2 border-dashed border-indigo-500/20 rounded-full animate-spin" style="animation-duration: 8s;"></div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-black tracking-widest text-slate-800 font-sans uppercase" id="splash-app-name">PORTFLOW</h1>
|
||||
<p class="text-xs text-indigo-600 font-medium mt-2 tracking-wider">系统端口与映像进程分析内核</p>
|
||||
<div class="w-48 h-1 bg-slate-200 rounded-full mt-8 overflow-hidden">
|
||||
<div class="h-full w-1/2 bg-gradient-to-r from-indigo-500 to-indigo-400 rounded-full animate-[shimmer_1.5s_infinite_linear]" style="animation-name: progress-shimmer;">
|
||||
<style>
|
||||
@keyframes progress-shimmer {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(200%); }
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[11px] text-slate-400 font-medium mt-3" id="splash-status">正在下钻底层网络套接字,映射物理端口映像...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="custom-titlebar" data-tauri-drag-region class="h-10 w-full bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 flex justify-between items-center pl-4 shrink-0 select-none cursor-default">
|
||||
<div class="flex items-center space-x-2 text-xs font-medium text-slate-500 dark:text-slate-400 pointer-events-none">
|
||||
<i class="fa-solid fa-network-wired text-indigo-500"></i>
|
||||
<span id="titlebar-app-name">PortFlow 进程管理器</span>
|
||||
</div>
|
||||
<div class="flex items-center h-full text-slate-500 dark:text-slate-400 text-xs">
|
||||
<button onclick="windowMinimize()" class="w-12 h-full hover:bg-slate-200 dark:hover:bg-slate-800 flex items-center justify-center transition-colors duration-75 cursor-pointer" title="最小化">
|
||||
<i class="fa-solid fa-minus"></i>
|
||||
</button>
|
||||
<button onclick="windowMaximize()" class="w-12 h-full hover:bg-slate-200 dark:hover:bg-slate-800 flex items-center justify-center transition-colors duration-75 cursor-pointer" title="最大化">
|
||||
<i id="max-icon" class="fa-regular fa-square"></i>
|
||||
</button>
|
||||
<button onclick="windowClose()" class="w-12 h-full hover:bg-rose-600 hover:text-white flex items-center justify-center transition-colors duration-75 cursor-pointer" title="关闭">
|
||||
<i class="fa-solid fa-xmark text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header class="border-b border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900/50 px-6 py-3.5 flex justify-between items-center shrink-0 shadow-xs">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="bg-slate-800 dark:bg-indigo-600 p-2 rounded-lg text-white">
|
||||
<i class="fa-solid fa-chart-pie text-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white flex items-center gap-2" id="header-app-name">PortFlow 进程管理器</h1>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500">实时监控系统端口与映像进程分析</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<button onclick="toggleTheme()" class="w-9 h-9 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800 flex items-center justify-center cursor-pointer shadow-2xs" title="切换主题">
|
||||
<i id="theme-icon" class="fa-solid fa-sun text-amber-500"></i>
|
||||
</button>
|
||||
<span class="flex items-center text-xs bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 px-2.5 py-1 rounded border border-indigo-500/20 font-mono" id="app-version-badge">
|
||||
<span class="w-1.5 h-1.5 bg-indigo-500 rounded-full mr-2 animate-pulse"></span>版本: 获取中...
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-7xl w-full mx-auto p-4 flex-1 min-h-0 flex flex-col space-y-4 overflow-hidden">
|
||||
<section class="grid grid-cols-2 md:grid-cols-4 gap-3 shrink-0">
|
||||
<div class="bg-white dark:bg-slate-900 p-3 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500">网络套接字总数</p>
|
||||
<p class="text-xl font-bold mt-0.5 text-slate-900 dark:text-white" id="total-ports">0</p>
|
||||
</div>
|
||||
<i class="fa-solid fa-chart-line text-indigo-500 opacity-30 text-xl"></i>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-900 p-3 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500">TCP 监听级</p>
|
||||
<p class="text-xl font-bold mt-0.5 text-blue-600 dark:text-blue-400" id="tcp-count">0</p>
|
||||
</div>
|
||||
<i class="fa-solid fa-arrow-right-arrows-left text-blue-500 opacity-30 text-xl"></i>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-900 p-3 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500">UDP 数据报</p>
|
||||
<p class="text-xl font-bold mt-0.5 text-purple-600 dark:text-purple-400" id="udp-count">0</p>
|
||||
</div>
|
||||
<i class="fa-solid fa-bolt text-purple-500 opacity-30 text-xl"></i>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-900 p-3 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500">已终止进程数</p>
|
||||
<p class="text-xl font-bold mt-0.5 text-rose-600 dark:text-rose-400" id="killed-count">0</p>
|
||||
</div>
|
||||
<i class="fa-solid fa-ban text-rose-500 opacity-30 text-xl"></i>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 flex-1 min-h-0 items-stretch">
|
||||
<div class="lg:col-span-2 flex flex-col space-y-4 min-h-0" id="left-panel">
|
||||
<div class="bg-white dark:bg-slate-900 p-4 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs shrink-0 flex gap-2 items-center">
|
||||
<div class="relative flex-1">
|
||||
<i class="fa-solid fa-filter absolute left-3 top-3 text-slate-400 text-xs"></i>
|
||||
<input id="search-input" type="text" placeholder="输入端口号或进程名称过滤 (例如: 8080 或 node)..." class="w-full bg-slate-50 border border-slate-200 dark:bg-slate-950 dark:border-slate-800 rounded-md pl-9 pr-4 py-1.5 text-sm placeholder-slate-400 focus:outline-none focus:border-indigo-500 transition-all">
|
||||
</div>
|
||||
<button onclick="throttledLoadPorts()" class="text-xs bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 px-3 py-2 border border-slate-200 dark:border-slate-700 rounded-md font-medium cursor-pointer flex items-center gap-1.5 shrink-0 transition-all active:scale-95">
|
||||
<i id="refresh-icon" class="fa-solid fa-arrows-rotate transition-transform duration-500"></i>
|
||||
<span>刷新列表</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
<div class="overflow-y-auto flex-1 custom-scrollbar">
|
||||
<table class="w-full text-left border-collapse table-fixed">
|
||||
<thead class="sticky top-0 bg-slate-100 dark:bg-slate-800 z-10 border-b border-slate-200 dark:border-slate-700 shadow-xs">
|
||||
<tr class="text-slate-600 dark:text-slate-300 text-xs font-semibold">
|
||||
<th class="py-2.5 px-4 w-20">类型</th>
|
||||
<th class="py-2.5 px-4 w-28">本地端口</th>
|
||||
<th class="py-2.5 px-4">关联映像名称 (Process)</th>
|
||||
<th class="py-2.5 px-4 w-24">PID</th>
|
||||
<th class="py-2.5 px-4 w-32">状态</th>
|
||||
<th class="py-2.5 px-4 w-24 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="port-table-body" class="divide-y divide-slate-100 dark:divide-slate-800/60 text-xs font-mono"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="right-log-card" class="bg-white dark:bg-slate-900 p-4 rounded-lg border border-slate-200 dark:border-slate-800 shadow-2xs flex flex-col min-h-0">
|
||||
<div class="flex items-center justify-between mb-2 shrink-0">
|
||||
<h3 class="text-xs font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fa-solid fa-terminal mr-1.5 text-indigo-500"></i> 事件与诊断日志
|
||||
</h3>
|
||||
<button onclick="clearLogs()" class="text-[10px] text-slate-400 hover:text-slate-600 transition-colors cursor-pointer">清空</button>
|
||||
</div>
|
||||
<div id="terminal-log" class="bg-slate-950 border border-slate-900 rounded p-3 text-[11px] font-mono text-slate-400 flex-1 overflow-y-auto space-y-1.5 shadow-inner custom-scrollbar">
|
||||
<div class="text-slate-600">[内核层] 初始化高级视口与多线程异步引擎就绪。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="text-center py-2 text-[11px] text-slate-400 dark:text-slate-600 border-t border-slate-200 dark:border-slate-900 shrink-0 bg-white dark:bg-slate-900/50" id="footer-app-name">
|
||||
PortFlow 进程管理器
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const invoke = window.__TAURI__ ? (window.__TAURI__.core ? window.__TAURI__.core.invoke : window.__TAURI__.invoke) : null;
|
||||
|
||||
let appWindow = null;
|
||||
if (window.__TAURI__ && window.__TAURI__.window) {
|
||||
appWindow = window.__TAURI__.window.getCurrentWindow();
|
||||
}
|
||||
|
||||
let allPortsData = [];
|
||||
let killedCount = 0;
|
||||
let isInitialLoad = true; // 💡 控制全屏 Loading 仅执行一次
|
||||
|
||||
const stateTranslation = {
|
||||
'LISTENING': '监听中 (Listening)',
|
||||
'ESTABLISHED': '已建立连接',
|
||||
'CLOSE_WAIT': '等待关闭',
|
||||
'TIME_WAIT': '等待被动关闭',
|
||||
'SYN_SENT': '正在请求连接',
|
||||
'SYN_RECEIVED': '已收到连接请求',
|
||||
'FIN_WAIT_1': '等待远端确认关闭',
|
||||
'FIN_WAIT_2': '等待远端关闭请求',
|
||||
'LAST_ACK': '等待最后确认',
|
||||
'CLOSING': '正在关闭',
|
||||
'UNKNOWN': '未知状态'
|
||||
};
|
||||
|
||||
function getChineseState(stateStr) {
|
||||
if (!stateStr) return '活动中';
|
||||
const upperState = stateStr.toUpperCase();
|
||||
return stateTranslation[upperState] || stateStr;
|
||||
}
|
||||
|
||||
// 同步配置元数据
|
||||
async function syncAppMetadata() {
|
||||
if (window.__TAURI__) {
|
||||
try {
|
||||
const version = await window.__TAURI__.core.invoke("plugin:app|version");
|
||||
const name = await window.__TAURI__.core.invoke("plugin:app|name");
|
||||
|
||||
document.getElementById('app-version-badge').innerHTML = `<span class="w-1.5 h-1.5 bg-indigo-500 rounded-full mr-2 animate-pulse"></span>版本: V${version}`;
|
||||
document.getElementById('splash-app-name').innerText = name;
|
||||
document.getElementById('titlebar-app-name').innerText = `${name} 进程管理器`;
|
||||
document.getElementById('header-app-name').innerText = `${name} 进程管理器`;
|
||||
document.getElementById('footer-app-name').innerText = `${name} 进程管理器`;
|
||||
} catch (e) {
|
||||
console.error("元数据同步权限受限:", e);
|
||||
document.getElementById('app-version-badge').innerText = "版本: 内置环境";
|
||||
}
|
||||
} else {
|
||||
document.getElementById('app-version-badge').innerText = "版本: 演示模式";
|
||||
}
|
||||
}
|
||||
|
||||
function initWindowDrag() {
|
||||
const titlebar = document.getElementById('custom-titlebar');
|
||||
const splash = document.getElementById('splash-screen');
|
||||
|
||||
const attachDrag = (element) => {
|
||||
if (element && appWindow) {
|
||||
element.addEventListener('mousedown', (e) => {
|
||||
if (!e.target.closest('button')) {
|
||||
appWindow.startDragging();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
attachDrag(titlebar);
|
||||
attachDrag(splash);
|
||||
}
|
||||
|
||||
async function windowMinimize() { if (appWindow) await appWindow.minimize(); }
|
||||
async function windowMaximize() {
|
||||
if (appWindow) {
|
||||
try {
|
||||
const isMax = await appWindow.isMaximized();
|
||||
if (isMax) {
|
||||
await appWindow.unmaximize();
|
||||
document.getElementById('max-icon').className = 'fa-regular fa-square';
|
||||
} else {
|
||||
await appWindow.maximize();
|
||||
document.getElementById('max-icon').className = 'fa-regular fa-clone';
|
||||
}
|
||||
} catch (err) { console.error(err); }
|
||||
}
|
||||
}
|
||||
async function windowClose() { if (appWindow) await appWindow.close(); }
|
||||
|
||||
function matchHeight() {
|
||||
const leftPanel = document.getElementById('left-panel');
|
||||
const rightCard = document.getElementById('right-log-card');
|
||||
if (window.innerWidth >= 1024) {
|
||||
const leftHeight = leftPanel.offsetHeight;
|
||||
if (leftHeight > 0) rightCard.style.height = `${leftHeight}px`;
|
||||
} else { rightCard.style.height = 'auto'; }
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const isDark = document.documentElement.classList.toggle('dark');
|
||||
document.getElementById('theme-icon').className = isDark ? 'fa-solid fa-moon text-indigo-400' : 'fa-solid fa-sun text-amber-500';
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
}
|
||||
if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.getElementById('theme-icon').className = 'fa-solid fa-moon text-indigo-400';
|
||||
}
|
||||
|
||||
function logToTerminal(message, type = 'info') {
|
||||
const container = document.getElementById('terminal-log');
|
||||
const time = new Date().toLocaleTimeString();
|
||||
let colorClass = 'text-slate-400';
|
||||
if (type === 'success') colorClass = 'text-emerald-400';
|
||||
if (type === 'error') colorClass = 'text-rose-400';
|
||||
if (type === 'cmd') colorClass = 'text-indigo-400 font-bold';
|
||||
container.innerHTML += `<div class="${colorClass}">[${time}] ${message}</div>`;
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLogs() { document.getElementById('terminal-log').innerHTML = ''; }
|
||||
|
||||
function getProcessIcon(name) {
|
||||
if (!name) return '<i class="fa-solid fa-circle-question text-slate-400 mr-2 text-xs w-4 text-center"></i>';
|
||||
const lower = name.toLowerCase();
|
||||
if (lower.includes('node')) return '<i class="fa-brands fa-node-js text-emerald-500 mr-2 text-sm w-4 text-center"></i>';
|
||||
if (lower.includes('java')) return '<i class="fa-brands fa-java text-orange-500 mr-2 text-sm w-4 text-center"></i>';
|
||||
if (lower.includes('python')) return '<i class="fa-brands fa-python text-blue-400 mr-2 text-sm w-4 text-center"></i>';
|
||||
if (lower.includes('nginx') || lower.includes('apache')) return '<i class="fa-solid fa-server text-teal-500 mr-2 text-xs w-4 text-center"></i>';
|
||||
if (lower.includes('chrome') || lower.includes('edge') || lower.includes('browser')) return '<i class="fa-brands fa-chrome text-amber-500 mr-2 text-xs w-4 text-center"></i>';
|
||||
if (lower.includes('unknown') || lower.includes('已结束')) return '<i class="fa-solid fa-circle-question text-slate-400 mr-2 text-xs w-4 text-center"></i>';
|
||||
return '<i class="fa-solid fa-window-maximize text-indigo-400/80 mr-2 text-xs w-4 text-center"></i>';
|
||||
}
|
||||
|
||||
// 💡 优化:带精准区分首次与二次反馈机制的加载函数
|
||||
async function loadRealPorts() {
|
||||
logToTerminal(`$ sys_diagnostic --get-network-mapping`, 'cmd');
|
||||
const refreshIcon = document.getElementById('refresh-icon');
|
||||
|
||||
if (isInitialLoad) {
|
||||
const splash = document.getElementById('splash-screen');
|
||||
if (splash) {
|
||||
splash.style.display = 'flex';
|
||||
splash.classList.remove('opacity-0', 'pointer-events-none');
|
||||
}
|
||||
} else if (refreshIcon) {
|
||||
refreshIcon.classList.add('animate-spin'); // 仅让按钮旋转
|
||||
}
|
||||
|
||||
if (!invoke) {
|
||||
logToTerminal(`未检测到原生外壳,载入任务管理器级模拟排布。`, 'error');
|
||||
allPortsData = [
|
||||
{ protocol: 'TCP', port: 8080, name: 'nginx.exe', pid: 4120, state: 'LISTENING' },
|
||||
{ protocol: 'TCP', port: 3000, name: 'node.exe', pid: 10424, state: 'LISTENING' },
|
||||
{ protocol: 'TCP', port: 9000, name: 'java.exe', pid: 8840, state: 'CLOSE_WAIT' },
|
||||
{ protocol: 'UDP', port: 5353, name: 'chrome.exe', pid: 1402, state: 'ESTABLISHED' },
|
||||
{ protocol: 'TCP', port: 5432, name: 'postgres.exe', pid: 2112, state: 'LISTENING' }
|
||||
];
|
||||
|
||||
setTimeout(() => {
|
||||
renderTable(allPortsData);
|
||||
if (isInitialLoad) { hideSplashScreen(); isInitialLoad = false; }
|
||||
else if (refreshIcon) refreshIcon.classList.remove('animate-spin');
|
||||
}, 800);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行已经解耦、不会产生界面锁死假死的异步命令
|
||||
allPortsData = await invoke('get_active_ports');
|
||||
renderTable(allPortsData);
|
||||
logToTerminal(`核心映射成功:已提取出 ${allPortsData.length} 个物理网络映射映像。`, 'success');
|
||||
|
||||
if (isInitialLoad) { hideSplashScreen(); isInitialLoad = false; }
|
||||
else if (refreshIcon) refreshIcon.classList.remove('animate-spin');
|
||||
} catch (err) {
|
||||
logToTerminal(`提取进程映像失败: ${err}`, 'error');
|
||||
if (isInitialLoad) {
|
||||
document.getElementById('splash-status').innerText = "加载失败,请确保使用管理员身份运行: " + err;
|
||||
} else if (refreshIcon) refreshIcon.classList.remove('animate-spin');
|
||||
}
|
||||
}
|
||||
|
||||
function hideSplashScreen() {
|
||||
const splash = document.getElementById('splash-screen');
|
||||
if (splash) {
|
||||
splash.classList.add('opacity-0', 'pointer-events-none');
|
||||
setTimeout(() => { splash.style.display = 'none'; }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function filterPorts() {
|
||||
const query = document.getElementById('search-input').value.toLowerCase().trim();
|
||||
if (!query) { renderTable(allPortsData); return; }
|
||||
const filtered = allPortsData.filter(item =>
|
||||
item.port.toString().includes(query) ||
|
||||
item.name.toLowerCase().includes(query) ||
|
||||
item.pid.toString().includes(query) ||
|
||||
getChineseState(item.state).includes(query)
|
||||
);
|
||||
renderTable(filtered);
|
||||
}
|
||||
|
||||
function debounce(fn, delay) {
|
||||
let timer = null;
|
||||
return function (...args) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => { fn.apply(this, args); }, delay);
|
||||
};
|
||||
}
|
||||
|
||||
function throttle(fn, limit) {
|
||||
let lastFunc = null, lastRan = null;
|
||||
return function (...args) {
|
||||
const context = this;
|
||||
if (!lastRan) { fn.apply(context, args); lastRan = Date.now(); }
|
||||
else {
|
||||
if (lastFunc) clearTimeout(lastFunc);
|
||||
lastFunc = setTimeout(function () {
|
||||
if ((Date.now() - lastRan) >= limit) { fn.apply(context, args); lastRan = Date.now(); }
|
||||
}, limit - (Date.now() - lastRan));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const debouncedFilter = debounce(filterPorts, 200);
|
||||
const throttledLoadPorts = throttle(() => { loadRealPorts(); }, 1500);
|
||||
|
||||
function renderTable(ports) {
|
||||
const tbody = document.getElementById('port-table-body');
|
||||
document.getElementById('total-ports').innerText = allPortsData.length;
|
||||
document.getElementById('tcp-count').innerText = allPortsData.filter(p=>p.protocol==='TCP').length;
|
||||
document.getElementById('udp-count').innerText = allPortsData.filter(p=>p.protocol==='UDP').length;
|
||||
tbody.innerHTML = '';
|
||||
if (ports.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="text-center py-8 text-slate-400 italic">没有匹配的系统映像活动条目</td></tr>`;
|
||||
matchHeight();
|
||||
return;
|
||||
}
|
||||
ports.forEach(item => {
|
||||
const isTcp = item.protocol === 'TCP';
|
||||
const typeBadge = isTcp ? '<span class="text-blue-600 bg-blue-500/10 border border-blue-500/20 px-1.5 rounded text-[10px]">TCP</span>' : '<span class="text-purple-600 bg-purple-500/10 border border-purple-500/20 px-1.5 rounded text-[10px]">UDP</span>';
|
||||
const cnState = getChineseState(item.state);
|
||||
const itemName = item.name || "未知映像";
|
||||
|
||||
tbody.innerHTML += `
|
||||
<tr class="hover:bg-slate-100 dark:hover:bg-slate-800/40 border-b border-slate-100 dark:border-slate-800/40 transition-colors">
|
||||
<td class="py-2 px-4 font-semibold">${typeBadge}</td>
|
||||
<td class="py-2 px-4 text-indigo-600 dark:text-indigo-400 font-bold text-sm">:${item.port}</td>
|
||||
<td class="py-2 px-4 text-slate-700 dark:text-slate-200 flex items-center overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
${getProcessIcon(itemName)}
|
||||
<span class="truncate" title="${itemName}">${itemName}</span>
|
||||
</td>
|
||||
<td class="py-2 px-4 text-slate-500 dark:text-slate-400">${item.pid}</td>
|
||||
<td class="py-2 px-4"><span class="text-[11px] text-emerald-600 font-medium whitespace-nowrap"><span class="w-1 h-1 inline-block bg-emerald-500 rounded-full mr-1.5 mb-0.5"></span>${cnState}</span></td>
|
||||
<td class="py-2 px-4 text-center whitespace-nowrap"><button onclick="killRealProcess(${item.pid}, '${itemName}')" class="bg-rose-600 hover:bg-rose-700 text-white font-sans px-2.5 py-0.5 rounded text-[11px] transition-all cursor-pointer shadow-3xs whitespace-nowrap inline-block">结束任务</button></td>
|
||||
</tr>`;
|
||||
});
|
||||
matchHeight();
|
||||
}
|
||||
|
||||
async function killRealProcess(pid, name) {
|
||||
if (confirm(`【Windows 任务管理器核心警告】\n\n确定要强行终止映像进程 "${name}" (PID: ${pid}) 吗?\n结束该进程会导致关联的物理端口立即释放,未保存的更改会直接丢失。`)) {
|
||||
logToTerminal(`$ taskkill /F /PID ${pid}`, 'cmd');
|
||||
if (!invoke) {
|
||||
allPortsData = allPortsData.filter(p => p.pid !== pid); renderTable(allPortsData); return;
|
||||
}
|
||||
try {
|
||||
const successMsg = await invoke('kill_process_by_pid', { pid: pid });
|
||||
logToTerminal(successMsg, 'success');
|
||||
killedCount++; document.getElementById('killed-count').innerText = killedCount;
|
||||
await loadRealPorts();
|
||||
document.getElementById('search-input').value = '';
|
||||
} catch (err) {
|
||||
logToTerminal(`进程销毁遭遇拒绝: ${err}`, 'error');
|
||||
alert(`拒绝访问:强杀此系统核心或高权限进程需要以管理员权限重新启动软件。\n\n${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', matchHeight);
|
||||
window.onload = async () => {
|
||||
initWindowDrag();
|
||||
await syncAppMetadata();
|
||||
await loadRealPorts();
|
||||
document.getElementById('search-input').addEventListener('input', debouncedFilter);
|
||||
setTimeout(matchHeight, 150);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user