From ebce860860eb2163bcd0614fc7e82c3f3a259cf3 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Wed, 24 Nov 2021 12:12:03 -0800 Subject: WIP --- app/controllers/basic.go | 8 ++ cmd/web/server.go | 4 + templates/account.tpl | 58 +++++++++++++++ templates/assets/site.css | 33 ++++++++ templates/assets/site.js | 186 ++++++++++++++++++++++++++++++++++++++++++++++ templates/index.tpl | 18 +++++ templates/user.tpl | 38 ++++++++++ 7 files changed, 345 insertions(+) create mode 100644 templates/account.tpl create mode 100644 templates/user.tpl diff --git a/app/controllers/basic.go b/app/controllers/basic.go index eff97e1..5363640 100644 --- a/app/controllers/basic.go +++ b/app/controllers/basic.go @@ -11,6 +11,14 @@ func IndexHandler(c echo.Context) error { return c.Render(http.StatusOK, "index.tpl", nil) } +func AccountFormHandler(c echo.Context) error { + return c.Render(http.StatusOK, "account.tpl", nil) +} + +func UserFormHandler(c echo.Context) error { + return c.Render(http.StatusOK, "user.tpl", nil) +} + func LogoutHandler(c echo.Context) error { glecho.DeleteAllCookies(c) return c.Redirect(http.StatusFound, "/") diff --git a/cmd/web/server.go b/cmd/web/server.go index 9f750bc..282bf39 100644 --- a/cmd/web/server.go +++ b/cmd/web/server.go @@ -174,6 +174,10 @@ func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) { s.GET("/favicon.ico", echo.NotFoundHandler) s.GET("/logout", controllers.LogoutHandler) s.CachedStaticRoute("/assets", "assets") + s.GET("/account", controllers.AccountFormHandler, am.Middleware) + s.GET("/account/*", controllers.AccountFormHandler, am.Middleware) + s.GET("/user", controllers.UserFormHandler, am.Middleware) + s.GET("/user/*", controllers.UserFormHandler, am.Middleware) s.GET("/", controllers.IndexHandler, am.Middleware) runner := service.NewAppRunner(ctx, s.Logger) diff --git a/templates/account.tpl b/templates/account.tpl new file mode 100644 index 0000000..1f3bbfa --- /dev/null +++ b/templates/account.tpl @@ -0,0 +1,58 @@ + + + + Manage Account + + + + + +

Error communicating with the API.

+
Saved!
+ +

Manage Account

+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
  5. + + +
  6. +
  7. + + +
  8. +
  9. + + +
  10. +
  11. + + +
  12. +
  13. + + +
  14. +
  15. + + +
  16. +
  17. + + +
  18. +
+
+ + + diff --git a/templates/assets/site.css b/templates/assets/site.css index df1bab8..93503ab 100644 --- a/templates/assets/site.css +++ b/templates/assets/site.css @@ -82,6 +82,39 @@ tt { body.isAdmin .admin { display: initial; } +form ol { + list-style: none; + padding: 0; + margin: 0; +} +form ol li { + display: flex; + margin: 0.5em 0; +} +form ol li * { + flex: 2; +} +form ol li label { + flex: 0.5; + font-weight: bolder; +} +input[type="number"] { + appearance: textfield; +} +form ol li button { + flex: 0; + padding: 0.5em 2em; + margin: 0 0.5em; +} +form ol li.submit { + justify-content: right; +} +.user-is-admin { + background-color: red; +} +.user-is-service { + background-color: green; +} .tag { display: block; float: right; diff --git a/templates/assets/site.js b/templates/assets/site.js index 7ba4b15..66fd43f 100644 --- a/templates/assets/site.js +++ b/templates/assets/site.js @@ -59,6 +59,25 @@ function accountTableLinkClick(event) { return false; } +function populateUserRow(row) { + var [key, value] = row; + var tokens = ""; + + if (value.is_admin) { + tokens += 'Admin'; + } + + if (value.is_service) { + tokens += 'Service'; + } + + var out = fillTemplate("user_row_template", { + "username": key, + "tokens": tokens, + }); + document.querySelector("#user-table tr").insertAdjacentHTML("afterend", out); +} + function populateAccountRow(row) { document.querySelector("#account-table tr").insertAdjacentHTML("afterend", fillTemplate(row["vendor"] + "_account_row_template", row)); @@ -84,13 +103,180 @@ function isAdmin() { return parseJWT()["admin"]; } +function getApiUrl(formId) { + var urlParts = window.location.pathname.split("/"); + if (urlParts.length != 3) { + return null; + } + var shortName = urlParts[urlParts.length-1]; + return document.getElementById(formId).action + "/" + shortName; +} + +function submitForm(target, url, method) { + var result = {}; + + target.querySelectorAll("form input, form select").forEach(e => { + if (e.selectedOptions !== undefined) { + var items = []; + Array.from(e.selectedOptions).forEach(i => items.push(i.value)); + result[e.id] = items; + } else if (e.type == "number") { + result[e.id] = parseInt(e.value); + } else { + result[e.id] = e.value; + } + }); + + fetch(url, { + "method": method, + "headers": { "Content-Type": "application/vnd.broker.v2+json" }, + "body": JSON.stringify(result) + }) + .then(r => { + if (r.status < 299) { + return { "request": r }; + } else { + return r.json().then(j => ({ "json": j, "request": r })) + } + }) + .then(r => { + if (r.request.status > 299) { + document.getElementById("api-error").style.display = "block"; + document.querySelector("#api-error .sub-message").innerText = r.json.message; + } else { + document.getElementById("api-success").style.display = "block"; + } + }) + .catch(_ => document.getElementById("api-error").style.display = "block"); +} + +function populateUsers(selected) { + fetch("/api/user") + .then(r => { + if (r.status == 404) { + return {}; + } + // If they can't load the user endpoint they will not be able to submt updates + document.getElementById("submit-form").disabled = false; + return r.json() + }) + .then(r => { + var e = document.getElementById("users"); + + Object.entries(r).forEach(([key, value]) => { + if (!selected[key]) { + var opt = document.createElement("option"); + opt.value = key; + opt.innerText = key; + e.appendChild(opt); + } + }); + }) + .catch(_ => document.getElementById("api-error").style.display = "block"); +} + +function setupUserPage() { + document.getElementById("back").addEventListener('click', _ => window.location.pathname = "/"); + document.getElementById("user-form").addEventListener('submit', e => e.preventDefault()); + + var apiUrl = getApiUrl("user-form"); + if (apiUrl == null) { + document.getElementById("submit-form").addEventListener('click', e => { + var target = document.getElementById("user-form"); + submitForm(target, target.action, "POST"); + e.preventDefault(); + return false; + }); + } else { + document.getElementById("submit-form").addEventListener('click', e => { + var target = document.getElementById("user-form"); + submitForm(target, getApiUrl(target.id), "PUT"); + e.preventDefault(); + return false; + }); + + fetch(apiUrl) + .then(r => r.json()) + .then(r => { + Object.entries(r).forEach(([key, value]) => { + var e = document.getElementById(key); + if (e === null) { + return; + } else if (e.type == "text") { + e.value = value; + } else if (e.type == "checkbox") { + e.checked = value; + } + }); + }) + .catch(_ => document.getElementById("api-error").style.display = "block"); + } +} + +function setupManageAccount() { + document.getElementById("submit-form").disabled = true; + document.getElementById("back").addEventListener('click', _ => window.location.pathname = "/"); + document.getElementById("account-form").addEventListener('submit', e => e.preventDefault()); + + var apiUrl = getApiUrl("account-form"); + if (apiUrl == null) { + populateUsers({}); + document.getElementById("submit-form").addEventListener('click', e => { + var target = document.getElementById("account-form"); + submitForm(target, target.action, "POST"); + e.preventDefault(); + return false; + }); + } else { + document.getElementById("submit-form").addEventListener('click', e => { + var target = document.getElementById("account-form"); + submitForm(target, getApiUrl(target.id), "PUT"); + e.preventDefault(); + return false; + }); + + fetch(apiUrl) + .then(r => r.json()) + .then(r => { + var selectedUsers = {}; + + Object.entries(r).forEach(([key, value]) => { + var e = document.getElementById(key); + if (e.type !== "select") { + e.value = value; + } + + if (key == "users") { + value.forEach(v => { + var opt = document.createElement("option"); + opt.value = v; + opt.selected = true; + opt.innerText = v; + e.appendChild(opt); + selectedUsers[v] = true; + }); + } + }); + + return selectedUsers; + }) + .then(populateUsers) + .catch(_ => document.getElementById("api-error").style.display = "block"); + } +} + function setupHomePage() { + document.getElementById("add-account").addEventListener('click', _ => window.location.pathname = "/account"); + document.getElementById("add-user").addEventListener('click', _ => window.location.pathname = "/user"); + fetch("/api/account").then(getJSON).then(r => r.forEach(populateAccountRow)); document.getElementById("username").innerText = parseJWT()["sub"]; if (isAdmin()) { document.body.classList.add("isAdmin"); + + fetch("/api/user").then(getJSON).then(r => Object.entries(r).forEach(populateUserRow)); } document.getElementById("show-api-key").addEventListener("click", _ => { diff --git a/templates/index.tpl b/templates/index.tpl index 19d533e..2d7d22b 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -20,6 +20,15 @@ AWS CLI | Bash | Powershell + | Edit + + + + @@ -42,6 +51,15 @@ Credentials +

+ +

User Accounts

+ + + + +
Username
+

API Key

diff --git a/templates/user.tpl b/templates/user.tpl new file mode 100644 index 0000000..cae7947 --- /dev/null +++ b/templates/user.tpl @@ -0,0 +1,38 @@ + + + + Manage User + + + + + +

Error communicating with the API.

+
Saved!
+ +

Manage User

+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
  5. + + +
  6. +
  7. + + +
  8. +
+
+ + + -- cgit v1.2.3