diff --git a/devices/hubdevice/hubdevice.go b/devices/hubdevice/hubdevice.go index 5769850..f16ed14 100644 --- a/devices/hubdevice/hubdevice.go +++ b/devices/hubdevice/hubdevice.go @@ -26,10 +26,6 @@ func (h *hubDevice) GetConfig() device.Config { Targets: []string{"x86-64", "rpi", "koyeb"}, BgColor: "sunflower", FgColor: "black", - PacketHandlers: device.PacketHandlers{ - "/created": &device.PacketHandler[device.MsgCreated]{device.BroadcastUp}, - "/destroyed": &device.PacketHandler[device.MsgDestroyed]{device.BroadcastUp}, - }, } } diff --git a/pkg/device/api.go b/pkg/device/api.go index 57e682f..53cbd0a 100644 --- a/pkg/device/api.go +++ b/pkg/device/api.go @@ -467,6 +467,23 @@ func (d *device) showInstructionsTarget(w http.ResponseWriter, r *http.Request) } } +func (d *device) showModel(w http.ResponseWriter, r *http.Request) { + view := r.URL.Query().Get("view") + template := "model-" + view + ".tmpl" + if err := d.renderTmpl(w, template, d.Config); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } +} + +func (d *device) showNewModal(w http.ResponseWriter, r *http.Request) { + err := d.renderTmpl(w, "modal-new.tmpl", map[string]any{ + "models": d.childModels(), + "newid": generateRandomId(), + }) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } +} func (d *device) editName(w http.ResponseWriter, r *http.Request) { if err := d.renderTmpl(w, "edit-name.tmpl", d.Name); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -510,20 +527,27 @@ func (d *device) apiRouteDown(w http.ResponseWriter, r *http.Request) { pkt.SetDst(d.Id).RouteDown() } -func (d *device) showModel(w http.ResponseWriter, r *http.Request) { - view := r.URL.Query().Get("view") - template := "model-" + view + ".tmpl" - if err := d.renderTmpl(w, template, d.Config); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } -} - type MsgCreated struct { Id string Model string Name string } +func (d *device) handleCreated(pkt *Packet) { + var msg MsgCreated + + pkt.Unmarshal(&msg) + + if err := addChild(d, msg.Id, msg.Model, msg.Name, flagLocked); err != nil { + LogError("Adding child failed", "device", d, "msg", msg) + return + } + + routesBuild(root) + + pkt.BroadcastUp() +} + func (d *device) createChild(w http.ResponseWriter, r *http.Request) { var msg MsgCreated @@ -541,7 +565,7 @@ func (d *device) createChild(w http.ResponseWriter, r *http.Request) { // TODO validate msg.Id, msg.Model, msg.Name - if err := addChild(d, msg.Id, msg.Model, msg.Name); err != nil { + if err := addChild(d, msg.Id, msg.Model, msg.Name, 0); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -552,14 +576,29 @@ func (d *device) createChild(w http.ResponseWriter, r *http.Request) { // Mark root dirty deviceDirty(root.Id) - // Route /created msg down - pkt.SetDst(d.Id).SetPath("/created").RouteDown() + // Route /created msg up + pkt.SetDst(d.Id).SetPath("/created").RouteUp() } type MsgDestroyed struct { ChildId string } +func (d *device) handleDestroyed(pkt *Packet) { + var msg MsgDestroyed + + pkt.Unmarshal(&msg) + + if err := removeChild(msg.ChildId); err != nil { + LogError("Removing child failed", "device", d, "msg", msg) + return + } + + routesBuild(root) + + pkt.BroadcastUp() +} + func (d *device) destroyChild(w http.ResponseWriter, r *http.Request) { var msg MsgDestroyed @@ -582,16 +621,6 @@ func (d *device) destroyChild(w http.ResponseWriter, r *http.Request) { // Mark root dirty deviceDirty(root.Id) - // Route /destroyed msg down - pkt.SetDst(parentId).SetPath("/destroyed").RouteDown() -} - -func (d *device) showNewModal(w http.ResponseWriter, r *http.Request) { - err := d.renderTmpl(w, "modal-new.tmpl", map[string]any{ - "models": d.childModels(), - "newid": generateRandomId(), - }) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } + // Route /destroyed msg up + pkt.SetDst(parentId).SetPath("/destroyed").RouteUp() } diff --git a/pkg/device/device-linux.go b/pkg/device/device-linux.go index db9074a..be3b3a8 100644 --- a/pkg/device/device-linux.go +++ b/pkg/device/device-linux.go @@ -65,6 +65,11 @@ func (d *device) _buildOS() error { // Build the device templates using combined funcs d.templates, err = d.layeredFS.parseFS("template/*.tmpl", d.FuncMap) + // Default handlers for Linux devices + d.PacketHandlers["/created"] = &PacketHandler[MsgCreated]{d.handleCreated} + d.PacketHandlers["/destroyed"] = &PacketHandler[MsgDestroyed]{d.handleDestroyed} + d.PacketHandlers["/downloaded"] = &PacketHandler[MsgDownloaded]{d.handleDownloaded} + return err } @@ -141,7 +146,7 @@ func devicesSetupAPI() { } } -func addChild(parent *device, id, model, name string) error { +func addChild(parent *device, id, model, name string, flags flags) error { var resurrect bool @@ -185,6 +190,8 @@ func addChild(parent *device, id, model, name string) error { return err } + child._set(flags) + parent.Children = append(parent.Children, id) devices[id] = child diff --git a/pkg/device/device.go b/pkg/device/device.go index 10f7375..b9821f2 100644 --- a/pkg/device/device.go +++ b/pkg/device/device.go @@ -66,7 +66,7 @@ func (d *device) _build(maker Maker) error { d._set(flagDemo | flagOnline | flagMetal) } - // Special handlers + // Default handlers for all devices d.PacketHandlers["/state"] = &PacketHandler[any]{d.handleState} d.PacketHandlers["/online"] = &PacketHandler[any]{d.handleOnline} d.PacketHandlers["/offline"] = &PacketHandler[any]{d.handleOffline} diff --git a/pkg/device/devices.go b/pkg/device/devices.go index 0103da1..adb0929 100644 --- a/pkg/device/devices.go +++ b/pkg/device/devices.go @@ -19,3 +19,15 @@ func getDevice(id string) (*device, error) { } return nil, deviceNotFound(id) } + +func aliveDevices() (alive deviceMap) { + devicesMu.RLock() + defer devicesMu.RUnlock() + alive = make(deviceMap) + for id, d := range devices { + if !d.isSet(flagGhost) { + alive[id] = d + } + } + return +} diff --git a/pkg/device/funcs.go b/pkg/device/funcs.go index 40a5374..dc2619a 100644 --- a/pkg/device/funcs.go +++ b/pkg/device/funcs.go @@ -30,18 +30,6 @@ func (d *device) stateJSON() (string, error) { return string(bytes), err } -func aliveDevices() (alive deviceMap) { - devicesMu.RLock() - defer devicesMu.RUnlock() - alive = make(deviceMap) - for id, d := range devices { - if !d.isSet(flagGhost) { - alive[id] = d - } - } - return -} - func devicesJSON() (string, error) { bytes, err := json.MarshalIndent(aliveDevices(), "", "\t") return string(bytes), err diff --git a/pkg/device/handle.go b/pkg/device/handle.go index 6550937..8d69757 100644 --- a/pkg/device/handle.go +++ b/pkg/device/handle.go @@ -28,7 +28,7 @@ func (h *PacketHandler[T]) cb(pkt *Packet) { type PacketHandlers map[string]packetHandler func (d *device) handle(pkt *Packet) { - if d.isSet(flagOnline) || pkt.Path == "/online" { + if d.isSet(flagOnline) || pkt.Path == "/online" || pkt.Path == "/downloaded" { if handler, ok := d.PacketHandlers[pkt.Path]; ok { LogDebug("Handling", "pkt", pkt) d.stateMu.Lock() diff --git a/pkg/device/image.go b/pkg/device/image.go index 7590ce4..d47d91c 100644 --- a/pkg/device/image.go +++ b/pkg/device/image.go @@ -6,6 +6,7 @@ import ( "bytes" "encoding/base64" "fmt" + "html/template" "io" "net/http" "net/url" @@ -313,6 +314,17 @@ func (d *device) downloadMsgError(sessionId string, downloadErr error) { sessionSend(sessionId, string(buf.Bytes())) } +type MsgDownloaded struct { + DeployParams template.HTML +} + +func (d *device) handleDownloaded(pkt *Packet) { + var msg MsgDownloaded + pkt.Unmarshal(&msg) + d.formConfig(string(msg.DeployParams)) + pkt.BroadcastUp() +} + func (d *device) downloadImage(w http.ResponseWriter, r *http.Request) { var sessionId = r.PathValue("sessionId") @@ -356,4 +368,10 @@ func (d *device) downloadImage(w http.ResponseWriter, r *http.Request) { deviceDirty(root.Id) downlinkClose(d.Id) } + + // Send a /downloaded msg up so uplinks can update their DeployParams + + msg := MsgDownloaded{d.DeployParams} + pkt := Packet{Dst: d.Id, Path: "/downloaded"} + pkt.Marshal(&msg).RouteUp() } diff --git a/pkg/device/pkt.go b/pkg/device/pkt.go index 5ca55d8..67b9399 100644 --- a/pkg/device/pkt.go +++ b/pkg/device/pkt.go @@ -6,6 +6,8 @@ import ( "net/http" ) +const maxLength = 100 // Max length of packet string + // NoMsg is an empty message type for PacketHandle's type NoMsg struct{} @@ -50,7 +52,6 @@ func (p *Packet) String() string { // Convert msg to string and truncate if needed msgStr := fmt.Sprintf("%v", msg) - const maxLength = 100 if len(msgStr) > maxLength { msgStr = msgStr[:maxLength] + "..." } @@ -152,8 +153,8 @@ func (p *Packet) RouteDown() { // the websocket, and JSON-decoded by the receiving uplink device. func (p *Packet) RouteUp() { LogDebug("RouteUp", "pkt", p) - sessionsRoute(p) uplinksRoute(p) + sessionsRoute(p) } // RouteUp is a device packet handler that routes the packet up diff --git a/pkg/device/template/downloaded-detail.tmpl b/pkg/device/template/downloaded-detail.tmpl new file mode 100644 index 0000000..f5728a1 --- /dev/null +++ b/pkg/device/template/downloaded-detail.tmpl @@ -0,0 +1 @@ +{{ template "device-detail.tmpl" . }} diff --git a/pkg/device/template/downloaded-info.tmpl b/pkg/device/template/downloaded-info.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/pkg/device/template/downloaded-overview.tmpl b/pkg/device/template/downloaded-overview.tmpl new file mode 100644 index 0000000..8e4c134 --- /dev/null +++ b/pkg/device/template/downloaded-overview.tmpl @@ -0,0 +1 @@ +{{ template "device-overview.tmpl" . }} diff --git a/pkg/device/template/downloaded-settings.tmpl b/pkg/device/template/downloaded-settings.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/pkg/device/template/downloaded-state.tmpl b/pkg/device/template/downloaded-state.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/pkg/device/ws-client.go b/pkg/device/ws-client.go index 7a56470..96d9b6f 100644 --- a/pkg/device/ws-client.go +++ b/pkg/device/ws-client.go @@ -67,7 +67,7 @@ func wsClient(conn *websocket.Conn) { } devicesMu.RLock() - pkt.Marshal(&devices) + pkt.Marshal(aliveDevices()) devicesMu.RUnlock() // Send announcement @@ -107,7 +107,7 @@ func wsClient(conn *websocket.Conn) { LogError("Receiving packet", "err", err) break } - LogDebug("Route packet DOWN", "pkt", pkt) + LogDebug("-> Route packet DOWN", "pkt", pkt) downlinksRoute(pkt) } diff --git a/pkg/device/ws-server.go b/pkg/device/ws-server.go index a6bf98f..775fcc4 100644 --- a/pkg/device/ws-server.go +++ b/pkg/device/ws-server.go @@ -32,7 +32,7 @@ func wsServer(conn *websocket.Conn) { LogError("Expected announcement, got", "path", pkt.Path) return } - LogDebug("Announcement", "pkt", pkt) + LogDebug("-> Announcement", "pkt", pkt) var annDevices = make(deviceMap) pkt.Unmarshal(&annDevices) @@ -77,7 +77,7 @@ func wsServer(conn *websocket.Conn) { // Announcement is good, reply with /welcome packet pkt.ClearMsg().SetPath("/welcome") - LogDebug("Sending welcome", "pkt", pkt) + LogDebug("<- Sending welcome", "pkt", pkt) link.Send(pkt) // Add as active download link @@ -94,7 +94,7 @@ func wsServer(conn *websocket.Conn) { LogError("Receiving packet", "err", err) break } - LogDebug("Route packet UP", "pkt", pkt) + LogDebug("-> Route packet UP", "pkt", pkt) deviceRouteUp(pkt.Dst, pkt) }