// +build js package main import ( "errors" "github.com/gopherjs/gopherjs/js" "github.com/mame82/P4wnP1_aloa/common_web" pb "github.com/mame82/P4wnP1_aloa/proto/gopherjs" "github.com/mame82/hvue" "strconv" ) var eNoLogEvent = errors.New("No log event") var eNoHidEvent = errors.New("No HID event") type jsMasterTemplate struct { *js.Object TemplateNameBluetooth string `js:"TemplateNameBluetooth"` TemplateNameUSB string `js:"TemplateNameUSB"` TemplateNameWiFi string `js:"TemplateNameWiFi"` TemplateNameTriggerActions string `js:"TemplateNameTriggerActions"` TemplateNamesNetwork []string `js:"TemplateNamesNetwork"` } func (target *jsMasterTemplate) fromGo(src *pb.MasterTemplate) { target.TemplateNamesNetwork = src.TemplateNamesNetwork target.TemplateNameBluetooth = src.TemplateNameBluetooth target.TemplateNameWiFi = src.TemplateNameWifi target.TemplateNameUSB = src.TemplateNameUsb target.TemplateNameTriggerActions = src.TemplateNameTriggerActions } func (src *jsMasterTemplate) toGo() (target *pb.MasterTemplate) { target = &pb.MasterTemplate{} target.TemplateNamesNetwork = src.TemplateNamesNetwork target.TemplateNameBluetooth = src.TemplateNameBluetooth target.TemplateNameWifi = src.TemplateNameWiFi target.TemplateNameUsb = src.TemplateNameUSB target.TemplateNameTriggerActions = src.TemplateNameTriggerActions return target } func NewMasterTemplate() (res *jsMasterTemplate) { res = &jsMasterTemplate{Object: O()} res.TemplateNameBluetooth = "" res.TemplateNameWiFi = "" res.TemplateNameUSB = "" res.TemplateNameTriggerActions = "" res.TemplateNamesNetwork = []string{} return res } type jsRequestMasterTemplateStorage struct { *js.Object TemplateName string `js:"TemplateName"` Template *jsMasterTemplate `js:"Template"` } func (rs *jsRequestMasterTemplateStorage) toGo() *pb.RequestMasterTemplateStorage { return &pb.RequestMasterTemplateStorage{ TemplateName: rs.TemplateName, Template: rs.Template.toGo(), } } func (rs *jsRequestMasterTemplateStorage) fromGo(src *pb.RequestMasterTemplateStorage) { rs.TemplateName = src.TemplateName rs.Template = NewMasterTemplate() rs.Template.fromGo(src.Template) } func NewRequestMasterTemplateStorage() (res *jsRequestMasterTemplateStorage) { res = &jsRequestMasterTemplateStorage{Object:O()} res.Template = NewMasterTemplate() res.TemplateName = "" return res } type jsLoadHidScriptSourceMode int const ( HID_SCRIPT_SOURCE_LOAD_MODE_PREPEND jsLoadHidScriptSourceMode = iota HID_SCRIPT_SOURCE_LOAD_MODE_APPEND HID_SCRIPT_SOURCE_LOAD_MODE_REPLACE ) type jsLoadHidScriptSourceReq struct { *js.Object FileName string `js:"FileName"` Mode jsLoadHidScriptSourceMode `js:"Mode"` } type jsWifiRequestSettingsStorage struct { *js.Object TemplateName string `js:"TemplateName"` Settings *jsWiFiSettings `js:"Settings"` } func (rs *jsWifiRequestSettingsStorage) toGo() *pb.WifiRequestSettingsStorage { return &pb.WifiRequestSettingsStorage{ Settings: rs.Settings.toGo(), TemplateName: rs.TemplateName, } } func (rs *jsWifiRequestSettingsStorage) fromGo(src *pb.WifiRequestSettingsStorage) { rs.TemplateName = src.TemplateName rs.Settings = NewWifiSettings() rs.Settings.fromGo(src.Settings) } func NewWifiRequestSettingsStorage() *jsWifiRequestSettingsStorage { res := &jsWifiRequestSettingsStorage{Object:O()} res.TemplateName = "" res.Settings = NewWifiSettings() return res } /* USB Gadget types corresponding to gRPC messages */ type jsUSBRequestSettingsStorage struct { *js.Object TemplateName string `js:"TemplateName"` Settings *jsGadgetSettings `js:"Settings"` } func (rs *jsUSBRequestSettingsStorage) toGo() *pb.USBRequestSettingsStorage { return &pb.USBRequestSettingsStorage{ Settings: rs.Settings.toGo(), TemplateName: rs.TemplateName, } } func (rs *jsUSBRequestSettingsStorage) fromGo(src *pb.USBRequestSettingsStorage) { rs.TemplateName = src.TemplateName rs.Settings = NewUSBGadgetSettings() rs.Settings.fromGo(src.Settings) } func NewUSBRequestSettingsStorage() *jsUSBRequestSettingsStorage { res := &jsUSBRequestSettingsStorage{Object:O()} res.TemplateName = "" res.Settings = NewUSBGadgetSettings() return res } type jsGadgetSettings struct { *js.Object Enabled bool `js:"Enabled"` Vid string `js:"Vid"` Pid string `js:"Pid"` Manufacturer string `js:"Manufacturer"` Product string `js:"Product"` Serial string `js:"Serial"` Use_CDC_ECM bool `js:"Use_CDC_ECM"` Use_RNDIS bool `js:"Use_RNDIS"` Use_HID_KEYBOARD bool `js:"Use_HID_KEYBOARD"` Use_HID_MOUSE bool `js:"Use_HID_MOUSE"` Use_HID_RAW bool `js:"Use_HID_RAW"` Use_UMS bool `js:"Use_UMS"` Use_SERIAL bool `js:"Use_SERIAL"` RndisSettings *jsGadgetSettingsEthernet `js:"RndisSettings"` CdcEcmSettings *jsGadgetSettingsEthernet `js:"CdcEcmSettings"` UmsSettings *jsGadgetSettingsUMS `js:"UmsSettings"` } type jsGadgetSettingsEthernet struct { *js.Object HostAddr string `js:"HostAddr"` DevAddr string `js:"DevAddr"` } type jsGadgetSettingsUMS struct { *js.Object Cdrom bool `js:"Cdrom"` File string `js:"File"` } func (jsGS jsGadgetSettings) toGo() (gs *pb.GadgetSettings) { return &pb.GadgetSettings{ Serial: jsGS.Serial, Use_SERIAL: jsGS.Use_SERIAL, Use_UMS: jsGS.Use_UMS, Use_HID_RAW: jsGS.Use_HID_RAW, Use_HID_MOUSE: jsGS.Use_HID_MOUSE, Use_HID_KEYBOARD: jsGS.Use_HID_KEYBOARD, Use_RNDIS: jsGS.Use_RNDIS, Use_CDC_ECM: jsGS.Use_CDC_ECM, Product: jsGS.Product, Manufacturer: jsGS.Manufacturer, Vid: jsGS.Vid, Pid: jsGS.Pid, Enabled: jsGS.Enabled, UmsSettings: &pb.GadgetSettingsUMS{ Cdrom: jsGS.UmsSettings.Cdrom, File: jsGS.UmsSettings.File, }, CdcEcmSettings: &pb.GadgetSettingsEthernet{ DevAddr: jsGS.CdcEcmSettings.DevAddr, HostAddr: jsGS.CdcEcmSettings.HostAddr, }, RndisSettings: &pb.GadgetSettingsEthernet{ DevAddr: jsGS.RndisSettings.DevAddr, HostAddr: jsGS.RndisSettings.HostAddr, }, } } func (jsGS *jsGadgetSettings) fromGo(gs *pb.GadgetSettings) { println(gs) jsGS.Enabled = gs.Enabled jsGS.Vid = gs.Vid jsGS.Pid = gs.Pid jsGS.Manufacturer = gs.Manufacturer jsGS.Product = gs.Product jsGS.Serial = gs.Serial jsGS.Use_CDC_ECM = gs.Use_CDC_ECM jsGS.Use_RNDIS = gs.Use_RNDIS jsGS.Use_HID_KEYBOARD = gs.Use_HID_KEYBOARD jsGS.Use_HID_MOUSE = gs.Use_HID_MOUSE jsGS.Use_HID_RAW = gs.Use_HID_RAW jsGS.Use_UMS = gs.Use_UMS jsGS.Use_SERIAL = gs.Use_SERIAL jsGS.RndisSettings = &jsGadgetSettingsEthernet{ Object: O(), } jsGS.RndisSettings.DevAddr = "" jsGS.RndisSettings.HostAddr = "" if gs.RndisSettings != nil { jsGS.RndisSettings.HostAddr = gs.RndisSettings.HostAddr jsGS.RndisSettings.DevAddr = gs.RndisSettings.DevAddr } jsGS.CdcEcmSettings = &jsGadgetSettingsEthernet{ Object: O(), } jsGS.CdcEcmSettings.DevAddr = "" jsGS.CdcEcmSettings.HostAddr = "" if gs.CdcEcmSettings != nil { jsGS.CdcEcmSettings.HostAddr = gs.CdcEcmSettings.HostAddr jsGS.CdcEcmSettings.DevAddr = gs.CdcEcmSettings.DevAddr } jsGS.UmsSettings = &jsGadgetSettingsUMS{ Object: O(), } jsGS.UmsSettings.Cdrom = false jsGS.UmsSettings.File = "" if gs.UmsSettings != nil { jsGS.UmsSettings.File = gs.UmsSettings.File jsGS.UmsSettings.Cdrom = gs.UmsSettings.Cdrom } } func NewUSBGadgetSettings() *jsGadgetSettings { gs := &jsGadgetSettings{ Object: O(), } gs.fromGo(&pb.GadgetSettings{}) //start with empty settings, but create nested structs return gs } /** Events **/ type jsEvent struct { *js.Object Type int64 `js:"type"` Values []interface{} JSValues *js.Object `js:"values"` } func NewJsEventFromNative(event *pb.Event) (res *jsEvent) { res = &jsEvent{Object: O()} res.JSValues = js.Global.Get("Array").New() res.Type = event.Type res.Values = make([]interface{}, len(event.Values)) for idx, val := range event.Values { switch valT := val.Val.(type) { case *pb.EventValue_Tint64: res.Values[idx] = valT.Tint64 res.JSValues.Call("push", valT.Tint64) case *pb.EventValue_Tstring: res.Values[idx] = valT.Tstring res.JSValues.Call("push", valT.Tstring) case *pb.EventValue_Tbool: res.Values[idx] = valT.Tbool res.JSValues.Call("push", valT.Tbool) default: println("error parsing event value", valT) } } //println("result",res) return res } //Log event type jsLogEvent struct { *js.Object EvLogSource string `js:"source"` EvLogLevel int `js:"level"` EvLogMessage string `js:"message"` EvLogTime int64 `js:"time"` } //HID event type jsHidEvent struct { *js.Object EvType int64 `js:"evtype"` VMId int64 `js:"vmId"` JobId int64 `js:"jobId"` HasError bool `js:"hasError"` Result string `js:"result"` Error string `js:"error"` Message string `js:"message"` EvLogTime int64 `js:"time"` } func (jsEv *jsEvent) toLogEvent() (res *jsLogEvent, err error) { if jsEv.Type != common_web.EVT_LOG || len(jsEv.Values) != 4 { return nil, eNoLogEvent } res = &jsLogEvent{Object: O()} var ok bool res.EvLogSource, ok = jsEv.Values[0].(string) if !ok { return nil, eNoLogEvent } ll, ok := jsEv.Values[1].(int64) if !ok { return nil, eNoLogEvent } res.EvLogLevel = int(ll) res.EvLogMessage, ok = jsEv.Values[2].(string) if !ok { return nil, eNoLogEvent } res.EvLogTime, ok = jsEv.Values[3].(int64) if !ok { return nil, eNoLogEvent } println("EvLogTime", res.EvLogTime) return res, nil } func (jsEv *jsEvent) toHidEvent() (res *jsHidEvent, err error) { if jsEv.Type != common_web.EVT_HID || len(jsEv.Values) != 8 { return nil, eNoHidEvent } res = &jsHidEvent{Object: O()} var ok bool res.EvType, ok = jsEv.Values[0].(int64) if !ok { return nil, eNoHidEvent } res.VMId, ok = jsEv.Values[1].(int64) if !ok { return nil, eNoHidEvent } res.JobId, ok = jsEv.Values[2].(int64) if !ok { return nil, eNoHidEvent } res.HasError, ok = jsEv.Values[3].(bool) if !ok { return nil, eNoHidEvent } res.Result, ok = jsEv.Values[4].(string) if !ok { return nil, eNoHidEvent } res.Error, ok = jsEv.Values[5].(string) if !ok { return nil, eNoHidEvent } res.Message, ok = jsEv.Values[6].(string) if !ok { return nil, eNoHidEvent } res.EvLogTime, ok = jsEv.Values[7].(int64) if !ok { return nil, eNoHidEvent } return res, nil } /* HIDJobList */ type jsHidJobState struct { *js.Object Id int64 `js:"id"` VmId int64 `js:"vmId"` HasFailed bool `js:"hasFailed"` HasSucceeded bool `js:"hasSucceeded"` LastMessage string `js:"lastMessage"` TextResult string `js:"textResult"` // TextError string `js:"textError"` LastUpdateTime int64 `js:"lastUpdateTime"` //JSON timestamp from server ScriptSource string `js:"textSource"` } type jsHidJobStateList struct { *js.Object Jobs *js.Object `js:"jobs"` } func NewHIDJobStateList() *jsHidJobStateList { jl := &jsHidJobStateList{Object: O()} jl.Jobs = O() /* //ToDo: Delete added a test jobs jl.UpdateEntry(99,1,false,false, "This is the latest event message", "current result", "current error","16:00", "type('hello world')") jl.UpdateEntry(100,1,false,true, "SUCCESS", "current result", "current error","16:00", "type('hello world')") jl.UpdateEntry(101,1,true,false, "FAIL", "current result", "current error","16:00", "type('hello world')") jl.UpdateEntry(102,1,true,true, "Error and Success at same time --> UNKNOWN", "current result", "current error","16:00", "type('hello world')") jl.UpdateEntry(102,1,true,true, "Error and Success at same time --> UNKNOWN, repeated ID", "current result", "current error","16:00", "type('hello world')") */ return jl } // The Method updates data of the HidJobStateList and is called from two source // 1) ... from an actively triggered RPC call to "HIDGetRunningJobState", in order to prefill the list with state data of // running jobs (job id, id of executing vm and job script source). The RPC call only returns information for running jobs, // not for finished ones (no matter if they failed or succeeded), as the RPC server doesn't keep state for finished jobs // in order to save memory. // 2) ... from HID events received via streaming RPC call to EventListen. The events provide additional information on // results of finished jobs (hasSucceeded, hasError ...). This information is used to update the job state of already // running jobs. Finished jobs, updated based on such events are kept in the web client's global state, even if the RPC // server already deleted the job after firing the according events. // // Thanks to this two information sources, the client has an overall job state, which is built from RPC server state and // HID job events (big picture). This mechanism isn't too robust: f.e. if running jobs are received and, let's say a new // job with id "66" is started before the event receiver is listening, the client will not know that job "66" exists. // There are a bunch of such race conditions, but it is considered sufficient to fetch the state of all running jobs once // on client startup with "HIDGetRunningJobState" and update the via events from "EventListen", later on, for the web // client's muse case. // // On Go's end, the job state list is a map, which uses a string representation of the respective job ID as key. // On JS end, this map has to be represented by a *js.Object, which get's keys added at runtime (every time a new element // is added to the map). As the job state list is used to present job state data to Vue.JS/Vuex a new problem arises: // Vue.JS's change detection doesn't work when properties are added or removed from JS objects // (see https://vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats). In order to deal with that, the // "UpdateEntry" method uses the approach proposed by Vue.Js and doesn't update the js.Object, which is backing the job // state list, directly, but instead uses the "Vue.set()" method to update the object, while making vue aware of it. // This means: THE "UpdateEntry" METHOD RELIES ON THE PRESENCE OF THE "Vue" OBJECT IN JAVASCRIPT GLOBAL SCOPE. This again // means Vue.JS has to be loaded, BEFORE THIS METHOD IS CALLED" func (jl *jsHidJobStateList) UpdateEntry(id, vmId int64, hasFailed, hasSucceeded bool, message string, textResult string, lastUpdateTime int64, scriptSource string) { key := strconv.Itoa(int(id)) //Check if job exists, update existing one if already present var j *jsHidJobState if res := jl.Jobs.Get(key); res == js.Undefined { j = &jsHidJobState{Object: O()} } else { j = &jsHidJobState{Object: res} } //Create job object j.Id = id j.VmId = vmId j.HasFailed = hasFailed j.HasSucceeded = hasSucceeded j.LastMessage = message j.TextResult = textResult j.LastUpdateTime = lastUpdateTime if len(scriptSource) > 0 { j.ScriptSource = scriptSource } //jl.Jobs.Set(strconv.Itoa(int(j.Id)), j) //jobs["j.ID"]=j <--Property addition/update can't be detected by Vue.js, see https://vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats hvue.Set(jl.Jobs, key, j) } func (jl *jsHidJobStateList) Clear() { hvue.Set(jl,"jobs",O()) } func (jl *jsHidJobStateList) DeleteEntry(id int64) { key := strconv.Itoa(int(id)) hvue.Delete(jl.Jobs, key) // vue reactive version //jl.Jobs.Delete(key) //JS version //delete(jl.Jobs, key) // go version } /* WiFi settings */ type jsWiFiState struct { *js.Object Mode int `js:"mode"` Channel uint32 `js:"channel"` Ssid string `js:"ssid"` CurrentSettings *jsWiFiSettings `js:"CurrentSettings"` } func (target *jsWiFiState) fromGo(src *pb.WiFiState) { target.Mode = int(src.Mode) target.Channel = src.Channel target.Ssid = src.Ssid target.CurrentSettings = NewWifiSettings() target.CurrentSettings.fromGo(src.CurrentSettings) return } func (src *jsWiFiState) toGo() (target *pb.WiFiState) { target = &pb.WiFiState{ Channel: src.Channel, Mode: pb.WiFiStateMode(src.Mode), Ssid: src.Ssid, CurrentSettings: src.CurrentSettings.toGo(), } return } func (src jsWiFiState) ModeString() (strMode string) { switch src.Mode { case int(pb.WiFiStateMode_STA_NOT_CONNECTED): return "Not connected" case int(pb.WiFiStateMode_AP_UP): return "Access Point" case int(pb.WiFiStateMode_STA_CONNECTED): return "Connected" default: return "UNKNOWN" } } func NewWiFiState() *jsWiFiState { res := &jsWiFiState{Object: O()} res.Channel = 0 res.Mode = int(pb.WiFiStateMode_STA_NOT_CONNECTED) res.Ssid = "" res.CurrentSettings = NewWifiSettings() return res } type jsWiFiBSSCfg struct { *js.Object SSID string `js:"ssid"` PSK string `js:"psk"` } type jsWiFiSettings struct { *js.Object Name string `js:"name"` Disabled bool `js:"disabled"` Reg string `js:"reg"` Mode int `js:"mode"` //AP, STA, Failover AuthMode int `js:"authMode"` //WPA2_PSK, OPEN Channel int `js:"channel"` HideSsid bool `js:"hideSsid"` Nexmon bool `js:"nexmon"` Ap_BSS *jsWiFiBSSCfg `js:"apBss"` Client_BSSList *js.Object `js:"staBssList"` //pointer to js array } func (src *jsWiFiSettings) toGo() (target *pb.WiFiSettings) { // assure undefined strings end up as empty strings target = &pb.WiFiSettings{ Name: src.Name, Disabled: src.Disabled, Regulatory: src.Reg, WorkingMode: pb.WiFiWorkingMode(src.Mode), AuthMode: pb.WiFiAuthMode(src.AuthMode), Nexmon: src.Nexmon, Channel: uint32(src.Channel), HideSsid: src.HideSsid, Ap_BSS: &pb.WiFiBSSCfg{ SSID: src.Ap_BSS.SSID, PSK: src.Ap_BSS.PSK, }, } //Check if ranges are present if src.Client_BSSList != js.Undefined { if numClientBss := src.Client_BSSList.Length(); numClientBss > 0 { target.Client_BSSList = make([]*pb.WiFiBSSCfg, numClientBss) //iterate over JS array for i := 0; i < numClientBss; i++ { jsBsscfg := &jsWiFiBSSCfg{Object: src.Client_BSSList.Index(i)} target.Client_BSSList[i] = &pb.WiFiBSSCfg{ SSID: jsBsscfg.SSID, PSK: jsBsscfg.PSK, } } } } else { // at least on empty entry target.Client_BSSList = []*pb.WiFiBSSCfg{ &pb.WiFiBSSCfg{}, } } return target } func (target *jsWiFiSettings) fromGo(src *pb.WiFiSettings) { // assure undefined strings end up as empty strings target.Name = src.Name target.Mode = int(src.WorkingMode) target.Disabled = src.Disabled target.Reg = src.Regulatory target.AuthMode = int(src.AuthMode) target.Nexmon = src.Nexmon target.Channel = int(src.Channel) target.HideSsid = src.HideSsid // ToDo: Errorcheck existence of nested structs target.Ap_BSS = &jsWiFiBSSCfg{Object: O()} if src.Ap_BSS != nil { target.Ap_BSS.SSID = src.Ap_BSS.SSID target.Ap_BSS.PSK = src.Ap_BSS.PSK } else { target.Ap_BSS.SSID = "" target.Ap_BSS.PSK = "" } target.Client_BSSList = js.Global.Get("Array").New() for _, clientBSS := range src.Client_BSSList { jsClBSS := jsWiFiBSSCfg{Object: O()} jsClBSS.SSID = clientBSS.SSID jsClBSS.PSK = clientBSS.PSK target.Client_BSSList.Call("push", jsClBSS) } //if no entry in array, push at least one (needed in vue model) if target.Client_BSSList.Length() == 0 { jsClBSS := jsWiFiBSSCfg{Object: O()} jsClBSS.SSID = "" jsClBSS.PSK = "" target.Client_BSSList.Call("push", jsClBSS) } /* target.AP_SSID = src.Ap_BSS.SSID target.AP_PSK = src.Ap_BSS.PSK // ToDo: change STA settings to array target.STA_SSID = src.Client_BSSList[0].SSID target.STA_PSK = src.Client_BSSList[0].PSK */ } func NewWifiSettings() *jsWiFiSettings { res := &jsWiFiSettings{Object: O()} res.Disabled = true res.Mode = int(pb.WiFiWorkingMode_UNKNOWN) res.Name = "default" res.Ap_BSS = &jsWiFiBSSCfg{Object: O()} res.Ap_BSS.PSK = "" res.Ap_BSS.SSID = "" res.AuthMode = int(pb.WiFiAuthMode_OPEN) res.Channel = 1 res.Nexmon = false res.Client_BSSList = js.Global.Get("Array").New() //no entry in array, push one (needed in vue model) jsClBSS := jsWiFiBSSCfg{Object: O()} jsClBSS.SSID = "" jsClBSS.PSK = "" res.Client_BSSList.Call("push", jsClBSS) res.HideSsid = false res.Reg = "US" return res } /* Network Settings */ type jsEthernetRequestSettingsStorage struct { *js.Object TemplateName string `js:"TemplateName"` Settings *jsEthernetInterfaceSettings `js:"Settings"` } func (rs *jsEthernetRequestSettingsStorage) toGo() *pb.EthernetRequestSettingsStorage { return &pb.EthernetRequestSettingsStorage{ Settings: rs.Settings.toGo(), TemplateName: rs.TemplateName, } } func (rs *jsEthernetRequestSettingsStorage) fromGo(src *pb.EthernetRequestSettingsStorage) { rs.TemplateName = src.TemplateName rs.Settings = &jsEthernetInterfaceSettings{Object:O()} rs.Settings.fromGo(src.Settings) } func NewEthernetRequestSettingsStorage() *jsEthernetRequestSettingsStorage { res := &jsEthernetRequestSettingsStorage{Object:O()} res.TemplateName = "" res.Settings = &jsEthernetInterfaceSettings{Object:O()} return res } type jsEthernetSettingsArray struct { *js.Object Interfaces *js.Object `js:"interfaces"` //every object property represents an EthernetSettings struct, the key is the interface name } func (isl *jsEthernetSettingsArray) fromGo(src *pb.DeployedEthernetInterfaceSettings) { //Options array (converted from map) isl.Interfaces = js.Global.Get("Array").New() for _, ifSets := range src.List { jsIfSets := &jsEthernetInterfaceSettings{Object: O()} jsIfSets.fromGo(ifSets) isl.Interfaces.Call("push", jsIfSets) } // sort the resulting array /* store.state.InterfaceSettings.interfaces.sort(function(a,b) { return a.name>b.name ? 1 : (a.name b.Name: return 1 case a.Name < b.Name: return -1 default: return 0 } }) } func (isl *jsEthernetSettingsArray) updateSingleInterface(updatedSettings *jsEthernetInterfaceSettings) { //Options array (converted from map) for i:=0; i 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 target.LeaseFile = src.LeaseFile target.NotAuthoritative = src.NotAuthoritative target.DoNotBindInterface = src.DoNotBindInterface target.CallbackScript = src.CallbackScript //Ranges array target.Ranges = js.Global.Get("Array").New() for _, dhcpRange := range src.Ranges { jsRange := &jsDHCPServerRange{Object: O()} jsRange.fromGo(dhcpRange) target.Ranges.Call("push", jsRange) } //Options array (converted from map) target.Options = js.Global.Get("Array").New() for optId, optVal := range src.Options { jsOption := &jsDHCPServerOption{Object: O()} jsOption.fromGo(optId, optVal) target.Options.Call("push", jsOption) } //StaticHosts array target.StaticHosts = js.Global.Get("Array").New() for _, staticHost := range src.StaticHosts { jsStaticHost := &jsDHCPServerStaticHost{Object: O()} jsStaticHost.fromGo(staticHost) target.Ranges.Call("push", jsStaticHost) } } type jsDHCPServerRange struct { *js.Object RangeLower string `js:"rangeLower"` RangeUpper string `js:"rangeUpper"` LeaseTime string `js:"leaseTime"` } func (target *jsDHCPServerRange) fromGo(src *pb.DHCPServerRange) { target.RangeLower = src.RangeLower target.RangeUpper = src.RangeUpper target.LeaseTime = src.LeaseTime } type jsDHCPServerOption struct { *js.Object Option int `js:"option"` Value string `js:"value"` } func (target *jsDHCPServerOption) fromGo(srcID uint32, srcVal string) { target.Option = int(srcID) target.Value = srcVal } type jsDHCPServerStaticHost struct { *js.Object Mac string `js:"mac"` Ip string `js:"ip"` } func (target *jsDHCPServerStaticHost) fromGo(src *pb.DHCPServerStaticHost) { target.Mac = src.Mac target.Ip = src.Ip } /* EVENT LOGGER */ type jsEventProcessor struct { *js.Object LogArray *js.Object `js:"logArray"` HidEventArray *js.Object `js:"eventHidArray"` MaxEntries int `js:"maxEntries"` JobList *jsHidJobStateList `js:"jobList"` //Needs to be exposed to JS in order to use JobList.UpdateEntry() from this JS object } func NewEventProcessor(maxEntries int, jobList *jsHidJobStateList) *jsEventProcessor { eventProcesssor := &jsEventProcessor{ Object: js.Global.Get("Object").New(), } eventProcesssor.LogArray = js.Global.Get("Array").New() eventProcesssor.HidEventArray = js.Global.Get("Array").New() eventProcesssor.MaxEntries = maxEntries eventProcesssor.JobList = jobList return eventProcesssor } func (data *jsEventProcessor) handleHidEvent(hEv *jsHidEvent) { println("Received HID EVENT", hEv) switch hEv.EvType { case common_web.HidEventType_JOB_STARTED: // Note: the JOB_STARTED event carries the script source in the message field, (no need to re-request the job // state in order to retrieve the source code of the job, when adding it to the job state list) data.JobList.UpdateEntry(hEv.JobId, hEv.VMId, hEv.HasError, false, "Script started", "", hEv.EvLogTime, hEv.Message) case common_web.HidEventType_JOB_FAILED: data.JobList.UpdateEntry(hEv.JobId, hEv.VMId, hEv.HasError, false, hEv.Message, hEv.Error, hEv.EvLogTime, "") QuasarNotifyError("HIDScript job " + strconv.Itoa(int(hEv.JobId)) + " failed", hEv.Error, QUASAR_NOTIFICATION_POSITION_TOP) case common_web.HidEventType_JOB_SUCCEEDED: data.JobList.UpdateEntry(hEv.JobId, hEv.VMId, hEv.HasError, true, hEv.Message, hEv.Result, hEv.EvLogTime, "") QuasarNotifySuccess("HIDScript job " + strconv.Itoa(int(hEv.JobId)) + " succeeded", hEv.Result, QUASAR_NOTIFICATION_POSITION_TOP) case common_web.HidEventType_JOB_CANCELLED: data.JobList.UpdateEntry(hEv.JobId, hEv.VMId, true, false, hEv.Message, hEv.Message, hEv.EvLogTime, "") default: println("unhandled hid event "+common_web.EventTypeHIDName[hEv.EvType], hEv) } } /* This method gets internalized and therefor the mutex won't be accessible*/ func (data *jsEventProcessor) HandleEvent(ev *pb.Event) { go func() { jsEv := NewJsEventFromNative(ev) switch jsEv.Type { //if LOG event add to logArray case common_web.EVT_LOG: if logEv, err := jsEv.toLogEvent(); err == nil { data.LogArray.Call("push", logEv) } else { println("couldn't convert to LogEvent: ", jsEv) } //if HID event add to eventHidArray case common_web.EVT_HID: if hidEv, err := jsEv.toHidEvent(); err == nil { data.HidEventArray.Call("push", hidEv) //handle event data.handleHidEvent(hidEv) } else { println("couldn't convert to HidEvent: ", jsEv) } } //reduce to length (note: kebab case 'max-entries' is translated to camel case 'maxEntries' by vue) for data.LogArray.Length() > data.MaxEntries { data.LogArray.Call("shift") // remove first element } for data.HidEventArray.Length() > data.MaxEntries { data.HidEventArray.Call("shift") // remove first element } }() }