Enhance error handling and user feedback in the web server's index handler
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>';
|
||||||
|
|||||||
Reference in New Issue
Block a user