280 lines
6.3 KiB
Go
280 lines
6.3 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/widget"
|
|
|
|
"ip_scan_go/internal/scan"
|
|
)
|
|
|
|
const appVersion = "V0.0.1"
|
|
|
|
const (
|
|
ipPlaceholder = "例子:\n127.0.0.1 - 单IP\n192.168.0.0/24 - 掩码IP段\n0.0.0.0-255.255.255.255 - IP段\ndomain.com - 单域名"
|
|
portPlaceholder = "例子:\n22 - 单端口\n80;443 - 多个不连续端口\n3000-65535 - 连续的端口范围"
|
|
)
|
|
|
|
type scanForm struct {
|
|
app fyne.App
|
|
|
|
ipList *widget.Entry
|
|
portList *widget.Entry
|
|
timeout *widget.Entry
|
|
threads *widget.Entry
|
|
output *widget.Entry
|
|
progress *widget.ProgressBar
|
|
|
|
scanBtn *widget.Button
|
|
pingBtn *widget.Button
|
|
keypadBtn []*widget.Button
|
|
dispatcher *scan.Dispatcher
|
|
scanning bool
|
|
}
|
|
|
|
func newScanForm(app fyne.App) *scanForm {
|
|
f := &scanForm{app: app}
|
|
|
|
f.ipList = widget.NewMultiLineEntry()
|
|
f.ipList.SetPlaceHolder(ipPlaceholder)
|
|
f.ipList.SetMinRowsVisible(4)
|
|
|
|
f.portList = widget.NewMultiLineEntry()
|
|
f.portList.SetPlaceHolder(portPlaceholder)
|
|
f.portList.SetMinRowsVisible(4)
|
|
|
|
f.timeout = newIntEntry(100, 1, 10000)
|
|
f.threads = newIntEntry(64, 1, 1024)
|
|
|
|
f.output = widget.NewMultiLineEntry()
|
|
f.output.SetMinRowsVisible(6)
|
|
|
|
f.progress = widget.NewProgressBar()
|
|
|
|
f.scanBtn = widget.NewButton("开始扫描", f.toggleScan)
|
|
f.pingBtn = widget.NewButton("仅Ping", f.togglePing)
|
|
|
|
return f
|
|
}
|
|
|
|
func (f *scanForm) build() fyne.CanvasObject {
|
|
ipSection := container.NewVBox(
|
|
widget.NewLabel("IP"),
|
|
f.ipList,
|
|
)
|
|
ipKeys, ipBtns := newIPKeypad(f.ipList)
|
|
ipSection.Add(ipKeys)
|
|
portSection := container.NewVBox(
|
|
widget.NewLabel("端口"),
|
|
f.portList,
|
|
)
|
|
portKeys, portBtns := newPortKeypad(f.portList)
|
|
portSection.Add(portKeys)
|
|
f.keypadBtn = append(f.keypadBtn, ipBtns...)
|
|
f.keypadBtn = append(f.keypadBtn, portBtns...)
|
|
|
|
topRow := container.New(layout.NewGridLayoutWithColumns(2), ipSection, portSection)
|
|
controlRow := f.buildControlRow()
|
|
topArea := container.NewVBox(topRow, controlRow)
|
|
|
|
footer := container.NewHBox(
|
|
widget.NewLabel(appVersion),
|
|
layout.NewSpacer(),
|
|
widget.NewButton("Blog", func() { f.openURL("https://wnfed.com/") }),
|
|
widget.NewButton("Gitea", func() { f.openURL("https://git.lmve.net/kevin/ip_scan_go") }),
|
|
widget.NewButton("GitHub", func() { f.openURL("https://github.com/wuwenfengmi1998") }),
|
|
)
|
|
|
|
return container.NewBorder(topArea, footer, nil, nil, f.output)
|
|
}
|
|
|
|
func (f *scanForm) buildControlRow() fyne.CanvasObject {
|
|
left := container.NewHBox(
|
|
widget.NewButton("清空输出", func() { f.output.SetText("") }),
|
|
widget.NewButton("保存输出", f.saveOutput),
|
|
container.NewHBox(widget.NewLabel("超时ms"), f.timeout),
|
|
container.NewHBox(widget.NewLabel("线程"), f.threads),
|
|
widget.NewButton("清空IP", func() { f.ipList.SetText("") }),
|
|
f.pingBtn,
|
|
)
|
|
right := container.NewHBox(
|
|
widget.NewButton("清空端口", func() { f.portList.SetText("") }),
|
|
f.scanBtn,
|
|
)
|
|
return container.NewBorder(nil, nil, left, right, f.progress)
|
|
}
|
|
|
|
func (f *scanForm) toggleScan() {
|
|
if !f.scanning {
|
|
ips, err := scan.ParseIPs(f.ipList.Text)
|
|
if err != nil {
|
|
f.appendOutput("IP解析错误: " + err.Error() + "\n")
|
|
return
|
|
}
|
|
ports, err := scan.ParsePorts(f.portList.Text)
|
|
if err != nil {
|
|
f.appendOutput("端口解析错误: " + err.Error() + "\n")
|
|
return
|
|
}
|
|
if len(ips) == 0 || len(ports) == 0 {
|
|
f.appendOutput("请输入IP和端口\n")
|
|
return
|
|
}
|
|
f.startScan(false, ips, ports)
|
|
} else {
|
|
f.stopScan()
|
|
}
|
|
}
|
|
|
|
func (f *scanForm) togglePing() {
|
|
if !f.scanning {
|
|
ips, err := scan.ParseIPs(f.ipList.Text)
|
|
if err != nil {
|
|
f.appendOutput("IP解析错误: " + err.Error() + "\n")
|
|
return
|
|
}
|
|
if len(ips) == 0 {
|
|
f.appendOutput("请输入IP\n")
|
|
return
|
|
}
|
|
f.startScan(true, ips, nil)
|
|
} else {
|
|
f.stopScan()
|
|
}
|
|
}
|
|
|
|
func (f *scanForm) startScan(pingOnly bool, ips []string, ports []int) {
|
|
f.scanning = true
|
|
if pingOnly {
|
|
f.pingBtn.SetText("停止Ping")
|
|
f.scanBtn.Disable()
|
|
} else {
|
|
f.scanBtn.SetText("停止扫描")
|
|
f.pingBtn.Disable()
|
|
}
|
|
f.setInputsEnabled(false)
|
|
f.progress.SetValue(0)
|
|
|
|
timeout := time.Duration(parseIntEntry(f.timeout, 100)) * time.Millisecond
|
|
workers := parseIntEntry(f.threads, 64)
|
|
mode := "扫描"
|
|
if pingOnly {
|
|
mode = "Ping"
|
|
}
|
|
f.appendOutput(fmt.Sprintf("开始%s (IP:%d 线程:%d 超时:%s)\n", mode, len(ips), workers, timeout))
|
|
|
|
d := &scan.Dispatcher{
|
|
IPs: ips,
|
|
Ports: ports,
|
|
Timeout: timeout,
|
|
Workers: workers,
|
|
PingOnly: pingOnly,
|
|
OnResult: func(r scan.Result) {
|
|
fyne.Do(func() {
|
|
f.appendOutput(r.Text + "\n")
|
|
})
|
|
},
|
|
OnProgress: func(p scan.Progress) {
|
|
pct := float64(p.Done) / float64(p.Total)
|
|
fyne.Do(func() {
|
|
f.progress.SetValue(pct)
|
|
})
|
|
},
|
|
OnFinish: func() {
|
|
fyne.Do(func() {
|
|
f.resetAfterScan(pingOnly)
|
|
f.appendOutput(fmt.Sprintf("%s完成\n", mode))
|
|
})
|
|
},
|
|
}
|
|
f.dispatcher = d
|
|
go d.Start()
|
|
}
|
|
|
|
func (f *scanForm) stopScan() {
|
|
if f.dispatcher != nil {
|
|
f.dispatcher.Stop()
|
|
}
|
|
}
|
|
|
|
func (f *scanForm) resetAfterScan(pingOnly bool) {
|
|
f.scanning = false
|
|
if pingOnly {
|
|
f.pingBtn.SetText("仅Ping")
|
|
} else {
|
|
f.scanBtn.SetText("开始扫描")
|
|
}
|
|
f.scanBtn.Enable()
|
|
f.pingBtn.Enable()
|
|
f.setInputsEnabled(true)
|
|
f.dispatcher = nil
|
|
}
|
|
|
|
func (f *scanForm) setInputsEnabled(enabled bool) {
|
|
for _, b := range f.keypadBtn {
|
|
if enabled {
|
|
b.Enable()
|
|
} else {
|
|
b.Disable()
|
|
}
|
|
}
|
|
if enabled {
|
|
f.ipList.Enable()
|
|
f.portList.Enable()
|
|
f.timeout.Enable()
|
|
f.threads.Enable()
|
|
} else {
|
|
f.ipList.Disable()
|
|
f.portList.Disable()
|
|
f.timeout.Disable()
|
|
f.threads.Disable()
|
|
}
|
|
}
|
|
|
|
func (f *scanForm) saveOutput() {
|
|
f.appendOutput("(TODO: 保存输出到文件)\n")
|
|
}
|
|
|
|
func (f *scanForm) appendOutput(text string) {
|
|
f.output.Append(text)
|
|
f.output.CursorRow = strings.Count(f.output.Text, "\n")
|
|
f.output.CursorColumn = 0
|
|
f.output.Refresh()
|
|
}
|
|
|
|
func (f *scanForm) openURL(urlStr string) {
|
|
u, err := url.Parse(urlStr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_ = f.app.OpenURL(u)
|
|
}
|
|
|
|
func newIntEntry(val, min, max int) *widget.Entry {
|
|
e := widget.NewEntry()
|
|
e.SetText(strconv.Itoa(val))
|
|
e.Validator = func(s string) error {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil || n < min || n > max {
|
|
return fmt.Errorf("需为 %d-%d", min, max)
|
|
}
|
|
return nil
|
|
}
|
|
return e
|
|
}
|
|
|
|
func parseIntEntry(e *widget.Entry, fallback int) int {
|
|
n, err := strconv.Atoi(e.Text)
|
|
if err != nil || n <= 0 {
|
|
return fallback
|
|
}
|
|
return n
|
|
}
|