Compare commits
3 commits
3e29e04ac3
...
44001bb7e7
Author | SHA1 | Date | |
---|---|---|---|
44001bb7e7 | |||
7465ef3380 | |||
85c0073651 |
31 changed files with 1670 additions and 59 deletions
3
Makefile
3
Makefile
|
@ -17,6 +17,9 @@ build-server:
|
||||||
|
|
||||||
build: build-ui build-server
|
build: build-ui build-server
|
||||||
|
|
||||||
|
generate-apiclient:
|
||||||
|
openapi-generator generate -i openapi.yml -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f build/*
|
rm -f build/*
|
||||||
rm -rf ${UI_DIR}/dist/**
|
rm -rf ${UI_DIR}/dist/**
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -19,6 +19,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8=
|
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8=
|
||||||
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
|
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
43
openapi.yml
43
openapi.yml
|
@ -118,6 +118,10 @@ components:
|
||||||
SystemInfo:
|
SystemInfo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
hostname:
|
||||||
|
description: Hostname of the system
|
||||||
|
type: string
|
||||||
|
example: "raspberrypi3"
|
||||||
os_name:
|
os_name:
|
||||||
description: Name of the os distribution
|
description: Name of the os distribution
|
||||||
type: string
|
type: string
|
||||||
|
@ -138,21 +142,21 @@ components:
|
||||||
description: Compatible firmware variant
|
description: Compatible firmware variant
|
||||||
type: string
|
type: string
|
||||||
example: "rpi-prod"
|
example: "rpi-prod"
|
||||||
rauc_booted:
|
|
||||||
description: Currently booted rootfs
|
|
||||||
type: string
|
|
||||||
example: "rootfs.0"
|
|
||||||
rauc_boot_primary:
|
|
||||||
description: Primary rootfs to boot from
|
|
||||||
type: string
|
|
||||||
example: "rootfs.1"
|
|
||||||
rauc_rootfs:
|
rauc_rootfs:
|
||||||
description: List of RAUC root filesystems
|
description: List of RAUC root filesystems
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
$ref: "#/components/schemas/RaucFS"
|
$ref: "#/components/schemas/Rootfs"
|
||||||
|
required:
|
||||||
|
- hostname
|
||||||
|
- os_name
|
||||||
|
- os_version
|
||||||
|
- uptime
|
||||||
|
- rauc_compatible
|
||||||
|
- rauc_variant
|
||||||
|
- rauc_rootfs
|
||||||
|
|
||||||
RaucFS:
|
Rootfs:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
device:
|
device:
|
||||||
|
@ -163,11 +167,6 @@ components:
|
||||||
description: Filesystem
|
description: Filesystem
|
||||||
type: string
|
type: string
|
||||||
example: ext4
|
example: ext4
|
||||||
state:
|
|
||||||
description: Current state of filesystem
|
|
||||||
type: string
|
|
||||||
enum: [active, inactive, booted]
|
|
||||||
example: booted
|
|
||||||
mountpoint:
|
mountpoint:
|
||||||
description: Mount path (null when not mounted)
|
description: Mount path (null when not mounted)
|
||||||
type: string
|
type: string
|
||||||
|
@ -176,6 +175,19 @@ components:
|
||||||
bootable:
|
bootable:
|
||||||
description: "Is the filesystem bootable"
|
description: "Is the filesystem bootable"
|
||||||
type: boolean
|
type: boolean
|
||||||
|
booted:
|
||||||
|
description: "Is the filesystem booted"
|
||||||
|
type: boolean
|
||||||
|
primary:
|
||||||
|
description: "Is the filesystem the next boot target"
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- device
|
||||||
|
- type
|
||||||
|
- mountpoint
|
||||||
|
- bootable
|
||||||
|
- booted
|
||||||
|
- primary
|
||||||
|
|
||||||
StatusMessage:
|
StatusMessage:
|
||||||
type: object
|
type: object
|
||||||
|
@ -188,4 +200,5 @@ components:
|
||||||
type: string
|
type: string
|
||||||
example: Update started
|
example: Update started
|
||||||
required:
|
required:
|
||||||
|
- success
|
||||||
- msg
|
- msg
|
||||||
|
|
|
@ -41,22 +41,34 @@ LastError: Failed to check bundle identifier: Invalid identifier. ` +
|
||||||
idle
|
idle
|
||||||
Installing ` + "/app/demo` failed"
|
Installing ` + "/app/demo` failed"
|
||||||
|
|
||||||
|
const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` +
|
||||||
|
`"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` +
|
||||||
|
`"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` +
|
||||||
|
`"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` +
|
||||||
|
`{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` +
|
||||||
|
`"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}`
|
||||||
|
|
||||||
|
func printLinesWithDelay(lines string) {
|
||||||
|
for _, line := range strings.Split(lines, "\n") {
|
||||||
|
fmt.Println(line)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
arg := ""
|
arg := ""
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
arg = os.Args[1]
|
arg = os.Args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines string
|
|
||||||
switch arg {
|
switch arg {
|
||||||
case "fail":
|
case "fail":
|
||||||
lines = outputFailure
|
printLinesWithDelay(outputFailure)
|
||||||
|
case "install":
|
||||||
|
printLinesWithDelay(outputSuccess)
|
||||||
|
case "status":
|
||||||
|
fmt.Println(statusJson)
|
||||||
default:
|
default:
|
||||||
lines = outputSuccess
|
os.Exit(1)
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range strings.Split(lines, "\n") {
|
|
||||||
fmt.Println(line)
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
src/fixtures/testfiles/os-release
Normal file
5
src/fixtures/testfiles/os-release
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
ID=tsgrain
|
||||||
|
NAME="TSGRain distro"
|
||||||
|
VERSION="0.0.1"
|
||||||
|
VERSION_ID=0.0.1
|
||||||
|
PRETTY_NAME="TSGRain distro 0.0.1"
|
|
@ -70,7 +70,7 @@ func (r *Rauc) RunRauc(updateFile string) error {
|
||||||
}
|
}
|
||||||
r.broadcast <- r.GetStatusJson()
|
r.broadcast <- r.GetStatusJson()
|
||||||
|
|
||||||
cmd := util.CommandFromString(fmt.Sprintf("%s %s", util.UpdateCmd, updateFile))
|
cmd := util.CommandFromString(fmt.Sprintf("%s install %s", util.RaucCmd, updateFile))
|
||||||
|
|
||||||
readPipe, _ := cmd.StdoutPipe()
|
readPipe, _ := cmd.StdoutPipe()
|
||||||
cmd.Stderr = cmd.Stdout
|
cmd.Stderr = cmd.Stdout
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.thetadev.de/TSGRain/SEBRAUC/src/rauc"
|
"code.thetadev.de/TSGRain/SEBRAUC/src/rauc"
|
||||||
|
"code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo"
|
||||||
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
|
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
|
||||||
"code.thetadev.de/TSGRain/SEBRAUC/ui"
|
"code.thetadev.de/TSGRain/SEBRAUC/ui"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -88,6 +89,7 @@ func (srv *SEBRAUCServer) Run() error {
|
||||||
app.Get("/api/ws", websocket.New(srv.hub.Handler))
|
app.Get("/api/ws", websocket.New(srv.hub.Handler))
|
||||||
app.Post("/api/update", srv.controllerUpdate)
|
app.Post("/api/update", srv.controllerUpdate)
|
||||||
app.Get("/api/status", srv.controllerStatus)
|
app.Get("/api/status", srv.controllerStatus)
|
||||||
|
app.Get("/api/info", srv.controllerInfo)
|
||||||
app.Post("/api/reboot", srv.controllerReboot)
|
app.Post("/api/reboot", srv.controllerReboot)
|
||||||
|
|
||||||
// Start messaging hub
|
// Start messaging hub
|
||||||
|
@ -131,6 +133,17 @@ func (srv *SEBRAUCServer) controllerStatus(c *fiber.Ctx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *SEBRAUCServer) controllerInfo(c *fiber.Ctx) error {
|
||||||
|
info, err := sysinfo.GetSysinfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Context().SetStatusCode(200)
|
||||||
|
_ = c.JSON(info)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error {
|
func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error {
|
||||||
go util.Reboot(5 * time.Second)
|
go util.Reboot(5 * time.Second)
|
||||||
|
|
||||||
|
|
180
src/sysinfo/sysinfo.go
Normal file
180
src/sysinfo/sysinfo.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemInfo struct {
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
OsName string `json:"os_name"`
|
||||||
|
OsVersion string `json:"os_version"`
|
||||||
|
Uptime int `json:"uptime"`
|
||||||
|
RaucCompatible string `json:"rauc_compatible"`
|
||||||
|
RaucVariant string `json:"rauc_variant"`
|
||||||
|
RaucRootfs map[string]Rootfs `json:"rauc_rootfs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rootfs struct {
|
||||||
|
Device string `json:"device"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
bootname string
|
||||||
|
Mountpoint *string `json:"mountpoint"`
|
||||||
|
Bootable bool `json:"bootable"`
|
||||||
|
Booted bool `json:"booted"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type raucInfo struct {
|
||||||
|
Compatible string `json:"compatible"`
|
||||||
|
Variant string `json:"variant"`
|
||||||
|
Booted string `json:"booted"`
|
||||||
|
BootPrimary string `json:"boot_primary"`
|
||||||
|
Slots []map[string]raucFS `json:"slots"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type raucFS struct {
|
||||||
|
Class string `json:"class"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Bootname string `json:"bootname"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Mountpoint *string `json:"mountpoint"`
|
||||||
|
BootStatus string `json:"boot_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type osRelease struct {
|
||||||
|
OsName string `json:"os_name"`
|
||||||
|
OsVersion string `json:"os_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rexpOsName = regexp.MustCompile(`(?m)^NAME="(.+)"`)
|
||||||
|
rexpOsVersion = regexp.MustCompile(`(?m)^VERSION="(.+)"`)
|
||||||
|
rexpUptime = regexp.MustCompile(`^\d+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) {
|
||||||
|
res := raucInfo{}
|
||||||
|
err := json.Unmarshal(raucInfoJson, &res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOsRelease(osReleaseFile string) (osRelease, error) {
|
||||||
|
osReleaseTxt, err := os.ReadFile(osReleaseFile)
|
||||||
|
if err != nil {
|
||||||
|
return osRelease{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nameMatch := rexpOsName.FindSubmatch(osReleaseTxt)
|
||||||
|
versionMatch := rexpOsVersion.FindSubmatch(osReleaseTxt)
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if nameMatch != nil {
|
||||||
|
name = string(nameMatch[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
version := ""
|
||||||
|
if versionMatch != nil {
|
||||||
|
version = string(versionMatch[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return osRelease{
|
||||||
|
OsName: name,
|
||||||
|
OsVersion: version,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapRootfs(rinf raucInfo) map[string]Rootfs {
|
||||||
|
res := make(map[string]Rootfs)
|
||||||
|
|
||||||
|
for _, slot := range rinf.Slots {
|
||||||
|
for name, fs := range slot {
|
||||||
|
if fs.Class == "rootfs" {
|
||||||
|
res[name] = Rootfs{
|
||||||
|
Device: fs.Device,
|
||||||
|
Type: fs.Type,
|
||||||
|
bootname: fs.Bootname,
|
||||||
|
Mountpoint: fs.Mountpoint,
|
||||||
|
Bootable: fs.BootStatus == "good",
|
||||||
|
Booted: fs.State == "booted",
|
||||||
|
Primary: rinf.BootPrimary == name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string {
|
||||||
|
for name, rfs := range rfslist {
|
||||||
|
if rfs.bootname == bootname {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, hostname string) SystemInfo {
|
||||||
|
rfslist := mapRootfs(rinf)
|
||||||
|
|
||||||
|
return SystemInfo{
|
||||||
|
Hostname: hostname,
|
||||||
|
OsName: osr.OsName,
|
||||||
|
OsVersion: osr.OsVersion,
|
||||||
|
Uptime: uptime,
|
||||||
|
RaucCompatible: rinf.Compatible,
|
||||||
|
RaucVariant: rinf.Variant,
|
||||||
|
RaucRootfs: rfslist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUptime() (int, error) {
|
||||||
|
uptimeRaw, err := os.ReadFile("/proc/uptime")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uptimeChars := rexpUptime.Find(uptimeRaw)
|
||||||
|
return strconv.Atoi(string(uptimeChars))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHostname() string {
|
||||||
|
hostname, err := os.ReadFile("/etc/hostname")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSysinfo() (SystemInfo, error) {
|
||||||
|
cmd := util.CommandFromString(util.RaucCmd + " status")
|
||||||
|
rinfJson, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return SystemInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rinf, err := parseRaucInfo(rinfJson)
|
||||||
|
if err != nil {
|
||||||
|
return SystemInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
osinf, err := parseOsRelease("/etc/os-release")
|
||||||
|
if err != nil {
|
||||||
|
return SystemInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uptime, err := getUptime()
|
||||||
|
if err != nil {
|
||||||
|
return SystemInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := getHostname()
|
||||||
|
|
||||||
|
return mapSysinfo(rinf, osinf, uptime, hostname), nil
|
||||||
|
}
|
122
src/sysinfo/sysinfo_test.go
Normal file
122
src/sysinfo/sysinfo_test.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` +
|
||||||
|
`"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` +
|
||||||
|
`"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` +
|
||||||
|
`"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` +
|
||||||
|
`{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` +
|
||||||
|
`"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}`
|
||||||
|
|
||||||
|
var mountRoot = "/"
|
||||||
|
|
||||||
|
var expectedRaucInfo = raucInfo{
|
||||||
|
Compatible: "TSGRain",
|
||||||
|
Variant: "dev",
|
||||||
|
Booted: "A",
|
||||||
|
BootPrimary: "rootfs.0",
|
||||||
|
Slots: []map[string]raucFS{
|
||||||
|
{
|
||||||
|
"rootfs.1": {
|
||||||
|
Class: "rootfs",
|
||||||
|
Device: "/dev/mmcblk0p3",
|
||||||
|
Type: "ext4",
|
||||||
|
Bootname: "B",
|
||||||
|
State: "inactive",
|
||||||
|
Mountpoint: nil,
|
||||||
|
BootStatus: "good",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootfs.0": {
|
||||||
|
Class: "rootfs",
|
||||||
|
Device: "/dev/mmcblk0p2",
|
||||||
|
Type: "ext4",
|
||||||
|
Bootname: "A",
|
||||||
|
State: "booted",
|
||||||
|
Mountpoint: &mountRoot,
|
||||||
|
BootStatus: "good",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedRootfsList = map[string]Rootfs{
|
||||||
|
"rootfs.0": {
|
||||||
|
Device: "/dev/mmcblk0p2",
|
||||||
|
Type: "ext4",
|
||||||
|
bootname: "A",
|
||||||
|
Mountpoint: &mountRoot,
|
||||||
|
Bootable: true,
|
||||||
|
Booted: true,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
"rootfs.1": {
|
||||||
|
Device: "/dev/mmcblk0p3",
|
||||||
|
Type: "ext4",
|
||||||
|
bootname: "B",
|
||||||
|
Mountpoint: nil,
|
||||||
|
Bootable: true,
|
||||||
|
Booted: false,
|
||||||
|
Primary: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRaucInfo(t *testing.T) {
|
||||||
|
info, err := parseRaucInfo([]byte(statusJson))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedRaucInfo, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOsRelease(t *testing.T) {
|
||||||
|
testfiles := fixtures.GetTestfilesDir()
|
||||||
|
osReleaseFile := filepath.Join(testfiles, "os-release")
|
||||||
|
|
||||||
|
osRel, err := parseOsRelease(osReleaseFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := osRelease{
|
||||||
|
OsName: "TSGRain distro",
|
||||||
|
OsVersion: "0.0.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, osRel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapRootfsList(t *testing.T) {
|
||||||
|
rootfsList := mapRootfs(expectedRaucInfo)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedRootfsList, rootfsList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFSNameFromBootname(t *testing.T) {
|
||||||
|
rootfsList := mapRootfs(expectedRaucInfo)
|
||||||
|
|
||||||
|
assert.Equal(t, "rootfs.0", getFSNameFromBootname(rootfsList, "A"))
|
||||||
|
assert.Equal(t, "rootfs.1", getFSNameFromBootname(rootfsList, "B"))
|
||||||
|
assert.Equal(t, "n/a", getFSNameFromBootname(rootfsList, "C"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSysinfo(t *testing.T) {
|
||||||
|
sysinfo, err := GetSysinfo()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Greater(t, sysinfo.Uptime, 0)
|
||||||
|
assert.Equal(t, "TSGRain", sysinfo.RaucCompatible)
|
||||||
|
assert.Equal(t, "dev", sysinfo.RaucVariant)
|
||||||
|
assert.Equal(t, expectedRootfsList, sysinfo.RaucRootfs)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ package util
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RebootCmd = "shutdown -r 0"
|
RebootCmd = "shutdown -r 0"
|
||||||
UpdateCmd = "rauc install"
|
RaucCmd = "rauc"
|
||||||
|
|
||||||
TestMode = false
|
TestMode = false
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ package util
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RebootCmd = "touch /tmp/sebrauc_reboot_test"
|
RebootCmd = "touch /tmp/sebrauc_reboot_test"
|
||||||
UpdateCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock"
|
RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock"
|
||||||
|
|
||||||
TestMode = true
|
TestMode = true
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {Component} from "preact"
|
import {Component} from "preact"
|
||||||
import {mdiTriangleOutline} from "@mdi/js"
|
import {mdiTriangleOutline} from "@mdi/js"
|
||||||
import Icon from "../Icon/Icon"
|
import Icon from "../Icon/Icon"
|
||||||
|
import colors from "../../util/colors"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
source?: string
|
source?: string
|
||||||
|
@ -20,7 +21,7 @@ export default class Alert extends Component<Props> {
|
||||||
return (
|
return (
|
||||||
<div class="alert">
|
<div class="alert">
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={mdiTriangleOutline} color="#FF0039" />
|
<Icon icon={mdiTriangleOutline} color={colors.RED} />
|
||||||
{msg}
|
{msg}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {mdiCheckCircleOutline, mdiRestore} from "@mdi/js"
|
import {mdiCheckCircleOutline, mdiRestore} from "@mdi/js"
|
||||||
import axios, {AxiosError, AxiosResponse} from "axios"
|
|
||||||
import {Component} from "preact"
|
import {Component} from "preact"
|
||||||
import {apiUrl} from "../../util/apiUrls"
|
import {sebraucApi} from "../../util/apiUrls"
|
||||||
import Icon from "../Icon/Icon"
|
import Icon from "../Icon/Icon"
|
||||||
|
|
||||||
export default class Reboot extends Component {
|
export default class Reboot extends Component {
|
||||||
|
@ -9,9 +8,9 @@ export default class Reboot extends Component {
|
||||||
const res = confirm("Reboot the system?")
|
const res = confirm("Reboot the system?")
|
||||||
if (!res) return
|
if (!res) return
|
||||||
|
|
||||||
axios
|
sebraucApi
|
||||||
.post(apiUrl + "/reboot")
|
.startReboot()
|
||||||
.then((response: AxiosResponse) => {
|
.then((response) => {
|
||||||
const msg = response.data.msg
|
const msg = response.data.msg
|
||||||
|
|
||||||
if (msg !== undefined) {
|
if (msg !== undefined) {
|
||||||
|
@ -20,7 +19,7 @@ export default class Reboot extends Component {
|
||||||
alert("No response")
|
alert("No response")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: AxiosError) => {
|
.catch((error) => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
const msg = error.response.data.msg
|
const msg = error.response.data.msg
|
||||||
|
|
||||||
|
|
159
ui/src/components/Updater/SysinfoCard.tsx
Normal file
159
ui/src/components/Updater/SysinfoCard.tsx
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import {Component} from "preact"
|
||||||
|
import {SystemInfo} from "../../sebrauc-client"
|
||||||
|
import {sebraucApi} from "../../util/apiUrls"
|
||||||
|
import {secondsToString} from "../../util/functions"
|
||||||
|
import Icon from "../Icon/Icon"
|
||||||
|
import {
|
||||||
|
mdiAlphaVCircleOutline,
|
||||||
|
mdiCheckCircleOutline,
|
||||||
|
mdiCircleOutline,
|
||||||
|
mdiClockOutline,
|
||||||
|
mdiCloseCircleOutline,
|
||||||
|
mdiMonitor,
|
||||||
|
mdiPenguin,
|
||||||
|
mdiTagMultipleOutline,
|
||||||
|
mdiTagOutline,
|
||||||
|
} from "@mdi/js"
|
||||||
|
import colors from "../../util/colors"
|
||||||
|
|
||||||
|
type Props = {}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
sysinfo: SystemInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SysinfoCard extends Component<Props, State> {
|
||||||
|
constructor(props?: Props | undefined, context?: any) {
|
||||||
|
super(props, context)
|
||||||
|
this.fetchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchInfo = () => {
|
||||||
|
sebraucApi
|
||||||
|
.getInfo()
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
this.setState({sysinfo: response.data})
|
||||||
|
} else {
|
||||||
|
console.log("error fetching info", response.data)
|
||||||
|
console.log("error fetching info", response.data)
|
||||||
|
window.setTimeout(this.fetchInfo, 3000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
console.log("error fetching info", reason)
|
||||||
|
window.setTimeout(this.fetchInfo, 3000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSysinfo() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="card">
|
||||||
|
<p class="top">System information</p>
|
||||||
|
<table class="table no-bottom-border">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiMonitor} /> Hostname
|
||||||
|
</td>
|
||||||
|
<td>{this.state.sysinfo.hostname}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiPenguin} /> Operating system
|
||||||
|
</td>
|
||||||
|
<td>{this.state.sysinfo.os_name}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiAlphaVCircleOutline} /> OS version
|
||||||
|
</td>
|
||||||
|
<td>{this.state.sysinfo.os_version}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiClockOutline} /> Uptime
|
||||||
|
</td>
|
||||||
|
<td>{secondsToString(this.state.sysinfo.uptime)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiTagOutline} /> Compatible FW
|
||||||
|
</td>
|
||||||
|
<td>{this.state.sysinfo.rauc_compatible}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<Icon icon={mdiTagMultipleOutline} /> Compatible FW
|
||||||
|
variant
|
||||||
|
</td>
|
||||||
|
<td>{this.state.sysinfo.rauc_variant}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card">
|
||||||
|
<p class="top">Rootfs slots</p>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table class="table no-bottom-border">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Mountpoint</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.keys(this.state.sysinfo.rauc_rootfs).map(
|
||||||
|
(k, i) => {
|
||||||
|
const rfs = this.state.sysinfo.rauc_rootfs[k]
|
||||||
|
let icon = mdiCircleOutline
|
||||||
|
let iconColor = colors.BLUE
|
||||||
|
|
||||||
|
if (!rfs.bootable) {
|
||||||
|
icon = mdiCloseCircleOutline
|
||||||
|
iconColor = colors.RED
|
||||||
|
} else if (rfs.primary) {
|
||||||
|
icon = mdiCheckCircleOutline
|
||||||
|
iconColor = colors.GREEN
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>
|
||||||
|
<Icon
|
||||||
|
icon={icon}
|
||||||
|
color={iconColor}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{k}</td>
|
||||||
|
<td>{rfs.device}</td>
|
||||||
|
<td>{rfs.mountpoint}</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLoadingAnimation() {
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<p>loading sysinfo...</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.sysinfo) {
|
||||||
|
return this.renderSysinfo()
|
||||||
|
}
|
||||||
|
return this.renderLoadingAnimation()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
.uploader {
|
.updater-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 500px;
|
max-width: 600px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
padding: 15px 8px;
|
margin-top: 25px;
|
||||||
margin: 8px 0;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -29,6 +29,14 @@
|
||||||
.top {
|
.top {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.pad {
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
|
@ -42,3 +50,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-top-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ import Dropzone from "../Dropzone/Dropzone"
|
||||||
import ProgressCircle from "../ProgressCircle/ProgressCircle"
|
import ProgressCircle from "../ProgressCircle/ProgressCircle"
|
||||||
import Icon from "../Icon/Icon"
|
import Icon from "../Icon/Icon"
|
||||||
import "./Updater.scss"
|
import "./Updater.scss"
|
||||||
import axios from "axios"
|
|
||||||
import Alert from "./Alert"
|
import Alert from "./Alert"
|
||||||
import Reboot from "./Reboot"
|
import Reboot from "./Reboot"
|
||||||
import {apiUrl, wsUrl} from "../../util/apiUrls"
|
import {sebraucApi, wsUrl} from "../../util/apiUrls"
|
||||||
|
import colors from "../../util/colors"
|
||||||
|
|
||||||
class UploadStatus {
|
class UploadStatus {
|
||||||
uploading = false
|
uploading = false
|
||||||
|
@ -50,7 +50,7 @@ type State = {
|
||||||
wsConnected: boolean
|
wsConnected: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Updater extends Component<Props, State> {
|
export default class UpdaterCard extends Component<Props, State> {
|
||||||
private dropzoneRef = createRef<Dropzone>()
|
private dropzoneRef = createRef<Dropzone>()
|
||||||
private conn: WebSocket | undefined
|
private conn: WebSocket | undefined
|
||||||
|
|
||||||
|
@ -77,19 +77,13 @@ export default class Updater extends Component<Props, State> {
|
||||||
if (files.length === 0) return
|
if (files.length === 0) return
|
||||||
const newFile = files[0]
|
const newFile = files[0]
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append("updateFile", newFile)
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
uploadStatus: new UploadStatus(true, newFile.size, 0),
|
uploadStatus: new UploadStatus(true, newFile.size, 0),
|
||||||
uploadFilename: newFile.name,
|
uploadFilename: newFile.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
axios
|
sebraucApi
|
||||||
.post(apiUrl + "/update", formData, {
|
.startUpdate(newFile, {
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
onUploadProgress: (progressEvent: {loaded: number; total: number}) => {
|
onUploadProgress: (progressEvent: {loaded: number; total: number}) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
uploadStatus: UploadStatus.fromProgressEvent(progressEvent),
|
uploadStatus: UploadStatus.fromProgressEvent(progressEvent),
|
||||||
|
@ -132,8 +126,6 @@ export default class Updater extends Component<Props, State> {
|
||||||
JSON.parse(messages[i])
|
JSON.parse(messages[i])
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(this.state.raucStatus)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -163,9 +155,9 @@ export default class Updater extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private circleColor(): string {
|
private circleColor(): string {
|
||||||
if (this.state.raucStatus.installing) return "#FF0039"
|
if (this.state.raucStatus.installing) return colors.RED
|
||||||
if (this.state.uploadStatus.uploading) return "#148420"
|
if (this.state.uploadStatus.uploading) return colors.GREEN
|
||||||
return "#1f85de"
|
return colors.BLUE
|
||||||
}
|
}
|
||||||
|
|
||||||
private circlePercentage(): number {
|
private circlePercentage(): number {
|
||||||
|
@ -195,12 +187,13 @@ export default class Updater extends Component<Props, State> {
|
||||||
topText = "Updating firmware"
|
topText = "Updating firmware"
|
||||||
bottomText = this.state.raucStatus.message
|
bottomText = this.state.raucStatus.message
|
||||||
} else {
|
} else {
|
||||||
topText = "Upload firmware package"
|
topText = "Firmware update"
|
||||||
|
bottomText = "Upload *.raucb FW package"
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="uploader">
|
<div>
|
||||||
<div class="card upload">
|
<div class="card pad">
|
||||||
<div>
|
<div>
|
||||||
<p class="top">{topText}</p>
|
<p class="top">{topText}</p>
|
||||||
</div>
|
</div>
|
48
ui/src/components/Updater/UpdaterView.tsx
Normal file
48
ui/src/components/Updater/UpdaterView.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import {mdiInformation, mdiUpload} from "@mdi/js"
|
||||||
|
import {Component} from "preact"
|
||||||
|
import Icon from "../Icon/Icon"
|
||||||
|
import SysinfoCard from "./SysinfoCard"
|
||||||
|
import UpdaterCard from "./UpdaterCard"
|
||||||
|
import "./Updater.scss"
|
||||||
|
|
||||||
|
type Props = {}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
flipped: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UpdaterView extends Component<Props, State> {
|
||||||
|
constructor(props?: Props | undefined, context?: any) {
|
||||||
|
super(props, context)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
flipped: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private flipCard = () => {
|
||||||
|
this.setState({flipped: !this.state.flipped})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="iconButton button-top-right"
|
||||||
|
onClick={this.flipCard}
|
||||||
|
aria-label={
|
||||||
|
this.state.flipped
|
||||||
|
? "Switch to updater"
|
||||||
|
: "Switch to system info"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon icon={this.state.flipped ? mdiUpload : mdiInformation} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="updater-view">
|
||||||
|
{!this.state.flipped ? <UpdaterCard /> : <SysinfoCard />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {Component} from "preact"
|
import {Component} from "preact"
|
||||||
import Updater from "./Updater/Updater"
|
import UpdaterView from "./Updater/UpdaterView"
|
||||||
import logo from "../assets/logo.svg"
|
import logo from "../assets/logo.svg"
|
||||||
import {version} from "../util/version"
|
import {version} from "../util/version"
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ export default class App extends Component {
|
||||||
<div>
|
<div>
|
||||||
<img src={logo} alt="SEBRAUC" height="64" />
|
<img src={logo} alt="SEBRAUC" height="64" />
|
||||||
{version}
|
{version}
|
||||||
<Updater />
|
<UpdaterView />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
27
ui/src/sebrauc-client/.openapi-generator-ignore
Normal file
27
ui/src/sebrauc-client/.openapi-generator-ignore
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
||||||
|
|
||||||
|
/.gitignore
|
||||||
|
/.npmignore
|
||||||
|
/git_push.sh
|
5
ui/src/sebrauc-client/.openapi-generator/FILES
Normal file
5
ui/src/sebrauc-client/.openapi-generator/FILES
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
api.ts
|
||||||
|
base.ts
|
||||||
|
common.ts
|
||||||
|
configuration.ts
|
||||||
|
index.ts
|
1
ui/src/sebrauc-client/.openapi-generator/VERSION
Normal file
1
ui/src/sebrauc-client/.openapi-generator/VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5.3.0
|
539
ui/src/sebrauc-client/api.ts
Normal file
539
ui/src/sebrauc-client/api.ts
Normal file
|
@ -0,0 +1,539 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* SEBRAUC
|
||||||
|
* REST API for the SEBRAUC firmware updater
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Configuration} from "./configuration"
|
||||||
|
import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios"
|
||||||
|
// Some imports not used depending on template conditions
|
||||||
|
// @ts-ignore
|
||||||
|
import {
|
||||||
|
DUMMY_BASE_URL,
|
||||||
|
assertParamExists,
|
||||||
|
setApiKeyToObject,
|
||||||
|
setBasicAuthToObject,
|
||||||
|
setBearerAuthToObject,
|
||||||
|
setOAuthToObject,
|
||||||
|
setSearchParams,
|
||||||
|
serializeDataIfNeeded,
|
||||||
|
toPathString,
|
||||||
|
createRequestFunction,
|
||||||
|
} from "./common"
|
||||||
|
// @ts-ignore
|
||||||
|
import {
|
||||||
|
BASE_PATH,
|
||||||
|
COLLECTION_FORMATS,
|
||||||
|
RequestArgs,
|
||||||
|
BaseAPI,
|
||||||
|
RequiredError,
|
||||||
|
} from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface RaucStatus
|
||||||
|
*/
|
||||||
|
export interface RaucStatus {
|
||||||
|
/**
|
||||||
|
* True if the installer is running
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof RaucStatus
|
||||||
|
*/
|
||||||
|
installing: boolean
|
||||||
|
/**
|
||||||
|
* Installation progress
|
||||||
|
* @type {number}
|
||||||
|
* @memberof RaucStatus
|
||||||
|
*/
|
||||||
|
percent: number
|
||||||
|
/**
|
||||||
|
* Current installation step
|
||||||
|
* @type {string}
|
||||||
|
* @memberof RaucStatus
|
||||||
|
*/
|
||||||
|
message: string
|
||||||
|
/**
|
||||||
|
* Installation error message
|
||||||
|
* @type {string}
|
||||||
|
* @memberof RaucStatus
|
||||||
|
*/
|
||||||
|
last_error: string
|
||||||
|
/**
|
||||||
|
* Full command line output of the current installation
|
||||||
|
* @type {string}
|
||||||
|
* @memberof RaucStatus
|
||||||
|
*/
|
||||||
|
log: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface Rootfs
|
||||||
|
*/
|
||||||
|
export interface Rootfs {
|
||||||
|
/**
|
||||||
|
* Block device
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
device: string
|
||||||
|
/**
|
||||||
|
* Filesystem
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
type: string
|
||||||
|
/**
|
||||||
|
* Mount path (null when not mounted)
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
mountpoint: string | null
|
||||||
|
/**
|
||||||
|
* Is the filesystem bootable
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
bootable: boolean
|
||||||
|
/**
|
||||||
|
* Is the filesystem booted
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
booted: boolean
|
||||||
|
/**
|
||||||
|
* Is the filesystem the next boot target
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof Rootfs
|
||||||
|
*/
|
||||||
|
primary: boolean
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface StatusMessage
|
||||||
|
*/
|
||||||
|
export interface StatusMessage {
|
||||||
|
/**
|
||||||
|
* Is operation successful
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof StatusMessage
|
||||||
|
*/
|
||||||
|
success: boolean
|
||||||
|
/**
|
||||||
|
* Success message
|
||||||
|
* @type {string}
|
||||||
|
* @memberof StatusMessage
|
||||||
|
*/
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemInfo
|
||||||
|
*/
|
||||||
|
export interface SystemInfo {
|
||||||
|
/**
|
||||||
|
* Hostname of the system
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
hostname: string
|
||||||
|
/**
|
||||||
|
* Name of the os distribution
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
os_name: string
|
||||||
|
/**
|
||||||
|
* Operating system version
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
os_version: string
|
||||||
|
/**
|
||||||
|
* System uptime in seconds
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
uptime: number
|
||||||
|
/**
|
||||||
|
* Compatible firmware name
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
rauc_compatible: string
|
||||||
|
/**
|
||||||
|
* Compatible firmware variant
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
rauc_variant: string
|
||||||
|
/**
|
||||||
|
* List of RAUC root filesystems
|
||||||
|
* @type {{ [key: string]: Rootfs; }}
|
||||||
|
* @memberof SystemInfo
|
||||||
|
*/
|
||||||
|
rauc_rootfs: {[key: string]: Rootfs}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - axios parameter creator
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/info`
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
|
||||||
|
let baseOptions
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {method: "GET", ...baseOptions, ...options}
|
||||||
|
const localVarHeaderParameter = {} as any
|
||||||
|
const localVarQueryParameter = {} as any
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter)
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {}
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getStatus: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/status`
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
|
||||||
|
let baseOptions
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {method: "GET", ...baseOptions, ...options}
|
||||||
|
const localVarHeaderParameter = {} as any
|
||||||
|
const localVarQueryParameter = {} as any
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter)
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {}
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
startReboot: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/reboot`
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
|
||||||
|
let baseOptions
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
|
||||||
|
const localVarHeaderParameter = {} as any
|
||||||
|
const localVarQueryParameter = {} as any
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter)
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {}
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} [updateFile]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
startUpdate: async (
|
||||||
|
updateFile?: any,
|
||||||
|
options: AxiosRequestConfig = {}
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/update`
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
|
||||||
|
let baseOptions
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
|
||||||
|
const localVarHeaderParameter = {} as any
|
||||||
|
const localVarQueryParameter = {} as any
|
||||||
|
const localVarFormParams = new ((configuration &&
|
||||||
|
configuration.formDataCtor) ||
|
||||||
|
FormData)()
|
||||||
|
|
||||||
|
if (updateFile !== undefined) {
|
||||||
|
localVarFormParams.append("updateFile", updateFile as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
localVarHeaderParameter["Content-Type"] = "multipart/form-data"
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter)
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {}
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
}
|
||||||
|
localVarRequestOptions.data = localVarFormParams
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - functional programming interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiFp = function (configuration?: Configuration) {
|
||||||
|
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getInfo(
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SystemInfo>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getInfo(options)
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getStatus(
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RaucStatus>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getStatus(options)
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async startReboot(
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.startReboot(
|
||||||
|
options
|
||||||
|
)
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} [updateFile]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async startUpdate(
|
||||||
|
updateFile?: any,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.startUpdate(
|
||||||
|
updateFile,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - factory interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiFactory = function (
|
||||||
|
configuration?: Configuration,
|
||||||
|
basePath?: string,
|
||||||
|
axios?: AxiosInstance
|
||||||
|
) {
|
||||||
|
const localVarFp = DefaultApiFp(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getInfo(options?: any): AxiosPromise<SystemInfo> {
|
||||||
|
return localVarFp
|
||||||
|
.getInfo(options)
|
||||||
|
.then((request) => request(axios, basePath))
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getStatus(options?: any): AxiosPromise<RaucStatus> {
|
||||||
|
return localVarFp
|
||||||
|
.getStatus(options)
|
||||||
|
.then((request) => request(axios, basePath))
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
startReboot(options?: any): AxiosPromise<StatusMessage> {
|
||||||
|
return localVarFp
|
||||||
|
.startReboot(options)
|
||||||
|
.then((request) => request(axios, basePath))
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} [updateFile]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
startUpdate(updateFile?: any, options?: any): AxiosPromise<StatusMessage> {
|
||||||
|
return localVarFp
|
||||||
|
.startUpdate(updateFile, options)
|
||||||
|
.then((request) => request(axios, basePath))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - object-oriented interface
|
||||||
|
* @export
|
||||||
|
* @class DefaultApi
|
||||||
|
* @extends {BaseAPI}
|
||||||
|
*/
|
||||||
|
export class DefaultApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public getInfo(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration)
|
||||||
|
.getInfo(options)
|
||||||
|
.then((request) => request(this.axios, this.basePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public getStatus(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration)
|
||||||
|
.getStatus(options)
|
||||||
|
.then((request) => request(this.axios, this.basePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public startReboot(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration)
|
||||||
|
.startReboot(options)
|
||||||
|
.then((request) => request(this.axios, this.basePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} [updateFile]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public startUpdate(updateFile?: any, options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration)
|
||||||
|
.startUpdate(updateFile, options)
|
||||||
|
.then((request) => request(this.axios, this.basePath))
|
||||||
|
}
|
||||||
|
}
|
74
ui/src/sebrauc-client/base.ts
Normal file
74
ui/src/sebrauc-client/base.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* SEBRAUC
|
||||||
|
* REST API for the SEBRAUC firmware updater
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Configuration} from "./configuration"
|
||||||
|
// Some imports not used depending on template conditions
|
||||||
|
// @ts-ignore
|
||||||
|
import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios"
|
||||||
|
|
||||||
|
export const BASE_PATH = "http://localhost:8080/api".replace(/\/+$/, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const COLLECTION_FORMATS = {
|
||||||
|
csv: ",",
|
||||||
|
ssv: " ",
|
||||||
|
tsv: "\t",
|
||||||
|
pipes: "|",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface RequestArgs
|
||||||
|
*/
|
||||||
|
export interface RequestArgs {
|
||||||
|
url: string
|
||||||
|
options: AxiosRequestConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class BaseAPI
|
||||||
|
*/
|
||||||
|
export class BaseAPI {
|
||||||
|
protected configuration: Configuration | undefined
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
configuration?: Configuration,
|
||||||
|
protected basePath: string = BASE_PATH,
|
||||||
|
protected axios: AxiosInstance = globalAxios
|
||||||
|
) {
|
||||||
|
if (configuration) {
|
||||||
|
this.configuration = configuration
|
||||||
|
this.basePath = configuration.basePath || this.basePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class RequiredError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
export class RequiredError extends Error {
|
||||||
|
name: "RequiredError" = "RequiredError"
|
||||||
|
constructor(public field: string, msg?: string) {
|
||||||
|
super(msg)
|
||||||
|
}
|
||||||
|
}
|
181
ui/src/sebrauc-client/common.ts
Normal file
181
ui/src/sebrauc-client/common.ts
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* SEBRAUC
|
||||||
|
* REST API for the SEBRAUC firmware updater
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Configuration} from "./configuration"
|
||||||
|
import {RequiredError, RequestArgs} from "./base"
|
||||||
|
import {AxiosInstance, AxiosResponse} from "axios"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DUMMY_BASE_URL = "https://example.com"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const assertParamExists = function (
|
||||||
|
functionName: string,
|
||||||
|
paramName: string,
|
||||||
|
paramValue: unknown
|
||||||
|
) {
|
||||||
|
if (paramValue === null || paramValue === undefined) {
|
||||||
|
throw new RequiredError(
|
||||||
|
paramName,
|
||||||
|
`Required parameter ${paramName} was null or undefined when calling ${functionName}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setApiKeyToObject = async function (
|
||||||
|
object: any,
|
||||||
|
keyParamName: string,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.apiKey) {
|
||||||
|
const localVarApiKeyValue =
|
||||||
|
typeof configuration.apiKey === "function"
|
||||||
|
? await configuration.apiKey(keyParamName)
|
||||||
|
: await configuration.apiKey
|
||||||
|
object[keyParamName] = localVarApiKeyValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setBasicAuthToObject = function (
|
||||||
|
object: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && (configuration.username || configuration.password)) {
|
||||||
|
object["auth"] = {
|
||||||
|
username: configuration.username,
|
||||||
|
password: configuration.password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setBearerAuthToObject = async function (
|
||||||
|
object: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.accessToken) {
|
||||||
|
const accessToken =
|
||||||
|
typeof configuration.accessToken === "function"
|
||||||
|
? await configuration.accessToken()
|
||||||
|
: await configuration.accessToken
|
||||||
|
object["Authorization"] = "Bearer " + accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setOAuthToObject = async function (
|
||||||
|
object: any,
|
||||||
|
name: string,
|
||||||
|
scopes: string[],
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.accessToken) {
|
||||||
|
const localVarAccessTokenValue =
|
||||||
|
typeof configuration.accessToken === "function"
|
||||||
|
? await configuration.accessToken(name, scopes)
|
||||||
|
: await configuration.accessToken
|
||||||
|
object["Authorization"] = "Bearer " + localVarAccessTokenValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setSearchParams = function (url: URL, ...objects: any[]) {
|
||||||
|
const searchParams = new URLSearchParams(url.search)
|
||||||
|
for (const object of objects) {
|
||||||
|
for (const key in object) {
|
||||||
|
if (Array.isArray(object[key])) {
|
||||||
|
searchParams.delete(key)
|
||||||
|
for (const item of object[key]) {
|
||||||
|
searchParams.append(key, item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
searchParams.set(key, object[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.search = searchParams.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const serializeDataIfNeeded = function (
|
||||||
|
value: any,
|
||||||
|
requestOptions: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
const nonString = typeof value !== "string"
|
||||||
|
const needsSerialization =
|
||||||
|
nonString && configuration && configuration.isJsonMime
|
||||||
|
? configuration.isJsonMime(requestOptions.headers["Content-Type"])
|
||||||
|
: nonString
|
||||||
|
return needsSerialization
|
||||||
|
? JSON.stringify(value !== undefined ? value : {})
|
||||||
|
: value || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const toPathString = function (url: URL) {
|
||||||
|
return url.pathname + url.search + url.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const createRequestFunction = function (
|
||||||
|
axiosArgs: RequestArgs,
|
||||||
|
globalAxios: AxiosInstance,
|
||||||
|
BASE_PATH: string,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
return <T = unknown, R = AxiosResponse<T>>(
|
||||||
|
axios: AxiosInstance = globalAxios,
|
||||||
|
basePath: string = BASE_PATH
|
||||||
|
) => {
|
||||||
|
const axiosRequestArgs = {
|
||||||
|
...axiosArgs.options,
|
||||||
|
url: (configuration?.basePath || basePath) + axiosArgs.url,
|
||||||
|
}
|
||||||
|
return axios.request<T, R>(axiosRequestArgs)
|
||||||
|
}
|
||||||
|
}
|
123
ui/src/sebrauc-client/configuration.ts
Normal file
123
ui/src/sebrauc-client/configuration.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* SEBRAUC
|
||||||
|
* REST API for the SEBRAUC firmware updater
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ConfigurationParameters {
|
||||||
|
apiKey?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name: string) => string)
|
||||||
|
| ((name: string) => Promise<string>)
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
accessToken?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name?: string, scopes?: string[]) => string)
|
||||||
|
| ((name?: string, scopes?: string[]) => Promise<string>)
|
||||||
|
basePath?: string
|
||||||
|
baseOptions?: any
|
||||||
|
formDataCtor?: new () => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Configuration {
|
||||||
|
/**
|
||||||
|
* parameter for apiKey security
|
||||||
|
* @param name security name
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
apiKey?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name: string) => string)
|
||||||
|
| ((name: string) => Promise<string>)
|
||||||
|
/**
|
||||||
|
* parameter for basic security
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
username?: string
|
||||||
|
/**
|
||||||
|
* parameter for basic security
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
password?: string
|
||||||
|
/**
|
||||||
|
* parameter for oauth2 security
|
||||||
|
* @param name security name
|
||||||
|
* @param scopes oauth2 scope
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
accessToken?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name?: string, scopes?: string[]) => string)
|
||||||
|
| ((name?: string, scopes?: string[]) => Promise<string>)
|
||||||
|
/**
|
||||||
|
* override base path
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
basePath?: string
|
||||||
|
/**
|
||||||
|
* base options for axios calls
|
||||||
|
*
|
||||||
|
* @type {any}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
baseOptions?: any
|
||||||
|
/**
|
||||||
|
* The FormData constructor that will be used to create multipart form data
|
||||||
|
* requests. You can inject this here so that execution environments that
|
||||||
|
* do not support the FormData class can still run the generated client.
|
||||||
|
*
|
||||||
|
* @type {new () => FormData}
|
||||||
|
*/
|
||||||
|
formDataCtor?: new () => any
|
||||||
|
|
||||||
|
constructor(param: ConfigurationParameters = {}) {
|
||||||
|
this.apiKey = param.apiKey
|
||||||
|
this.username = param.username
|
||||||
|
this.password = param.password
|
||||||
|
this.accessToken = param.accessToken
|
||||||
|
this.basePath = param.basePath
|
||||||
|
this.baseOptions = param.baseOptions
|
||||||
|
this.formDataCtor = param.formDataCtor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given MIME is a JSON MIME.
|
||||||
|
* JSON MIME examples:
|
||||||
|
* application/json
|
||||||
|
* application/json; charset=UTF8
|
||||||
|
* APPLICATION/JSON
|
||||||
|
* application/vnd.company+json
|
||||||
|
* @param mime - MIME (Multipurpose Internet Mail Extensions)
|
||||||
|
* @return True if the given MIME is JSON, false otherwise.
|
||||||
|
*/
|
||||||
|
public isJsonMime(mime: string): boolean {
|
||||||
|
const jsonMime: RegExp = new RegExp(
|
||||||
|
"^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$",
|
||||||
|
"i"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
mime !== null &&
|
||||||
|
(jsonMime.test(mime) ||
|
||||||
|
mime.toLowerCase() === "application/json-patch+json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
16
ui/src/sebrauc-client/index.ts
Normal file
16
ui/src/sebrauc-client/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* SEBRAUC
|
||||||
|
* REST API for the SEBRAUC firmware updater
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./api"
|
||||||
|
export * from "./configuration"
|
|
@ -1,3 +1,5 @@
|
||||||
|
@use "table";
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
47
ui/src/style/table.scss
Normal file
47
ui/src/style/table.scss
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
.table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.no-bottom-border {
|
||||||
|
&,
|
||||||
|
> tr:last-child,
|
||||||
|
:not(thead) tr:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table caption {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0.5em 0 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 0.35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
padding: 0.625em;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: #1f85de;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
font-size: 0.85em;
|
||||||
|
letter-spacing: 0.085em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {Configuration, DefaultApi} from "../sebrauc-client"
|
||||||
|
|
||||||
let apiHost = document.location.host
|
let apiHost = document.location.host
|
||||||
const httpProto = document.location.protocol
|
const httpProto = document.location.protocol
|
||||||
const wsProto = httpProto === "https:" ? "wss:" : "ws:"
|
const wsProto = httpProto === "https:" ? "wss:" : "ws:"
|
||||||
|
@ -9,4 +11,10 @@ if (import.meta.env.VITE_API_HOST !== undefined) {
|
||||||
const apiUrl = `${httpProto}//${apiHost}/api`
|
const apiUrl = `${httpProto}//${apiHost}/api`
|
||||||
const wsUrl = `${wsProto}//${apiHost}/api/ws`
|
const wsUrl = `${wsProto}//${apiHost}/api/ws`
|
||||||
|
|
||||||
export {apiUrl, wsUrl}
|
let apicfg = new Configuration({
|
||||||
|
basePath: apiUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
const sebraucApi = new DefaultApi(apicfg)
|
||||||
|
|
||||||
|
export {apiUrl, wsUrl, sebraucApi}
|
||||||
|
|
7
ui/src/util/colors.ts
Normal file
7
ui/src/util/colors.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class colors {
|
||||||
|
static readonly RED = "#FF0039"
|
||||||
|
static readonly GREEN = "#148420"
|
||||||
|
static readonly BLUE = "#1f85de"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default colors
|
18
ui/src/util/functions.ts
Normal file
18
ui/src/util/functions.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
function secondsToString(seconds: number): string {
|
||||||
|
const numyears = Math.floor(seconds / 31536000)
|
||||||
|
const numdays = Math.floor((seconds % 31536000) / 86400)
|
||||||
|
const numhours = Math.floor(((seconds % 31536000) % 86400) / 3600)
|
||||||
|
const numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60)
|
||||||
|
const numseconds = (((seconds % 31536000) % 86400) % 3600) % 60
|
||||||
|
|
||||||
|
let res = []
|
||||||
|
if (numyears > 0) res.push(numyears + "yr")
|
||||||
|
if (numdays > 0) res.push(numdays + "d")
|
||||||
|
if (numhours > 0) res.push(numhours + "h")
|
||||||
|
if (numminutes > 0) res.push(numminutes + "m")
|
||||||
|
if (seconds < 60) res.push(numseconds + "s")
|
||||||
|
|
||||||
|
return res.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
export {secondsToString}
|
Loading…
Reference in a new issue