Enhance error handling and user feedback in the web server's index handler

This commit is contained in:
MiaoMint
2025-09-23 02:08:09 +08:00
parent 3ce69dbe2d
commit d917b7238e
3 changed files with 82 additions and 30 deletions

View File

@@ -85,7 +85,7 @@ func main() {
} }
func showUsage() { func showUsage() {
fmt.Println(` fmt.Print(`
🔌 NAS 端口管理器 🔌 NAS 端口管理器
================ ================
@@ -134,6 +134,6 @@ func showUsage() {
✅ 响应式Web界面设计 ✅ 响应式Web界面设计
更多信息: 更多信息:
项目地址: https://github.com/miaomint/port-manager 项目地址: https://github.com/miaomint/port-manager`)
`) fmt.Println()
} }

View File

@@ -102,7 +102,7 @@ func (ps *PortScanner) parseNetstatOutput(output, protocol string) ([]types.Port
continue continue
} }
port, err := ps.parseNetstatLine(line, protocol) port, err := ps.parseNetstatLineMacOS(line, protocol)
if err != nil { if err != nil {
continue // 跳过解析失败的行 continue // 跳过解析失败的行
} }
@@ -118,31 +118,54 @@ func (ps *PortScanner) parseNetstatOutput(output, protocol string) ([]types.Port
return ports, nil return ports, nil
} }
// parseNetstatLine 解析netstat的单行输出 // parseNetstatLineMacOS 解析macOS netstat的单行输出
func (ps *PortScanner) parseNetstatLine(line, protocol string) (*types.PortInfo, error) { func (ps *PortScanner) parseNetstatLineMacOS(line, protocol string) (*types.PortInfo, error) {
fields := regexp.MustCompile(`\s+`).Split(line, -1) fields := regexp.MustCompile(`\s+`).Split(line, -1)
if len(fields) < 3 { if len(fields) < 4 {
return nil, fmt.Errorf("invalid line format") return nil, fmt.Errorf("invalid line format")
} }
// 解析本地地址 // macOS netstat格式: Proto Recv-Q Send-Q Local-Address Foreign-Address (state)
localAddr := fields[0] localAddr := fields[3]
parts := strings.Split(localAddr, ":") state := ""
if len(parts) < 2 { if len(fields) >= 6 {
return nil, fmt.Errorf("invalid address format") state = fields[5]
}
// 解析本地地址可能包含IPv6地址
var portStr string
if strings.Contains(localAddr, ".") {
// IPv4 格式: ip.port
parts := strings.Split(localAddr, ".")
if len(parts) >= 2 {
portStr = parts[len(parts)-1]
}
} else if strings.Contains(localAddr, ":") {
// IPv6 格式: [ip]:port 或 ip:port
if strings.Contains(localAddr, "]:") {
// [ip]:port
parts := strings.Split(localAddr, "]:")
if len(parts) == 2 {
portStr = parts[1]
}
} else {
// ip:port (简单情况)
parts := strings.Split(localAddr, ":")
if len(parts) >= 2 {
portStr = parts[len(parts)-1]
}
}
}
if portStr == "" {
return nil, fmt.Errorf("could not extract port from address: %s", localAddr)
} }
portStr := parts[len(parts)-1]
port, err := strconv.Atoi(portStr) port, err := strconv.Atoi(portStr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid port: %s", portStr) return nil, fmt.Errorf("invalid port: %s", portStr)
} }
state := ""
if protocol == "tcp" && len(fields) >= 4 {
state = fields[3]
}
return &types.PortInfo{ return &types.PortInfo{
Port: port, Port: port,
Protocol: protocol, Protocol: protocol,
@@ -154,15 +177,18 @@ func (ps *PortScanner) parseNetstatLine(line, protocol string) (*types.PortInfo,
// shouldIncludePort 判断是否应该包含此端口 // shouldIncludePort 判断是否应该包含此端口
func (ps *PortScanner) shouldIncludePort(port *types.PortInfo) bool { func (ps *PortScanner) shouldIncludePort(port *types.PortInfo) bool {
if port.Protocol == "tcp" { if port.Protocol == "tcp" {
if port.State == "LISTEN" { // macOS netstat 状态值可能包括: LISTEN, ESTABLISHED, SYN_SENT, etc.
if strings.Contains(port.State, "LISTEN") {
return ps.includeListening return ps.includeListening
} }
if port.State == "ESTABLISHED" { if strings.Contains(port.State, "ESTABLISHED") {
return ps.includeEstablished return ps.includeEstablished
} }
// 对于其他状态,如果设置了包含监听端口,也包含进来
return ps.includeListening
} }
// UDP端口默认包含 // UDP端口默认包含(如果启用了监听端口)
if port.Protocol == "udp" { if port.Protocol == "udp" {
return ps.includeListening return ps.includeListening
} }

View File

@@ -178,12 +178,29 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
try { try {
const response = await fetch('/api/ports'); const response = await fetch('/api/ports');
if (!response.ok) {
throw new Error('HTTP error! status: ' + response.status);
}
const data = await response.json(); const data = await response.json();
// 检查是否有错误
if (data.error) {
throw new Error(data.error);
}
// 检查 ports 是否存在
if (data.ports) {
displayPorts(data.ports); displayPorts(data.ports);
} else {
displayPorts([]);
}
} catch (error) { } catch (error) {
console.error('扫描失败:', error); console.error('扫描失败:', error);
alert('扫描失败: ' + error.message); alert('扫描失败: ' + error.message);
// 显示空表格
displayPorts([]);
} finally { } finally {
document.getElementById('loading').style.display = 'none'; document.getElementById('loading').style.display = 'none';
} }
@@ -209,6 +226,15 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
const tbody = document.getElementById('portsBody'); const tbody = document.getElementById('portsBody');
tbody.innerHTML = ''; tbody.innerHTML = '';
// 检查 ports 是否为空或无效
if (!ports || !Array.isArray(ports) || ports.length === 0) {
const row = document.createElement('tr');
row.innerHTML = '<td colspan="5" style="text-align: center; color: #666;">暂无发现端口</td>';
tbody.appendChild(row);
document.getElementById('portsTable').style.display = 'table';
return;
}
ports.forEach(port => { ports.forEach(port => {
const row = document.createElement('tr'); const row = document.createElement('tr');
@@ -217,8 +243,8 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
: '-'; : '-';
row.innerHTML = row.innerHTML =
'<td class="port-number">' + port.port + '</td>' + '<td class="port-number">' + (port.port || '-') + '</td>' +
'<td><span class="protocol-badge">' + port.protocol.toUpperCase() + '</span></td>' + '<td><span class="protocol-badge">' + (port.protocol ? port.protocol.toUpperCase() : 'UNKNOWN') + '</span></td>' +
'<td class="status-active">' + (port.state || 'ACTIVE') + '</td>' + '<td class="status-active">' + (port.state || 'ACTIVE') + '</td>' +
'<td>' + (port.serviceName || '未知服务') + '</td>' + '<td>' + (port.serviceName || '未知服务') + '</td>' +
'<td>' + serviceCell + '</td>'; '<td>' + serviceCell + '</td>';