-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(APIKeys: (67) Added API Key UI (#69)
* update to api-key-ui * update * update * Added API Key ui * remove * update to api key create page * update to api key create page * added permission select ui * remove logs * some review comments * processed review comments * work on merging ui services * removed deps * update api key ui to be in tenants settings page * makefile admin cmd to create base user, added snackbar to tenants ui * added ordered map key for showing permissions in create api key page * Partial changes * added flash messages * update flash messages * update flash messages utils * some changes * update to api key ui and some changes in api err handling in dashboard * moved flash messages to pkg * added close button to flash message * removed test err * removed logs * added comments --------- Co-authored-by: Tim van Osch <[email protected]>
- Loading branch information
1 parent
6dd87ec
commit 8e03045
Showing
68 changed files
with
5,956 additions
and
1,141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,3 +67,7 @@ golib: golib-clean | |
--git-host=sensorbucket.nl --git-repo-id=api \ | ||
--enable-post-process-file \ | ||
--additional-properties=packageName=api,packageUrl='https://sensorbucket.nl' | ||
|
||
admin: | ||
echo '{"schema_id":"default", "traits": {"email":"[email protected]"}}' | http post 127.0.0.1:4434/admin/identities | jq .id | \ | ||
xargs -I uid echo '{"identity_id":"uid"}' | http post 127.0.0.1:4434/admin/recovery/code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package flash_messages | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
) | ||
|
||
type FlashMessage struct { | ||
Title string | ||
Description string | ||
MessageType FlashMessageType | ||
CopyButton bool | ||
} | ||
|
||
type FlashMessageType int | ||
|
||
const ( | ||
Warning FlashMessageType = iota | ||
Error | ||
Success | ||
) | ||
|
||
type FlashMessageRenderer func(msg FlashMessage) string | ||
|
||
type FlashMessages []FlashMessage | ||
|
||
type ctxKey int | ||
|
||
const ( | ||
ctxFlashMessagesKey ctxKey = iota | ||
) | ||
|
||
func (fm *FlashMessages) AsCookie() (http.Cookie, error) { | ||
b, err := json.Marshal(&fm) | ||
if err != nil { | ||
return http.Cookie{}, err | ||
} | ||
|
||
return http.Cookie{ | ||
Name: "flash_messages", | ||
Value: base64.StdEncoding.WithPadding(base64.NoPadding).EncodeToString(b), | ||
Secure: true, | ||
HttpOnly: true, | ||
Path: "/", | ||
|
||
// If somehow the messages are not shown after 5 seconds they're not relevant anymore | ||
MaxAge: 5, | ||
}, nil | ||
} | ||
|
||
func ExtractFlashMessage(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
if cookie, err := r.Cookie("flash_messages"); err == nil { | ||
|
||
// found flash messages to display to the user | ||
flashMessages := FlashMessages{} | ||
decoded, err := base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(cookie.Value) | ||
if err != nil { | ||
fmt.Printf("[Warning] found flash_messages cookie, but was not a valid base64 string\n") | ||
} else { | ||
err = json.Unmarshal(decoded, &flashMessages) | ||
if err != nil { | ||
fmt.Printf("[Warning] found flash_messages cookie, but couldnt convert to flash message: %s\n", err) | ||
} else { | ||
ctx = context.WithValue(ctx, ctxFlashMessagesKey, flashMessages) | ||
} | ||
} | ||
|
||
// unset the cookie, flash messages should only be shown once | ||
cookie.MaxAge = -1 | ||
cookie.Path = "/" | ||
http.SetCookie(w, cookie) | ||
} | ||
next.ServeHTTP(w, r.WithContext(ctx)) | ||
}) | ||
} | ||
|
||
func FlashMessagesFromContext(ctx context.Context) (FlashMessages, bool) { | ||
if messages := ctx.Value(ctxFlashMessagesKey); messages != nil { | ||
if flashMessages, ok := messages.(FlashMessages); ok { | ||
return flashMessages, ok | ||
} else { | ||
log.Printf("[Warning] found flash messages in request context but couldnt parse\n") | ||
} | ||
} | ||
return FlashMessages{}, false | ||
} | ||
|
||
func AddContextFlashMessages(r *http.Request, container *FlashMessagesContainer) { | ||
if inContext, ok := FlashMessagesFromContext(r.Context()); ok { | ||
container.contextFlashMessages = inContext | ||
} | ||
container.flashMessages = append(container.stagedFlashMessages, container.contextFlashMessages...) | ||
} | ||
|
||
func AddSuccessFlashMessageToPage(r *http.Request, container *FlashMessagesContainer, message string) { | ||
if inContext, ok := FlashMessagesFromContext(r.Context()); ok { | ||
container.contextFlashMessages = inContext | ||
} | ||
container.stagedFlashMessages = append(container.stagedFlashMessages, getFlashMessage("Success", message, Success, false)) | ||
container.flashMessages = append(container.contextFlashMessages, container.stagedFlashMessages...) | ||
} | ||
|
||
func AddWarningFlashMessageToPage(r *http.Request, container *FlashMessagesContainer, title string, message string, copyButton bool) { | ||
if inContext, ok := FlashMessagesFromContext(r.Context()); ok { | ||
container.contextFlashMessages = inContext | ||
} | ||
container.stagedFlashMessages = append(container.stagedFlashMessages, getFlashMessage(title, message, Warning, copyButton)) | ||
container.flashMessages = append(container.contextFlashMessages, container.stagedFlashMessages...) | ||
} | ||
|
||
func AddErrorFlashMessageToPage(r *http.Request, container *FlashMessagesContainer, message string) { | ||
if inContext, ok := FlashMessagesFromContext(r.Context()); ok { | ||
container.contextFlashMessages = inContext | ||
} | ||
container.stagedFlashMessages = append(container.stagedFlashMessages, getFlashMessage("Error", message, Error, false)) | ||
container.flashMessages = append(container.contextFlashMessages, container.stagedFlashMessages...) | ||
} | ||
|
||
func AddSuccessFlashMessage(w http.ResponseWriter, r *http.Request, message string) context.Context { | ||
return addFlashMessage(w, r, "Success", message, Success, false) | ||
} | ||
|
||
func AddWarningFlashMessage(w http.ResponseWriter, r *http.Request, title string, message string, copyButton bool) context.Context { | ||
return addFlashMessage(w, r, title, message, Warning, copyButton) | ||
} | ||
|
||
func AddErrorFlashMessage(w http.ResponseWriter, r *http.Request, message string) context.Context { | ||
return addFlashMessage(w, r, "Error", message, Error, false) | ||
} | ||
|
||
func WriteSuccessFlashMessage(w http.ResponseWriter, r *http.Request, message string) { | ||
fm := getFlashMessage("Success", message, Success, false) | ||
w.Write([]byte(RenderFlashMessage(fm))) | ||
} | ||
|
||
func WriteWarningFlashMessage(w http.ResponseWriter, r *http.Request, title string, message string, copyButton bool) { | ||
fm := getFlashMessage("Warning", message, Warning, copyButton) | ||
w.Write([]byte(RenderFlashMessage(fm))) | ||
} | ||
|
||
func WriteErrorFlashMessage(w http.ResponseWriter, r *http.Request, message string) { | ||
fm := getFlashMessage("Error", message, Error, false) | ||
w.Write([]byte(RenderFlashMessage(fm))) | ||
} | ||
|
||
func addFlashMessage(w http.ResponseWriter, r *http.Request, title string, description string, msgType FlashMessageType, copyButton bool) context.Context { | ||
flashMessages := FlashMessages{} | ||
flashMessages, _ = FlashMessagesFromContext(r.Context()) | ||
flashMessages = append(flashMessages, getFlashMessage(title, description, msgType, copyButton)) | ||
newCtx := context.WithValue(r.Context(), ctxFlashMessagesKey, flashMessages) | ||
writeFlashMessagesToCookie(w, r.WithContext(newCtx)) | ||
return newCtx | ||
} | ||
|
||
func getFlashMessage(title string, description string, msgType FlashMessageType, copyButton bool) FlashMessage { | ||
return FlashMessage{ | ||
Title: title, | ||
Description: description, | ||
MessageType: msgType, | ||
CopyButton: copyButton, | ||
} | ||
} | ||
|
||
func writeFlashMessagesToCookie(w http.ResponseWriter, r *http.Request) { | ||
if messages, ok := FlashMessagesFromContext(r.Context()); ok { | ||
res, err := messages.AsCookie() | ||
if err != nil { | ||
log.Printf("[Warning] couldnt set flash_messages cookie %s\n", err) | ||
} else { | ||
http.SetCookie(w, &res) | ||
} | ||
} else { | ||
log.Printf("[Warning] no flash_messages could be found to be set as cookie\n") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
|
||
{% func (fm *FlashMessagesContainer) Render() %} | ||
<div id="flash-messages" > | ||
{% for _, flashMessage := range fm.flashMessages %} | ||
{%= RenderFlashMessage(flashMessage) %} | ||
{% endfor %} | ||
</div> | ||
{% endfunc %} | ||
|
||
{% func RenderFlashMessage(msg FlashMessage) %} | ||
{% code | ||
color := "" | ||
icon := "" | ||
|
||
switch msg.MessageType { | ||
case Success: | ||
color = "green" | ||
icon = "mdi:success-circle-outline" | ||
case Warning: | ||
color = "orange" | ||
icon = "carbon:warning" | ||
case Error: | ||
color = "red" | ||
icon = "icon-park-outline:error" | ||
} | ||
%} | ||
<div class="p-3"> | ||
<!-- Required to include below tailwind classes in styles --> | ||
<div class="hidden"> | ||
<div class="text-red-600 bg-red-400 border-red-500"></div> | ||
<div class="border-red-400 bg-red-500"></div> | ||
<div class="bg-red-100"></div> | ||
|
||
<div class="text-orange-600 bg-orange-400 border-orange-500"></div> | ||
<div class="border-orange-400 bg-orange-500"></div> | ||
<div class="bg-orange-100"></div> | ||
|
||
<div class="text-green-600 bg-green-400 border-green-500"></div> | ||
<div class="border-green-400 bg-green-500"></div> | ||
<div class="bg-green-100"></div> | ||
</div> | ||
<div class="bg-{%s color %}-100 border-l-4 flash-message border-{%s color %}-400 text-{%s color %}-600 p-4" role="alert"> | ||
<div class="flex w-full"> | ||
<div class="py-2"> | ||
<iconify-icon icon="{%s icon %}" width="24" | ||
class="px-4 float-right text-{%s color %}-600"></iconify-icon> | ||
</div> | ||
<div class="w-full"> | ||
<p class="text-sm font-bold">{%s msg.Title %}</p> | ||
<br /> | ||
<div class="flex justify-start"> | ||
<p id="dialogue-value" class="text-sm w-full truncate"> | ||
{%s msg.Description %} | ||
</p> | ||
{% if msg.CopyButton %} | ||
<button onclick="copyValueToClipboard()" | ||
class="text-sm ml-1 bg-{%s color %}-400 hover:bg-{%s color %}-500 text-white border border-{%s color %}-500 rounded px-2 py-1"> | ||
Copy | ||
</button> | ||
{% endif %} | ||
</div> | ||
</div> | ||
<iconify-icon _="on click hide closest .flash-message" class="cursor-pointer" icon="material-symbols-light:close" width="18"></iconify-icon> | ||
</div> | ||
</div> | ||
<script type="text/javascript"> | ||
function copyValueToClipboard() { | ||
const text = document.getElementById("dialogue-value").innerText; | ||
navigator.clipboard.writeText(text); | ||
} | ||
</script> | ||
|
||
</div> | ||
{% endfunc %} | ||
|
||
{% code | ||
type FlashMessagesContainer struct { | ||
|
||
// Staged flash messages are set by the package user and will immediately be shown upon rendering | ||
stagedFlashMessages FlashMessages | ||
|
||
// Context flash messages are present in the request context, which is based of a cookie value 'flash_messages' | ||
contextFlashMessages FlashMessages | ||
|
||
// Accumulation of both staged and context flash messages | ||
flashMessages FlashMessages | ||
} | ||
%} |
Oops, something went wrong.