commit
7833c49c4a
@ -0,0 +1,39 @@ |
||||
build/bin |
||||
build |
||||
node_modules |
||||
frontend/dist |
||||
.idea |
||||
|
||||
.DS_Store |
||||
.thumbs.db |
||||
node_modules |
||||
|
||||
# Quasar core related directories |
||||
.quasar |
||||
/dist |
||||
/quasar.config.*.temporary.compiled* |
||||
|
||||
# Cordova related directories and files |
||||
/src-cordova/node_modules |
||||
/src-cordova/platforms |
||||
/src-cordova/plugins |
||||
/src-cordova/www |
||||
|
||||
# Capacitor related directories and files |
||||
/src-capacitor/www |
||||
/src-capacitor/node_modules |
||||
|
||||
# Log files |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
|
||||
# local .env files |
||||
.env.local* |
@ -0,0 +1,180 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/hex" |
||||
"eruhs/core" |
||||
"eruhs/core/parser" |
||||
"eruhs/describer" |
||||
"fmt" |
||||
"github.com/wailsapp/wails/v2/pkg/runtime" |
||||
"time" |
||||
) |
||||
|
||||
// App struct
|
||||
type App struct { |
||||
ctx context.Context |
||||
capture *core.Capture |
||||
} |
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App { |
||||
return &App{} |
||||
} |
||||
|
||||
// startup is called when the app starts. The context is saved
|
||||
// so we can call the runtime methods
|
||||
func (a *App) startup(ctx context.Context) { |
||||
a.ctx = ctx |
||||
} |
||||
|
||||
func (a *App) GetNetworkDeviceList() ([]core.Device, error) { |
||||
devices, err := core.GetAllDevice() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return devices, nil |
||||
} |
||||
|
||||
var globalPackCnt = 0 |
||||
var startTime = time.Now().UnixMilli() |
||||
var prevSendTime = time.Now().UnixMilli() |
||||
var batchSendBuffer []UIDataPackView |
||||
var packetMap = map[int]*core.DataPack{} |
||||
|
||||
func (a *App) StartCapture(device string) { |
||||
if a.capture == nil { |
||||
a.capture = core.NewCapture(packCallback(a.ctx), errCallback(a.ctx)) |
||||
} |
||||
|
||||
globalPackCnt = 0 |
||||
startTime = time.Now().UnixMilli() |
||||
prevSendTime = time.Now().UnixMilli() |
||||
batchSendBuffer = nil |
||||
batchSendBuffer = make([]UIDataPackView, 0, 1000) |
||||
runtime.LogPrint(a.ctx, "StartCapture") |
||||
|
||||
a.capture.StartCaptureOnDevice(device) |
||||
} |
||||
|
||||
func (a *App) PauseCapture() { |
||||
|
||||
} |
||||
|
||||
func (a *App) StopCapture() { |
||||
a.capture.Stop() |
||||
} |
||||
|
||||
func (a *App) GetDetail(id int) map[int]LayerDescribe { |
||||
pack := packetMap[id] |
||||
if pack == nil { |
||||
return map[int]LayerDescribe{} |
||||
} |
||||
|
||||
result := map[int]LayerDescribe{} |
||||
for _, layer := range pack.LayerStack { |
||||
switch layer.LayerType() { |
||||
case parser.LayerTypeEthernet: |
||||
eth := layer.(*parser.Ethernet) |
||||
result[0] = LayerDescribe{ |
||||
HeaderRaw: hex.Dump(eth.Header), |
||||
LayerName: "ETH", |
||||
Describe: (&describer.EthernetDescriber{Eth: eth}).GetDetailDescribe(), |
||||
} |
||||
case parser.LayerTypeARP: |
||||
arp := layer.(*parser.ARP) |
||||
result[1] = LayerDescribe{ |
||||
HeaderRaw: hex.Dump(arp.Header), |
||||
LayerName: "ARP", |
||||
Describe: (&describer.ARPDescriber{ARP: arp}).GetDetailDescribe(), |
||||
} |
||||
case parser.LayerTypeIPv4: |
||||
ipv4 := layer.(*parser.IPv4) |
||||
result[1] = LayerDescribe{ |
||||
HeaderRaw: hex.Dump(ipv4.Header), |
||||
LayerName: "IPv4", |
||||
Describe: (&describer.IPv4Describer{IPv4: ipv4}).GetDetailDescribe(), |
||||
} |
||||
case parser.LayerTypeTCP: |
||||
tcp := layer.(*parser.TCP) |
||||
result[2] = LayerDescribe{ |
||||
HeaderRaw: hex.Dump(tcp.Header), |
||||
ApplicationPayloadRaw: hex.Dump(tcp.Payload), |
||||
LayerName: "TCP", |
||||
Describe: (&describer.TCPDescriber{TCP: tcp}).GetDetailDescribe(), |
||||
} |
||||
case parser.LayerTypeUDP: |
||||
udp := layer.(*parser.UDP) |
||||
result[2] = LayerDescribe{ |
||||
HeaderRaw: hex.Dump(udp.Header), |
||||
ApplicationPayloadRaw: hex.Dump(udp.Payload), |
||||
LayerName: "UDP", |
||||
Describe: (&describer.UDPDescriber{UDP: udp}).GetDetailDescribe(), |
||||
} |
||||
default: |
||||
} |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
func errCallback(ctx context.Context) func(err error) { |
||||
return func(err error) { |
||||
fmt.Println(err) |
||||
runtime.EventsEmit(ctx, "onErrOccurrence", err.Error()) |
||||
} |
||||
} |
||||
|
||||
func packCallback(ctx context.Context) func(pack *core.DataPack) { |
||||
return func(pack *core.DataPack) { |
||||
globalPackCnt++ |
||||
packetMap[globalPackCnt] = pack |
||||
dataPackView := UIDataPackView{ |
||||
Id: globalPackCnt, |
||||
Time: time.Duration(time.Now().UnixMilli() - startTime), |
||||
Source: "", |
||||
Dest: "", |
||||
Proto: "", |
||||
Length: pack.TotalLength, |
||||
} |
||||
|
||||
for _, layer := range pack.LayerStack { |
||||
switch layer.LayerType() { |
||||
case parser.LayerTypeEthernet: |
||||
eth := layer.(*parser.Ethernet) |
||||
dataPackView.Source = eth.Source.String() |
||||
dataPackView.Dest = eth.Destination.String() |
||||
dataPackView.Proto = "ETH" |
||||
case parser.LayerTypeARP: |
||||
dataPackView.Proto = "ARP" |
||||
case parser.LayerTypeIPv4: |
||||
ipv4 := layer.(*parser.IPv4) |
||||
dataPackView.Source = ipv4.SrcIP.String() |
||||
dataPackView.Dest = ipv4.DstIP.String() |
||||
dataPackView.Proto = "IPv4" |
||||
case parser.LayerTypeTCP: |
||||
tcp := layer.(*parser.TCP) |
||||
dataPackView.Source = fmt.Sprintf("%s:%d", dataPackView.Source, tcp.SrcPort) |
||||
dataPackView.Dest = fmt.Sprintf("%s:%d", dataPackView.Dest, tcp.DstPort) |
||||
dataPackView.Proto = "TCP" |
||||
case parser.LayerTypeUDP: |
||||
udp := layer.(*parser.UDP) |
||||
dataPackView.Source = fmt.Sprintf("%s:%d", dataPackView.Source, udp.SrcPort) |
||||
dataPackView.Dest = fmt.Sprintf("%s:%d", dataPackView.Dest, udp.DstPort) |
||||
dataPackView.Proto = "UDP" |
||||
default: |
||||
} |
||||
} |
||||
|
||||
if (time.Now().UnixMilli() - prevSendTime) > int64(0.2*1000) { |
||||
runtime.EventsEmit(ctx, "onNewDataPacketBatch", batchSendBuffer) |
||||
batchSendBuffer = nil |
||||
batchSendBuffer = make([]UIDataPackView, 0, 1000) |
||||
prevSendTime = time.Now().UnixMilli() |
||||
} else { |
||||
batchSendBuffer = append(batchSendBuffer, dataPackView) |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,86 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"context" |
||||
"eruhs/core/parser" |
||||
"github.com/dustin/go-humanize" |
||||
"github.com/google/gopacket" |
||||
"github.com/google/gopacket/pcap" |
||||
"time" |
||||
) |
||||
|
||||
type DataPack struct { |
||||
TotalLength int |
||||
LayerStack []parser.Layer |
||||
} |
||||
|
||||
func (d *DataPack) AddLayer(layer parser.Layer) { |
||||
d.LayerStack = append(d.LayerStack, layer) |
||||
} |
||||
|
||||
type Capture struct { |
||||
handle *pcap.Handle |
||||
started bool |
||||
stopFunc func() |
||||
callback func(pack *DataPack) |
||||
errCallback func(err error) |
||||
} |
||||
|
||||
func NewCapture(callback func(pack *DataPack), errCallback func(err error)) *Capture { |
||||
return &Capture{ |
||||
callback: callback, |
||||
errCallback: errCallback, |
||||
started: false, |
||||
} |
||||
} |
||||
|
||||
// StartCaptureOnDevice 传入设备ID,启动抓包
|
||||
func (c *Capture) StartCaptureOnDevice(device string) { |
||||
if c.started { |
||||
return |
||||
} else { |
||||
c.started = true |
||||
} |
||||
|
||||
handle, err := pcap.OpenLive(device, 4*humanize.KByte, false, 1*time.Second) |
||||
if err != nil { |
||||
c.errCallback(err) |
||||
return |
||||
} |
||||
|
||||
c.handle = handle |
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
c.stopFunc = cancel |
||||
|
||||
// 开启一个goroutine协程,实时读取数据包,并在抓取到数据包后调用callback,继续下一步的解析操作
|
||||
go func(ctx context.Context) { |
||||
defer c.handle.Close() |
||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) |
||||
for packet := range packetSource.Packets() { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
default: |
||||
parsedPack, err := c.handlePacket(packet) |
||||
if err != nil { |
||||
if parsedPack != nil { |
||||
c.callback(parsedPack) |
||||
} else { |
||||
c.errCallback(err) |
||||
} |
||||
} |
||||
|
||||
c.callback(parsedPack) |
||||
} |
||||
} |
||||
}(ctx) |
||||
} |
||||
|
||||
func (c *Capture) Pause() { |
||||
|
||||
} |
||||
|
||||
func (c *Capture) Stop() { |
||||
c.stopFunc() |
||||
c.started = false |
||||
} |
@ -0,0 +1,50 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"eruhs/core/parser" |
||||
"fmt" |
||||
_ "net/http/pprof" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestCapture_StartCaptureOnDevice(t *testing.T) { |
||||
cnt := 0 |
||||
capture := NewCapture(func(pack *DataPack) { |
||||
cnt++ |
||||
for _, layer := range pack.LayerStack { |
||||
switch layer.(type) { |
||||
case *parser.Ethernet: |
||||
fmt.Printf("%+v", layer.(*parser.Ethernet)) |
||||
case *parser.IPv4: |
||||
fmt.Printf("%+v", layer.(*parser.IPv4)) |
||||
case *parser.ARP: |
||||
fmt.Printf("%+v", layer.(*parser.ARP)) |
||||
case *parser.TCP: |
||||
fmt.Printf("%+v", layer.(*parser.TCP)) |
||||
case *parser.UDP: |
||||
fmt.Printf("%+v", layer.(*parser.UDP)) |
||||
default: |
||||
j, err := json.MarshalIndent(layer, "", " ") |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
fmt.Println(j) |
||||
} |
||||
} |
||||
fmt.Println("----------------") |
||||
//if cnt == 16 {
|
||||
// t.SkipNow()
|
||||
//}
|
||||
}, func(err error) { |
||||
t.Error(err) |
||||
}) |
||||
|
||||
go func() { |
||||
time.Sleep(3 * time.Second) |
||||
capture.Stop() |
||||
}() |
||||
|
||||
capture.StartCaptureOnDevice("\\Device\\NPF_{AA5A8BD6-43B3-489B-9858-0ECB96EAB05E}") |
||||
} |
@ -0,0 +1,34 @@ |
||||
package core |
||||
|
||||
import "github.com/google/gopacket/pcap" |
||||
|
||||
type Device struct { |
||||
Name string `json:"Name"` |
||||
Description string `json:"Description"` |
||||
Flags uint32 `json:"Flags"` |
||||
Addresses []string `json:"Addresses"` |
||||
} |
||||
|
||||
func GetAllDevice() ([]Device, error) { |
||||
netInterfaces, err := pcap.FindAllDevs() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
devices := make([]Device, 0, len(netInterfaces)) |
||||
for _, netInterface := range netInterfaces { |
||||
addr := make([]string, 0, len(netInterface.Addresses)) |
||||
for _, address := range netInterface.Addresses { |
||||
addr = append(addr, address.IP.String()) |
||||
} |
||||
|
||||
devices = append(devices, Device{ |
||||
Name: netInterface.Name, |
||||
Description: netInterface.Description, |
||||
Flags: netInterface.Flags, |
||||
Addresses: addr, |
||||
}) |
||||
} |
||||
|
||||
return devices, nil |
||||
} |
@ -0,0 +1,32 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestGetDevice(t *testing.T) { |
||||
devices, err := GetAllDevice() |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
for _, device := range devices { |
||||
println("--------") |
||||
j, _ := json.MarshalIndent(device, "", " ") |
||||
//fmt.Printf("%+v\n", device)
|
||||
println(string(j)) |
||||
} |
||||
} |
||||
|
||||
func TestName(t *testing.T) { |
||||
a := []int{1, 2, 3, 4, 5} |
||||
fmt.Printf("%+v\n", a) |
||||
as(a) |
||||
fmt.Printf("%+v\n", a) |
||||
} |
||||
|
||||
func as(a []int) { |
||||
a[1] = 500 |
||||
} |
@ -0,0 +1,79 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"eruhs/core/parser" |
||||
"github.com/google/gopacket" |
||||
) |
||||
|
||||
func (c *Capture) handlePacket(packet gopacket.Packet) (*DataPack, error) { |
||||
parsedPack := DataPack{} |
||||
data := packet.Data() |
||||
parsedPack.TotalLength = len(data) |
||||
eth, err := parser.DecodeEthernet(data) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
parsedPack.AddLayer(eth) |
||||
|
||||
switch eth.Type { |
||||
case parser.EthernetTypeARP: |
||||
return c.handleARP(eth.Payload, parsedPack) |
||||
case parser.EthernetTypeIPv4: |
||||
return c.handleIPv4(eth.Payload, parsedPack) |
||||
//case custom.EthernetTypeIPv6:
|
||||
default: |
||||
} |
||||
|
||||
return &parsedPack, nil |
||||
} |
||||
|
||||
func (c *Capture) handleARP(data []byte, parsedPack DataPack) (*DataPack, error) { |
||||
arp, err := parser.DecodeARP(data) |
||||
if err != nil { |
||||
return &parsedPack, err |
||||
} |
||||
|
||||
parsedPack.AddLayer(arp) |
||||
return &parsedPack, nil |
||||
} |
||||
|
||||
func (c *Capture) handleIPv4(data []byte, parsedPack DataPack) (*DataPack, error) { |
||||
ipv4, err := parser.DecodeIPV4(data) |
||||
if err != nil { |
||||
return &parsedPack, err |
||||
} |
||||
|
||||
parsedPack.AddLayer(ipv4) |
||||
|
||||
switch ipv4.Protocol { |
||||
case parser.IPv4ProtoTCP: |
||||
return c.handleTCP(ipv4.Payload, parsedPack) |
||||
case parser.IPv4ProtoUDP: |
||||
return c.handleUDP(ipv4.Payload, parsedPack) |
||||
default: |
||||
// unsupported protocol
|
||||
} |
||||
|
||||
return &parsedPack, nil |
||||
} |
||||
|
||||
func (c *Capture) handleTCP(data []byte, parsedPack DataPack) (*DataPack, error) { |
||||
tcp, err := parser.DecodeTCP(data) |
||||
if err != nil { |
||||
return &parsedPack, err |
||||
} |
||||
|
||||
parsedPack.AddLayer(tcp) |
||||
return &parsedPack, nil |
||||
} |
||||
|
||||
func (c *Capture) handleUDP(data []byte, parsedPack DataPack) (*DataPack, error) { |
||||
udp, err := parser.DecodeUDP(data) |
||||
if err != nil { |
||||
return &parsedPack, err |
||||
} |
||||
|
||||
parsedPack.AddLayer(udp) |
||||
return &parsedPack, nil |
||||
} |
@ -0,0 +1,61 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
"net" |
||||
) |
||||
|
||||
// ARP ref: https://zh.wikipedia.org/zh-cn/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE#%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F
|
||||
type ARP struct { |
||||
BaseLayer |
||||
HardwareType uint16 // 硬件类型
|
||||
ProtoType uint16 // 协议类型
|
||||
HwAddressSize uint8 // 硬件地址长度
|
||||
ProtoAddrSize uint8 // 协议地址长度
|
||||
Opcode uint16 // 操作码
|
||||
SrcHwAddress net.HardwareAddr // 源硬件地址
|
||||
SrcProtoAddress net.IP // 源协议地址
|
||||
DstHwAddress net.HardwareAddr // 目标硬件地址
|
||||
DstProtoAddress net.IP // 目标协议地址
|
||||
} |
||||
|
||||
func (A *ARP) LayerType() int { |
||||
return LayerTypeARP |
||||
} |
||||
|
||||
// DecodeARP ref: https://zh.wikipedia.org/zh-cn/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE#%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F
|
||||
func DecodeARP(data []byte) (*ARP, error) { |
||||
// 至少要有8bytes
|
||||
if len(data) < 8 { |
||||
return nil, fmt.Errorf("[arp] 数据包太短: %d bytes", len(data)) |
||||
} |
||||
|
||||
arp := ARP{} |
||||
arp.HardwareType = binary.BigEndian.Uint16(data[0:2]) |
||||
arp.ProtoType = binary.BigEndian.Uint16(data[2:4]) |
||||
arp.HwAddressSize = data[4] |
||||
arp.ProtoAddrSize = data[5] |
||||
arp.Opcode = binary.BigEndian.Uint16(data[6:8]) |
||||
arpLen := 8 + 2*arp.HwAddressSize + 2*arp.ProtoAddrSize |
||||
if len(data) < int(arpLen) { |
||||
return nil, fmt.Errorf("[arp] 实际长度小于声明长度:实际:%d,声明:%d", len(data), arpLen) |
||||
} |
||||
|
||||
var offset uint8 = 8 |
||||
|
||||
arp.SrcHwAddress = data[offset : offset+arp.HwAddressSize] |
||||
offset += arp.HwAddressSize |
||||
|
||||
arp.SrcProtoAddress = data[offset : offset+arp.ProtoAddrSize] |
||||
offset += arp.ProtoAddrSize |
||||
|
||||
arp.DstHwAddress = data[offset : offset+arp.HwAddressSize] |
||||
offset += arp.HwAddressSize |
||||
|
||||
arp.DstProtoAddress = data[offset : offset+arp.ProtoAddrSize] |
||||
|
||||
arp.Header = data[:arpLen] |
||||
arp.Payload = data[arpLen:] |
||||
return &arp, nil |
||||
} |
@ -0,0 +1,20 @@ |
||||
package parser |
||||
|
||||
const ( |
||||
LayerTypeEthernet = iota |
||||
LayerTypeARP |
||||
LayerTypeIPv4 |
||||
LayerTypeIPv6 |
||||
LayerTypeICMP |
||||
LayerTypeTCP |
||||
LayerTypeUDP |
||||
) |
||||
|
||||
type Layer interface { |
||||
LayerType() int |
||||
} |
||||
|
||||
type BaseLayer struct { |
||||
Header []byte |
||||
Payload []byte |
||||
} |
@ -0,0 +1,49 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"net" |
||||
) |
||||
|
||||
// 取值参考自https://en.wikipedia.org/wiki/EtherType,只取常用的ipv4/6和arp类型
|
||||
const ( |
||||
EthernetTypeIPv4 = 0x0800 |
||||
EthernetTypeARP = 0x0806 |
||||
EthernetTypeIPv6 = 0x86DD |
||||
) |
||||
|
||||
type Ethernet struct { |
||||
BaseLayer |
||||
Source net.HardwareAddr // 源mac
|
||||
Destination net.HardwareAddr // 目的mac
|
||||
Type uint16 // 网络层协议类型
|
||||
} |
||||
|
||||
func (e *Ethernet) LayerType() int { |
||||
return LayerTypeEthernet |
||||
} |
||||
|
||||
func (e *Ethernet) GetSourceMac() string { |
||||
return e.Source.String() |
||||
} |
||||
|
||||
func (e *Ethernet) GetDestinationMac() string { |
||||
return e.Destination.String() |
||||
} |
||||
|
||||
func DecodeEthernet(data []byte) (*Ethernet, error) { |
||||
// 帧数据大小小于14,不符合以太网帧格式要求
|
||||
if len(data) < 14 { |
||||
return nil, errors.New("[ethernet] 数据包太小,不符合以太网帧格式要求") |
||||
} |
||||
|
||||
eth := &Ethernet{} |
||||
eth.Destination = data[:6] // 0~5 byte: 目标mac
|
||||
eth.Source = data[6:12] // 6~11 byte: 源mac
|
||||
eth.Type = binary.BigEndian.Uint16(data[12:14]) // 12~13 byte: 类型字段,大端序,uint16
|
||||
|
||||
eth.Header = data[:14] |
||||
eth.Payload = data[14:] |
||||
return eth, nil |
||||
} |
@ -0,0 +1,33 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
) |
||||
|
||||
type ICMP struct { |
||||
BaseLayer |
||||
TypeCode uint16 |
||||
Checksum uint16 |
||||
Id uint16 |
||||
Seq uint16 |
||||
} |
||||
|
||||
func (I *ICMP) LayerType() int { |
||||
return LayerTypeICMP |
||||
} |
||||
|
||||
func DecodeICMP(data []byte) (*ICMP, error) { |
||||
icmp := ICMP{} |
||||
if len(data) < 8 { |
||||
return nil, errors.New("ICMP数据包头部少于8字节") |
||||
} |
||||
|
||||
icmp.TypeCode = binary.BigEndian.Uint16(data[0:2]) |
||||
icmp.Checksum = binary.BigEndian.Uint16(data[2:4]) |
||||
icmp.Id = binary.BigEndian.Uint16(data[4:6]) |
||||
icmp.Seq = binary.BigEndian.Uint16(data[6:8]) |
||||
icmp.BaseLayer = BaseLayer{data[:8], data[8:]} |
||||
|
||||
return &icmp, nil |
||||
} |
@ -0,0 +1,136 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
) |
||||
|
||||
const ( |
||||
IPv4ProtoICMP = 1 //互联网控制消息协议
|
||||
IPv4ProtoIGMP = 2 //互联网组管理协议
|
||||
IPv4ProtoTCP = 6 //传输控制协议
|
||||
IPv4ProtoUDP = 17 //用户数据报协议
|
||||
IPv4ProtoENCAP = 41 //IPv6封装
|
||||
IPv4ProtoOSPF = 89 //开放式最短路径优先
|
||||
IPv4ProtoSCTP = 132 //流控制传输协议
|
||||
) |
||||
|
||||
type IPv4 struct { |
||||
BaseLayer |
||||
Version uint8 // 版本
|
||||
IHL uint8 // 首部长度
|
||||
DS uint8 // 区分服务
|
||||
TotalLength uint16 // 总长
|
||||
Id uint16 // 标识符
|
||||
Flags uint8 // 标志
|
||||
FragOffset uint16 // 分片偏移
|
||||
TTL uint8 // 存活时间
|
||||
Protocol uint8 // 协议
|
||||
Checksum uint16 // 首部校验和
|
||||
SrcIP net.IP // 源地址
|
||||
DstIP net.IP // 目的地址
|
||||
Options []IPv4Option // 选项字段
|
||||
Padding []byte // 填充
|
||||
} |
||||
|
||||
type IPv4Option struct { |
||||
OptionType uint8 |
||||
OptionLength uint8 |
||||
OptionData []byte |
||||
} |
||||
|
||||
func (I *IPv4) LayerType() int { |
||||
return LayerTypeIPv4 |
||||
} |
||||
|
||||
// DecodeIPV4 ref: https://zh.wikipedia.org/wiki/IPv4#%E6%8A%A5%E6%96%87%E7%BB%93%E6%9E%84
|
||||
func DecodeIPV4(data []byte) (*IPv4, error) { |
||||
// 至少要有20bytes
|
||||
if len(data) < 20 { |
||||
return nil, fmt.Errorf("[ipv4] 无效的长度:%d, 至少需要20", len(data)) |
||||
} |
||||
|
||||
ipv4 := IPv4{} |
||||
ipv4.Version = data[0] >> 4 |
||||
ipv4.IHL = uint8(data[0]) & 0x0F |
||||
ipv4.DS = data[1] |
||||
ipv4.TotalLength = binary.BigEndian.Uint16(data[2:4]) |
||||
ipv4.Id = binary.BigEndian.Uint16(data[4:6]) |
||||
flagsFrags := binary.BigEndian.Uint16(data[6:8]) |
||||
ipv4.Flags = uint8(flagsFrags >> 13) |
||||
ipv4.FragOffset = flagsFrags & 0x1FFF |
||||
ipv4.TTL = data[8] |
||||
ipv4.Protocol = data[9] |
||||
ipv4.Checksum = binary.BigEndian.Uint16(data[10:12]) |
||||
ipv4.SrcIP = data[12:16] |
||||
ipv4.DstIP = data[16:20] |
||||
|
||||
if ipv4.TotalLength == 0 { |
||||
ipv4.TotalLength = uint16(len(data)) |
||||
} |
||||
|
||||
// 检查数据
|
||||
if ipv4.TotalLength < 20 { |
||||
return nil, fmt.Errorf("[IPv4] 数据包长度太小 (%d < 20)", ipv4.TotalLength) |
||||
} else if ipv4.IHL < 5 { |
||||
return nil, fmt.Errorf("[IPv4] 标头长度太小 (%d < 5)", ipv4.IHL) |
||||
} else if int(ipv4.IHL*4) > int(ipv4.TotalLength) { |
||||
return nil, fmt.Errorf("[IPv4] 标头长度 > IP 总长度 (%d > %d)", ipv4.IHL, ipv4.TotalLength) |
||||
} |
||||
|
||||
// 检查数据实际长度是否大于声明长度,大于(有padding)则除去padding拿到实际长度的payload
|
||||
if cmp := len(data) - int(ipv4.TotalLength); cmp > 0 { |
||||
data = data[:ipv4.TotalLength] |
||||
} else if cmp < 0 { |
||||
// 如果声明的头部长度比整体数据还大,异常
|
||||
if int(ipv4.IHL)*4 > len(data) { |
||||
return nil, errors.New("[ipv4] 数据包标头长度大于总数据包长度,标头长度无效或某些数据丢失") |
||||
} |
||||
} |
||||
|
||||
ipv4.Header = data[:ipv4.IHL*4] |
||||
ipv4.Payload = data[ipv4.IHL*4:] |
||||
|
||||
// 20bytes固定头部往后到IHL都是选项字段了
|
||||
data = data[20 : ipv4.IHL*4] |
||||
|
||||
// 解析可选字段,如果data[20 : ipv4.IHL*4]之间还有数据,说明选项字段有内容
|
||||
for len(data) > 0 { |
||||
if ipv4.Options == nil { |
||||
// Pre-allocate to avoid growing the slice too much.
|
||||
ipv4.Options = make([]IPv4Option, 0, 4) |
||||
} |
||||
opt := IPv4Option{OptionType: data[0]} |
||||
switch opt.OptionType { |
||||
case 0: // option字段结束,EOL标志
|
||||
opt.OptionLength = 1 |
||||
ipv4.Options = append(ipv4.Options, opt) |
||||
ipv4.Padding = data[1:] |
||||
goto FINISH |
||||
case 1: // 1byte填充
|
||||
opt.OptionLength = 1 |
||||
data = data[1:] |
||||
ipv4.Options = append(ipv4.Options, opt) |
||||
default: |
||||
// 字段未结束且不是1byte填充字段长度不足2byte
|
||||
if len(data) < 2 { |
||||
return nil, fmt.Errorf("[IPv4] 选项长度 %d 小于2", len(data)) |
||||
} |
||||
opt.OptionLength = data[1] |
||||
if len(data) < int(opt.OptionLength) { |
||||
return nil, fmt.Errorf("[IPv4] 可选字段长度超过剩余 IP 标头大小,选项类型:%v, 长度:%v", opt.OptionType, opt.OptionLength) |
||||
} |
||||
if opt.OptionLength <= 2 { |
||||
return nil, fmt.Errorf("[IPv4] 可选字段长度小于2,选项类型:%v 长度:%d", opt.OptionType, opt.OptionLength) |
||||
} |
||||
opt.OptionData = data[2:opt.OptionLength] |
||||
data = data[opt.OptionLength:] |
||||
ipv4.Options = append(ipv4.Options, opt) |
||||
} |
||||
} |
||||
|
||||
FINISH: |
||||
return &ipv4, nil |
||||
} |
@ -0,0 +1,20 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"github.com/google/gopacket/layers" |
||||
"net" |
||||
) |
||||
|
||||
type IPv6 struct { |
||||
layers.BaseLayer |
||||
Version uint8 |
||||
TrafficClass uint8 |
||||
FlowLabel uint32 |
||||
Length uint16 |
||||
NextHeader uint8 |
||||
HopLimit uint8 |
||||
SrcIP net.IP |
||||
DstIP net.IP |
||||
HopByHop *layers.IPv6HopByHop |
||||
hbh layers.IPv6HopByHop |
||||
} |
@ -0,0 +1,116 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
) |
||||
|
||||
const ( |
||||
TCPOptionKindEndList = 0 // 选项表结束(1字节)
|
||||
TCPOptionKindNop = 1 // 无操作(1字节)
|
||||
TCPOptionKindMSS = 2 // 最大报文段长度(4字节,Maximum Segment Size,MSS)
|
||||
TCPOptionKindWindowScale = 3 // 窗口扩大因子(3字节,wscale)
|
||||
TCPOptionKindSACKOK = 4 // sackOK—发送端支持并同意使用SACK选项。
|
||||
TCPOptionKindSACK = 5 // SACK实际工作的选项。
|
||||
TCPOptionKindTimestamps = 8 // 时间戳(10字节,TCP Timestamps Option,TSopt)
|
||||
) |
||||
|
||||
// TCP ref: https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE#%E7%8A%B6%E6%80%81%E7%BC%96%E7%A0%81
|
||||
type TCP struct { |
||||
BaseLayer |
||||
SrcPort, DstPort uint16 // 源、目的端口
|
||||
Seq uint32 // 序列号
|
||||
Ack uint32 // 确认号
|
||||
DataOffset uint8 // 数据偏移量(起始)
|
||||
NS, CWR, ECE, URG, ACK, PSH, RST, SYN, FIN bool // 标志符
|
||||
Window uint16 // 窗口
|
||||
Checksum uint16 // 校验和
|
||||
Urgent uint16 // 紧急指针
|
||||
Options []TCPOption // 可选字段
|
||||
Padding []byte // 填充
|
||||
} |
||||
|
||||
type TCPOption struct { |
||||
OptionType uint8 |
||||
OptionLength uint8 |
||||
OptionData []byte |
||||
} |
||||
|
||||
func (T *TCP) LayerType() int { |
||||
return LayerTypeTCP |
||||
} |
||||
|
||||
// DecodeTCP ref: https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE#%E7%8A%B6%E6%80%81%E7%BC%96%E7%A0%81
|
||||
// 解码TCP包,参考自wiki
|
||||
func DecodeTCP(data []byte) (*TCP, error) { |
||||
if len(data) < 20 { |
||||
return nil, fmt.Errorf("[TCP] 无效标头。长度 %d bytes,至少需要20 bytes", len(data)) |
||||
} |
||||
|
||||
tcp := TCP{} |
||||
tcp.SrcPort = binary.BigEndian.Uint16(data[0:2]) |
||||
tcp.DstPort = binary.BigEndian.Uint16(data[2:4]) |
||||
tcp.Seq = binary.BigEndian.Uint32(data[4:8]) |
||||
tcp.Ack = binary.BigEndian.Uint32(data[8:12]) |
||||
tcp.DataOffset = data[12] >> 4 |
||||
|
||||
{ // 位运算取相得应的标志位
|
||||
tcp.NS = data[12]&0x01 != 0 |
||||
tcp.CWR = data[13]&0x80 != 0 |
||||
tcp.ECE = data[13]&0x40 != 0 |
||||
tcp.URG = data[13]&0x20 != 0 |
||||
tcp.ACK = data[13]&0x10 != 0 |
||||
tcp.PSH = data[13]&0x08 != 0 |
||||
tcp.RST = data[13]&0x04 != 0 |
||||
tcp.SYN = data[13]&0x02 != 0 |
||||
tcp.FIN = data[13]&0x01 != 0 |
||||
} |
||||
|
||||
tcp.Window = binary.BigEndian.Uint16(data[14:16]) |
||||
tcp.Checksum = binary.BigEndian.Uint16(data[16:18]) |
||||
tcp.Urgent = binary.BigEndian.Uint16(data[18:20]) |
||||
|
||||
dataStart := int(tcp.DataOffset) * 4 |
||||
if dataStart > len(data) { |
||||
return nil, errors.New("[TCP] 数据偏移大于数据包长度") |
||||
} |
||||
|
||||
tcp.Header = data[:dataStart] |
||||
tcp.Payload = data[dataStart:] |
||||
|
||||
// 20bytes固定头部往后到dataStart都是选项字段了
|
||||
data = data[20:dataStart] |
||||
|
||||
// 解析可选字段
|
||||
for len(data) > 0 { |
||||
tcp.Options = append(tcp.Options, TCPOption{OptionType: data[0]}) |
||||
opt := &tcp.Options[len(tcp.Options)-1] |
||||
switch opt.OptionType { |
||||
case TCPOptionKindEndList: // 选项表结束
|
||||
opt.OptionLength = 1 |
||||
opt.OptionData = []byte{0} |
||||
tcp.Padding = data[1:] |
||||
goto FINISH |
||||
case TCPOptionKindNop: // 无操作(1字节),用于选项字段之间的字边界对齐。
|
||||
opt.OptionLength = 1 |
||||
opt.OptionData = []byte{0} |
||||
data = data[opt.OptionLength:] |
||||
default: |
||||
if len(data) < 2 { |
||||
return nil, fmt.Errorf("[TCP] 可选字段长度无效") |
||||
} |
||||
opt.OptionLength = data[1] |
||||
if opt.OptionLength < 2 { |
||||
return nil, fmt.Errorf("[TCP] 可选字段长度无效,%d < 2", opt.OptionLength) |
||||
} else if int(opt.OptionLength) > len(data) { |
||||
return nil, fmt.Errorf("[TCP] 无效可选字段长度: %d, 超出剩余的 %d 个字节", opt.OptionLength, len(data)) |
||||
} |
||||
opt.OptionData = data[2:opt.OptionLength] |
||||
data = data[opt.OptionLength:] |
||||
} |
||||
} |
||||
|
||||
FINISH: |
||||
return &tcp, nil |
||||
} |
@ -0,0 +1,45 @@ |
||||
package parser |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
) |
||||
|
||||
type UDP struct { |
||||
BaseLayer |
||||
SrcPort, DstPort uint16 // 源端口(可选)、目标端口
|
||||
Length uint16 // 数据包总长(header+payload)
|
||||
Checksum uint16 // 校验和(ipv4下可选)
|
||||
} |
||||
|
||||
func (U *UDP) LayerType() int { |
||||
return LayerTypeUDP |
||||
} |
||||
|
||||
func DecodeUDP(data []byte) (*UDP, error) { |
||||
if len(data) < 8 { |
||||
return nil, fmt.Errorf("[UDP] 无效数据包长度: %d bytes, 至少需要8 bytes", len(data)) |
||||
} |
||||
|
||||
udp := UDP{} |
||||
udp.SrcPort = binary.BigEndian.Uint16(data[0:2]) |
||||
udp.DstPort = binary.BigEndian.Uint16(data[2:4]) |
||||
udp.Length = binary.BigEndian.Uint16(data[4:6]) |
||||
udp.Checksum = binary.BigEndian.Uint16(data[6:8]) |
||||
udp.Header = data[:8] |
||||
|
||||
switch { |
||||
case udp.Length >= 8: |
||||
hlen := int(udp.Length) |
||||
if hlen > len(data) { |
||||
hlen = len(data) |
||||
} |
||||
udp.Payload = data[8:hlen] |
||||
case udp.Length == 0: // IPv6 Jumbogram, 剩下的所有数据都认为是payload
|
||||
udp.Payload = data[8:] |
||||
default: |
||||
return nil, fmt.Errorf("[UDP] 数据包太小:%d bytes", udp.Length) |
||||
} |
||||
|
||||
return &udp, nil |
||||
} |
@ -0,0 +1,28 @@ |
||||
package describer |
||||
|
||||
import ( |
||||
"eruhs/core/parser" |
||||
"fmt" |
||||
) |
||||
|
||||
type ARPDescriber struct { |
||||
ARP *parser.ARP |
||||
} |
||||
|
||||
func (a *ARPDescriber) GetHeader() []byte { |
||||
return a.ARP.Header |
||||
} |
||||
|
||||
func (a *ARPDescriber) GetDetailDescribe() Describe { |
||||
return Describe{ |
||||
1: {"硬件类型", fmt.Sprintf("%d", a.ARP.HardwareType)}, |
||||
2: {"协议类型", fmt.Sprintf("%d", a.ARP.ProtoType)}, |
||||
3: {"硬件地址长度", fmt.Sprintf("%d", a.ARP.HwAddressSize)}, |
||||
4: {"协议地址长度", fmt.Sprintf("%d", a.ARP.ProtoAddrSize)}, |
||||
5: {"操作码", fmt.Sprintf("%d", a.ARP.Opcode)}, |
||||
6: {"源硬件地址", a.ARP.SrcHwAddress.String()}, |
||||
7: {"源协议地址", a.ARP.SrcProtoAddress.String()}, |
||||
8: {"目标硬件地址", a.ARP.DstHwAddress.String()}, |
||||
9: {"目标协议地址", a.ARP.DstProtoAddress.String()}, |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
package describer |
||||
|
||||
type DescribeItem struct { |
||||
Name string |
||||
Describe string |
||||
} |
||||
|
||||
type Describe map[int]DescribeItem |
||||
|
||||
type LayerDescriber interface { |
||||
GetHeader() []byte |
||||
GetDetailDescribe() Describe |
||||
} |
@ -0,0 +1,33 @@ |
||||
package describer |
||||
|
||||
import ( |
||||
"eruhs/core/parser" |
||||
"strings" |
||||
) |
||||
|
||||
type EthernetDescriber struct { |
||||
Eth *parser.Ethernet |
||||
} |
||||
|
||||
func (e *EthernetDescriber) GetHeader() []byte { |
||||
return e.Eth.Header |
||||
} |
||||
|
||||
func (e *EthernetDescriber) GetDetailDescribe() Describe { |
||||
protoName := "ETH" |
||||
switch e.Eth.Type { |
||||
case parser.EthernetTypeIPv4: |
||||
protoName = "IPv4" |
||||
case parser.EthernetTypeIPv6: |
||||
protoName = "IPv6" |
||||
case parser.LayerTypeARP: |
||||
protoName = "ARP" |
||||
default: |
||||
} |
||||
|
||||
return Describe{ |
||||
1: {"源地址", strings.ToUpper(e.Eth.Source.String())}, |
||||
2: {"目的地址", strings.ToUpper(e.Eth.Destination.String())}, |
||||
3: {"网络层协议类型", protoName}, |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
package describer |
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"eruhs/core/parser" |
||||
"fmt" |
||||
) |
||||
|
||||
type IPv4Describer struct { |
||||
IPv4 *parser.IPv4 |
||||
} |
||||
|
||||
func (i *IPv4Describer) GetHeader() []byte { |
||||
return i.IPv4.Header |
||||
} |
||||
|
||||
func (i *IPv4Describer) GetDetailDescribe() Describe { |
||||
optionDescribe := make([]map[string]string, 0) |
||||
for _, option := range i.IPv4.Options { |
||||
optionDescribe = append(optionDescribe, map[string]string{ |
||||
"类型": fmt.Sprintf("%d", option.OptionType), |
||||
"长度": fmt.Sprintf("%d", option.OptionLength), |
||||
"数据": hex.EncodeToString(option.OptionData), |
||||
}) |
||||
} |
||||
|
||||
protoName := "" |
||||
switch i.IPv4.Protocol { |
||||
case parser.IPv4ProtoICMP: |
||||
protoName = "ICMP" |
||||
case parser.IPv4ProtoIGMP: |
||||
protoName = "IGMP" |
||||
case parser.IPv4ProtoTCP: |
||||
protoName = "TCP" |
||||
case parser.IPv4ProtoUDP: |
||||
protoName = "UDP" |
||||
case parser.IPv4ProtoENCAP: |
||||
protoName = "ENCAP" |
||||
case parser.IPv4ProtoOSPF: |
||||
protoName = "OSPF" |
||||
case parser.IPv4ProtoSCTP: |
||||
protoName = "SCTP" |
||||
} |
||||
|
||||
return Describe{ |
||||
1: {"版本", fmt.Sprintf("%d", i.IPv4.Version)}, |
||||
2: {"首部长度", fmt.Sprintf("%d", i.IPv4.IHL)}, |
||||
3: {"区分服务", fmt.Sprintf("%d", i.IPv4.DS)}, |
||||
4: {"总长", fmt.Sprintf("%d", i.IPv4.TotalLength)}, |
||||
5: {"标识符", fmt.Sprintf("%d", i.IPv4.Id)}, |
||||
6: {"标志", fmt.Sprintf("%d", i.IPv4.Flags)}, |
||||
7: {"分片偏移", fmt.Sprintf("%d", i.IPv4.FragOffset)}, |
||||
8: {"存活时间", fmt.Sprintf("%d", i.IPv4.TTL)}, |
||||
9: {"应用层协议", protoName}, |
||||
10: {"首部校验和", fmt.Sprintf("%d", i.IPv4.Checksum)}, |
||||
11: {"源地址", i.IPv4.SrcIP.String()}, |
||||
12: {"目的地址", i.IPv4.DstIP.String()}, |
||||
13: {"选项字段", fmt.Sprintf("%+v", optionDescribe)}, |
||||
14: {"填充", fmt.Sprintf("%d Byte", len(i.IPv4.Padding))}, |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
package describer |
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"eruhs/core/parser" |
||||
"fmt" |
||||
) |
||||
|
||||
type TCPDescriber struct { |
||||
TCP *parser.TCP |
||||
} |
||||
|
||||
func (t *TCPDescriber) GetHeader() []byte { |
||||
return t.TCP.Header |
||||
} |
||||
|
||||
func (t *TCPDescriber) GetDetailDescribe() Describe { |
||||
tcpFlags := make([]string, 0) |
||||
if t.TCP.NS { |
||||
tcpFlags = append(tcpFlags, "NS") |
||||
} |
||||
if t.TCP.CWR { |
||||
tcpFlags = append(tcpFlags, "CWR") |
||||
} |
||||
if t.TCP.ECE { |
||||
tcpFlags = append(tcpFlags, "ECE") |
||||
} |
||||
if t.TCP.URG { |
||||
tcpFlags = append(tcpFlags, "URG") |
||||
} |
||||
if t.TCP.ACK { |
||||
tcpFlags = append(tcpFlags, "ACK") |
||||
} |
||||
if t.TCP.PSH { |
||||
tcpFlags = append(tcpFlags, "PSH") |
||||
} |
||||
if t.TCP.RST { |
||||
tcpFlags = append(tcpFlags, "RST") |
||||
} |
||||
if t.TCP.SYN { |
||||
tcpFlags = append(tcpFlags, "SYN") |
||||
} |
||||
if t.TCP.FIN { |
||||
tcpFlags = append(tcpFlags, "FIN") |
||||
} |
||||
|
||||
optionDescribe := make([]map[string]string, 0) |
||||
for _, option := range t.TCP.Options { |
||||
optionDescribe = append(optionDescribe, map[string]string{ |
||||
"类型": fmt.Sprintf("%d", option.OptionType), |
||||
"长度": fmt.Sprintf("%d", option.OptionLength), |
||||
"数据": hex.EncodeToString(option.OptionData), |
||||
}) |
||||
} |
||||
|
||||
return Describe{ |
||||
1: {"源端口号", fmt.Sprintf("%d", t.TCP.SrcPort)}, |
||||
2: {"目的端口号", fmt.Sprintf("%d", t.TCP.DstPort)}, |
||||
3: {"序列号", fmt.Sprintf("%d", t.TCP.Seq)}, |
||||
4: {"确认号", fmt.Sprintf("%d", t.TCP.Ack)}, |
||||
5: {"数据偏移", fmt.Sprintf("%d", t.TCP.DataOffset)}, |
||||
6: {"标志符", fmt.Sprintf("%v", tcpFlags)}, |
||||
7: {"窗口", fmt.Sprintf("%d", t.TCP.Window)}, |
||||
8: {"校验和", fmt.Sprintf("%d", t.TCP.Checksum)}, |
||||
9: {"紧急指针", fmt.Sprintf("%d", t.TCP.Urgent)}, |
||||
10: {"可选字段", fmt.Sprintf("%+v", optionDescribe)}, |
||||
11: {"填充", fmt.Sprintf("%d", len(t.TCP.Padding))}, |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package describer |
||||
|
||||
import ( |
||||
"eruhs/core/parser" |
||||
"fmt" |
||||
) |
||||
|
||||
type UDPDescriber struct { |
||||
UDP *parser.UDP |
||||
} |
||||
|
||||
func (u *UDPDescriber) GetHeader() []byte { |
||||
return u.UDP.Header |
||||
} |
||||
|
||||
func (u *UDPDescriber) GetDetailDescribe() Describe { |
||||
return Describe{ |
||||
1: {"源端口(0为无)", fmt.Sprintf("%d", u.UDP.SrcPort)}, |
||||
2: {"目标端口", fmt.Sprintf("%d", u.UDP.DstPort)}, |
||||
3: {"数据包总长", fmt.Sprintf("%d", u.UDP.Length)}, |
||||
4: {"校验和(0为无)", fmt.Sprintf("%d", u.UDP.Checksum)}, |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
root = true |
||||
|
||||
[*] |
||||
charset = utf-8 |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
insert_final_newline = true |
||||
trim_trailing_whitespace = true |
@ -0,0 +1,7 @@ |
||||
/dist |
||||
/src-capacitor |
||||
/src-cordova |
||||
/.quasar |
||||
/node_modules |
||||
.eslintrc.cjs |
||||
/quasar.config.*.temporary.compiled* |
@ -0,0 +1,66 @@ |
||||
module.exports = { |
||||
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy |
||||
// This option interrupts the configuration hierarchy at this file |
||||
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) |
||||
root: true, |
||||
|
||||
parserOptions: { |
||||
ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features |
||||
}, |
||||
|
||||
env: { |
||||
node: true, |
||||
browser: true |
||||
}, |
||||
|
||||
// Rules order is important, please avoid shuffling them |
||||
extends: [ |
||||
// Base ESLint recommended rules |
||||
// 'eslint:recommended', |
||||
|
||||
// Uncomment any of the lines below to choose desired strictness, |
||||
// but leave only one uncommented! |
||||
// See https://eslint.vuejs.org/rules/#available-rules |
||||
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) |
||||
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) |
||||
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) |
||||
|
||||
// https://github.com/prettier/eslint-config-prettier#installation |
||||
// usage with Prettier, provided by 'eslint-config-prettier'. |
||||
'prettier' |
||||
], |
||||
|
||||
plugins: [ |
||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files |
||||
// required to lint *.vue files |
||||
'vue', |
||||
|
||||
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 |
||||
// Prettier has not been included as plugin to avoid performance impact |
||||
// add it as an extension for your IDE |
||||
|
||||
], |
||||
|
||||
globals: { |
||||
ga: 'readonly', // Google Analytics |
||||
cordova: 'readonly', |
||||
__statics: 'readonly', |
||||
__QUASAR_SSR__: 'readonly', |
||||
__QUASAR_SSR_SERVER__: 'readonly', |
||||
__QUASAR_SSR_CLIENT__: 'readonly', |
||||
__QUASAR_SSR_PWA__: 'readonly', |
||||
process: 'readonly', |
||||
Capacitor: 'readonly', |
||||
chrome: 'readonly' |
||||
}, |
||||
|
||||
// add your custom rules here |
||||
rules: { |
||||
'vue/no-unused-components': 'off', |
||||
'vue/no-unused-vars': 'off', |
||||
'prefer-promise-reject-errors': 'off', |
||||
|
||||
// allow debugger during development only |
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
.DS_Store |
||||
.thumbs.db |
||||
node_modules |
||||
|
||||
# Quasar core related directories |
||||
.quasar |
||||
/dist |
||||
/quasar.config.*.temporary.compiled* |
||||
|
||||
# Cordova related directories and files |
||||
/src-cordova/node_modules |
||||
/src-cordova/platforms |
||||
/src-cordova/plugins |
||||
/src-cordova/www |
||||
|
||||
# Capacitor related directories and files |
||||
/src-capacitor/www |
||||
/src-capacitor/node_modules |
||||
|
||||
# Log files |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
|
||||
# local .env files |
||||
.env.local* |
@ -0,0 +1,5 @@ |
||||
# pnpm-related options |
||||
shamefully-hoist=true |
||||
strict-peer-dependencies=false |
||||
# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463 |
||||
resolution-mode=highest |
@ -0,0 +1,3 @@ |
||||
# eruhs-ui |
||||
|
||||
undefined |
@ -0,0 +1,21 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<title><%= productName %></title> |
||||
|
||||
<meta charset="utf-8"> |
||||
<meta name="description" content="<%= productDescription %>"> |
||||
<meta name="format-detection" content="telephone=no"> |
||||
<meta name="msapplication-tap-highlight" content="no"> |
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"> |
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png"> |
||||
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png"> |
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png"> |
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png"> |
||||
<link rel="icon" type="image/ico" href="favicon.ico"> |
||||
</head> |
||||
<body> |
||||
<!-- quasar:entry-point --> |
||||
</body> |
||||
</html> |
@ -0,0 +1,39 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"baseUrl": ".", |
||||
"paths": { |
||||
"src/*": [ |
||||
"src/*" |
||||
], |
||||
"app/*": [ |
||||
"*" |
||||
], |
||||
"components/*": [ |
||||
"src/components/*" |
||||
], |
||||
"layouts/*": [ |
||||
"src/layouts/*" |
||||
], |
||||
"pages/*": [ |
||||
"src/pages/*" |
||||
], |
||||
"assets/*": [ |
||||
"src/assets/*" |
||||
], |
||||
"boot/*": [ |
||||
"src/boot/*" |
||||
], |
||||
"stores/*": [ |
||||
"src/stores/*" |
||||
], |
||||
"vue$": [ |
||||
"node_modules/vue/dist/vue.runtime.esm-bundler.js" |
||||
] |
||||
} |
||||
}, |
||||
"exclude": [ |
||||
"dist", |
||||
".quasar", |
||||
"node_modules" |
||||
] |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@ |
||||
{ |
||||
"name": "eruhs-ui", |
||||
"version": "0.0.1", |
||||
"description": "eruhs ui", |
||||
"productName": "eruhs", |
||||
"author": "1@2.com", |
||||
"private": true, |
||||
"scripts": { |
||||
"lint": "eslint --ext .js,.vue ./", |
||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", |
||||
"test": "echo \"No test specified\" && exit 0", |
||||
"dev": "quasar dev", |
||||
"build": "quasar build" |
||||
}, |
||||
"dependencies": { |
||||
"@quasar/extras": "^1.16.4", |
||||
"quasar": "^2.8.0", |
||||
"vue": "^3.4.18", |
||||
"vue-router": "^4.0.12" |
||||
}, |
||||
"devDependencies": { |
||||
"@quasar/app-vite": "^1.8.0", |
||||
"autoprefixer": "^10.4.18", |
||||
"eslint": "^8.11.0", |
||||
"eslint-config-prettier": "^8.1.0", |
||||
"eslint-plugin-vue": "^9.0.0", |
||||
"postcss": "^8.4.35", |
||||
"prettier": "^2.5.1", |
||||
"tailwindcss": "^3.4.1", |
||||
"vite-plugin-checker": "^0.6.4" |
||||
}, |
||||
"engines": { |
||||
"node": "^20 || ^18 || ^16", |
||||
"npm": ">= 6.13.4", |
||||
"yarn": ">= 1.21.1" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
7d942fd325b75f919c368510ac080ec8 |
@ -0,0 +1,27 @@ |
||||
/* eslint-disable */ |
||||
// https://github.com/michael-ciniawsky/postcss-load-config |
||||
|
||||
module.exports = { |
||||
plugins: [ |
||||
// https://github.com/postcss/autoprefixer |
||||
require('autoprefixer')({ |
||||
overrideBrowserslist: [ |
||||
'last 4 Chrome versions', |
||||
'last 4 Firefox versions', |
||||
'last 4 Edge versions', |
||||
'last 4 Safari versions', |
||||
'last 4 Android versions', |
||||
'last 4 ChromeAndroid versions', |
||||
'last 4 FirefoxAndroid versions', |
||||
'last 4 iOS versions' |
||||
] |
||||
}) |
||||
|
||||
// https://github.com/elchininet/postcss-rtlcss |
||||
// If you want to support RTL css, then |
||||
// 1. yarn/npm install postcss-rtlcss |
||||
// 2. optionally set quasar.config.js > framework > lang to an RTL language |
||||
// 3. uncomment the following line: |
||||
// require('postcss-rtlcss') |
||||
] |
||||
} |
@ -0,0 +1,6 @@ |
||||
module.exports = { |
||||
plugins: { |
||||
tailwindcss: {}, |
||||
autoprefixer: {}, |
||||
}, |
||||
} |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 859 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,209 @@ |
||||
/* eslint-env node */ |
||||
|
||||
/* |
||||
* This file runs in a Node context (it's NOT transpiled by Babel), so use only |
||||
* the ES6 features that are supported by your Node version. https://node.green/
|
||||
*/ |
||||
|
||||
// Configuration for your app
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||
|
||||
|
||||
const { configure } = require('quasar/wrappers'); |
||||
|
||||
|
||||
module.exports = configure(function (/* ctx */) { |
||||
return { |
||||
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
||||
// preFetch: true,
|
||||
|
||||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: [ |
||||
|
||||
|
||||
], |
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: [ |
||||
'app.scss' |
||||
], |
||||
|
||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||
extras: [ |
||||
// 'ionicons-v4',
|
||||
// 'mdi-v7',
|
||||
// 'fontawesome-v6',
|
||||
// 'eva-icons',
|
||||
// 'themify',
|
||||
// 'line-awesome',
|
||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||
|
||||
'roboto-font', // optional, you are not bound to it
|
||||
'material-icons', // optional, you are not bound to it
|
||||
], |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||
build: { |
||||
target: { |
||||
browser: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ], |
||||
node: 'node20' |
||||
}, |
||||
|
||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||
// vueRouterBase,
|
||||
// vueDevtools,
|
||||
// vueOptionsAPI: false,
|
||||
|
||||
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
|
||||
|
||||
// publicPath: '/',
|
||||
// analyze: true,
|
||||
// env: {},
|
||||
// rawDefine: {}
|
||||
// ignorePublicFolder: true,
|
||||
// minify: false,
|
||||
// polyfillModulePreload: true,
|
||||
// distDir
|
||||
|
||||
// extendViteConf (viteConf) {},
|
||||
// viteVuePluginOptions: {},
|
||||
|
||||
vitePlugins: [ |
||||
['vite-plugin-checker', { |
||||
eslint: { |
||||
lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"' |
||||
} |
||||
}, { server: false }] |
||||
] |
||||
}, |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: { |
||||
// https: true
|
||||
open: true, // opens browser window automatically
|
||||
}, |
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||
framework: { |
||||
config: {}, |
||||
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
// For special cases outside of where the auto-import strategy can have an impact
|
||||
// (like functional components as one of the examples),
|
||||
// you can manually specify Quasar components/directives to be available everywhere:
|
||||
//
|
||||
// components: [],
|
||||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: [ |
||||
'Dialog' |
||||
] |
||||
}, |
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
// https://v2.quasar.dev/options/animations
|
||||
animations: [], |
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
|
||||
// sourceFiles: {
|
||||
// rootComponent: 'src/App.vue',
|
||||
// router: 'src/router/index',
|
||||
// store: 'src/store/index',
|
||||
// registerServiceWorker: 'src-pwa/register-service-worker',
|
||||
// serviceWorker: 'src-pwa/custom-service-worker',
|
||||
// pwaManifestFile: 'src-pwa/manifest.json',
|
||||
// electronMain: 'src-electron/electron-main',
|
||||
// electronPreload: 'src-electron/electron-preload'
|
||||
// },
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
|
||||
ssr: { |
||||
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
|
||||
// will mess up SSR
|
||||
|
||||
// extendSSRWebserverConf (esbuildConf) {},
|
||||
// extendPackageJson (json) {},
|
||||
|
||||
pwa: false, |
||||
|
||||
// manualStoreHydration: true,
|
||||
// manualPostHydrationTrigger: true,
|
||||
|
||||
prodPort: 3000, // The default port that the production server should use
|
||||
// (gets superseded if process.env.PORT is specified at runtime)
|
||||
|
||||
middlewares: [ |
||||
'render' // keep this as last one
|
||||
] |
||||
}, |
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
||||
pwa: { |
||||
workboxMode: 'generateSW', // or 'injectManifest'
|
||||
injectPwaMetaTags: true, |
||||
swFilename: 'sw.js', |
||||
manifestFilename: 'manifest.json', |
||||
useCredentialsForManifestTag: false, |
||||
// useFilenameHashes: true,
|
||||
// extendGenerateSWOptions (cfg) {}
|
||||
// extendInjectManifestOptions (cfg) {},
|
||||
// extendManifestJson (json) {}
|
||||
// extendPWACustomSWConf (esbuildConf) {}
|
||||
}, |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
|
||||
cordova: { |
||||
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
|
||||
}, |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
|
||||
capacitor: { |
||||
hideSplashscreen: true |
||||
}, |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
|
||||
electron: { |
||||
// extendElectronMainConf (esbuildConf)
|
||||
// extendElectronPreloadConf (esbuildConf)
|
||||
|
||||
// specify the debugging port to use for the Electron app when running in development mode
|
||||
inspectPort: 5858, |
||||
|
||||
bundler: 'packager', // 'packager' or 'builder'
|
||||
|
||||
packager: { |
||||
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||
|
||||
// OS X / Mac App Store
|
||||
// appBundleId: '',
|
||||
// appCategoryType: '',
|
||||
// osxSign: '',
|
||||
// protocol: 'myapp://path',
|
||||
|
||||
// Windows only
|
||||
// win32metadata: { ... }
|
||||
}, |
||||
|
||||
builder: { |
||||
// https://www.electron.build/configuration/configuration
|
||||
|
||||
appId: 'eruhs-ui' |
||||
} |
||||
}, |
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||
bex: { |
||||
contentScripts: [ |
||||
'my-content-script' |
||||
], |
||||
|
||||
// extendBexScriptsConf (esbuildConf) {}
|
||||
// extendBexManifestJson (json) {}
|
||||
} |
||||
} |
||||
}); |
@ -0,0 +1,11 @@ |
||||
<template> |
||||
<router-view /> |
||||
</template> |
||||
|
||||
<script> |
||||
import { defineComponent } from 'vue' |
||||
|
||||
export default defineComponent({ |
||||
name: 'App' |
||||
}) |
||||
</script> |
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,107 @@ |
||||
<script setup> |
||||
import {ref} from "vue"; |
||||
import {EventsOn} from "app/wailsjs/runtime"; |
||||
import {GetDetail, StartCapture, StopCapture} from "app/wailsjs/go/main/App"; |
||||
import {useQuasar} from "quasar"; |
||||
|
||||
const columns = [ |
||||
{name: 'id', label: '序号', field: 'id', align: 'left'}, |
||||
{name: 'time', label: '时间', field: 'time', align: 'left'}, |
||||
{name: 'source', label: '来源', field: 'source', align: 'left'}, |
||||
{name: 'dest', label: '目的地', field: 'dest', align: 'left'}, |
||||
{name: 'proto', label: '协议', field: 'proto', align: 'left'}, |
||||
{name: 'length', label: '包长度', field: 'length', align: 'left'}, |
||||
] |
||||
|
||||
const packets = ref([]) |
||||
|
||||
const networkDevice = ref({Name: "", Description: "", Flags: 0, Addresses: [""]}) |
||||
|
||||
const pagination = ref({ |
||||
rowsPerPage: 10000 |
||||
}) |
||||
|
||||
const colorScheme = { |
||||
"TCP": '#c8e4fb', |
||||
"UDP": '#d0d4ed', |
||||
"ARP": '#d3ebd3', |
||||
"ETH": '#dacfed' |
||||
} |
||||
|
||||
const detailDialogStatus = ref(false) |
||||
|
||||
EventsOn("onNewDataPacket", data => { |
||||
packets.value.push(data) |
||||
}) |
||||
|
||||
EventsOn("onNewDataPacketBatch", data => { |
||||
packets.value.push(...data) |
||||
}) |
||||
|
||||
EventsOn("onErrOccurrence", err => { |
||||
console.warn(err) |
||||
}) |
||||
|
||||
const packetDetailView = ref() |
||||
|
||||
function startCapture(device) { |
||||
networkDevice.value = device |
||||
packets.value.splice(0, packets.value.length) |
||||
|
||||
StartCapture(networkDevice.value.Name) |
||||
.then(() => console.log("capture started")) |
||||
.catch(e => console.warn(e)) |
||||
} |
||||
|
||||
function stopCapture() { |
||||
// packets.value.splice(0, packets.value.length) |
||||
StopCapture() |
||||
.then(() => console.log("capture stopped")) |
||||
.catch(e => console.warn(e)) |
||||
} |
||||
|
||||
function showDetail(id) { |
||||
GetDetail(id) |
||||
.then(data => { |
||||
console.log(data) |
||||
packetDetailView.value = data |
||||
detailDialogStatus.value = true |
||||
}) |
||||
.catch(e => console.warn(e)) |
||||
} |
||||
|
||||
defineExpose({ |
||||
startCapture, |
||||
stopCapture |
||||
}) |
||||
|
||||
function formattedText(text) { |
||||
if (text === '[]') { |
||||
return '无' |
||||
} |
||||
|
||||
return text |
||||
} |
||||
|
||||
const quasar = useQuasar() |
||||
|
||||
function viewRowHeader(name, text) { |
||||
console.log(text) |
||||
quasar.dialog({ |
||||
title: `${name}层原始头部数据`, |
||||
message: text.replaceAll('\n', '<br>').replaceAll(' ', ' '), |
||||
style: 'font-family: Consolas, Cascadia Code, Cascadia Mono, monospace', |
||||
fullWidth: true, |
||||
ok: true, |
||||
html: true, |
||||
}) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div></div> |
||||
</template> |
||||
|
||||
<style lang="sass"> |
||||
|
||||
</style> |
@ -0,0 +1,12 @@ |
||||
// app global css in SCSS form |
||||
@tailwind base; |
||||
@tailwind components; |
||||
@tailwind utilities; |
||||
// |
||||
//* { |
||||
// user-select: none; |
||||
//} |
||||
|
||||
body, #app, #q-app { |
||||
height: 100vh; |
||||
} |
@ -0,0 +1,25 @@ |
||||
// Quasar SCSS (& Sass) Variables |
||||
// -------------------------------------------------- |
||||
// To customize the look and feel of this app, you can override |
||||
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files. |
||||
|
||||
// Check documentation for full list of Quasar variables |
||||
|
||||
// Your own variables (that are declared here) and Quasar's own |
||||
// ones will be available out of the box in your .vue/.scss/.sass files |
||||
|
||||
// It's highly recommended to change the default colors |
||||
// to match your app's branding. |
||||
// Tip: Use the "Theme Builder" on Quasar's documentation website. |
||||
|
||||
$primary : #1976D2; |
||||
$secondary : #26A69A; |
||||
$accent : #9C27B0; |
||||
|
||||
$dark : #1D1D1D; |
||||
$dark-page : #121212; |
||||
|
||||
$positive : #21BA45; |
||||
$negative : #C10015; |
||||
$info : #31CCEC; |
||||
$warning : #F2C037; |
@ -0,0 +1,323 @@ |
||||
<script setup> |
||||
import {ref, watch} from "vue"; |
||||
import {GetNetworkDeviceList} from "app/wailsjs/go/main/App"; |
||||
import {useQuasar} from "quasar"; |
||||
|
||||
const dialogOpen = ref(false) |
||||
|
||||
const quasar = useQuasar() |
||||
|
||||
let started = ref(false) |
||||
|
||||
const selectedDevice = ref('') |
||||
const selectedNetworkDevice = ref({Name: "", Description: "", Flags: 0, Addresses: [""]}) |
||||
const deviceMap = new Map() |
||||
const devices = ref([{ |
||||
Name: "", |
||||
Description: "", |
||||
Flags: 0, |
||||
Addresses: [""] |
||||
}]) |
||||
|
||||
watch(selectedDevice, (n, old) => { |
||||
console.log("new value", n) |
||||
console.log('device', deviceMap.get(n)) |
||||
selectedNetworkDevice.value = deviceMap.get(n) |
||||
console.log('selectedNetworkDevice.value', selectedNetworkDevice.value) |
||||
}) |
||||
|
||||
import {EventsOn} from "app/wailsjs/runtime"; |
||||
import {GetDetail, StartCapture, StopCapture} from "app/wailsjs/go/main/App"; |
||||
|
||||
const columns = [ |
||||
{name: 'id', label: '序号', field: 'id', align: 'left'}, |
||||
{name: 'time', label: '时间', field: 'time', align: 'left'}, |
||||
{name: 'source', label: '来源', field: 'source', align: 'left'}, |
||||
{name: 'dest', label: '目的地', field: 'dest', align: 'left'}, |
||||
{name: 'proto', label: '协议', field: 'proto', align: 'left'}, |
||||
{name: 'length', label: '包长度', field: 'length', align: 'left'}, |
||||
] |
||||
|
||||
const packets = ref([]) |
||||
|
||||
GetNetworkDeviceList() |
||||
.then(deviceList => { |
||||
devices.value = deviceList; |
||||
for (const device of deviceList) { |
||||
deviceMap.set(device.Name, device) |
||||
} |
||||
|
||||
if (selectedDevice.value === '') { |
||||
selectedDevice.value = deviceList?.[0].Name; |
||||
} |
||||
|
||||
dialogOpen.value = !dialogOpen.value; |
||||
}) |
||||
.catch(e => quasar.dialog({ |
||||
title: '出错了...', |
||||
message: e, |
||||
ok: true |
||||
})) |
||||
|
||||
const pagination = ref({ |
||||
rowsPerPage: 10000 |
||||
}) |
||||
|
||||
const colorScheme = { |
||||
"TCP": '#c8e4fb', |
||||
"UDP": '#d0d4ed', |
||||
"ARP": '#d3ebd3', |
||||
"ETH": '#dacfed' |
||||
} |
||||
|
||||
const detailDialogStatus = ref(false) |
||||
|
||||
EventsOn("onNewDataPacket", data => { |
||||
packets.value.push(data) |
||||
}) |
||||
|
||||
EventsOn("onNewDataPacketBatch", data => { |
||||
packets.value.push(...data) |
||||
}) |
||||
|
||||
EventsOn("onErrOccurrence", err => { |
||||
console.warn(err) |
||||
}) |
||||
|
||||
const packetDetailView = ref() |
||||
|
||||
function startCapture() { |
||||
if (started.value) { |
||||
return |
||||
} |
||||
|
||||
packets.value.splice(0, packets.value.length) |
||||
|
||||
StartCapture(selectedNetworkDevice.value.Name) |
||||
.then(() => { |
||||
console.log("capture started"); |
||||
started.value = true; |
||||
}) |
||||
.catch(e => { |
||||
console.warn(e); |
||||
started.value = false; |
||||
}) |
||||
} |
||||
|
||||
function stopCapture() { |
||||
// packets.value.splice(0, packets.value.length) |
||||
StopCapture() |
||||
.then(() => { |
||||
console.log("capture stopped"); |
||||
started.value = false; |
||||
}) |
||||
.catch(e => console.warn(e)) |
||||
} |
||||
|
||||
function showDetail(id) { |
||||
GetDetail(id) |
||||
.then(data => { |
||||
console.log(data) |
||||
packetDetailView.value = data |
||||
detailDialogStatus.value = true |
||||
}) |
||||
.catch(e => console.warn(e)) |
||||
} |
||||
|
||||
function formattedText(text) { |
||||
if (text === '[]') { |
||||
return '无' |
||||
} |
||||
|
||||
return text |
||||
} |
||||
|
||||
function viewRowHeader(name, text) { |
||||
console.log(text) |
||||
quasar.dialog({ |
||||
title: `${name}层原始头部数据`, |
||||
message: text.replaceAll('\n', '<br>').replaceAll(' ', ' '), |
||||
style: 'font-family: Consolas, Cascadia Code, Cascadia Mono, monospace', |
||||
fullWidth: true, |
||||
ok: true, |
||||
html: true, |
||||
}) |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<template> |
||||
<q-layout class="h-full select-none" view="hHh lpR fFf"> |
||||
<q-header class="bg-primary text-white"> |
||||
<q-toolbar> |
||||
<q-toolbar-title> |
||||
eruhs |
||||
</q-toolbar-title> |
||||
<div class="flex flex-row gap-3"> |
||||
<q-btn :outline=!started :unelevated=started |
||||
:icon="started ? 'stop_circle' : 'play_arrow'" |
||||
:color="started ? 'red' : 'white' " |
||||
@click="started ? stopCapture() : startCapture()"> |
||||
<span class="ml-1.5">{{ !started ? 'Start' : 'Stop' }}</span> |
||||
</q-btn> |
||||
</div> |
||||
</q-toolbar> |
||||
</q-header> |
||||
|
||||
<q-drawer show-if-above side="left" bordered> |
||||
|
||||
<q-card-section class="z-10 sticky top-0 bg-white" > |
||||
<div class="text-h6 ">选择侦听网卡</div> |
||||
</q-card-section> |
||||
|
||||
<q-list dense> |
||||
<div class="" v-for="device in devices" :key="device"> |
||||
<q-item :disable="started" tag="label" v-ripple> |
||||
<q-item-section avatar> |
||||
<q-radio v-model="selectedDevice" :val="device.Name" color="primary"></q-radio> |
||||
</q-item-section> |
||||
<q-item-section> |
||||
<q-item-label>{{ device.Description }}</q-item-label> |
||||
<q-item-label caption> |
||||
设备ID: {{ device.Name }} <br> |
||||
地址:<span v-for="(addr, idx) in device.Addresses" :key="addr"> {{ idx === 0 ? '' : ', ' }}{{ |
||||
addr |
||||
}}</span> <br> |
||||
Flag: {{ device.Flags }} |
||||
</q-item-label> |
||||
</q-item-section> |
||||
</q-item> |
||||
</div> |
||||
</q-list> |
||||
</q-drawer> |
||||
|
||||
<q-page-container class="h-full"> |
||||
<div class="text-center h-full"> |
||||
<div class="h-full"> |
||||
<q-table |
||||
class="sticky-header-table h-full" |
||||
dense flat |
||||
:title="`网卡${selectedNetworkDevice.Description}上的包信息`" |
||||
:columns="columns" |
||||
:rows="packets" |
||||
row-key="id" |
||||
virtual-scroll |
||||
v-model:pagination="pagination" |
||||
:rows-per-page-options="[0]" |
||||
> |
||||
<template v-slot:body="props"> |
||||
<q-tr :props="props" @click="showDetail(props.row.id)" |
||||
:style="`background-color: ${colorScheme[props.row.proto]}`"> |
||||
<q-td key="id" :props="props">{{ props.row.id }}</q-td> |
||||
<q-td key="time" :props="props">{{ props.row.time / 1000.0 }}s</q-td> |
||||
<q-td key="source" :props="props">{{ props.row.source }}</q-td> |
||||
<q-td key="dest" :props="props">{{ props.row.dest }}</q-td> |
||||
<q-td key="proto" :props="props">{{ props.row.proto }}</q-td> |
||||
<q-td key="length" :props="props">{{ props.row.length }}</q-td> |
||||
</q-tr> |
||||
</template> |
||||
</q-table> |
||||
|
||||
<q-dialog v-model="detailDialogStatus" persistent maximized transition-show="slide-up" transition-hide="slide-down"> |
||||
<q-card class="bg-primary text-white select-none"> |
||||
<q-bar class="sticky top-0 z-10"> |
||||
<q-space/> |
||||
<q-btn dense flat icon="close" v-close-popup> |
||||
<q-tooltip class="bg-white text-primary">关闭</q-tooltip> |
||||
</q-btn> |
||||
</q-bar> |
||||
|
||||
<div class="p-4" > |
||||
<div class="text-4xl">数据包详情</div> |
||||
<div v-for="layer in packetDetailView" :key="layer"> |
||||
<br> |
||||
<div class="my-2 h-0.5 bg-white"></div> |
||||
<div class="flex flex-row"> |
||||
<span class="text-3xl">{{ layer.name }}层</span> |
||||
<q-space></q-space> |
||||
<q-btn outline @click="viewRowHeader(layer.name, layer.header)">查看原始头部</q-btn> |
||||
</div> |
||||
<br> |
||||
<table class="describe-table"> |
||||
<tr v-for="(desc, item) in layer.describe" :key="item"> |
||||
<td class="text-center">{{ item }}</td> |
||||
<td class="min-w-28">{{ desc.Name }}</td> |
||||
<td class="select-text">{{ formattedText(desc.Describe) }}<br></td> |
||||
</tr> |
||||
</table> |
||||
<div v-if="layer.payload !== ''"> |
||||
<br> |
||||
<div class="my-2 h-0.5 bg-white"></div> |
||||
<div class="flex flex-row"> |
||||
<span class="text-3xl">上层数据</span> |
||||
</div> |
||||
<br> |
||||
<!-- <pre>--> |
||||
<!-- <code>--> |
||||
<!-- {{ layer.payload }}--> |
||||
<!-- </code>--> |
||||
<!-- </pre>--> |
||||
<p style="font-family: Consolas, Cascadia Code, Cascadia Mono, monospace" |
||||
class="select-text text-base" |
||||
v-for="line in layer.payload.split('\n')" :key="line">{{ line.replaceAll(' ', ' ') }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</q-card> |
||||
</q-dialog> |
||||
</div> |
||||
</div> |
||||
</q-page-container> |
||||
|
||||
</q-layout> |
||||
</template> |
||||
|
||||
<style lang="sass"> |
||||
.describe-table |
||||
td:first-child |
||||
padding: 0 |
||||
|
||||
th, td |
||||
padding: 0.25rem 0.5rem |
||||
|
||||
.sticky-header-table |
||||
user-select: none |
||||
transition: all 0.2s linear 0.2s |
||||
|
||||
th, td |
||||
font-size: 1rem !important |
||||
|
||||
th |
||||
font-weight: bold |
||||
|
||||
tr th |
||||
background-color: white |
||||
// |
||||
//thead tr:first-child th:first-child |
||||
// background-color: white |
||||
// |
||||
//tbody tr:nth-child(odd) |
||||
// background-color: rgb(240, 240, 240) |
||||
// |
||||
//tbody tr:nth-child(even) |
||||
// background-color: white |
||||
|
||||
th:first-child, |
||||
td:first-child |
||||
position: sticky |
||||
left: 0 |
||||
z-index: 1 |
||||
|
||||
th:first-child |
||||
z-index: 3 !important |
||||
|
||||
thead tr th |
||||
position: sticky |
||||
z-index: 2 |
||||
|
||||
thead tr:first-child |
||||
height: 48px !important |
||||
|
||||
thead tr:first-child th |
||||
top: 0 |
||||
</style> |
@ -0,0 +1,30 @@ |
||||
import { route } from 'quasar/wrappers' |
||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' |
||||
import routes from './routes' |
||||
|
||||
/* |
||||
* If not building with SSR mode, you can |
||||
* directly export the Router instantiation; |
||||
* |
||||
* The function below can be async too; either use |
||||
* async/await or return a Promise which resolves |
||||
* with the Router instance. |
||||
*/ |
||||
|
||||
export default route(function (/* { store, ssrContext } */) { |
||||
const createHistory = process.env.SERVER |
||||
? createMemoryHistory |
||||
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) |
||||
|
||||
const Router = createRouter({ |
||||
scrollBehavior: () => ({ left: 0, top: 0 }), |
||||
routes, |
||||
|
||||
// Leave this as is and make changes in quasar.conf.js instead!
|
||||
// quasar.conf.js -> build -> vueRouterMode
|
||||
// quasar.conf.js -> build -> publicPath
|
||||
history: createHistory(process.env.VUE_ROUTER_BASE) |
||||
}) |
||||
|
||||
return Router |
||||
}) |
@ -0,0 +1,8 @@ |
||||
const routes = [ |
||||
{ |
||||
path: '/', |
||||
component: () => import('pages/IndexPage.vue'), |
||||
}, |
||||
] |
||||
|
||||
export default routes |
@ -0,0 +1,11 @@ |
||||
/** @type {import('tailwindcss').Config} */ |
||||
export default { |
||||
content: [ |
||||
"./index.html", |
||||
"./src/**/*.{vue,js,ts,jsx,tsx}", |
||||
], |
||||
theme: { |
||||
extend: {}, |
||||
}, |
||||
plugins: [], |
||||
} |
@ -0,0 +1,14 @@ |
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {main} from '../models'; |
||||
import {core} from '../models'; |
||||
|
||||
export function GetDetail(arg1:number):Promise<{[key: number]: main.LayerDescribe}>; |
||||
|
||||
export function GetNetworkDeviceList():Promise<Array<core.Device>>; |
||||
|
||||
export function PauseCapture():Promise<void>; |
||||
|
||||
export function StartCapture(arg1:string):Promise<void>; |
||||
|
||||
export function StopCapture():Promise<void>; |
@ -0,0 +1,23 @@ |
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function GetDetail(arg1) { |
||||
return window['go']['main']['App']['GetDetail'](arg1); |
||||
} |
||||
|
||||
export function GetNetworkDeviceList() { |
||||
return window['go']['main']['App']['GetNetworkDeviceList'](); |
||||
} |
||||
|
||||
export function PauseCapture() { |
||||
return window['go']['main']['App']['PauseCapture'](); |
||||
} |
||||
|
||||
export function StartCapture(arg1) { |
||||
return window['go']['main']['App']['StartCapture'](arg1); |
||||
} |
||||
|
||||
export function StopCapture() { |
||||
return window['go']['main']['App']['StopCapture'](); |
||||
} |
@ -0,0 +1,23 @@ |
||||
export namespace core { |
||||
|
||||
export class Device { |
||||
Name: string; |
||||
Description: string; |
||||
Flags: number; |
||||
Addresses: string[]; |
||||
|
||||
static createFrom(source: any = {}) { |
||||
return new Device(source); |
||||
} |
||||
|
||||
constructor(source: any = {}) { |
||||
if ('string' === typeof source) source = JSON.parse(source); |
||||
this.Name = source["Name"]; |
||||
this.Description = source["Description"]; |
||||
this.Flags = source["Flags"]; |
||||
this.Addresses = source["Addresses"]; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,24 @@ |
||||
{ |
||||
"name": "@wailsapp/runtime", |
||||
"version": "2.0.0", |
||||
"description": "Wails Javascript runtime library", |
||||
"main": "runtime.js", |
||||
"types": "runtime.d.ts", |
||||
"scripts": { |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/wailsapp/wails.git" |
||||
}, |
||||
"keywords": [ |
||||
"Wails", |
||||
"Javascript", |
||||
"Go" |
||||
], |
||||
"author": "Lea Anthony <lea.anthony@gmail.com>", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/wailsapp/wails/issues" |
||||
}, |
||||
"homepage": "https://github.com/wailsapp/wails#readme" |
||||
} |
@ -0,0 +1,235 @@ |
||||
/* |
||||
_ __ _ __ |
||||
| | / /___ _(_) /____ |
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ ) |
||||
|__/|__/\__,_/_/_/____/ |
||||
The electron alternative for Go |
||||
(c) Lea Anthony 2019-present |
||||
*/ |
||||
|
||||
export interface Position { |
||||
x: number; |
||||
y: number; |
||||
} |
||||
|
||||
export interface Size { |
||||
w: number; |
||||
h: number; |
||||
} |
||||
|
||||
export interface Screen { |
||||
isCurrent: boolean; |
||||
isPrimary: boolean; |
||||
width : number |
||||
height : number |
||||
} |
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo { |
||||
buildType: string; |
||||
platform: string; |
||||
arch: string; |
||||
} |
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void; |
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; |
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; |
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; |
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; |
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void; |
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void; |
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void; |
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void; |
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void; |
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void; |
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void; |
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void; |
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void; |
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void; |
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void; |
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void; |
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void; |
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void; |
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void; |
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void; |
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void; |
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void; |
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>; |
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): Promise<Size>; |
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>; |
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void; |
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void; |
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void; |
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>; |
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void; |
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void; |
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void; |
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void; |
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void; |
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>; |
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void; |
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void; |
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>; |
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>; |
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; |
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>; |
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void; |
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>; |
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void; |
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void; |
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void; |
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>; |
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>; |
@ -0,0 +1,202 @@ |
||||
/* |
||||
_ __ _ __ |
||||
| | / /___ _(_) /____ |
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ ) |
||||
|__/|__/\__,_/_/_/____/ |
||||
The electron alternative for Go |
||||
(c) Lea Anthony 2019-present |
||||
*/ |
||||
|
||||
export function LogPrint(message) { |
||||
window.runtime.LogPrint(message); |
||||
} |
||||
|
||||
export function LogTrace(message) { |
||||
window.runtime.LogTrace(message); |
||||
} |
||||
|
||||
export function LogDebug(message) { |
||||
window.runtime.LogDebug(message); |
||||
} |
||||
|
||||
export function LogInfo(message) { |
||||
window.runtime.LogInfo(message); |
||||
} |
||||
|
||||
export function LogWarning(message) { |
||||
window.runtime.LogWarning(message); |
||||
} |
||||
|
||||
export function LogError(message) { |
||||
window.runtime.LogError(message); |
||||
} |
||||
|
||||
export function LogFatal(message) { |
||||
window.runtime.LogFatal(message); |
||||
} |
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) { |
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); |
||||
} |
||||
|
||||
export function EventsOn(eventName, callback) { |
||||
return EventsOnMultiple(eventName, callback, -1); |
||||
} |
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) { |
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames); |
||||
} |
||||
|
||||
export function EventsOnce(eventName, callback) { |
||||
return EventsOnMultiple(eventName, callback, 1); |
||||
} |
||||
|
||||
export function EventsEmit(eventName) { |
||||
let args = [eventName].slice.call(arguments); |
||||
return window.runtime.EventsEmit.apply(null, args); |
||||
} |
||||
|
||||
export function WindowReload() { |
||||
window.runtime.WindowReload(); |
||||
} |
||||
|
||||
export function WindowReloadApp() { |
||||
window.runtime.WindowReloadApp(); |
||||
} |
||||
|
||||
export function WindowSetAlwaysOnTop(b) { |
||||
window.runtime.WindowSetAlwaysOnTop(b); |
||||
} |
||||
|
||||
export function WindowSetSystemDefaultTheme() { |
||||
window.runtime.WindowSetSystemDefaultTheme(); |
||||
} |
||||
|
||||
export function WindowSetLightTheme() { |
||||
window.runtime.WindowSetLightTheme(); |
||||
} |
||||
|
||||
export function WindowSetDarkTheme() { |
||||
window.runtime.WindowSetDarkTheme(); |
||||
} |
||||
|
||||
export function WindowCenter() { |
||||
window.runtime.WindowCenter(); |
||||
} |
||||
|
||||
export function WindowSetTitle(title) { |
||||
window.runtime.WindowSetTitle(title); |
||||
} |
||||
|
||||
export function WindowFullscreen() { |
||||
window.runtime.WindowFullscreen(); |
||||
} |
||||
|
||||
export function WindowUnfullscreen() { |
||||
window.runtime.WindowUnfullscreen(); |
||||
} |
||||
|
||||
export function WindowIsFullscreen() { |
||||
return window.runtime.WindowIsFullscreen(); |
||||
} |
||||
|
||||
export function WindowGetSize() { |
||||
return window.runtime.WindowGetSize(); |
||||
} |
||||
|
||||
export function WindowSetSize(width, height) { |
||||
window.runtime.WindowSetSize(width, height); |
||||
} |
||||
|
||||
export function WindowSetMaxSize(width, height) { |
||||
window.runtime.WindowSetMaxSize(width, height); |
||||
} |
||||
|
||||
export function WindowSetMinSize(width, height) { |
||||
window.runtime.WindowSetMinSize(width, height); |
||||
} |
||||
|
||||
export function WindowSetPosition(x, y) { |
||||
window.runtime.WindowSetPosition(x, y); |
||||
} |
||||
|
||||
export function WindowGetPosition() { |
||||
return window.runtime.WindowGetPosition(); |
||||
} |
||||
|
||||
export function WindowHide() { |
||||
window.runtime.WindowHide(); |
||||
} |
||||
|
||||
export function WindowShow() { |
||||
window.runtime.WindowShow(); |
||||
} |
||||
|
||||
export function WindowMaximise() { |
||||
window.runtime.WindowMaximise(); |
||||
} |
||||
|
||||
export function WindowToggleMaximise() { |
||||
window.runtime.WindowToggleMaximise(); |
||||
} |
||||
|
||||
export function WindowUnmaximise() { |
||||
window.runtime.WindowUnmaximise(); |
||||
} |
||||
|
||||
export function WindowIsMaximised() { |
||||
return window.runtime.WindowIsMaximised(); |
||||
} |
||||
|
||||
export function WindowMinimise() { |
||||
window.runtime.WindowMinimise(); |
||||
} |
||||
|
||||
export function WindowUnminimise() { |
||||
window.runtime.WindowUnminimise(); |
||||
} |
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) { |
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A); |
||||
} |
||||
|
||||
export function ScreenGetAll() { |
||||
return window.runtime.ScreenGetAll(); |
||||
} |
||||
|
||||
export function WindowIsMinimised() { |
||||
return window.runtime.WindowIsMinimised(); |
||||
} |
||||
|
||||
export function WindowIsNormal() { |
||||
return window.runtime.WindowIsNormal(); |
||||
} |
||||
|
||||
export function BrowserOpenURL(url) { |
||||
window.runtime.BrowserOpenURL(url); |
||||
} |
||||
|
||||
export function Environment() { |
||||
return window.runtime.Environment(); |
||||
} |
||||
|
||||
export function Quit() { |
||||
window.runtime.Quit(); |
||||
} |
||||
|
||||
export function Hide() { |
||||
window.runtime.Hide(); |
||||
} |
||||
|
||||
export function Show() { |
||||
window.runtime.Show(); |
||||
} |
||||
|
||||
export function ClipboardGetText() { |
||||
return window.runtime.ClipboardGetText(); |
||||
} |
||||
|
||||
export function ClipboardSetText(text) { |
||||
return window.runtime.ClipboardSetText(text); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@ |
||||
module eruhs |
||||
|
||||
go 1.21 |
||||
|
||||
toolchain go1.22.0 |
||||
|
||||
require ( |
||||
github.com/dustin/go-humanize v1.0.1 |
||||
github.com/google/gopacket v1.1.19 |
||||
github.com/wailsapp/wails/v2 v2.8.0 |
||||
) |
||||
|
||||
require ( |
||||
github.com/bep/debounce v1.2.1 // indirect |
||||
github.com/go-ole/go-ole v1.2.6 // indirect |
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect |
||||
github.com/google/uuid v1.3.0 // indirect |
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect |
||||
github.com/labstack/echo/v4 v4.10.2 // indirect |
||||
github.com/labstack/gommon v0.4.0 // indirect |
||||
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect |
||||
github.com/leaanthony/gosod v1.0.3 // indirect |
||||
github.com/leaanthony/slicer v1.6.0 // indirect |
||||
github.com/leaanthony/u v1.1.0 // indirect |
||||
github.com/mattn/go-colorable v0.1.13 // indirect |
||||
github.com/mattn/go-isatty v0.0.19 // indirect |
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect |
||||
github.com/pkg/errors v0.9.1 // indirect |
||||
github.com/rivo/uniseg v0.4.4 // indirect |
||||
github.com/samber/lo v1.38.1 // indirect |
||||
github.com/tkrajina/go-reflector v0.5.6 // indirect |
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect |
||||
github.com/valyala/fasttemplate v1.2.2 // indirect |
||||
github.com/wailsapp/go-webview2 v1.0.10 // indirect |
||||
github.com/wailsapp/mimetype v1.4.1 // indirect |
||||
golang.org/x/crypto v0.18.0 // indirect |
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect |
||||
golang.org/x/net v0.20.0 // indirect |
||||
golang.org/x/sys v0.16.0 // indirect |
||||
golang.org/x/text v0.14.0 // indirect |
||||
) |
@ -0,0 +1,110 @@ |
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= |
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= |
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= |
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= |
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= |
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= |
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= |
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= |
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= |
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= |
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= |
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= |
||||
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= |
||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= |
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= |
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= |
||||
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= |
||||
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= |
||||
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= |
||||
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= |
||||
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= |
||||
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= |
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= |
||||
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= |
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= |
||||
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= |
||||
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= |
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= |
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= |
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= |
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= |
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= |
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= |
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= |
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= |
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= |
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= |
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= |
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= |
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= |
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= |
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= |
||||
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= |
||||
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= |
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= |
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= |
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= |
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= |
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= |
||||
github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w= |
||||
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= |
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= |
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= |
||||
github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4= |
||||
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0= |
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= |
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= |
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= |
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= |
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= |
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= |
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= |
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= |
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= |
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= |
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,37 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"embed" |
||||
|
||||
"github.com/wailsapp/wails/v2" |
||||
"github.com/wailsapp/wails/v2/pkg/options" |
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver" |
||||
_ "net/http/pprof" |
||||
) |
||||
|
||||
//go:embed all:frontend/dist/spa
|
||||
var assets embed.FS |
||||
|
||||
func main() { |
||||
// Create an instance of the app structure
|
||||
app := NewApp() |
||||
|
||||
// Create application with options
|
||||
err := wails.Run(&options.App{ |
||||
Title: "eruhs", |
||||
Width: 1480, |
||||
Height: 940, |
||||
AssetServer: &assetserver.Options{ |
||||
Assets: assets, |
||||
}, |
||||
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 1}, |
||||
OnStartup: app.startup, |
||||
Bind: []interface{}{ |
||||
app, &UIDataPackView{}, |
||||
}, |
||||
}) |
||||
|
||||
if err != nil { |
||||
println("Error:", err.Error()) |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"eruhs/describer" |
||||
"time" |
||||
) |
||||
|
||||
type UIDataPackView struct { |
||||
Id int `json:"id"` |
||||
Time time.Duration `json:"time"` |
||||
Source string `json:"source"` |
||||
Dest string `json:"dest"` |
||||
Proto string `json:"proto"` |
||||
Length int `json:"length"` |
||||
} |
||||
|
||||
type LayerDescribe struct { |
||||
HeaderRaw string `json:"header"` |
||||
ApplicationPayloadRaw string `json:"payload"` |
||||
LayerName string `json:"name"` |
||||
Describe describer.Describe `json:"describe"` |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"$schema": "https://wails.io/schemas/config.v2.json", |
||||
"name": "eruhs", |
||||
"outputfilename": "eruhs", |
||||
"frontend:install": "npm install", |
||||
"frontend:build": "npm run build", |
||||
"frontend:dev:watcher": "npm run dev", |
||||
"frontend:dev:serverUrl": "http://127.0.0.1:9000", |
||||
"author": { |
||||
"name": "lensfrex", |
||||
"email": "lensferno@outlook.com" |
||||
} |
||||
} |
Loading…
Reference in new issue