feat(wifi): added wifi segment for windows and wsl

This commit is contained in:
Mike Sigsworth 2021-11-22 17:32:05 -07:00 committed by Jan De Dobbeleer
parent fd81f4a00c
commit 4a68c444c6
5 changed files with 303 additions and 0 deletions

47
docs/docs/segment-wifi.md Normal file
View file

@ -0,0 +1,47 @@
---
id: wifi
title: WiFi
sidebar_label: WiFi
---
## What
Show details about the connected WiFi network.
## Sample Configuration
```json
{
"type": "wifi",
"style": "powerline",
"background": "#8822ee",
"foreground": "#eeeeee",
"powerline_symbol": "\uE0B0",
"properties": {
"template": "{{if .Connectected}}\uFAA8{{else}}\uFAA9{{end}}{{if .Connected}}{{.SSID}} {{.Signal}}% {{.ReceiveRate}}Mbps{{else}}{{.State}}{{end}}"
}
}
```
## Properties
- connected_icon: `string` - the icon to use when WiFi is connected - defaults to `\uFAA8`
- connected_icon: `string` - the icon to use when WiFi is disconnected - defaults to `\uFAA9`
- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig]
utilizing the properties below -
defaults to `{{if .Connectected}}\uFAA8{{else}}\uFAA9{{end}}{{if .Connected}}{{.SSID}} {{.Signal}}% {{.ReceiveRate}}Mbps{{else}}{{.State}}{{end}}`
## Template Properties
- `.Connected`: `bool` - if WiFi is currently connected
- `.State`: `string` - WiFi connection status - e.g. connected or disconnected
- `.SSID`: `string` - the SSID of the current wifi network
- `.RadioType`: `string` - the radio type - e.g. 802.11ac, 802.11ax, 802.11n, etc.
- `.Authentication`: `string` - the authentication type - e.g. WPA2-Personal, WPA2-Enterprise, etc.
- `.Channel`: `int` - the current channel number
- `.ReceiveRate`: `int` - the receive rate (Mbps)
- `.TransmitRate`: `int` - the transmit rate (Mbps)
- `.Signal`: `int` - the signal strength (%)
[go-text-template]: https://golang.org/pkg/text/template/
[sprig]: https://masterminds.github.io/sprig/

View file

@ -72,6 +72,7 @@ module.exports = {
"terraform",
"text",
"time",
"wifi",
"ytm",
],
},

View file

@ -132,6 +132,8 @@ const (
PHP SegmentType = "php"
// Nightscout is an open source diabetes system
Nightscout SegmentType = "nightscout"
// WiFi writes details about the current WiFi connection
WiFi SegmentType = "wifi"
)
func (segment *Segment) string() string {
@ -266,6 +268,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
Angular: &angular{},
PHP: &php{},
Nightscout: &nightscout{},
WiFi: &wifi{},
}
if writer, ok := functions[segment.Type]; ok {
props := &properties{

114
src/segment_wifi.go Normal file
View file

@ -0,0 +1,114 @@
package main
import (
"fmt"
"strconv"
"strings"
)
type wifi struct {
props *properties
env environmentInfo
Connected bool
State string
SSID string
RadioType string
Authentication string
Channel int
ReceiveRate int
TransmitRate int
Signal int
}
const (
defaultTemplate = "{{ if .Connected }}\uFAA8{{ else }}\uFAA9{{ end }}{{ if .Connected }}{{ .SSID }} {{ .Signal }}% {{ .ReceiveRate }}Mbps{{ else }}{{ .State }}{{ end }}"
)
func (w *wifi) enabled() bool {
// This segment only supports Windows/WSL for now
if w.env.getPlatform() != windowsPlatform && !w.env.isWsl() {
return false
}
// Bail out of no netsh command found
cmd := "netsh.exe"
if !w.env.hasCommand(cmd) {
return false
}
// Attempt to retrieve output from netsh command
cmdResult, err := w.env.runCommand(cmd, "wlan", "show", "interfaces")
displayError := w.props.getBool(DisplayError, false)
if err != nil && displayError {
w.State = fmt.Sprintf("WIFI ERR: %s", err)
return true
}
if err != nil {
return false
}
// Extract data from netsh cmdResult
parseNetshCmdResult(cmdResult, w)
return true
}
func (w *wifi) string() string {
segmentTemplate := w.props.getString(SegmentTemplate, defaultTemplate)
template := &textTemplate{
Template: segmentTemplate,
Context: w,
Env: w.env,
}
text, err := template.render()
if err != nil {
return err.Error()
}
return text
}
func (w *wifi) init(props *properties, env environmentInfo) {
w.props = props
w.env = env
}
func parseNetshCmdResult(netshCmdResult string, w *wifi) {
lines := strings.Split(netshCmdResult, "\n")
for _, line := range lines {
matches := strings.Split(line, " : ")
if len(matches) != 2 {
continue
}
name := strings.TrimSpace(matches[0])
value := strings.TrimSpace(matches[1])
switch name {
case "State":
w.State = value
w.Connected = value == "connected"
case "SSID":
w.SSID = value
case "Radio type":
w.RadioType = value
case "Authentication":
w.Authentication = value
case "Channel":
if intValue, err := strconv.Atoi(value); err == nil {
w.Channel = intValue
}
case "Receive rate (Mbps)":
if intValue, err := strconv.Atoi(strings.Split(value, ".")[0]); err == nil {
w.ReceiveRate = intValue
}
case "Transmit rate (Mbps)":
if intValue, err := strconv.Atoi(strings.Split(value, ".")[0]); err == nil {
w.TransmitRate = intValue
}
case "Signal":
if intValue, err := strconv.Atoi(strings.TrimRight(value, "%")); err == nil {
w.Signal = intValue
}
}
}
}

138
src/segment_wifi_test.go Normal file
View file

@ -0,0 +1,138 @@
package main
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type netshStringArgs struct {
state string
ssid string
radioType string
authentication string
channel int
receiveRate int
transmitRate int
signal int
}
func getNetshString(args *netshStringArgs) string {
const netshString string = `
There is 1 interface on the system:
Name : Wi-Fi
Description : Intel(R) Wireless-AC 9560 160MHz
GUID : 6bb8def2-9af2-4bd4-8be2-6bd54e46bdc9
Physical address : d4:3b:04:e6:10:40
State : %s
SSID : %s
BSSID : 5c:7d:7d:82:c5:73
Network type : Infrastructure
Radio type : %s
Authentication : %s
Cipher : CCMP
Connection mode : Profile
Channel : %d
Receive rate (Mbps) : %d
Transmit rate (Mbps) : %d
Signal : %d%%
Profile : ohsiggy
Hosted network status : Not available`
return fmt.Sprintf(netshString, args.state, args.ssid, args.radioType, args.authentication, args.channel, args.receiveRate, args.transmitRate, args.signal)
}
func TestWiFiSegment(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
ExpectedEnabled bool
CommandNotFound bool
CommandOutput string
CommandError error
DisplayError bool
Template string
ExpectedState string
}{
{
Case: "not enabled on windows when netsh command not found",
ExpectedEnabled: false,
ExpectedString: "",
CommandNotFound: true,
},
{
Case: "not enabled on windows when netsh command fails",
ExpectedEnabled: false,
ExpectedString: "",
CommandError: errors.New("intentional testing failure"),
},
{
Case: "enabled on windows with DisplayError=true",
ExpectedEnabled: true,
ExpectedString: "WIFI ERR: intentional testing failure",
CommandError: errors.New("intentional testing failure"),
DisplayError: true,
Template: "{{.State}}",
},
{
Case: "enabled on windows with every property in template",
ExpectedEnabled: true,
ExpectedString: "connected testing 802.11ac WPA2-Personal 99 500 400 80",
CommandOutput: getNetshString(&netshStringArgs{
state: "connected",
ssid: "testing",
radioType: "802.11ac",
authentication: "WPA2-Personal",
channel: 99,
receiveRate: 500.0,
transmitRate: 400.0,
signal: 80,
}),
Template: "{{.State}} {{.SSID}} {{.RadioType}} {{.Authentication}} {{.Channel}} {{.ReceiveRate}} {{.TransmitRate}} {{.Signal}}",
},
{
Case: "enabled on windows but wifi not connected",
ExpectedEnabled: true,
ExpectedString: "disconnected",
CommandOutput: getNetshString(&netshStringArgs{
state: "disconnected",
}),
Template: "{{if not .Connected}}{{.State}}{{end}}",
},
{
Case: "enabled on windows but template is invalid",
ExpectedEnabled: true,
ExpectedString: "unable to create text based on template",
CommandOutput: getNetshString(&netshStringArgs{}),
Template: "{{.DoesNotExist}}",
},
}
for _, tc := range cases {
env := new(MockedEnvironment)
env.On("getPlatform", nil).Return(windowsPlatform)
env.On("isWsl", nil).Return(false)
env.On("hasCommand", "netsh.exe").Return(!tc.CommandNotFound)
env.On("runCommand", mock.Anything, mock.Anything).Return(tc.CommandOutput, tc.CommandError)
props := &properties{
values: map[Property]interface{}{
DisplayError: tc.DisplayError,
SegmentTemplate: tc.Template,
},
}
w := &wifi{
env: env,
props: props,
}
assert.Equal(t, tc.ExpectedEnabled, w.enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, w.string(), tc.Case)
}
}