diff --git a/config/config.go b/config/config.go
index 830fb8f..e179641 100644
--- a/config/config.go
+++ b/config/config.go
@@ -8,16 +8,16 @@ import (
)
type Device struct {
- Alias string
- MAC string
- IP string
+ Alias string `form:"Alias"`
+ MAC string `form:"MAC"`
+ IP string `form:"IP"`
}
type config struct {
Server string
PassHash string
SessionTTL float64
- Device []Device
+ Devices []Device
}
var Config config
diff --git a/example/config.toml b/example/config.toml
index 7ac5149..779ab5b 100644
--- a/example/config.toml
+++ b/example/config.toml
@@ -2,12 +2,12 @@ Server = ":8080" # The address the webserver should bind to
PassHash = "$2a$10$I.26oCzkjZ8qwfhbmeYM3.kppBjxtPsxkeE1Y.ULjVvA1IBPcQP42" # "password"
SessionTTL = 60 # How many minutes sessions last for
-[[Device]]
+[[Devices]]
Alias = "SomeDevice"
MAC = "DE-AD-BE-EF-F0-05" # Delimiter dashes/colons, upper/lowercase
IP = "192.168.178.255" # Broadcast for most home networks
-[[Device]]
+[[Devices]]
Alias = "Another Device"
MAC = ""
IP = ""
\ No newline at end of file
diff --git a/web/auth.go b/web/auth.go
index 867128e..91500ac 100644
--- a/web/auth.go
+++ b/web/auth.go
@@ -5,7 +5,11 @@ import (
"net/http"
"time"
+ "git.ulra.eu/adro/miniwol/config"
+
+ "github.com/google/uuid"
"github.com/labstack/echo/v4"
+ "golang.org/x/crypto/bcrypt"
)
var sessions map[string]time.Time
@@ -39,3 +43,33 @@ func withAuth(handler echo.HandlerFunc) echo.HandlerFunc {
return handler(c)
}
}
+
+// Handlers
+func auth(c echo.Context) error {
+ password := c.FormValue("Password")
+ if bcrypt.CompareHashAndPassword([]byte(config.Config.PassHash), []byte(password)) != nil {
+ return c.String(401, "Wrong Password")
+ }
+ token := uuid.New().String()
+ sessions[token] = time.Now().Add(time.Second * time.Duration(config.Config.SessionTTL*60))
+ c.SetCookie(&http.Cookie{
+ Name: "session",
+ Value: token,
+ Path: "/",
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ Expires: sessions[token],
+ })
+ return c.Redirect(http.StatusSeeOther, "/")
+}
+
+func deauth(c echo.Context) error {
+ session, err := c.Cookie("session")
+ if err != nil {
+ return err
+ }
+
+ delete(sessions, session.Value)
+ return c.Redirect(http.StatusSeeOther, "/")
+}
diff --git a/web/device.go b/web/device.go
new file mode 100644
index 0000000..f78dcf4
--- /dev/null
+++ b/web/device.go
@@ -0,0 +1,68 @@
+package web
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+
+ "git.ulra.eu/adro/miniwol/config"
+ "git.ulra.eu/adro/miniwol/lib"
+
+ "github.com/labstack/echo/v4"
+)
+
+func add(c echo.Context) error {
+ device := config.Device{}
+ err := c.Bind(&device)
+ if err != nil {
+ return err
+ }
+
+ config.Config.Devices = append(config.Config.Devices, device)
+ config.Save()
+ if err != nil {
+ return err
+ }
+ return c.Redirect(http.StatusSeeOther, "/")
+}
+
+func wake(c echo.Context) error {
+ _device := config.Device{}
+ err := c.Bind(&_device)
+ if err != nil {
+ return err
+ }
+ for _, device := range config.Config.Devices {
+ if device == _device {
+ if !strings.Contains(device.IP, ":") {
+ device.IP += ":9"
+ }
+ err := lib.SendPacket(":0", device.IP, device.MAC)
+ if err != nil {
+ return err
+ }
+
+ return c.Redirect(http.StatusSeeOther, "/")
+ }
+ }
+ return errors.New("device not found")
+}
+
+func remove(c echo.Context) error {
+ _device := config.Device{}
+ err := c.Bind(&_device)
+ if err != nil {
+ return err
+ }
+ for i, device := range config.Config.Devices {
+ if device == _device {
+ config.Config.Devices = append(config.Config.Devices[:i], config.Config.Devices[i+1:]...)
+ err := config.Save()
+ if err != nil {
+ return err
+ }
+ return c.Redirect(http.StatusSeeOther, "/")
+ }
+ }
+ return errors.New("device not found")
+}
diff --git a/web/template/device.html b/web/template/device.html
new file mode 100644
index 0000000..328c463
--- /dev/null
+++ b/web/template/device.html
@@ -0,0 +1,51 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/template/device.html.tmpl b/web/template/device.html.tmpl
deleted file mode 100644
index 1d6a655..0000000
--- a/web/template/device.html.tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
- Device Alias |
- MAC Address |
- IP/Broadcast |
- Actions |
-
- {{range $i, $d := .Device}}
-
- {{$d.Alias}} |
- {{$d.MAC}} |
- {{$d.IP}} |
-
-
- |
-
- {{end}}
-
\ No newline at end of file
diff --git a/web/template/login.html b/web/template/login.html
new file mode 100644
index 0000000..9438442
--- /dev/null
+++ b/web/template/login.html
@@ -0,0 +1,8 @@
+
diff --git a/web/template/login.html.tmpl b/web/template/login.html.tmpl
deleted file mode 100644
index f12ac23..0000000
--- a/web/template/login.html.tmpl
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/template/page.html b/web/template/page.html
new file mode 100644
index 0000000..ed1f961
--- /dev/null
+++ b/web/template/page.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+ {{ .Title }} - miniwol
+
+
+
+ {{ .Content }}
+
+
diff --git a/web/template/page.html.tmpl b/web/template/page.html.tmpl
deleted file mode 100644
index 4976644..0000000
--- a/web/template/page.html.tmpl
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
- {{ .Title }}
-
-
- {{ .Content }}
-
-
diff --git a/web/template/style.css b/web/template/style.css
new file mode 100644
index 0000000..3d28fe3
--- /dev/null
+++ b/web/template/style.css
@@ -0,0 +1,96 @@
+* {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+ color: inherit;
+ line-height: 1;
+}
+
+nav, div, fieldset {
+ display: flex;
+ flex-flow: column nowrap;
+ padding: 0.5rem;
+ gap: 0.5rem;
+}
+
+nav {
+ flex-flow: row nowrap;
+}
+
+fieldset {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ flex-grow: 1;
+
+ overflow-x: auto;
+}
+
+fieldset > label:after {
+ content: ":";
+ pointer-events: none;
+}
+
+form {
+ max-width: max-content;
+}
+
+input[type=submit] {
+ grid-column: 1 / 3;
+ padding: 0.25rem;
+ cursor: pointer;
+}
+
+.actions {
+ text-align: center;
+}
+
+.actions > form {
+ display: contents;
+}
+
+table {
+ grid-column: 1 / 3;
+ border-collapse: collapse;
+}
+
+legend {
+ font-weight: bold;
+}
+
+/* Colors */
+
+:root {
+ --cl-fg: #222;
+ --cl-bg-page: #eee;
+ --cl-bg-block: #ddd;
+ --cl-bg-input: #ccc;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --cl-fg: #ddd;
+ --cl-bg-page: #333;
+ --cl-bg-block: #222;
+ --cl-bg-input: #444;
+ }
+}
+
+:root {
+ font-family: sans-serif;
+ background: var(--cl-bg-page);
+ color: var(--cl-fg);
+}
+
+nav, fieldset, table {
+ background: var(--cl-bg-block);
+}
+
+input {
+ border: 1px solid var(--cl-fg);
+ background: var(--cl-bg-input);
+}
+
+input[type=submit]:hover,
+tr:nth-child(even) {
+ background: var(--cl-bg-page);
+}
diff --git a/web/web.go b/web/web.go
index 98dd087..39b13d5 100644
--- a/web/web.go
+++ b/web/web.go
@@ -3,19 +3,11 @@ package web
import (
"bytes"
"embed"
- "errors"
- "fmt"
"html/template"
- "net/http"
- "strings"
- "time"
"git.ulra.eu/adro/miniwol/config"
- "git.ulra.eu/adro/miniwol/lib"
- "github.com/google/uuid"
"github.com/labstack/echo/v4"
- "golang.org/x/crypto/bcrypt"
)
//go:embed template/*
@@ -25,7 +17,7 @@ var templates *template.Template
func init() {
var err error
- templates, err = template.ParseFS(templateFS, "template/*.html.tmpl")
+ templates, err = template.ParseFS(templateFS, "template/*.html")
if err != nil {
panic(err)
}
@@ -35,25 +27,31 @@ func Run() error {
e := echo.New()
e.GET("/", index)
+ e.GET("/style.css", style)
e.POST("/auth", auth)
+ e.POST("/deauth", withAuth(deauth))
+ e.POST("/add", withAuth(add))
e.POST("/wake", withAuth(wake))
+ e.POST("/remove", withAuth(remove))
return e.Start(config.Config.Server)
}
-func Page(c echo.Context, code int, title string, page string, data interface{}) error {
+func Page(c echo.Context, code int, title string, page string, auth bool, data interface{}) error {
var contentBuffer bytes.Buffer
err := templates.ExecuteTemplate(&contentBuffer, page, data)
if err != nil {
return err
}
var pageBuffer bytes.Buffer
- err = templates.ExecuteTemplate(&pageBuffer, "page.html.tmpl", struct {
+ err = templates.ExecuteTemplate(&pageBuffer, "page.html", struct {
Title string
Content template.HTML
+ Auth bool
}{
Title: title,
Content: template.HTML(contentBuffer.String()),
+ Auth: auth,
})
if err != nil {
return err
@@ -65,44 +63,16 @@ func Page(c echo.Context, code int, title string, page string, data interface{})
func index(c echo.Context) error {
session, err := c.Cookie("session")
if err != nil || checkAuth(session.Value) != nil {
- return Page(c, 200, "Login", "login.html.tmpl", nil)
+ return Page(c, 200, "Login", "login.html", false, nil)
} else {
- return Page(c, 200, "Device", "device.html.tmpl", config.Config)
+ return Page(c, 200, "Devices", "device.html", true, config.Config)
}
}
-func auth(c echo.Context) error {
- password := c.FormValue("password")
- if bcrypt.CompareHashAndPassword([]byte(config.Config.PassHash), []byte(password)) != nil {
- return c.String(401, "Wrong Password")
+func style(c echo.Context) error {
+ styleData, err := templateFS.ReadFile("template/style.css")
+ if err != nil {
+ return err
}
- token := uuid.New().String()
- sessions[token] = time.Now().Add(time.Second * time.Duration(config.Config.SessionTTL*60))
- c.SetCookie(&http.Cookie{
- Name: "session",
- Value: token,
- Path: "/",
- Secure: true,
- HttpOnly: true,
- SameSite: http.SameSiteStrictMode,
- Expires: sessions[token],
- })
- return c.Redirect(http.StatusSeeOther, "/")
-}
-
-func wake(c echo.Context) error {
- for i, device := range config.Config.Device {
- if c.FormValue("alias") == device.Alias && c.FormValue("index") == fmt.Sprint(i) {
- if !strings.Contains(device.IP, ":") {
- device.IP += ":9"
- }
- err := lib.SendPacket(":0", device.IP, device.MAC)
- if err != nil {
- return err
- }
-
- return c.Redirect(http.StatusSeeOther, "/")
- }
- }
- return errors.New("device not found")
+ return c.Blob(200, "text/css", styleData)
}