mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-04-04 09:58:40 +02:00
Initial version of network config in webclient, ToDo: event based updates
This commit is contained in:
parent
7958d53c5a
commit
0e525a49b8
@ -1,15 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mame82/hvue"
|
||||
//"github.com/mame82/hvue"
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
pb "github.com/mame82/P4wnP1_go/proto/gopherjs"
|
||||
|
||||
"github.com/HuckRidgeSW/hvue"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func InitComponentsNetwork() {
|
||||
|
||||
hvue.NewComponent(
|
||||
"network",
|
||||
hvue.Template(templateNetwork),
|
||||
hvue.Computed("interfaces", func(vm *hvue.VM) interface{} {
|
||||
return vm.Get("$store").Get("state").Get("InterfaceSettings").Get("interfaces")
|
||||
}),
|
||||
|
||||
hvue.DataFunc(func(vm *hvue.VM) interface{} {
|
||||
data := &struct {
|
||||
*js.Object
|
||||
CurrentInterface *jsEthernetInterfaceSettings `js:"current"`
|
||||
}{Object:O()}
|
||||
data.CurrentInterface = &jsEthernetInterfaceSettings{Object:js.Undefined}
|
||||
return data
|
||||
}),
|
||||
hvue.Created(func(vm *hvue.VM) {
|
||||
// data field "current" is still undefined, set to first interface of computed property "interfaces" (if there is one)
|
||||
if vm.Get("interfaces").Length() > 0 {
|
||||
hvue.Set(vm, "current", vm.Get("interfaces").Index(0))
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
hvue.NewComponent(
|
||||
"networkinterface",
|
||||
hvue.Template(templateNetworkInterface),
|
||||
@ -36,35 +61,93 @@ func InitComponentsNetwork() {
|
||||
return false
|
||||
}
|
||||
}),
|
||||
hvue.Computed("withDhcp", func(vm *hvue.VM) interface{} {
|
||||
if mode := vm.Get("interface").Get("mode").Int(); mode == pb.EthernetInterfaceSettings_Mode_value["DHCP_SERVER"] {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}),
|
||||
hvue.Method("deploy", func(vm *hvue.VM) {
|
||||
vm.Get("$store").Call("dispatch", VUEX_ACTION_DEPLOY_ETHERNET_INTERFACE_SETTINGS, vm.Get("interface"))
|
||||
}),
|
||||
)
|
||||
|
||||
hvue.NewComponent(
|
||||
"network",
|
||||
hvue.Template(templateNetwork),
|
||||
hvue.Computed("interfaces", func(vm *hvue.VM) interface{} {
|
||||
return vm.Store.Get("state").Get("InterfaceSettings").Get("interfaces")
|
||||
}),
|
||||
hvue.NewComponent("dhcpconfig",
|
||||
hvue.Props("interface"),
|
||||
hvue.Template(templateDHCPConfig),
|
||||
hvue.Computed("config", func(vm *hvue.VM) interface{} {
|
||||
if vm.Get("interface").Get("dhcpServerSettings") == js.Undefined {
|
||||
// no DHCP server settings present
|
||||
|
||||
//cast interface to struct
|
||||
iface := &jsEthernetInterfaceSettings{Object:vm.Get("interface")}
|
||||
iface.CreateDhcpSettingsForInterface() //Create proper DHCP server settings for interface
|
||||
|
||||
hvue.DataFunc(func(vm *hvue.VM) interface{} {
|
||||
data := &struct {
|
||||
*js.Object
|
||||
CurrentInterface *jsEthernetInterfaceSettings `js:"current"`
|
||||
}{Object:O()}
|
||||
data.CurrentInterface = &jsEthernetInterfaceSettings{Object:js.Undefined}
|
||||
return data
|
||||
}),
|
||||
hvue.Created(func(vm *hvue.VM) {
|
||||
// data field "current" is still undefined, set to first interface of computed property "interfaces" (if there is one)
|
||||
if vm.Get("interfaces").Length() > 0 {
|
||||
hvue.Set(vm, "current", vm.Get("interfaces").Index(0))
|
||||
}
|
||||
return vm.Get("interface").Get("dhcpServerSettings")
|
||||
|
||||
}),
|
||||
hvue.ComputedWithGetSet("authoritative",
|
||||
func(vm *hvue.VM) interface{} {
|
||||
return !vm.Get("config").Get("nonAuthoritative").Bool()
|
||||
},
|
||||
func(vm *hvue.VM, newValue *js.Object) {
|
||||
vm.Get("config").Set("nonAuthoritative", !newValue.Bool())
|
||||
}),
|
||||
)
|
||||
|
||||
hvue.NewComponent("dhcpranges",
|
||||
hvue.Props("serversettings"),
|
||||
hvue.Template(templateDHCPRanges),
|
||||
hvue.Method("addRange", func(vm *hvue.VM) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
r := &jsDHCPServerRange{Object:O()}
|
||||
r.RangeLower = ""
|
||||
r.RangeUpper = ""
|
||||
r.LeaseTime = "1m"
|
||||
s.AddRange(r)
|
||||
}),
|
||||
hvue.Method("removeRange", func(vm *hvue.VM, delRange *jsDHCPServerRange) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
s.RemoveRange(delRange)
|
||||
}),
|
||||
)
|
||||
hvue.NewComponent("dhcpoptions",
|
||||
hvue.Props("serversettings"),
|
||||
hvue.Template(templateDHCPOptions),
|
||||
hvue.Method("addOption", func(vm *hvue.VM) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
o := &jsDHCPServerOption{Object:O()}
|
||||
o.Option = 3
|
||||
o.Value = ""
|
||||
s.AddOption(o)
|
||||
}),
|
||||
hvue.Method("removeOption", func(vm *hvue.VM, delOption *jsDHCPServerOption) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
s.RemoveOption(delOption)
|
||||
}),
|
||||
)
|
||||
hvue.NewComponent("dhcpstatichosts",
|
||||
hvue.Props("serversettings"),
|
||||
hvue.Template(templateDHCPStaticHosts),
|
||||
hvue.Method("addStaticHost", func(vm *hvue.VM) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
sh := &jsDHCPServerStaticHost{Object:O()}
|
||||
sh.Ip = ""
|
||||
sh.Mac = ""
|
||||
s.AddStaticHost(sh)
|
||||
}),
|
||||
hvue.Method("removeStaticHost", func(vm *hvue.VM, delStaticHost *jsDHCPServerStaticHost) {
|
||||
s := &jsDHCPServerSettings{Object:vm.Get("serversettings")}
|
||||
s.RemoveStaticHost(delStaticHost)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const templateNetwork = `
|
||||
<div class="network-master">
|
||||
<select v-model="current">
|
||||
Interface selection <select v-model="current">
|
||||
<option v-for="iface in interfaces" :key="iface.name" :value="iface">{{ iface.name }}</option>
|
||||
</select>
|
||||
<networkinterface v-if="current" :interface="current"></networkinterface>
|
||||
@ -74,9 +157,19 @@ const templateNetwork = `
|
||||
|
||||
const templateNetworkInterface = `
|
||||
<div class="network-interface">
|
||||
<h1>{{interface.name}}</h1>
|
||||
<p>{{ interface }}</p>
|
||||
<h1>
|
||||
Interface settings for "{{interface.name}}"
|
||||
</h1>
|
||||
Deploy settings <button @click="deploy">DEPLOY</button>
|
||||
|
||||
<!-- <p>{{ interface }}</p> -->
|
||||
<table>
|
||||
<tr>
|
||||
<td>Enabled</td>
|
||||
<td>
|
||||
<toggle-switch v-model="interface.enabled"></toggle-switch>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mode</td>
|
||||
<td>
|
||||
@ -94,6 +187,79 @@ const templateNetworkInterface = `
|
||||
<td><input v-model="interface.netmask4"></input></td>
|
||||
</tr>
|
||||
</table>
|
||||
<dhcpconfig v-if="withDhcp" :interface="interface"></dhcpconfig>
|
||||
</div>
|
||||
`
|
||||
|
||||
const templateDHCPConfig = `
|
||||
<div>
|
||||
<p><b>DHCP server settings</b></p>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Listen port</td>
|
||||
<td><input v-model="config.listenPort"></input></td>
|
||||
<td>Port for DNS server (0 to disable DNS and use DHCP only)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lease file</td>
|
||||
<td>{{ config.leaseFile }}</td>
|
||||
<td>Path to lease file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Authoritative</td>
|
||||
<td><toggle-switch type="checkbox" v-model="authoritative"></toggle-switch></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bind only to {{ config.listenInterface }}</td>
|
||||
<td>{{ !config.doNotBindInterface }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
<dhcpranges :serversettings="config"></dhcpranges>
|
||||
<dhcpoptions :serversettings="config"></dhcpoptions>
|
||||
<dhcpstatichosts :serversettings="config"></dhcpstatichosts>
|
||||
<!-- {{ config }} -->
|
||||
</div>
|
||||
`
|
||||
|
||||
const templateDHCPRanges = `
|
||||
<div>
|
||||
<p><b>DHCP ranges</b></p>
|
||||
<button @click="addRange">ADD</button>
|
||||
<table>
|
||||
<tr v-for="range in serversettings.ranges">
|
||||
<td>First IP</td> <td><input v-model="range.rangeLower"></input></td>
|
||||
<td>Last IP</td> <td><input v-model="range.rangeUpper"></input></td>
|
||||
<td>Lease time</td> <td><input v-model="range.leaseTime"></input></td>
|
||||
<td><button @click="removeRange(range)">DEL</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
const templateDHCPOptions = `
|
||||
<div>
|
||||
<p><b>Options</b></p>
|
||||
<button @click="addOption">ADD</button>
|
||||
<table>
|
||||
<tr v-for="option in serversettings.options">
|
||||
<td>Option number</td> <td><input v-model.number="option.option"></input></td>
|
||||
<td>Option string</td> <td><input v-model="option.value"></input></td>
|
||||
<td><button @click="removeOption(option)">DEL</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
const templateDHCPStaticHosts = `
|
||||
<div>
|
||||
<p><b>Static host entries</b></p>
|
||||
<button @click="addStaticHost">ADD</button>
|
||||
<table>
|
||||
<tr v-for="statichost in serversettings.staticHosts">
|
||||
<td>Host Mac</td> <td><input v-model="statichost.mac"></input></td>
|
||||
<td>Host IP</td> <td><input v-model="statichost.ip"></input></td>
|
||||
<td><button @click="removeStaticHost(statichost)">DEL</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`
|
@ -377,11 +377,62 @@ func (target *jsEthernetInterfaceSettings) fromGo(src *pb.EthernetInterfaceSetti
|
||||
}
|
||||
}
|
||||
|
||||
func (src *jsEthernetInterfaceSettings) toGo() (target *pb.EthernetInterfaceSettings) {
|
||||
target = &pb.EthernetInterfaceSettings{
|
||||
Name: src.Name,
|
||||
Mode: pb.EthernetInterfaceSettings_Mode(src.Mode),
|
||||
IpAddress4: src.IpAddress4,
|
||||
Netmask4: src.Netmask4,
|
||||
Enabled: src.Enabled,
|
||||
SettingsInUse: src.SettingsInUse,
|
||||
}
|
||||
|
||||
if src.DhcpServerSettings.Object == js.Undefined {
|
||||
println("DHCPServerSettings on JS object undefined")
|
||||
target.DhcpServerSettings = nil
|
||||
} else {
|
||||
target.DhcpServerSettings = src.DhcpServerSettings.toGo()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (iface *jsEthernetInterfaceSettings) CreateDhcpSettingsForInterface() {
|
||||
//create dhcp server settings
|
||||
settings := &jsDHCPServerSettings{Object:O()}
|
||||
settings.ListenInterface = iface.Name
|
||||
settings.ListenPort = 0 // 0 means DNS is disabled
|
||||
settings.LeaseFile = common_web.NameLeaseFileDHCPSrv(iface.Name)
|
||||
settings.NotAuthoritative = false
|
||||
settings.DoNotBindInterface = false
|
||||
settings.CallbackScript = ""
|
||||
//ToDo: add missing fields
|
||||
|
||||
|
||||
//Ranges array
|
||||
settings.Ranges = js.Global.Get("Array").New()
|
||||
settings.Options = js.Global.Get("Array").New()
|
||||
settings.StaticHosts = js.Global.Get("Array").New()
|
||||
|
||||
//add empty option for router and DNS to prevent netmask from promoting itself via DHCP
|
||||
optNoRouter := &jsDHCPServerOption{Object:O()}
|
||||
optNoRouter.Option = 3
|
||||
optNoRouter.Value = ""
|
||||
settings.AddOption(optNoRouter)
|
||||
optNoDNS := &jsDHCPServerOption{Object:O()}
|
||||
optNoDNS.Option = 6
|
||||
optNoDNS.Value = ""
|
||||
settings.AddOption(optNoDNS)
|
||||
|
||||
//iface.DhcpServerSettings = settings
|
||||
// Update the field with Vue in order to have proper setters in place
|
||||
hvue.Set(iface,"dhcpServerSettings", settings)
|
||||
}
|
||||
|
||||
type jsDHCPServerSettings struct {
|
||||
*js.Object
|
||||
ListenPort int `js:"listenPort"`
|
||||
ListenInterface string `js:"listenInterface"`
|
||||
LeaseFile string `js:"jsLeaseFile"`
|
||||
LeaseFile string `js:"leaseFile"`
|
||||
NotAuthoritative bool `js:"nonAuthoritative"`
|
||||
DoNotBindInterface bool `js:"doNotBindInterface"`
|
||||
CallbackScript string `js:"callbackScript"`
|
||||
@ -390,6 +441,115 @@ type jsDHCPServerSettings struct {
|
||||
StaticHosts *js.Object `js:"staticHosts"`//[]*DHCPServerStaticHost
|
||||
}
|
||||
|
||||
func (src *jsDHCPServerSettings) toGo() (target *pb.DHCPServerSettings) {
|
||||
target = &pb.DHCPServerSettings{}
|
||||
|
||||
target.ListenPort = uint32(src.ListenPort)
|
||||
target.ListenInterface = src.ListenInterface
|
||||
target.LeaseFile = src.LeaseFile
|
||||
target.NotAuthoritative = src.NotAuthoritative
|
||||
target.DoNotBindInterface = src.DoNotBindInterface
|
||||
target.CallbackScript = src.CallbackScript
|
||||
|
||||
println("jsRanges", src.Ranges)
|
||||
|
||||
|
||||
//Check if ranges are present
|
||||
if src.Ranges != js.Undefined {
|
||||
if numRanges := src.Ranges.Length(); numRanges > 0 {
|
||||
target.Ranges = make([]*pb.DHCPServerRange, numRanges)
|
||||
//iterate over JS array
|
||||
for i:= 0; i < numRanges; i++ {
|
||||
jsRange := &jsDHCPServerRange{Object: src.Ranges.Index(i)}
|
||||
target.Ranges[i] = &pb.DHCPServerRange{}
|
||||
target.Ranges[i].RangeUpper = jsRange.RangeUpper
|
||||
target.Ranges[i].RangeLower = jsRange.RangeLower
|
||||
target.Ranges[i].LeaseTime = jsRange.LeaseTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if options are present
|
||||
if src.Options != js.Undefined {
|
||||
if numOptions := src.Options.Length(); numOptions > 0 {
|
||||
target.Options = make(map[uint32]string)
|
||||
//iterate over JS array
|
||||
for i:= 0; i < numOptions; i++ {
|
||||
jsOption := &jsDHCPServerOption{Object: src.Options.Index(i)}
|
||||
target.Options[uint32(jsOption.Option)] = jsOption.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if SaticHosts are present
|
||||
if src.StaticHosts != js.Undefined {
|
||||
if numStaticHosts := src.StaticHosts.Length(); numStaticHosts > 0 {
|
||||
target.StaticHosts = make([]*pb.DHCPServerStaticHost, numStaticHosts)
|
||||
//iterate over JS array
|
||||
for i:= 0; i < numStaticHosts; i++ {
|
||||
jsStaticHost := &jsDHCPServerStaticHost{Object: src.StaticHosts.Index(i)}
|
||||
target.StaticHosts[i] = &pb.DHCPServerStaticHost{}
|
||||
target.StaticHosts[i].Mac = jsStaticHost.Mac
|
||||
target.StaticHosts[i].Ip = jsStaticHost.Ip
|
||||
}
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
|
||||
func (settings *jsDHCPServerSettings) AddRange(dhcpRange *jsDHCPServerRange) {
|
||||
if settings.Ranges == js.Undefined {
|
||||
settings.Ranges = js.Global.Get("Array").New()
|
||||
}
|
||||
settings.Ranges.Call("push", dhcpRange)
|
||||
}
|
||||
|
||||
func (settings *jsDHCPServerSettings) RemoveRange(dhcpRange *jsDHCPServerRange) {
|
||||
if settings.Ranges == js.Undefined { return }
|
||||
|
||||
//Check if in array
|
||||
if idx := settings.Ranges.Call("indexOf", dhcpRange).Int(); idx > -1 {
|
||||
settings.Ranges.Call("splice", idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (settings *jsDHCPServerSettings) AddOption(dhcpOption *jsDHCPServerOption) {
|
||||
if settings.Options == js.Undefined {
|
||||
settings.Options = js.Global.Get("Array").New()
|
||||
}
|
||||
settings.Options.Call("push", dhcpOption)
|
||||
}
|
||||
|
||||
|
||||
func (settings *jsDHCPServerSettings) RemoveOption(dhcpOption *jsDHCPServerOption) {
|
||||
if settings.Options == js.Undefined { return }
|
||||
|
||||
//Check if in array
|
||||
if idx := settings.Options.Call("indexOf", dhcpOption).Int(); idx > -1 {
|
||||
settings.Options.Call("splice", idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (settings *jsDHCPServerSettings) AddStaticHost(dhcpStaticHost *jsDHCPServerStaticHost) {
|
||||
if settings.StaticHosts == js.Undefined {
|
||||
settings.StaticHosts = js.Global.Get("Array").New()
|
||||
}
|
||||
settings.StaticHosts.Call("push", dhcpStaticHost)
|
||||
}
|
||||
|
||||
|
||||
func (settings *jsDHCPServerSettings) RemoveStaticHost(dhcpStaticHost *jsDHCPServerStaticHost) {
|
||||
if settings.StaticHosts == js.Undefined { return }
|
||||
|
||||
//Check if in array
|
||||
if idx := settings.StaticHosts.Call("indexOf", dhcpStaticHost).Int(); idx > -1 {
|
||||
settings.StaticHosts.Call("splice", idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (target *jsDHCPServerSettings) fromGo(src *pb.DHCPServerSettings) {
|
||||
target.ListenPort = int(src.ListenPort)
|
||||
target.ListenInterface = src.ListenInterface
|
||||
|
@ -16,6 +16,7 @@ const (
|
||||
VUEX_ACTION_UPDATE_RUNNING_HID_JOBS = "updateRunningHidJobs"
|
||||
VUEX_ACTION_DEPLOY_CURRENT_GADGET_SETTINGS = "deployCurrentGadgetSettings"
|
||||
VUEX_ACTION_UPDATE_GADGET_SETTINGS_FROM_DEPLOYED = "updateCurrentGadgetSettingsFromDeployed"
|
||||
VUEX_ACTION_DEPLOY_ETHERNET_INTERFACE_SETTINGS = "deployEthernetInterfaceSettings"
|
||||
VUEX_MUTATION_SET_CURRENT_GADGET_SETTINGS_TO = "setCurrentGadgetSettings"
|
||||
VUEX_MUTATION_SET_CURRENT_HID_SCRIPT_SOURCE_TO = "setCurrentHIDScriptSource"
|
||||
|
||||
@ -121,6 +122,7 @@ func actionUpdateRunningHidJobs(store *mvuex.Store, context *mvuex.ActionContext
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func actionDeployCurrentGadgetSettings(store *mvuex.Store, context *mvuex.ActionContext, state *GlobalState) {
|
||||
go func() {
|
||||
// ToDo: Indicate deployment process via global state
|
||||
@ -152,6 +154,18 @@ func actionDeployCurrentGadgetSettings(store *mvuex.Store, context *mvuex.Action
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func actionDeployEthernetInterfaceSettings(store *mvuex.Store, context *mvuex.ActionContext, state *GlobalState, settings *jsEthernetInterfaceSettings) {
|
||||
go func() {
|
||||
println("Vuex dispatch deploy ethernet interface settings")
|
||||
// convert to Go type
|
||||
goSettings := settings.toGo()
|
||||
|
||||
err := RpcClient.DeployedEthernetInterfaceSettings(time.Second*3, goSettings)
|
||||
if err != nil {Alert(err)}
|
||||
}()
|
||||
}
|
||||
|
||||
func initMVuex() GlobalState {
|
||||
state := createGlobalStateStruct()
|
||||
globalState = &state //make accessible through global var
|
||||
@ -205,6 +219,7 @@ func initMVuex() GlobalState {
|
||||
mvuex.Action(VUEX_ACTION_UPDATE_GADGET_SETTINGS_FROM_DEPLOYED, actionUpdateGadgetSettingsFromDeployed),
|
||||
mvuex.Action(VUEX_ACTION_DEPLOY_CURRENT_GADGET_SETTINGS, actionDeployCurrentGadgetSettings),
|
||||
mvuex.Action(VUEX_ACTION_UPDATE_RUNNING_HID_JOBS, actionUpdateRunningHidJobs),
|
||||
mvuex.Action(VUEX_ACTION_DEPLOY_ETHERNET_INTERFACE_SETTINGS, actionDeployEthernetInterfaceSettings),
|
||||
)
|
||||
|
||||
// fetch deployed gadget settings
|
||||
|
@ -29,6 +29,15 @@ func NewRpcClient(addr string) Rpc {
|
||||
return rcl
|
||||
}
|
||||
|
||||
func (rpc *Rpc) DeployedEthernetInterfaceSettings(timeout time.Duration, settings *pb.EthernetInterfaceSettings) (err error) {
|
||||
// ToDo: The RPC call has to return an error in case deployment fails
|
||||
ctx,cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
_,err = rpc.Client.DeployEthernetInterfaceSettings(ctx, settings)
|
||||
return
|
||||
}
|
||||
|
||||
func (rpc *Rpc) GetAllDeployedEthernetInterfaceSettings(timeout time.Duration) (settingsList *jsEthernetSettingsList, err error) {
|
||||
ctx,cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
Loading…
x
Reference in New Issue
Block a user