M internal/components/adminpartials.templ => internal/components/adminpartials.templ +51 -0
@@ 41,3 41,54 @@ templ AdminPartialUsersList(users []db.User) {
</tbody>
</table>
}
+
+templ AdminPartialDomainsList(domains []db.Domain) {
+ <table class="table-auto">
+ <thead>
+ <tr>
+ <th class="text-start p-2">
+ Id
+ </th>
+ <th class="text-start p-2">
+ Domain
+ </th>
+ <th class="text-start p-2">
+ A Record
+ </th>
+ <th class="text-start p-2">
+ Owner (ID/Email)
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, domain := range domains {
+ <tr class="border">
+ <td>
+ <div class="p-2">
+ <input class="border" value={ domain.Id } disabled/>
+ </div>
+ </td>
+ <td>
+ <div class="p-2">
+ { domain.Domain }
+ </div>
+ </td>
+ <td>
+ <div class="p-2">
+ { domain.A }
+ </div>
+ </td>
+ <td>
+ <div class="p-2">
+ <div class="flex flex-row-reverse gap-1">
+ <input class="peer" type="checkbox"/>
+ <input class="border hidden peer-checked:block" value={ domain.Owner.Email } disabled/>
+ <input class="border block peer-checked:hidden" value={ domain.Owner.ID } disabled/>
+ </div>
+ </div>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+}
M internal/components/adminpartials_templ.go => internal/components/adminpartials_templ.go +112 -0
@@ 99,3 99,115 @@ func AdminPartialUsersList(users []db.User) templ.Component {
return err
})
}
+
+func AdminPartialDomainsList(domains []db.Domain) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_8 := templ.GetChildren(ctx)
+ if var_8 == nil {
+ var_8 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, err = templBuffer.WriteString("<table class=\"table-auto\"><thead><tr><th class=\"text-start p-2\">")
+ if err != nil {
+ return err
+ }
+ var_9 := `Id`
+ _, err = templBuffer.WriteString(var_9)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
+ if err != nil {
+ return err
+ }
+ var_10 := `Domain`
+ _, err = templBuffer.WriteString(var_10)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
+ if err != nil {
+ return err
+ }
+ var_11 := `A Record`
+ _, err = templBuffer.WriteString(var_11)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</th><th class=\"text-start p-2\">")
+ if err != nil {
+ return err
+ }
+ var_12 := `Owner (ID/Email)`
+ _, err = templBuffer.WriteString(var_12)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</th></tr></thead><tbody>")
+ if err != nil {
+ return err
+ }
+ for _, domain := range domains {
+ _, err = templBuffer.WriteString("<tr class=\"border\"><td><div class=\"p-2\"><input class=\"border\" value=\"")
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(templ.EscapeString(domain.Id))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("\" disabled></div></td><td><div class=\"p-2\">")
+ if err != nil {
+ return err
+ }
+ var var_13 string = domain.Domain
+ _, err = templBuffer.WriteString(templ.EscapeString(var_13))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</div></td><td><div class=\"p-2\">")
+ if err != nil {
+ return err
+ }
+ var var_14 string = domain.A
+ _, err = templBuffer.WriteString(templ.EscapeString(var_14))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("</div></td><td><div class=\"p-2\"><div class=\"flex flex-row-reverse gap-1\"><input class=\"peer\" type=\"checkbox\"><input class=\"border hidden peer-checked:block\" value=\"")
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(templ.EscapeString(domain.Owner.Email))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("\" disabled><input class=\"border block peer-checked:hidden\" value=\"")
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(templ.EscapeString(domain.Owner.ID))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("\" disabled></div></div></td></tr>")
+ if err != nil {
+ return err
+ }
+ }
+ _, err = templBuffer.WriteString("</tbody></table>")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
M internal/components/manageadmin.templ => internal/components/manageadmin.templ +1 -2
@@ 33,8 33,7 @@ templ ManageAdminUsers() {
templ ManageAdminDomains() {
@ManageBase("ManageAdmin") {
- <div>
- list of all domains on the instance, with owner details and such
+ <div hx-get="/manage/admin/partials/domains_list" hx-trigger="load" hx-target="this">
</div>
}
}
M internal/components/manageadmin_templ.go => internal/components/manageadmin_templ.go +1 -10
@@ 169,16 169,7 @@ func ManageAdminDomains() templ.Component {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
- _, err = templBuffer.WriteString("<div>")
- if err != nil {
- return err
- }
- var_14 := `list of all domains on the instance, with owner details and such`
- _, err = templBuffer.WriteString(var_14)
- if err != nil {
- return err
- }
- _, err = templBuffer.WriteString("</div>")
+ _, err = templBuffer.WriteString("<div hx-get=\"/manage/admin/partials/domains_list\" hx-trigger=\"load\" hx-target=\"this\"></div>")
if err != nil {
return err
}
M internal/db/domains.go => internal/db/domains.go +39 -0
@@ 13,11 13,18 @@ import (
"github.com/oklog/ulid/v2"
)
+type DomainOwner struct {
+ ID string
+ Email string
+}
+
type Domain struct {
Id string
ApiKey string
Domain string
A string
+
+ Owner DomainOwner
}
func FetchDomainsForUser(userId string) ([]Domain, error) {
@@ 45,6 52,38 @@ func FetchDomainsForUser(userId string) ([]Domain, error) {
return domains, nil
}
+func FetchAllDomains() ([]Domain, error) {
+ rows, err := DBConn.Query(`SELECT id, apikey, ddns_domain, a_record, owner
+ FROM domains`)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var domains []Domain
+ for rows.Next() {
+ var domain Domain
+ err = rows.Scan(&domain.Id, &domain.ApiKey, &domain.Domain, &domain.A, &domain.Owner.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ ownerEmail, err := FetchUserEmail(domain.Owner.ID)
+ if err != nil {
+ return nil, err
+ }
+ domain.Owner.Email = ownerEmail
+
+ domains = append(domains, domain)
+ }
+ err = rows.Err()
+ if err != nil {
+ return nil, err
+ }
+
+ return domains, nil
+}
+
func CreateDomain(domain string, aRecord string, owner string) error {
ulid := ulid.Make().String()
apikey := util.GenApiKey()
M internal/db/users.go => internal/db/users.go +11 -0
@@ 117,6 117,17 @@ func FetchAllUsers() ([]User, error) {
return users, nil
}
+func FetchUserEmail(id string) (string, error) {
+ var email string
+ err := DBConn.QueryRow(`SELECT email FROM users WHERE id = $1`,
+ id).Scan(&email)
+ if err != nil {
+ return "", err
+ }
+
+ return email, nil
+}
+
func DeleteUser(id string) error {
err := DeleteDomainsForUser(id)
if err != nil {
M internal/handlers/adminpartials.go => internal/handlers/adminpartials.go +13 -0
@@ 26,3 26,16 @@ func AdminPartialUsersList() gin.HandlerFunc {
}
}
}
+
+func AdminPartialDomainsList() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ users, err := db.FetchAllDomains()
+ if err != nil {
+ // TODO: Handle this better
+ c.String(http.StatusInternalServerError, "Something went wrong while fetching domains")
+ c.Abort()
+ } else {
+ c.HTML(http.StatusOK, "", components.AdminPartialDomainsList(users))
+ }
+ }
+}
M internal/routers/frontend.go => internal/routers/frontend.go +1 -0
@@ 59,6 59,7 @@ func SetupFrontendRouter(sm *scs.SessionManager) *gin.Engine {
manageAdmin.POST("/users", handlers.PostUser())
manageAdmin.GET("/partials/users_list", handlers.AdminPartialUsersList())
+ manageAdmin.GET("/partials/domains_list", handlers.AdminPartialDomainsList())
}
return r
M static/styles.css => static/styles.css +35 -7
@@ 534,6 534,10 @@ video {
--tw-backdrop-sepia: ;
}
+.block {
+ display: block;
+}
+
.flex {
display: flex;
}
@@ 542,6 546,10 @@ video {
display: table;
}
+.hidden {
+ display: none;
+}
+
.w-full {
width: 100%;
}
@@ 554,6 562,10 @@ video {
table-layout: auto;
}
+.flex-row-reverse {
+ flex-direction: row-reverse;
+}
+
.flex-col {
flex-direction: column;
}
@@ 570,10 582,19 @@ video {
gap: 1rem;
}
+.gap-1 {
+ gap: 0.25rem;
+}
+
.border {
border-width: 1px;
}
+.bg-emerald-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(167 243 208 / var(--tw-bg-opacity));
+}
+
.bg-lime-200 {
--tw-bg-opacity: 1;
background-color: rgb(217 249 157 / var(--tw-bg-opacity));
@@ 589,19 610,14 @@ video {
background-color: rgb(153 246 228 / var(--tw-bg-opacity));
}
-.bg-yellow-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 240 138 / var(--tw-bg-opacity));
-}
-
.bg-violet-200 {
--tw-bg-opacity: 1;
background-color: rgb(221 214 254 / var(--tw-bg-opacity));
}
-.bg-emerald-200 {
+.bg-yellow-200 {
--tw-bg-opacity: 1;
- background-color: rgb(167 243 208 / var(--tw-bg-opacity));
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}
.p-1 {
@@ 637,3 653,15 @@ video {
--tw-text-opacity: 1;
color: rgb(225 29 72 / var(--tw-text-opacity));
}
+
+.peer:checked ~ .peer-checked\:block {
+ display: block;
+}
+
+.peer:checked ~ .peer-checked\:hidden {
+ display: none;
+}
+
+.peer:disabled ~ .peer-disabled\:block {
+ display: block;
+}