aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2021-11-24 12:12:03 -0800
committerMike Crute <mike@crute.us>2021-11-24 12:15:09 -0800
commitebce860860eb2163bcd0614fc7e82c3f3a259cf3 (patch)
treee07ed8b290c37abf0134ed5ec8f5b6d5fce65d8f
parent09fa11a1dad5301e8e0aeb069fc1d11312b4a1c5 (diff)
downloadcloud-identity-broker-admin-pages.tar.bz2
cloud-identity-broker-admin-pages.tar.xz
cloud-identity-broker-admin-pages.zip
-rw-r--r--app/controllers/basic.go8
-rw-r--r--cmd/web/server.go4
-rw-r--r--templates/account.tpl58
-rw-r--r--templates/assets/site.css33
-rw-r--r--templates/assets/site.js186
-rw-r--r--templates/index.tpl18
-rw-r--r--templates/user.tpl38
7 files changed, 345 insertions, 0 deletions
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 {
11 return c.Render(http.StatusOK, "index.tpl", nil) 11 return c.Render(http.StatusOK, "index.tpl", nil)
12} 12}
13 13
14func AccountFormHandler(c echo.Context) error {
15 return c.Render(http.StatusOK, "account.tpl", nil)
16}
17
18func UserFormHandler(c echo.Context) error {
19 return c.Render(http.StatusOK, "user.tpl", nil)
20}
21
14func LogoutHandler(c echo.Context) error { 22func LogoutHandler(c echo.Context) error {
15 glecho.DeleteAllCookies(c) 23 glecho.DeleteAllCookies(c)
16 return c.Redirect(http.StatusFound, "/") 24 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) {
174 s.GET("/favicon.ico", echo.NotFoundHandler) 174 s.GET("/favicon.ico", echo.NotFoundHandler)
175 s.GET("/logout", controllers.LogoutHandler) 175 s.GET("/logout", controllers.LogoutHandler)
176 s.CachedStaticRoute("/assets", "assets") 176 s.CachedStaticRoute("/assets", "assets")
177 s.GET("/account", controllers.AccountFormHandler, am.Middleware)
178 s.GET("/account/*", controllers.AccountFormHandler, am.Middleware)
179 s.GET("/user", controllers.UserFormHandler, am.Middleware)
180 s.GET("/user/*", controllers.UserFormHandler, am.Middleware)
177 s.GET("/", controllers.IndexHandler, am.Middleware) 181 s.GET("/", controllers.IndexHandler, am.Middleware)
178 182
179 runner := service.NewAppRunner(ctx, s.Logger) 183 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 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Manage Account</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 <link rel="stylesheet" type="text/css" href="/assets/site.css" />
7 <script type="text/javascript" src="/assets/site.js"></script>
8 </head>
9 <body>
10 <div id="api-error"><p>Error communicating with the API.</p><p class="sub-message"></p></div>
11 <div id="api-success">Saved!</div>
12
13 <h1>Manage Account</h1>
14 <form method="PUT" action="/api/account" id="account-form">
15 <ol>
16 <li>
17 <label for="short_name">Short Name:</label>
18 <input type="text" id="short_name" name="short_name" />
19 </li>
20 <li>
21 <label for="account_type">Account Type:</label>
22 <input type="text" id="account_type" name="account_type" />
23 </li>
24 <li>
25 <label>Account Number:</label>
26 <input type="number" id="account_number" name="account_number" />
27 </li>
28 <li>
29 <label for="name">Name:</label>
30 <input type="text" id="name" name="name" />
31 </li>
32 <li>
33 <label for="console_session_duration">Console Session Duration (ms):</label>
34 <input type="number" id="console_session_duration" name="console_session_duration" value="21600000000000" />
35 </li>
36 <li>
37 <label for="vault_material">Vault Material:</label>
38 <input type="text" id="vault_material" name="vault_material" />
39 </li>
40 <li>
41 <label for="default_region">Default Region:</label>
42 <input type="text" id="default_region" name="default_region" />
43 </li>
44 <li>
45 <label for="users">Users:</label>
46 <select id="users" name="users" multiple></select>
47 </li>
48 <li class="submit">
49 <button id="back">Back</button>
50 <button id="submit-form">Save</button>
51 </li>
52 </ol>
53 </form>
54 <script type="text/javascript">
55 window.addEventListener('load', setupManageAccount);
56 </script>
57 </body>
58</html>
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 {
82body.isAdmin .admin { 82body.isAdmin .admin {
83 display: initial; 83 display: initial;
84} 84}
85form ol {
86 list-style: none;
87 padding: 0;
88 margin: 0;
89}
90form ol li {
91 display: flex;
92 margin: 0.5em 0;
93}
94form ol li * {
95 flex: 2;
96}
97form ol li label {
98 flex: 0.5;
99 font-weight: bolder;
100}
101input[type="number"] {
102 appearance: textfield;
103}
104form ol li button {
105 flex: 0;
106 padding: 0.5em 2em;
107 margin: 0 0.5em;
108}
109form ol li.submit {
110 justify-content: right;
111}
112.user-is-admin {
113 background-color: red;
114}
115.user-is-service {
116 background-color: green;
117}
85.tag { 118.tag {
86 display: block; 119 display: block;
87 float: right; 120 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) {
59 return false; 59 return false;
60} 60}
61 61
62function populateUserRow(row) {
63 var [key, value] = row;
64 var tokens = "";
65
66 if (value.is_admin) {
67 tokens += '<span class="tag user-is-admin">Admin</span>';
68 }
69
70 if (value.is_service) {
71 tokens += '<span class="tag user-is-service">Service</span>';
72 }
73
74 var out = fillTemplate("user_row_template", {
75 "username": key,
76 "tokens": tokens,
77 });
78 document.querySelector("#user-table tr").insertAdjacentHTML("afterend", out);
79}
80
62function populateAccountRow(row) { 81function populateAccountRow(row) {
63 document.querySelector("#account-table tr").insertAdjacentHTML("afterend", 82 document.querySelector("#account-table tr").insertAdjacentHTML("afterend",
64 fillTemplate(row["vendor"] + "_account_row_template", row)); 83 fillTemplate(row["vendor"] + "_account_row_template", row));
@@ -84,13 +103,180 @@ function isAdmin() {
84 return parseJWT()["admin"]; 103 return parseJWT()["admin"];
85} 104}
86 105
106function getApiUrl(formId) {
107 var urlParts = window.location.pathname.split("/");
108 if (urlParts.length != 3) {
109 return null;
110 }
111 var shortName = urlParts[urlParts.length-1];
112 return document.getElementById(formId).action + "/" + shortName;
113}
114
115function submitForm(target, url, method) {
116 var result = {};
117
118 target.querySelectorAll("form input, form select").forEach(e => {
119 if (e.selectedOptions !== undefined) {
120 var items = [];
121 Array.from(e.selectedOptions).forEach(i => items.push(i.value));
122 result[e.id] = items;
123 } else if (e.type == "number") {
124 result[e.id] = parseInt(e.value);
125 } else {
126 result[e.id] = e.value;
127 }
128 });
129
130 fetch(url, {
131 "method": method,
132 "headers": { "Content-Type": "application/vnd.broker.v2+json" },
133 "body": JSON.stringify(result)
134 })
135 .then(r => {
136 if (r.status < 299) {
137 return { "request": r };
138 } else {
139 return r.json().then(j => ({ "json": j, "request": r }))
140 }
141 })
142 .then(r => {
143 if (r.request.status > 299) {
144 document.getElementById("api-error").style.display = "block";
145 document.querySelector("#api-error .sub-message").innerText = r.json.message;
146 } else {
147 document.getElementById("api-success").style.display = "block";
148 }
149 })
150 .catch(_ => document.getElementById("api-error").style.display = "block");
151}
152
153function populateUsers(selected) {
154 fetch("/api/user")
155 .then(r => {
156 if (r.status == 404) {
157 return {};
158 }
159 // If they can't load the user endpoint they will not be able to submt updates
160 document.getElementById("submit-form").disabled = false;
161 return r.json()
162 })
163 .then(r => {
164 var e = document.getElementById("users");
165
166 Object.entries(r).forEach(([key, value]) => {
167 if (!selected[key]) {
168 var opt = document.createElement("option");
169 opt.value = key;
170 opt.innerText = key;
171 e.appendChild(opt);
172 }
173 });
174 })
175 .catch(_ => document.getElementById("api-error").style.display = "block");
176}
177
178function setupUserPage() {
179 document.getElementById("back").addEventListener('click', _ => window.location.pathname = "/");
180 document.getElementById("user-form").addEventListener('submit', e => e.preventDefault());
181
182 var apiUrl = getApiUrl("user-form");
183 if (apiUrl == null) {
184 document.getElementById("submit-form").addEventListener('click', e => {
185 var target = document.getElementById("user-form");
186 submitForm(target, target.action, "POST");
187 e.preventDefault();
188 return false;
189 });
190 } else {
191 document.getElementById("submit-form").addEventListener('click', e => {
192 var target = document.getElementById("user-form");
193 submitForm(target, getApiUrl(target.id), "PUT");
194 e.preventDefault();
195 return false;
196 });
197
198 fetch(apiUrl)
199 .then(r => r.json())
200 .then(r => {
201 Object.entries(r).forEach(([key, value]) => {
202 var e = document.getElementById(key);
203 if (e === null) {
204 return;
205 } else if (e.type == "text") {
206 e.value = value;
207 } else if (e.type == "checkbox") {
208 e.checked = value;
209 }
210 });
211 })
212 .catch(_ => document.getElementById("api-error").style.display = "block");
213 }
214}
215
216function setupManageAccount() {
217 document.getElementById("submit-form").disabled = true;
218 document.getElementById("back").addEventListener('click', _ => window.location.pathname = "/");
219 document.getElementById("account-form").addEventListener('submit', e => e.preventDefault());
220
221 var apiUrl = getApiUrl("account-form");
222 if (apiUrl == null) {
223 populateUsers({});
224 document.getElementById("submit-form").addEventListener('click', e => {
225 var target = document.getElementById("account-form");
226 submitForm(target, target.action, "POST");
227 e.preventDefault();
228 return false;
229 });
230 } else {
231 document.getElementById("submit-form").addEventListener('click', e => {
232 var target = document.getElementById("account-form");
233 submitForm(target, getApiUrl(target.id), "PUT");
234 e.preventDefault();
235 return false;
236 });
237
238 fetch(apiUrl)
239 .then(r => r.json())
240 .then(r => {
241 var selectedUsers = {};
242
243 Object.entries(r).forEach(([key, value]) => {
244 var e = document.getElementById(key);
245 if (e.type !== "select") {
246 e.value = value;
247 }
248
249 if (key == "users") {
250 value.forEach(v => {
251 var opt = document.createElement("option");
252 opt.value = v;
253 opt.selected = true;
254 opt.innerText = v;
255 e.appendChild(opt);
256 selectedUsers[v] = true;
257 });
258 }
259 });
260
261 return selectedUsers;
262 })
263 .then(populateUsers)
264 .catch(_ => document.getElementById("api-error").style.display = "block");
265 }
266}
267
87function setupHomePage() { 268function setupHomePage() {
269 document.getElementById("add-account").addEventListener('click', _ => window.location.pathname = "/account");
270 document.getElementById("add-user").addEventListener('click', _ => window.location.pathname = "/user");
271
88 fetch("/api/account").then(getJSON).then(r => r.forEach(populateAccountRow)); 272 fetch("/api/account").then(getJSON).then(r => r.forEach(populateAccountRow));
89 273
90 document.getElementById("username").innerText = parseJWT()["sub"]; 274 document.getElementById("username").innerText = parseJWT()["sub"];
91 275
92 if (isAdmin()) { 276 if (isAdmin()) {
93 document.body.classList.add("isAdmin"); 277 document.body.classList.add("isAdmin");
278
279 fetch("/api/user").then(getJSON).then(r => Object.entries(r).forEach(populateUserRow));
94 } 280 }
95 281
96 document.getElementById("show-api-key").addEventListener("click", _ => { 282 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 @@
20 <a data-content-type="application/vnd.broker.v2.credential.aws.ini" href="#/cli/[[ .short_name ]]">AWS CLI</a> | 20 <a data-content-type="application/vnd.broker.v2.credential.aws.ini" href="#/cli/[[ .short_name ]]">AWS CLI</a> |
21 <a data-content-type="application/vnd.broker.v2.credential.aws.sh" href="#/sh/[[ .short_name ]]">Bash</a> | 21 <a data-content-type="application/vnd.broker.v2.credential.aws.sh" href="#/sh/[[ .short_name ]]">Bash</a> |
22 <a data-content-type="application/vnd.broker.v2.credential.aws.psl" href="#/ps/[[ .short_name ]]">Powershell</a> 22 <a data-content-type="application/vnd.broker.v2.credential.aws.psl" href="#/ps/[[ .short_name ]]">Powershell</a>
23 <span class="admin">| <a href="/account/[[ .short_name ]]">Edit</a></span>
24 </td>
25 </tr>
26 </script>
27 <script id="user_row_template" type="text/template">
28 <tr>
29 <td>
30 <a href="/user/[[ .username ]]">[[ .username ]]</a>
31 [[ .tokens ]]
23 </td> 32 </td>
24 </tr> 33 </tr>
25 </script> 34 </script>
@@ -42,6 +51,15 @@
42 <th>Credentials</th> 51 <th>Credentials</th>
43 </tr> 52 </tr>
44 </table> 53 </table>
54 <p><button class="admin" id="add-account">Add Account</button></p>
55
56 <h1>User Accounts</h1>
57 <table id="user-table">
58 <tr>
59 <th>Username</th>
60 </tr>
61 </table>
62 <p><button class="admin" id="add-user">Add User</button></p>
45 63
46 <div id="api-key-block"> 64 <div id="api-key-block">
47 <h1>API Key</h1> 65 <h1>API Key</h1>
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 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Manage User</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 <link rel="stylesheet" type="text/css" href="/assets/site.css" />
7 <script type="text/javascript" src="/assets/site.js"></script>
8 </head>
9 <body>
10 <div id="api-error"><p>Error communicating with the API.</p><p class="sub-message"></p></div>
11 <div id="api-success">Saved!</div>
12
13 <h1>Manage User</h1>
14 <form method="PUT" action="/api/user" id="user-form">
15 <ol>
16 <li>
17 <label for="username">Username:</label>
18 <input type="text" id="username" name="username" />
19 </li>
20 <li>
21 <label for="is_admin">Admin:</label>
22 <input type="checkbox" id="is_admin" name="is_admin" />
23 </li>
24 <li>
25 <label for="is_service">Service User:</label>
26 <input type="checkbox" id="is_service" name="is_service" />
27 </li>
28 <li class="submit">
29 <button id="back">Back</button>
30 <button id="submit-form">Save</button>
31 </li>
32 </ol>
33 </form>
34 <script type="text/javascript">
35 window.addEventListener('load', setupUserPage);
36 </script>
37 </body>
38</html>