From 32846e652c40b85c824f180c70dbb2e251ec1ef3 Mon Sep 17 00:00:00 2001
From: MaMe82 <mame8282@googlemail.com>
Date: Tue, 30 Oct 2018 15:36:10 +0100
Subject: [PATCH] webclient,service,hid: Fixes of HIDScript state events; fix
 timestamp displaying

---
 hid/AsyncOtto.go                      |  7 +++
 hid/controller.go                     |  3 ++
 service/Event.go                      | 49 +++++++++++++-----
 service/triggerAction.go              |  2 +-
 web_client/common.go                  | 16 ++++--
 web_client/hvueCompHIDEvents.go       | 11 +++-
 web_client/hvueCompHIDJobs.go         | 38 +++++++++++---
 web_client/hvueCompHIDScript.go       | 29 ++---------
 web_client/hvueCompLogger.go          | 44 ++++++++++++----
 web_client/hvueCompUSBSettings.go     |  2 +-
 web_client/hvueComponentsBluetooth.go | 16 +++---
 web_client/jsDataHandling.go          | 32 +++---------
 web_client/main.go                    | 44 +++-------------
 web_client/mvuexGlobalState.go        | 74 ++++++++++++++++++++++-----
 web_client/rpcClient.go               | 34 ++++++++++++
 15 files changed, 258 insertions(+), 143 deletions(-)

diff --git a/hid/AsyncOtto.go b/hid/AsyncOtto.go
index 19e109e..336be51 100644
--- a/hid/AsyncOtto.go
+++ b/hid/AsyncOtto.go
@@ -167,6 +167,13 @@ func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}, anonymous
 				if caught == haltirq {
 					job.ResultErr = errors.New(fmt.Sprintf("Execution of job %d on VM %d interrupted\n", job.Id, avm.Id))
 
+					avm.emitEvent(Event{
+						Job:job,
+						Vm:job.executingVM,
+						Type:EventType_JOB_CANCELLED,
+						Message:"Script execution cancelled",
+					})
+
 					// signal Job finished
 					job.SetFinished()
 					return
diff --git a/hid/controller.go b/hid/controller.go
index 1204e89..45a8dc7 100644
--- a/hid/controller.go
+++ b/hid/controller.go
@@ -1,3 +1,4 @@
+// +build linux
 
 package hid
 
@@ -301,12 +302,14 @@ func (ctl *HIDController) CancelAllBackgroundJobs() {
 	for job,_ := range oldList {
 		fmt.Printf("Cancelling Job %d\n", job.Id)
 		job.Cancel()
+		/*
 		ctl.emitEvent(Event{
 			Job:job,
 			Vm:job.executingVM,
 			Type:EventType_JOB_CANCELLED,
 			Message:"Script execution cancelled",
 		})
+		*/
 	}
 	globalJobList = make(map[*AsyncOttoJob]bool) //Create new empty list
 	globalJobListMutex.Unlock()
diff --git a/service/Event.go b/service/Event.go
index 9233c6b..b057abd 100644
--- a/service/Event.go
+++ b/service/Event.go
@@ -1,15 +1,15 @@
 package service
 
 import (
-	"errors"
-	"fmt"
-	pb "github.com/mame82/P4wnP1_go/proto"
 	"context"
+	"fmt"
+	"github.com/mame82/P4wnP1_go/common_web"
+	"github.com/mame82/P4wnP1_go/hid"
+	pb "github.com/mame82/P4wnP1_go/proto"
+	"log"
 	"sync"
 	"time"
-	"log"
-	"github.com/mame82/P4wnP1_go/hid"
-	"github.com/mame82/P4wnP1_go/common_web"
+	"errors"
 )
 
 type EventManager struct {
@@ -53,7 +53,7 @@ func (em *EventManager) Emit(event *pb.Event) {
 }
 
 func (em *EventManager) Write(p []byte) (n int, err error) {
-	ev := ConstructEventLog("logWriter", 1, string(p))
+	ev := ConstructEventLog("logWriter", LOG_LEVEL_INFORMATION, string(p))
 	em.Emit(ev)
 	return len(p), nil
 }
@@ -165,8 +165,32 @@ func ConstructEventNotifyStateChange(stateType common_web.EvtStateChangeType) *p
 	}
 }
 
-func ConstructEventLog(source string, level int, message string) *pb.Event {
-	tJson, _ := time.Now().MarshalJSON()
+/*
+	case 1:
+		return prefix + "critical"
+	case 2:
+		return prefix + "error"
+	case 3:
+		return prefix + "warning"
+	case 4:
+		return prefix + "information"
+	case 5:
+		return prefix + "verbose"
+ */
+type LogLevel int
+const (
+	LOG_LEVEL_UNDEFINED LogLevel = iota
+	LOG_LEVEL_CRITICAL
+	LOG_LEVEL_ERROR
+	LOG_LEVEL_WARNING
+	LOG_LEVEL_INFORMATION
+	LOG_LEVEL_VERBOSE
+)
+
+func ConstructEventLog(source string, level LogLevel, message string) *pb.Event {
+	//tJson, _ := time.Now().MarshalJSON()
+
+	unixTimeMillis := time.Now().UnixNano() / 1e6
 
 	return &pb.Event{
 		Type: common_web.EVT_LOG,
@@ -174,7 +198,7 @@ func ConstructEventLog(source string, level int, message string) *pb.Event {
 			{Val: &pb.EventValue_Tstring{Tstring: source}},
 			{Val: &pb.EventValue_Tint64{Tint64: int64(level)}},
 			{Val: &pb.EventValue_Tstring{Tstring: message}},
-			{Val: &pb.EventValue_Tstring{Tstring: string(tJson)}},
+			{Val: &pb.EventValue_Tint64{Tint64: unixTimeMillis}}, //retrieve time in nano second accuracy and scale down to milliseconds
 		},
 	}
 }
@@ -266,7 +290,8 @@ func ConstructEventHID(hidEvent hid.Event) *pb.Event {
 		vmID = eVM.Id
 	}
 
-	tJson, _ := time.Now().MarshalJSON()
+
+	unixTimeMillis := time.Now().UnixNano() / 1e6
 
 	return &pb.Event{
 		Type: common_web.EVT_HID, //Type
@@ -278,7 +303,7 @@ func ConstructEventHID(hidEvent hid.Event) *pb.Event {
 			{Val: &pb.EventValue_Tstring{Tstring: resString}},          //result String
 			{Val: &pb.EventValue_Tstring{Tstring: errString}},          //error String (message in case of error)
 			{Val: &pb.EventValue_Tstring{Tstring: message}},            //Mesage text of event
-			{Val: &pb.EventValue_Tstring{Tstring: string(tJson)}},      //Timestamp of event genration
+			{Val: &pb.EventValue_Tint64{Tint64: unixTimeMillis}},      //Timestamp of event genration
 		},
 	}
 }
diff --git a/service/triggerAction.go b/service/triggerAction.go
index c684a5d..3a36c76 100644
--- a/service/triggerAction.go
+++ b/service/triggerAction.go
@@ -547,7 +547,7 @@ func (tam *TriggerActionManager) executeActionLog(evt *pb.Event, ta *pb.TriggerA
 	}
 
 	fmt.Printf("Trigger '%s' fired -> executing action '%s'\n", triggerName, actionName)
-	tam.rootSvc.SubSysEvent.Emit(ConstructEventLog("TriggerAction", 0, logMessage))
+	tam.rootSvc.SubSysEvent.Emit(ConstructEventLog("TriggerAction", LOG_LEVEL_INFORMATION, logMessage))
 }
 
 // checks if the triggerType of the given event (if trigger event at all), matches the TriggerType of the TriggerAction
diff --git a/web_client/common.go b/web_client/common.go
index a9c6361..7be45ee 100644
--- a/web_client/common.go
+++ b/web_client/common.go
@@ -3,12 +3,9 @@
 package main
 
 import (
-	"github.com/gopherjs/gopherjs/js"
 	"crypto/md5"
 	"encoding/hex"
-	"time"
-	pb "github.com/mame82/P4wnP1_go/proto/gopherjs"
-	"context"
+	"github.com/gopherjs/gopherjs/js"
 )
 
 func O() *js.Object {
@@ -27,6 +24,14 @@ func StringToMD5(input string) string {
 	return string(dst)
 }
 
+func BytesToMD5(input []byte) string {
+	sum := md5.Sum(input)
+	dst := make([]byte, hex.EncodedLen(len(sum)))
+	hex.Encode(dst, sum[:])
+	return string(dst)
+}
+
+/*
 func UploadHIDScript(filename string, content string) (err error) {
 	ctx,cancel := context.WithTimeout(context.Background(), 30*time.Second)
 	defer cancel()
@@ -65,4 +70,5 @@ func RunHIDScript(filename string, timeoutSeconds uint32) (job *pb.HIDScriptJob,
 		},
 	)
 
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/web_client/hvueCompHIDEvents.go b/web_client/hvueCompHIDEvents.go
index 5243ee4..e00238e 100644
--- a/web_client/hvueCompHIDEvents.go
+++ b/web_client/hvueCompHIDEvents.go
@@ -29,8 +29,11 @@ func InitCompHIDEvents() {
 			func(vm *hvue.VM) interface{} {
 				return vm.Get("$store").Get("state").Get("EventProcessor").Get("eventHidArray")
 			}),
+		hvue.Method("formatDate", func(vm *hvue.VM, timestamp *js.Object) interface{} {
+			return js.Global.Get("Quasar").Get("utils").Get("date").Call("formatDate", timestamp, "YYYY-MM-DD HH:mm:ss Z")
+		}),
 		hvue.Method("evIdToString", func(vm *hvue.VM, evID int64) (res string) {
-			println("EvID", evID)
+			//println("EvID", evID)
 			return common_web.EventTypeHIDName[evID]
 		}),
 	)
@@ -45,12 +48,16 @@ const (
 		<div>
 			<q-table
 				:data="events"
-				:columns="[{name:'type', field: 'evtype', label: 'Event Type', align: 'left'}, {name:'vmid', field: 'vmId', label: 'VM ID', align: 'left'}, {name:'jobid', field: 'jobId', label: 'Job ID', align: 'left'}, {name:'haserror', field: 'hasError', label: 'Has error', align: 'left'}, {name:'res', field: 'result', label: 'Result', align: 'left'}, {name:'errormsg', field: 'error', label: 'Error', align: 'left'}, {name:'msg', field: 'message', label: 'Message', align: 'left'}, {name:'timestamp', field: 'time', label: 'Time', align: 'left'}]"
+				:columns="[{name:'timestamp', field: 'time', label: 'Time', align: 'left'}, {name:'type', field: 'evtype', label: 'Event Type', align: 'left'}, {name:'vmid', field: 'vmId', label: 'VM ID', align: 'left'}, {name:'jobid', field: 'jobId', label: 'Job ID', align: 'left'}, {name:'haserror', field: 'hasError', label: 'Has error', align: 'left'}, {name:'res', field: 'result', label: 'Result', align: 'left'}, {name:'errormsg', field: 'error', label: 'Error', align: 'left'}, {name:'msg', field: 'message', label: 'Message', align: 'left'}]"
 				row-key="name"
 				:pagination="pagination"
 				hide-bottom
 			>
 
+				<q-td slot="body-cell-timestamp" slot-scope="props" :props="props">
+					{{ formatDate(props.value) }}
+				</q-td>
+
 				<q-td slot="body-cell-type" slot-scope="props" :props="props">
 					{{ evIdToString(props.value) }}
 				</q-td>
diff --git a/web_client/hvueCompHIDJobs.go b/web_client/hvueCompHIDJobs.go
index 2c9c7e6..6a72ca7 100644
--- a/web_client/hvueCompHIDJobs.go
+++ b/web_client/hvueCompHIDJobs.go
@@ -123,13 +123,16 @@ ScriptSource   string `js:"textSource"`
 			<q-popover>
 				{{ job.textResult }}
 			</q-popover>
+			<q-tooltip>
+				show job result
+			</q-tooltip>
 		</q-btn>
 	</q-item-side>
    	<q-item-side right v-if="!job.hasSucceeded && !job.hasFailed">
 		<q-btn flat round dense color="negative" icon="cancel" @click="cancel">
-			<q-popover>
+			<q-tooltip>
 				cancel HIDScript job {{ job.id }}
-			</q-popover>
+			</q-tooltip>
 		</q-btn>
 	</q-item-side>
 </q-item>
@@ -141,15 +144,31 @@ ScriptSource   string `js:"textSource"`
 compHIDJobOverviewTemplate = `
 	<q-card class="full-height">
 		<q-list>
-			<q-list-header>Running</q-list-header>
-			<hid-job-overview-item v-for="job in $store.getters.hidjobsRunning" :job="job" :key="job.id"></hid-job-overview-item>
+			<q-collapsible opened icon-toggle>
+				<template slot="header">
+					<q-item-main label="Running jobs" :sublabel="'(' + $store.getters.hidjobsRunning.length + ' running jobs)'"/>
+					<q-item-side v-if="$store.getters.hidjobsRunning.length > 0" right>
+						<q-btn icon="cancel" color="red" @click="$store.dispatch('cancelAllHIDJobs')" round inverted flat>
+							<q-tooltip>
+								cancel all running HIDScript jobs
+							</q-tooltip>
+						</q-btn>
+					</q-item-side>
+				</template>
+				<hid-job-overview-item v-for="job in $store.getters.hidjobsRunning" :job="job" :key="job.id"></hid-job-overview-item>
+			</q-collapsible>
 		</q-list>
+
 		<q-list>
-			<q-collapsible  opened icon-toggle>
+			<q-collapsible opened icon-toggle>
 				<template slot="header">
 					<q-item-main label="Succeeded" :sublabel="'(' + $store.getters.hidjobsSucceeded.length + ' successful jobs)'"/>
 					<q-item-side v-if="$store.getters.hidjobsSucceeded.length > 0" right>
-						<q-btn icon="delete" color="red" @click="$store.dispatch('removeSucceededHidJobs')" round inverted flat />
+						<q-btn icon="delete" color="red" @click="$store.dispatch('removeSucceededHidJobs')" round inverted flat>
+							<q-tooltip>
+								delete succeeded HID jobs from list
+							</q-tooltip>
+						</q-btn>
 					</q-item-side>
 				</template>
 				<hid-job-overview-item v-for="job in $store.getters.hidjobsSucceeded" :job="job" :key="job.id"></hid-job-overview-item>
@@ -160,7 +179,12 @@ compHIDJobOverviewTemplate = `
 				<template slot="header">
 					<q-item-main label="Failed" :sublabel="'(' + $store.getters.hidjobsFailed.length + ' failed jobs)'"/>
 					<q-item-side v-if="$store.getters.hidjobsFailed.length > 0" right>
-						<q-btn icon="delete" color="red" @click="$store.dispatch('removeFailedHidJobs')" round inverted flat />
+						<q-btn icon="delete" color="red" @click="$store.dispatch('removeFailedHidJobs')" round inverted flat>
+							<q-tooltip>
+								delete failed HID jobs from list
+							</q-tooltip>
+						</q-btn>
+
 					</q-item-side>
 				</template>
 				<hid-job-overview-item v-for="job in $store.getters.hidjobsFailed" :job="job" :key="job.id"></hid-job-overview-item>
diff --git a/web_client/hvueCompHIDScript.go b/web_client/hvueCompHIDScript.go
index 84fb07c..31f0683 100644
--- a/web_client/hvueCompHIDScript.go
+++ b/web_client/hvueCompHIDScript.go
@@ -5,7 +5,6 @@ package main
 import (
 	"github.com/gopherjs/gopherjs/js"
 	"github.com/mame82/hvue"
-	"strconv"
 )
 
 type CompHIDScriptCodeEditorData struct {
@@ -13,28 +12,6 @@ type CompHIDScriptCodeEditorData struct {
 	CodeMirrorOptions *CodeMirrorOptionsType `js:"codemirrorOptions"`
 }
 
-// ToDo: Change into action of vuex store
-func SendAndRun(vm *hvue.VM) {
-	sourceCode := vm.Get("$store").Get("state").Get("currentHIDScriptSource").String()
-	md5 := StringToMD5(sourceCode) //Calculate MD5 hexstring of current script content
-
-	go func() {
-		timeout := uint32(0)
-		err := UploadHIDScript(md5, sourceCode)
-		if err != nil {
-			QuasarNotifyError("Error uploading script", err.Error(), QUASAR_NOTIFICATION_POSITION_TOP)
-			return
-		}
-		job,err := RunHIDScript(md5, timeout)
-		if err != nil {
-			QuasarNotifyError("Error starting script as background job", err.Error(), QUASAR_NOTIFICATION_POSITION_TOP)
-			return
-		}
-
-		QuasarNotifySuccess("Script started successfully", "Job ID " + strconv.Itoa(int(job.Id)), QUASAR_NOTIFICATION_POSITION_TOP)
-	}()
-}
-
 type CodeMirrorMode struct {
 	*js.Object
 	Name string `js:"name"`
@@ -80,7 +57,6 @@ func InitComponentsHIDScript() {
 		"hid-script-code-editor",
 		hvue.Template(compHIDScriptCodeEditorTemplate),
 		hvue.DataFunc(newCompHIDScriptCodeEditorData),
-		hvue.Method("SendAndRun",	SendAndRun),
 		hvue.ComputedWithGetSet(
 			"scriptContent",
 			func(vm *hvue.VM) interface{} {
@@ -133,7 +109,10 @@ func InitComponentsHIDScript() {
 				vm.Get("$q").Call("notify", "store " + name.String())
 				vm.Store.Call("dispatch", VUEX_ACTION_STORE_CURRENT_HID_SCRIPT_SOURCE_TO_REMOTE_FILE, name)
 			}),
-		hvue.Method("SendAndRun",	SendAndRun),
+		hvue.Method("SendAndRun",	func (vm *hvue.VM) {
+			sourceCode := vm.Get("$store").Get("state").Get("currentHIDScriptSource").String()
+			vm.Store.Call("dispatch", VUEX_ACTION_AND_AND_RUN_HID_SCRIPT, sourceCode)
+		}),
 	)
 }
 
diff --git a/web_client/hvueCompLogger.go b/web_client/hvueCompLogger.go
index 4ff0368..b3d6b62 100644
--- a/web_client/hvueCompLogger.go
+++ b/web_client/hvueCompLogger.go
@@ -3,6 +3,7 @@
 package main
 
 import (
+	"github.com/gopherjs/gopherjs/js"
 	"github.com/mame82/hvue"
 )
 
@@ -30,22 +31,25 @@ func InitCompLogger()  {
 	hvue.NewComponent(
 		"logger",
 		hvue.Template(compLoggerTemplate),
-//		hvue.DataFunc(NewLoggerData),
-//		hvue.MethodsOf(&CompLoggerData{}),
+		hvue.DataFunc(func(vm *hvue.VM) interface{} {
+			data := &struct {
+				*js.Object
+				Pagination *jsDataTablePagination `js:"pagination"`
+			}{Object:O()}
+
+			data.Pagination = newPagination(0, 1)
+
+			return data
+		}),
 		hvue.Method("logLevelClass", LogLevelClass),
 		hvue.PropObj("max-entries", hvue.Types(hvue.PNumber), hvue.Default(5)),
-		hvue.Created(func(vm *hvue.VM) {
-			println("OnCreated")
-//			vm.Call("StartListening")
-		}),
-		hvue.Destroyed(func(vm *hvue.VM) {
-			println("OnDestroyed")
-//			vm.Call("StopListening")
-		}),
 
 		hvue.Computed("classFromLevel", func(vm *hvue.VM) interface{} {
 			return "info"
 		}),
+		hvue.Method("formatDate", func(vm *hvue.VM, timestamp *js.Object) interface{} {
+			return js.Global.Get("Quasar").Get("utils").Get("date").Call("formatDate", timestamp, "YYYY-MM-DD HH:mm:ss Z")
+		}),
 		hvue.Computed("logArray",
 			func(vm *hvue.VM) interface{} {
 				return vm.Get("$store").Get("state").Get("EventProcessor").Get("logArray")
@@ -57,7 +61,24 @@ func InitCompLogger()  {
 const (
 
 	compLoggerTemplate = `
-<q-page>
+<q-page padding>
+	<q-card>
+		<div>
+			<q-table
+				:data="logArray"
+				:columns="[{name:'logTime', field: 'time', label: 'Time', align: 'left'}, {name:'logSource', field: 'source', label: 'Source', align: 'left'}, {name:'logLevel', field: 'level', label: 'Level', align: 'left'}, {name:'logMessage', field: 'message', label: 'Message', align: 'left'}]"
+				row-key="name"
+				:pagination="pagination"
+				hide-bottom
+			>
+  <q-td slot="body-cell-logTime" slot-scope="props" :props="props">
+    {{ formatDate(props.value) }}
+  </q-td>
+			</q-table>
+		</div>
+	</q-card>
+
+<!--
 	<div class="logger">
 	<table class="log-entries">
 		<tr>
@@ -74,6 +95,7 @@ const (
 	    </tr>
 	</table>
 	</div>
+-->
 </q-page>
 `
 )
diff --git a/web_client/hvueCompUSBSettings.go b/web_client/hvueCompUSBSettings.go
index faa0542..13cba23 100644
--- a/web_client/hvueCompUSBSettings.go
+++ b/web_client/hvueCompUSBSettings.go
@@ -115,7 +115,7 @@ const (
 		<div class="col-12">
 			<q-card>
 				<q-card-title>
-					USB Gadget Settings
+					USB Gadget Settings ({{ deploying }})
 				</q-card-title>
 
 				<q-card-main>
diff --git a/web_client/hvueComponentsBluetooth.go b/web_client/hvueComponentsBluetooth.go
index c07e9a9..e9fc8c4 100644
--- a/web_client/hvueComponentsBluetooth.go
+++ b/web_client/hvueComponentsBluetooth.go
@@ -359,19 +359,23 @@ const templateBluetoothPage = `
 			</q-card>
 		</div>
 
-
+<!--
 		<div class="col-12">
 			{{ CurrentControllerInfo }}
 		</div>
-
+-->
 		<div class="col-12 col-lg">
 			<bluetooth-controller :controllerInfo="CurrentControllerInfo"></bluetooth-controller>
 		</div>
 		<div class="col-12 col-lg">
-			<bluetooth-controller-network-services :controllerInfo="CurrentControllerInfo"></bluetooth-controller-network-services>
-		</div>
-		<div class="col-12 col-lg">
-			<bluetooth-agent :bluetoothAgent="CurrentBluetoothAgentSettings"></bluetooth-agent>
+<div class="row gutter-y-sm">
+			<div class="col-12">
+				<bluetooth-controller-network-services :controllerInfo="CurrentControllerInfo"></bluetooth-controller-network-services>
+			</div>
+			<div class="col-12">
+				<bluetooth-agent :bluetoothAgent="CurrentBluetoothAgentSettings"></bluetooth-agent>
+			</div>
+</div>
 		</div>
 	</div>
 </q-page>
diff --git a/web_client/jsDataHandling.go b/web_client/jsDataHandling.go
index bd77e79..74343be 100644
--- a/web_client/jsDataHandling.go
+++ b/web_client/jsDataHandling.go
@@ -234,7 +234,7 @@ type jsLogEvent struct {
 	EvLogSource  string `js:"source"`
 	EvLogLevel   int    `js:"level"`
 	EvLogMessage string `js:"message"`
-	EvLogTime    string `js:"time"`
+	EvLogTime    int64 `js:"time"`
 }
 
 //HID event
@@ -247,7 +247,7 @@ type jsHidEvent struct {
 	Result    string `js:"result"`
 	Error     string `js:"error"`
 	Message   string `js:"message"`
-	EvLogTime string `js:"time"`
+	EvLogTime int64 `js:"time"`
 }
 
 func (jsEv *jsEvent) toLogEvent() (res *jsLogEvent, err error) {
@@ -273,10 +273,11 @@ func (jsEv *jsEvent) toLogEvent() (res *jsLogEvent, err error) {
 		return nil, eNoLogEvent
 	}
 
-	res.EvLogTime, ok = jsEv.Values[3].(string)
+	res.EvLogTime, ok = jsEv.Values[3].(int64)
 	if !ok {
 		return nil, eNoLogEvent
 	}
+	println("EvLogTime", res.EvLogTime)
 
 	return res, nil
 }
@@ -323,7 +324,7 @@ func (jsEv *jsEvent) toHidEvent() (res *jsHidEvent, err error) {
 		return nil, eNoHidEvent
 	}
 
-	res.EvLogTime, ok = jsEv.Values[7].(string)
+	res.EvLogTime, ok = jsEv.Values[7].(int64)
 	if !ok {
 		return nil, eNoHidEvent
 	}
@@ -341,7 +342,7 @@ type jsHidJobState struct {
 	LastMessage  string `js:"lastMessage"`
 	TextResult   string `js:"textResult"`
 	//	TextError      string `js:"textError"`
-	LastUpdateTime string `js:"lastUpdateTime"` //JSON timestamp from server
+	LastUpdateTime int64 `js:"lastUpdateTime"` //JSON timestamp from server
 	ScriptSource   string `js:"textSource"`
 }
 
@@ -392,7 +393,7 @@ func NewHIDJobStateList() *jsHidJobStateList {
 // 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, textResult, lastUpdateTime, scriptSource string) {
+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
@@ -998,29 +999,10 @@ func (data *jsEventProcessor) handleHidEvent(hEv *jsHidEvent) {
 		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)
-	/*
-		notification := &QuasarNotification{Object: O()}
-		notification.Message = "HIDScript job " + strconv.Itoa(int(hEv.JobId)) + " failed"
-		notification.Detail = hEv.Error
-		notification.Position = QUASAR_NOTIFICATION_POSITION_TOP
-		notification.Type = QUASAR_NOTIFICATION_TYPE_NEGATIVE
-		notification.Timeout = 5000
-		QuasarNotify(notification)
-	*/
 	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)
-
-	/*
-		notification := &QuasarNotification{Object: O()}
-		notification.Message = "HIDScript job " + strconv.Itoa(int(hEv.JobId)) + " succeeded"
-		notification.Detail = hEv.Result
-		notification.Position = QUASAR_NOTIFICATION_POSITION_TOP
-		notification.Type = QUASAR_NOTIFICATION_TYPE_POSITIVE
-		notification.Timeout = 5000
-		QuasarNotify(notification)
-	*/
 	case common_web.HidEventType_JOB_CANCELLED:
 		data.JobList.UpdateEntry(hEv.JobId, hEv.VMId, true, false, hEv.Message, hEv.Message, hEv.EvLogTime, "")
 	default:
diff --git a/web_client/main.go b/web_client/main.go
index c51a040..508a104 100644
--- a/web_client/main.go
+++ b/web_client/main.go
@@ -47,19 +47,18 @@ func Router(router *js.Object) hvue.ComponentOption {
 func main() {
 	println(GetBaseURL())
 
-println("====================---------")
 	store := InitGlobalState() //sets Vuex store in JS window.store
-	store.Dispatch(VUEX_ACTION_START_EVENT_LISTEN)
 
 //	RpcClient.StartListening() //Start event listening after global state is initiated (contains the event handlers)
 
 	// ToDo: delete because debug
 //	RpcClient.GetAllDeployedEthernetInterfaceSettings(time.Second*10)
 
-	router := NewVueRouter("/usb",
-		VueRouterRoute("/usb","", "<usb-settings></usb-settings>"),
+	router := NewVueRouter(
+		"/usb",
 		// route below could be used for an easter egg
 		//VueRouterRoute("/","", "<usb-settings></usb-settings>"),
+		VueRouterRoute("/usb","", "<usb-settings></usb-settings>"),
 		VueRouterRoute("/hid","", "<hid-script></hid-script>"),
 		VueRouterRoute("/hidjobs","", "<hid-job-event-overview></hid-job-event-overview>"),
 		VueRouterRoute("/logger","", "<logger :max-entries='7'></logger>"),
@@ -88,25 +87,7 @@ println("====================---------")
 	vm := hvue.NewVM(
 		hvue.El("#app"),
 		hvue.Template(templateMainApp),
-/*
-		//add "testString" to data
-		hvue.DataFunc(func(vm *hvue.VM) interface{} {
-			data := struct{
-				*js.Object
-				TestString string `js:"testString"`
-				SelectedTab string `js:"selectedTab"`
-			}{Object: O()}
-			data.SelectedTab = "USB"
-			data.TestString = "type('hello');"
-			return &data
-		}),
-*/
 		//add console to app as computed property, to allow debug output on vue events
-		hvue.Computed(
-			"console",
-			func(vm *hvue.VM) interface{} {
-			return js.Global.Get("console")
-		}),
 		hvue.Computed("state", func(vm *hvue.VM) interface{} {
 			return vm.Get("$store").Get("state") //works only with Vuex store option added
 		}),
@@ -118,7 +99,6 @@ println("====================---------")
 	)
 	// ToDo: remove next lines, debug code
 	js.Global.Set("vm",vm)
-	js.Global.Set("rpc", RpcClient)
 }
 
 const templateMainApp = `
@@ -132,13 +112,13 @@ const templateMainApp = `
             </q-toolbar>
             <q-tabs>
                 <q-route-tab default slot="title" to="usb" name="tab-usb" icon="usb" label="USB Settings"></q-route-tab>
-                <q-route-tab slot="title" to="hid" name="tab-hid-script" icon="code" label="HIDScript"></q-route-tab>
-                <q-route-tab slot="title" to="hidjobs" name="tab-hid-jobs" icon="schedule" label="HID Events"></q-route-tab>
-                <q-route-tab slot="title" to="logger" name="tab-logger" icon="message" label="Event Log"></q-route-tab>
-                <q-route-tab slot="title" to="network" name="tab-network" icon="settings_ethernet" label="Network settings"></q-route-tab>
                 <q-route-tab slot="title" to="wifi" name="tab-wifi" icon="wifi" label="WiFi settings"></q-route-tab>
-                <q-route-tab slot="title" to="triggeractions" name="tab-triggeraction" icon="whatshot" label="Trigger Actions"></q-route-tab>
                 <q-route-tab slot="title" to="bluetooth" name="tab-bluetooth" icon="bluetooth" label="Bluetooth"></q-route-tab>
+                <q-route-tab slot="title" to="network" name="tab-network" icon="settings_ethernet" label="Network settings"></q-route-tab>
+                <q-route-tab slot="title" to="triggeractions" name="tab-triggeraction" icon="whatshot" label="Trigger Actions"></q-route-tab>
+                <q-route-tab slot="title" to="hid" name="tab-hid-script" icon="keyboard" label="HIDScript"></q-route-tab>
+ <!--               <q-route-tab slot="title" to="hidjobs" name="tab-hid-jobs" icon="schedule" label="HID Events"></q-route-tab> -->
+                <q-route-tab slot="title" to="logger" name="tab-logger" icon="message" label="Event Log"></q-route-tab>
             </q-tabs>
         </q-layout-header>
 
@@ -155,14 +135,6 @@ const templateMainApp = `
             <router-view></router-view>
 
 			<disconnect-modal :value="!$store.getters.isConnected"></disconnect-modal>
-<!--
-            <q-modal v-model="!$store.getters.isConnected" minimized no-route-dismiss no-esc-dismiss no-backdrop-dismiss>
-                <div style="padding: 50px">
-                    <div class="q-display-1 q-mb-md">No connection to server</div>
-                    <p>Trying to reconnect ... (attempt {{ $store.state.failedConnectionAttempts }})</p>
-                </div>
-            </q-modal>
--->
         </q-page-container>
 
 
diff --git a/web_client/mvuexGlobalState.go b/web_client/mvuexGlobalState.go
index 359a479..5d098ec 100644
--- a/web_client/mvuexGlobalState.go
+++ b/web_client/mvuexGlobalState.go
@@ -12,6 +12,7 @@ import (
 	"github.com/pkg/errors"
 	"io"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -45,12 +46,14 @@ const (
 
 	//HIDScripts and jobs
 	VUEX_ACTION_UPDATE_RUNNING_HID_JOBS                           = "updateRunningHidJobs"
-	VUEX_ACTION_REMOVE_SUCCEEDED_HID_JOBS                           = "removeSucceededHidJobs"
-	VUEX_ACTION_REMOVE_FAILED_HID_JOBS                           = "removeFailedHidJobs"
+	VUEX_ACTION_REMOVE_SUCCEEDED_HID_JOBS                         = "removeSucceededHidJobs"
+	VUEX_ACTION_REMOVE_FAILED_HID_JOBS                            = "removeFailedHidJobs"
 	VUEX_ACTION_UPDATE_STORED_HID_SCRIPTS_LIST                    = "updateStoredHIDScriptsList"
 	VUEX_ACTION_UPDATE_CURRENT_HID_SCRIPT_SOURCE_FROM_REMOTE_FILE = "updateCurrentHidScriptSourceFromRemoteFile"
 	VUEX_ACTION_STORE_CURRENT_HID_SCRIPT_SOURCE_TO_REMOTE_FILE    = "storeCurrentHidScriptSourceToRemoteFile"
-	VUEX_ACTION_CANCEL_HID_JOB = "cancelHIDJob"
+	VUEX_ACTION_CANCEL_HID_JOB                                    = "cancelHIDJob"
+	VUEX_ACTION_CANCEL_ALL_HID_JOBS                               = "cancelAllHIDJobs"
+	VUEX_ACTION_AND_AND_RUN_HID_SCRIPT                            = "sendAndRunHIDScript"
 
 	VUEX_MUTATION_SET_CURRENT_HID_SCRIPT_SOURCE_TO = "setCurrentHIDScriptSource"
 	VUEX_MUTATION_SET_STORED_HID_SCRIPTS_LIST      = "setStoredHIDScriptsList"
@@ -155,6 +158,7 @@ func createGlobalStateStruct() GlobalState {
 	state.Title = "P4wnP1 by MaMe82"
 	state.CurrentHIDScriptSource = initHIDScript
 	state.CurrentGadgetSettings = NewUSBGadgetSettings()
+	state.CurrentlyDeployingGadgetSettings = false
 	state.CurrentlyDeployingWifiSettings = false
 	state.HidJobList = NewHIDJobStateList()
 	state.TriggerActionList = NewTriggerActionSet()
@@ -204,7 +208,7 @@ func processEvent(evt *pb.Event, store *mvuex.Store, state *GlobalState) {
 		case common_web.STATE_CHANGE_EVT_TYPE_NETWORK:
 			store.Dispatch(VUEX_ACTION_UPDATE_ALL_ETHERNET_INTERFACE_SETTINGS)
 		case common_web.STATE_CHANGE_EVT_TYPE_HID:
-			store.Dispatch(VUEX_ACTION_UPDATE_RUNNING_HID_JOBS) // handled by dedicated listener
+			//store.Dispatch(VUEX_ACTION_UPDATE_RUNNING_HID_JOBS) // handled by dedicated listener
 		case common_web.STATE_CHANGE_EVT_TYPE_WIFI:
 			store.Dispatch(VUEX_ACTION_UPDATE_WIFI_STATE)
 		case common_web.STATE_CHANGE_EVT_TYPE_TRIGGER_ACTIONS:
@@ -254,9 +258,34 @@ func actionUpdateAllStates(store *mvuex.Store, context *mvuex.ActionContext, sta
 
 }
 
+func actionSendAndRunHIDScript(store *mvuex.Store, context *mvuex.ActionContext, state *GlobalState, scriptContent *js.Object)  {
+	go func() {
+		strScriptContent := scriptContent.String()
+
+		println("Send and run HIDScript job")
+		//fetch deployed gadget settings
+		filename,err := RpcClient.UploadContentToTempFile(defaultTimeout, []byte(strScriptContent))
+		if err != nil {
+			println("Couldn't upload HIDScript job", err)
+			QuasarNotifyError("Error uploading script", err.Error(), QUASAR_NOTIFICATION_POSITION_TOP)
+			return
+		}
+
+		job,err := RpcClient.RunHIDScriptJob(defaultTimeout, "/tmp/" + filename)
+		if err != nil {
+			println("Couldn't start HIDScript job", err)
+			QuasarNotifyError("Error starting script as background job", err.Error(), QUASAR_NOTIFICATION_POSITION_TOP)
+			return
+		}
+
+		QuasarNotifySuccess("Script started successfully", "Job ID " + strconv.Itoa(int(job.Id)), QUASAR_NOTIFICATION_POSITION_TOP)
+
+		// ToDo: update HIDScriptJob list (should be done event based)
+	}()
+	return
+}
+
 func actionCancelHidJob(store *mvuex.Store, context *mvuex.ActionContext, state *GlobalState, jobID *js.Object)  {
-
-
 	go func() {
 		id := uint32(jobID.Int())
 		println("Cancel HIDScript job", id)
@@ -269,8 +298,21 @@ func actionCancelHidJob(store *mvuex.Store, context *mvuex.ActionContext, state
 
 		// ToDo: update HIDScriptJob list (should be done event based)
 	}()
+	return
+}
 
+func actionCancelAllHidJobs(store *mvuex.Store, context *mvuex.ActionContext, state *GlobalState)  {
+	go func() {
+		println("Cancel all HIDScript jobs")
+		//fetch deployed gadget settings
+		err := RpcClient.CancelAllHIDScriptJobs(defaultTimeout)
+		if err != nil {
+			println("Couldn't cancel all HIDScript jobs", err)
+			return
+		}
 
+		// ToDo: update HIDScriptJob list (should be done event based)
+	}()
 	return
 }
 
@@ -932,7 +974,8 @@ func actionUpdateRunningHidJobs(store *mvuex.Store, context *mvuex.ActionContext
 
 		for _, jobstate := range jobstates {
 			println("updateing jobstate", jobstate)
-			state.HidJobList.UpdateEntry(jobstate.Id, jobstate.VmId, false, false, "initial job state", "", time.Now().String(), jobstate.Source)
+			timeNowUnixMilli := time.Now().UnixNano()/1e6
+			state.HidJobList.UpdateEntry(jobstate.Id, jobstate.VmId, false, false, "initial job state", "", timeNowUnixMilli, jobstate.Source)
 		}
 	}()
 
@@ -1115,6 +1158,7 @@ func actionDeployCurrentGadgetSettings(store *mvuex.Store, context *mvuex.Action
 		notification.Timeout = 2000
 		QuasarNotify(notification)
 
+		return
 	}()
 
 	return
@@ -1267,9 +1311,12 @@ func initMVuex() *mvuex.Store {
 		mvuex.Action(VUEX_ACTION_START_EVENT_LISTEN, actionStartEventListen),
 		mvuex.Action(VUEX_ACTION_STOP_EVENT_LISTEN, actionStopEventListen),
 
+
 		mvuex.Action(VUEX_ACTION_REMOVE_SUCCEEDED_HID_JOBS, actionRemoveSucceededHidJobs),
 		mvuex.Action(VUEX_ACTION_REMOVE_FAILED_HID_JOBS, actionRemoveFailedHidJobs),
 		mvuex.Action(VUEX_ACTION_CANCEL_HID_JOB, actionCancelHidJob),
+		mvuex.Action(VUEX_ACTION_CANCEL_ALL_HID_JOBS, actionCancelAllHidJobs),
+		mvuex.Action(VUEX_ACTION_AND_AND_RUN_HID_SCRIPT, actionSendAndRunHIDScript),
 
 
 		mvuex.Getter("triggerActions", func(state *GlobalState) interface{} {
@@ -1327,6 +1374,9 @@ func initMVuex() *mvuex.Store {
 		}),
 	)
 
+	store.Dispatch(VUEX_ACTION_START_EVENT_LISTEN)
+
+/*
 	// fetch deployed gadget settings
 	store.Dispatch(VUEX_ACTION_UPDATE_CURRENT_USB_SETTINGS)
 
@@ -1335,14 +1385,14 @@ func initMVuex() *mvuex.Store {
 
 	// Update WiFi state
 	store.Dispatch(VUEX_ACTION_UPDATE_WIFI_STATE)
-
-	// propagate Vuex store to global scope to allow injecting it to Vue by setting the "store" option
-	js.Global.Set("store", store)
-
+*/
 
 	return store
 }
 
 func InitGlobalState() *mvuex.Store {
-	return initMVuex()
+	store := initMVuex()
+	// propagate Vuex store to global scope to allow injecting it to Vue by setting the "store" option
+	js.Global.Set("store", store)
+	return store
 }
diff --git a/web_client/rpcClient.go b/web_client/rpcClient.go
index 8ee5e22..8515254 100644
--- a/web_client/rpcClient.go
+++ b/web_client/rpcClient.go
@@ -29,6 +29,40 @@ func NewRpcClient(addr string) Rpc {
 	return rcl
 }
 
+func (rpc *Rpc) UploadContentToTempFile(timeout time.Duration, content []byte) (filename string, err error) {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	//create hex string of content MD5 sum
+	filename = BytesToMD5(content)
+
+	//upload file to `/tmp/{md5_hash_hex}`
+	_,err = rpc.Client.FSWriteFile(ctx,
+		&pb.WriteFileRequest{
+			Data:content,
+			Append:false,
+			Filename:filename,
+			Folder: pb.AccessibleFolder_TMP,
+			MustNotExist:false,
+		})
+
+	return
+}
+
+func (rpc *Rpc) RunHIDScriptJob(timeout time.Duration, filepath string) (job *pb.HIDScriptJob, err error) {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	//upload file to `/tmp/{md5_hash_hex}`
+	return rpc.Client.HIDRunScriptJob(
+		ctx,
+		&pb.HIDScriptRequest{
+			ScriptPath:     filepath,
+			TimeoutSeconds: uint32(0),
+		},
+	)
+}
+
 func (rpc *Rpc) CancelHIDScriptJob(timeout time.Duration, jobID uint32) (err error) {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()