DEVELOPMENT ENVIRONMENT

~liljamo/felu

0ea20b4e37252798761c5d4442dc1e0641c86673 — Jonni Liljamo 10 months ago 4882bac
feat: apikey refreshing and eye toggle
M internal/components/managepartials.templ => internal/components/managepartials.templ +37 -15
@@ 3,18 3,21 @@ package components
import "git.src.quest/~skye/felu-ddns/internal/db"
import "fmt"

script toggleApiKeyVisibility(id string) {
	var input = document.getElementById("domain_apikey_" + id);
	var eye_vis = document.getElementById("domain_eye_vis_" + id);
	var eye_hid = document.getElementById("domain_eye_hid_" + id);
	if (input.type === "password") {
		input.type = "text";
	} else {
		input.type = "password";
	};
	eye_vis.classList.toggle("hidden");
	eye_hid.classList.toggle("hidden");
}

templ ManagePartialDomains(domains []db.Domain) {
	if len(domains) > 0 {
		<script>
			function toggleApiKeyVisibility(inputId) {
				var input = document.getElementById(inputId)
				if (input.type === "password") {
					input.type = "text";
				} else {
					input.type = "password";
				}
			}
		</script>
		<table class="table-auto">
			<thead>
				<tr>


@@ 47,14 50,33 @@ templ ManagePartialDomains(domains []db.Domain) {
							</div>
						</td>
						<td>
							<div class="p-2">
								<!-- TODO: Add refreshing -->
							<div class="p-2 flex items-center gap-1">
								<input class="border" disabled type="password" value={ domain.ApiKey }
									id={ fmt.Sprintf("domain_apikey_%s", domain.Id) }
								/>
								<!-- TODO: Replace with one of those eye thingies -->
								<input type="checkbox" onclick="toggleApiKeyVisibility(this.previousElementSibling.id)"/>
								<span>show</span>
								<div class="border p-1 flex items-center justify-center">
									<input class="absolute w-6 h-6 appearance-none cursor-pointer" type="checkbox"
										onClick={ toggleApiKeyVisibility(domain.Id) }
									/>
									<div>
										<svg id={ fmt.Sprintf("domain_eye_vis_%s", domain.Id) } class="hidden w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
											<path d="M15.0007 12C15.0007 13.6569 13.6576 15 12.0007 15C10.3439 15 9.00073 13.6569 9.00073 12C9.00073 10.3431 10.3439 9 12.0007 9C13.6576 9 15.0007 10.3431 15.0007 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
											<path d="M12.0012 5C7.52354 5 3.73326 7.94288 2.45898 12C3.73324 16.0571 7.52354 19 12.0012 19C16.4788 19 20.2691 16.0571 21.5434 12C20.2691 7.94291 16.4788 5 12.0012 5Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
										</svg>

										<svg id={ fmt.Sprintf("domain_eye_hid_%s", domain.Id) } class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
											<path d="M2.99902 3L20.999 21M9.8433 9.91364C9.32066 10.4536 8.99902 11.1892 8.99902 12C8.99902 13.6569 10.3422 15 11.999 15C12.8215 15 13.5667 14.669 14.1086 14.133M6.49902 6.64715C4.59972 7.90034 3.15305 9.78394 2.45703 12C3.73128 16.0571 7.52159 19 11.9992 19C13.9881 19 15.8414 18.4194 17.3988 17.4184M10.999 5.04939C11.328 5.01673 11.6617 5 11.9992 5C16.4769 5 20.2672 7.94291 21.5414 12C21.2607 12.894 20.8577 13.7338 20.3522 14.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
										</svg>
									</div>
								</div>

								<button class="border p-1" hx-confirm="Refresh?"
									hx-post={ fmt.Sprintf("/manage/domains/%s/api_key", domain.Id) }
								>
									<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
										<path d="M11.5 20.5C6.80558 20.5 3 16.6944 3 12C3 7.30558 6.80558 3.5 11.5 3.5C16.1944 3.5 20 7.30558 20 12C20 13.5433 19.5887 14.9905 18.8698 16.238M22.5 15L18.8698 16.238M17.1747 12.3832L18.5289 16.3542L18.8698 16.238" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
									</svg>
								</button>
							</div>
						</td>
						<td>

M internal/components/managepartials_templ.go => internal/components/managepartials_templ.go +58 -45
@@ 12,6 12,23 @@ import "bytes"
import "git.src.quest/~skye/felu-ddns/internal/db"
import "fmt"

func toggleApiKeyVisibility(id string) templ.ComponentScript {
	return templ.ComponentScript{
		Name: `__templ_toggleApiKeyVisibility_542b`,
		Function: `function __templ_toggleApiKeyVisibility_542b(id){var input = document.getElementById("domain_apikey_" + id);
	var eye_vis = document.getElementById("domain_eye_vis_" + id);
	var eye_hid = document.getElementById("domain_eye_hid_" + id);
	if (input.type === "password") {
		input.type = "text";
	} else {
		input.type = "password";
	};
	eye_vis.classList.toggle("hidden");
	eye_hid.classList.toggle("hidden");}`,
		Call: templ.SafeScript(`__templ_toggleApiKeyVisibility_542b`, id),
	}
}

func ManagePartialDomains(domains []db.Domain) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
		templBuffer, templIsBuffer := w.(*bytes.Buffer)


@@ 26,29 43,20 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
		}
		ctx = templ.ClearChildren(ctx)
		if len(domains) > 0 {
			_, err = templBuffer.WriteString("<script>")
			_, err = templBuffer.WriteString("<table class=\"table-auto\"><thead><tr><th class=\"text-start p-2\">")
			if err != nil {
				return err
			}
			var_2 := `
			function toggleApiKeyVisibility(inputId) {
				var input = document.getElementById(inputId)
				if (input.type === "password") {
					input.type = "text";
				} else {
					input.type = "password";
				}
			}
		`
			var_2 := `Domain`
			_, err = templBuffer.WriteString(var_2)
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</script> <table class=\"table-auto\"><thead><tr><th class=\"text-start p-2\">")
			_, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
			if err != nil {
				return err
			}
			var_3 := `Domain`
			var_3 := `A Record`
			_, err = templBuffer.WriteString(var_3)
			if err != nil {
				return err


@@ 57,20 65,11 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
			if err != nil {
				return err
			}
			var_4 := `A Record`
			var_4 := `Api Key`
			_, err = templBuffer.WriteString(var_4)
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
			if err != nil {
				return err
			}
			var_5 := `Api Key`
			_, err = templBuffer.WriteString(var_5)
			if err != nil {
				return err
			}
			_, err = templBuffer.WriteString("</th></tr></thead><tbody>")
			if err != nil {
				return err


@@ 80,8 79,8 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
				if err != nil {
					return err
				}
				var var_6 string = domain.Domain
				_, err = templBuffer.WriteString(templ.EscapeString(var_6))
				var var_5 string = domain.Domain
				_, err = templBuffer.WriteString(templ.EscapeString(var_5))
				if err != nil {
					return err
				}


@@ 89,8 88,8 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
				if err != nil {
					return err
				}
				var var_7 string = getDomainPattern()
				_, err = templBuffer.WriteString(templ.EscapeString(var_7))
				var var_6 string = getDomainPattern()
				_, err = templBuffer.WriteString(templ.EscapeString(var_6))
				if err != nil {
					return err
				}


@@ 98,10 97,10 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
				if err != nil {
					return err
				}
				var_8 := ` TODO: Make this editable
				var_7 := ` TODO: Make this editable
									And refresh the div with /manage/partials/domains
								`
				_, err = templBuffer.WriteString(var_8)
				_, err = templBuffer.WriteString(var_7)
				if err != nil {
					return err
				}


@@ 113,50 112,64 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\"></div></td><td><div class=\"p-2\"><!--")
				_, err = templBuffer.WriteString("\"></div></td><td><div class=\"p-2 flex items-center gap-1\"><input class=\"border\" disabled type=\"password\" value=\"")
				if err != nil {
					return err
				}
				var_9 := ` TODO: Add refreshing `
				_, err = templBuffer.WriteString(var_9)
				_, err = templBuffer.WriteString(templ.EscapeString(domain.ApiKey))
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\" id=\"")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("--><input class=\"border\" disabled type=\"password\" value=\"")
				_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_apikey_%s", domain.Id)))
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString(templ.EscapeString(domain.ApiKey))
				_, err = templBuffer.WriteString("\"><div class=\"border p-1 flex items-center justify-center\">")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\" id=\"")
				err = templ.RenderScriptItems(ctx, templBuffer, toggleApiKeyVisibility(domain.Id))
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_apikey_%s", domain.Id)))
				_, err = templBuffer.WriteString("<input class=\"absolute w-6 h-6 appearance-none cursor-pointer\" type=\"checkbox\" onClick=\"")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\"><!--")
				var var_8 templ.ComponentScript = toggleApiKeyVisibility(domain.Id)
				_, err = templBuffer.WriteString(var_8.Call)
				if err != nil {
					return err
				}
				var_10 := ` TODO: Replace with one of those eye thingies `
				_, err = templBuffer.WriteString(var_10)
				_, err = templBuffer.WriteString("\"><div><svg id=\"")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("--><input type=\"checkbox\" onclick=\"toggleApiKeyVisibility(this.previousElementSibling.id)\"><span>")
				_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_vis_%s", domain.Id)))
				if err != nil {
					return err
				}
				var_11 := `show`
				_, err = templBuffer.WriteString(var_11)
				_, err = templBuffer.WriteString("\" class=\"hidden w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.0007 12C15.0007 13.6569 13.6576 15 12.0007 15C10.3439 15 9.00073 13.6569 9.00073 12C9.00073 10.3431 10.3439 9 12.0007 9C13.6576 9 15.0007 10.3431 15.0007 12Z\" stroke=\"black\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path><path d=\"M12.0012 5C7.52354 5 3.73326 7.94288 2.45898 12C3.73324 16.0571 7.52354 19 12.0012 19C16.4788 19 20.2691 16.0571 21.5434 12C20.2691 7.94291 16.4788 5 12.0012 5Z\" stroke=\"black\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg><svg id=\"")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("</span></div></td><td><div class=\"p-2\"><button class=\"border p-1\" hx-confirm=\"Sure?\" hx-delete=\"")
				_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_hid_%s", domain.Id)))
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\" class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.99902 3L20.999 21M9.8433 9.91364C9.32066 10.4536 8.99902 11.1892 8.99902 12C8.99902 13.6569 10.3422 15 11.999 15C12.8215 15 13.5667 14.669 14.1086 14.133M6.49902 6.64715C4.59972 7.90034 3.15305 9.78394 2.45703 12C3.73128 16.0571 7.52159 19 11.9992 19C13.9881 19 15.8414 18.4194 17.3988 17.4184M10.999 5.04939C11.328 5.01673 11.6617 5 11.9992 5C16.4769 5 20.2672 7.94291 21.5414 12C21.2607 12.894 20.8577 13.7338 20.3522 14.5\" stroke=\"black\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg></div></div><button class=\"border p-1\" hx-confirm=\"Refresh?\" hx-post=\"")
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s/api_key", domain.Id)))
				if err != nil {
					return err
				}
				_, err = templBuffer.WriteString("\"><svg class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M11.5 20.5C6.80558 20.5 3 16.6944 3 12C3 7.30558 6.80558 3.5 11.5 3.5C16.1944 3.5 20 7.30558 20 12C20 13.5433 19.5887 14.9905 18.8698 16.238M22.5 15L18.8698 16.238M17.1747 12.3832L18.5289 16.3542L18.8698 16.238\" stroke=\"black\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg></button></div></td><td><div class=\"p-2\"><button class=\"border p-1\" hx-confirm=\"Sure?\" hx-delete=\"")
				if err != nil {
					return err
				}


@@ 168,8 181,8 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
				if err != nil {
					return err
				}
				var_12 := `Delete`
				_, err = templBuffer.WriteString(var_12)
				var_9 := `Delete`
				_, err = templBuffer.WriteString(var_9)
				if err != nil {
					return err
				}

M internal/db/domains.go => internal/db/domains.go +9 -0
@@ 142,3 142,12 @@ func UpdateDomainARecord(ddns_domain string, providedApiKey string, aRecord stri
	
	return nil
}

func RefreshDomainApiKey(id string, user_id string) error {
	apiKey := util.GenApiKey()
	_, err := DBConn.Exec(`UPDATE domains SET apikey = $1 WHERE id = $2 AND owner = $3`, apiKey, id, user_id)
	if err != nil {
		return err
	}
	return nil
}

M internal/handlers/domains.go => internal/handlers/domains.go +23 -0
@@ 93,3 93,26 @@ func DeleteDomain() gin.HandlerFunc {
		c.Header("HX-Trigger", "update-domain-list")
	}
}

func RefreshDomainApiKey() gin.HandlerFunc {
	return func(c *gin.Context) {
		id := c.Param("id")

		userId, exists := c.Get("user_id")
		if !exists {
			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
			c.Abort()
			return
		}

		err := db.RefreshDomainApiKey(id, userId.(string))
		if err != nil {
			// FIXME: Handle better
			c.String(http.StatusInternalServerError, "Something went wrong while updating the api key")
			c.Abort()
			return
		}

		c.Header("HX-Trigger", "update-domain-list")
	}
}

M internal/routers/frontend.go => internal/routers/frontend.go +1 -0
@@ 46,6 46,7 @@ func SetupFrontendRouter(sm *scs.SessionManager) *gin.Engine {
		manage.POST("/domains", handlers.PostDomain())
		manage.PATCH("/domains/:id") // TODO:
		manage.DELETE("/domains/:id", handlers.DeleteDomain())
		manage.POST("/domains/:id/api_key", handlers.RefreshDomainApiKey())

		manage.GET("/partials/domains", handlers.ManagePartialDomains())
	}

M static/styles.css => static/styles.css +33 -7
@@ 534,8 534,8 @@ video {
  --tw-backdrop-sepia:  ;
}

.m-2 {
  margin: 0.5rem;
.absolute {
  position: absolute;
}

.block {


@@ 554,6 554,22 @@ video {
  display: none;
}

.h-5 {
  height: 1.25rem;
}

.h-6 {
  height: 1.5rem;
}

.w-5 {
  width: 1.25rem;
}

.w-6 {
  width: 1.5rem;
}

.w-full {
  width: 100%;
}


@@ 580,6 596,12 @@ video {
  list-style-type: none;
}

.appearance-none {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
}

.flex-row-reverse {
  flex-direction: row-reverse;
}


@@ 592,6 614,10 @@ video {
  align-items: center;
}

.justify-center {
  justify-content: center;
}

.gap-1 {
  gap: 0.25rem;
}


@@ 608,6 634,11 @@ video {
  border-width: 1px;
}

.bg-amber-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(253 230 138 / var(--tw-bg-opacity));
}

.bg-emerald-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(167 243 208 / var(--tw-bg-opacity));


@@ 638,11 669,6 @@ video {
  background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}

.bg-amber-200 {
  --tw-bg-opacity: 1;
  background-color: rgb(253 230 138 / var(--tw-bg-opacity));
}

.p-1 {
  padding: 0.25rem;
}