summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/database/accounts.go25
-rw-r--r--pkg/database/states.go26
-rw-r--r--pkg/database/versions.go35
-rw-r--r--pkg/model/version.go14
-rw-r--r--pkg/webui/html/base.html8
-rw-r--r--pkg/webui/html/state.html30
-rw-r--r--pkg/webui/html/states.html2
-rw-r--r--pkg/webui/index.go5
-rw-r--r--pkg/webui/routes.go1
-rw-r--r--pkg/webui/state.go54
-rw-r--r--pkg/webui/states.go2
-rw-r--r--pkg/webui/static/main.css3
12 files changed, 195 insertions, 10 deletions
diff --git a/pkg/database/accounts.go b/pkg/database/accounts.go
index 377ca80..9adb32d 100644
--- a/pkg/database/accounts.go
+++ b/pkg/database/accounts.go
@@ -47,6 +47,31 @@ func (db *DB) InitAdminAccount() error {
})
}
+func (db *DB) LoadAccountUsernames() (map[int]string, error) {
+ rows, err := db.Query(
+ `SELECT id, username FROM accounts;`)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load accounts from database: %w", err)
+ }
+ defer rows.Close()
+ accounts := make(map[int]string)
+ for rows.Next() {
+ var (
+ id int
+ username string
+ )
+ err = rows.Scan(&id, &username)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load account from row: %w", err)
+ }
+ accounts[id] = username
+ }
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("failed to load accounts from rows: %w", err)
+ }
+ return accounts, nil
+}
+
func (db *DB) LoadAccountById(id int) (*model.Account, error) {
account := model.Account{
Id: id,
diff --git a/pkg/database/states.go b/pkg/database/states.go
index 1bd3d76..d65dd7c 100644
--- a/pkg/database/states.go
+++ b/pkg/database/states.go
@@ -45,7 +45,29 @@ func (db *DB) GetState(path string) ([]byte, error) {
return db.dataEncryptionKey.DecryptAES256(encryptedData)
}
-func (db *DB) LoadStatesByPath() ([]model.State, error) {
+func (db *DB) LoadStateById(stateId int) (*model.State, error) {
+ state := model.State{
+ Id: stateId,
+ }
+ var (
+ created int64
+ updated int64
+ )
+ err := db.QueryRow(
+ `SELECT created, lock, path, updated FROM states WHERE id = ?;`,
+ stateId).Scan(&created, &state.Lock, &state.Path, &updated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("failed to load state id %s from database: %w", stateId, err)
+ }
+ state.Created = time.Unix(created, 0)
+ state.Updated = time.Unix(updated, 0)
+ return &state, nil
+}
+
+func (db *DB) LoadStates() ([]model.State, error) {
rows, err := db.Query(
`SELECT created, id, lock, path, updated FROM states;`)
if err != nil {
@@ -61,7 +83,7 @@ func (db *DB) LoadStatesByPath() ([]model.State, error) {
)
err = rows.Scan(&created, &state.Id, &state.Lock, &state.Path, &updated)
if err != nil {
- return nil, fmt.Errorf("failed to load states from row: %w", err)
+ return nil, fmt.Errorf("failed to load state from row: %w", err)
}
state.Created = time.Unix(created, 0)
state.Updated = time.Unix(updated, 0)
diff --git a/pkg/database/versions.go b/pkg/database/versions.go
new file mode 100644
index 0000000..5a6f11f
--- /dev/null
+++ b/pkg/database/versions.go
@@ -0,0 +1,35 @@
+package database
+
+import (
+ "fmt"
+ "time"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/model"
+)
+
+func (db *DB) LoadVersionsByState(state *model.State) ([]model.Version, error) {
+ rows, err := db.Query(
+ `SELECT account_id, created, data, id, lock
+ FROM versions
+ WHERE state_id = ?
+ ORDER BY id DESC;`, state.Id)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load versions from database: %w", err)
+ }
+ defer rows.Close()
+ versions := make([]model.Version, 0)
+ for rows.Next() {
+ var version model.Version
+ var created int64
+ err = rows.Scan(&version.AccountId, &created, &version.Data, &version.Id, &version.Lock)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load version from row: %w", err)
+ }
+ version.Created = time.Unix(created, 0)
+ versions = append(versions, version)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("failed to load versions from rows: %w", err)
+ }
+ return versions, nil
+}
diff --git a/pkg/model/version.go b/pkg/model/version.go
new file mode 100644
index 0000000..d8c3603
--- /dev/null
+++ b/pkg/model/version.go
@@ -0,0 +1,14 @@
+package model
+
+import (
+ "time"
+)
+
+type Version struct {
+ AccountId int
+ Created time.Time
+ Data []byte
+ Id int
+ Lock *string
+ StateId int
+}
diff --git a/pkg/webui/html/base.html b/pkg/webui/html/base.html
index 06328e4..c0138ac 100644
--- a/pkg/webui/html/base.html
+++ b/pkg/webui/html/base.html
@@ -12,11 +12,10 @@
<span>Login</span>
</a>
{{ else }}
-<a href="/states"{{ if eq .Page.Section "states" }} class="active"{{ end}}>
+<a href="/states"{{ if eq .Page.Section "states" }} class="fill"{{ end}}>
<i>home_storage</i>
<span>States</span>
</a>
-<hr>
<a href="/logout">
<i>logout</i>
<span>Logout</span>
@@ -39,6 +38,11 @@
<nav class="bottom s">{{ template "nav" . }}</nav>
<header>
<nav>
+ {{ if ne .Page.Precedent "" }}
+ <a href="{{ .Page.Precedent }}" class="button circle chip">
+ <i>arrow_back</i>
+ </a>
+ {{ end }}
<h5 class="max center-align">{{ .Page.Title }}</h5>
</nav>
</header>
diff --git a/pkg/webui/html/state.html b/pkg/webui/html/state.html
new file mode 100644
index 0000000..4439d9e
--- /dev/null
+++ b/pkg/webui/html/state.html
@@ -0,0 +1,30 @@
+{{ define "main" }}
+<main class="responsive" id="main">
+ <p>
+ Locked:
+ {{ if eq .State.Lock nil }}no{{ else }}
+ <span>yes</span>
+ <div class="tooltip left max">
+ <b>Lock</b>
+ <p>{{ .State.Lock }}</p>
+ </div>
+ {{ end }}
+ </p>
+ <table class="clickable-rows no-space">
+ <thead>
+ <tr>
+ <th>By</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{ range .Versions }}
+ <tr>
+ <td><a href="/version/{{ .Id }}">{{ index $.Usernames .AccountId }}</a></td>
+ <td><a href="/version/{{ .Id }}">{{ .Created }}</a></td>
+ </tr>
+ {{ end }}
+ </tbody>
+ </table>
+</main>
+{{ end }}
diff --git a/pkg/webui/html/states.html b/pkg/webui/html/states.html
index f61b87c..787ac73 100644
--- a/pkg/webui/html/states.html
+++ b/pkg/webui/html/states.html
@@ -1,6 +1,6 @@
{{ define "main" }}
<main class="responsive" id="main">
- <table class="clickable-rows no-space stripes">
+ <table class="clickable-rows no-space">
<thead>
<tr>
<th>Path</th>
diff --git a/pkg/webui/index.go b/pkg/webui/index.go
index deea5d1..89e5ad6 100644
--- a/pkg/webui/index.go
+++ b/pkg/webui/index.go
@@ -6,8 +6,9 @@ import (
)
type Page struct {
- Section string
- Title string
+ Precedent string
+ Section string
+ Title string
}
func handleIndexGET() http.Handler {
diff --git a/pkg/webui/routes.go b/pkg/webui/routes.go
index 9bf04ec..4330630 100644
--- a/pkg/webui/routes.go
+++ b/pkg/webui/routes.go
@@ -17,6 +17,7 @@ func addRoutes(
mux.Handle("POST /login", requireSession(handleLoginPOST(db)))
mux.Handle("GET /logout", requireLogin(handleLogoutGET(db)))
mux.Handle("GET /states", requireLogin(handleStatesGET(db)))
+ mux.Handle("GET /state/{id}", requireLogin(handleStateGET(db)))
mux.Handle("GET /static/", cache(http.FileServer(http.FS(staticFS))))
mux.Handle("GET /", requireLogin(handleIndexGET()))
}
diff --git a/pkg/webui/state.go b/pkg/webui/state.go
new file mode 100644
index 0000000..c7a6aaf
--- /dev/null
+++ b/pkg/webui/state.go
@@ -0,0 +1,54 @@
+package webui
+
+import (
+ "html/template"
+ "net/http"
+ "strconv"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/model"
+)
+
+var stateTemplate = template.Must(template.ParseFS(htmlFS, "html/base.html", "html/state.html"))
+
+func handleStateGET(db *database.DB) http.Handler {
+ type StatesData struct {
+ Page
+ State *model.State
+ Usernames map[int]string
+ Versions []model.Version
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ stateIdStr := r.PathValue("id")
+ stateId, err := strconv.Atoi(stateIdStr)
+ if err != nil {
+ errorResponse(w, http.StatusBadRequest, err)
+ return
+ }
+ state, err := db.LoadStateById(stateId)
+ if err != nil {
+ errorResponse(w, http.StatusInternalServerError, err)
+ return
+ }
+ versions, err := db.LoadVersionsByState(state)
+ if err != nil {
+ errorResponse(w, http.StatusInternalServerError, err)
+ return
+ }
+ usernames, err := db.LoadAccountUsernames()
+ if err != nil {
+ errorResponse(w, http.StatusInternalServerError, err)
+ return
+ }
+ render(w, stateTemplate, http.StatusOK, StatesData{
+ Page: Page{
+ Precedent: "/states",
+ Section: "states",
+ Title: state.Path,
+ },
+ State: state,
+ Usernames: usernames,
+ Versions: versions,
+ })
+ })
+}
diff --git a/pkg/webui/states.go b/pkg/webui/states.go
index 0e956fb..d99d310 100644
--- a/pkg/webui/states.go
+++ b/pkg/webui/states.go
@@ -16,7 +16,7 @@ func handleStatesGET(db *database.DB) http.Handler {
States []model.State
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- states, err := db.LoadStatesByPath()
+ states, err := db.LoadStates()
if err != nil {
errorResponse(w, http.StatusInternalServerError, err)
return
diff --git a/pkg/webui/static/main.css b/pkg/webui/static/main.css
index f0fe350..ae56cd0 100644
--- a/pkg/webui/static/main.css
+++ b/pkg/webui/static/main.css
@@ -1,6 +1,5 @@
-.clickable-rows tbody a {
+table tbody a {
display: block;
- padding: 0 1em 0;
text-decoration: none;
transition: all 0.25s ease-out;
}