forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview/pages: rework knots pages to support new flow

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

anirudh.fi 2c2b5320 fe10e193

verified
-16
appview/pages/pages.go
···
return p.executePlain("knots/fragments/knotListing", w, params)
}
-
type KnotListingFullParams struct {
-
Registrations []db.Registration
-
}
-
-
func (p *Pages) KnotListingFull(w io.Writer, params KnotListingFullParams) error {
-
return p.executePlain("knots/fragments/knotListingFull", w, params)
-
}
-
-
type KnotSecretParams struct {
-
Secret string
-
}
-
-
func (p *Pages) KnotSecret(w io.Writer, params KnotSecretParams) error {
-
return p.executePlain("knots/fragments/secret", w, params)
-
}
-
type SpindlesParams struct {
LoggedInUser *oauth.User
Spindles []db.Spindle
···
return p.executePlain("knots/fragments/knotListing", w, params)
}
type SpindlesParams struct {
LoggedInUser *oauth.User
Spindles []db.Spindle
+86 -29
appview/pages/templates/knots/dashboard.html
···
-
{{ define "title" }}{{ .Registration.Domain }}{{ end }}
{{ define "content" }}
-
<div class="px-6 py-4">
-
<div class="flex justify-between items-center">
-
<div id="left-side" class="flex gap-2 items-center">
-
<h1 class="text-xl font-bold dark:text-white">
-
{{ .Registration.Domain }}
-
</h1>
-
<span class="text-gray-500 text-base">
-
{{ template "repo/fragments/shortTimeAgo" .Registration.Created }}
-
</span>
-
</div>
-
<div id="right-side" class="flex gap-2">
-
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2" }}
-
{{ if .Registration.Registered }}
-
<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>
{{ template "knots/fragments/addMemberModal" .Registration }}
-
{{ else }}
-
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} pending</span>
{{ end }}
-
</div>
</div>
-
<div id="operation-error" class="dark:text-red-400"></div>
</div>
-
{{ if .Members }}
-
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">
-
<div class="flex flex-col gap-2">
-
{{ block "knotMember" . }} {{ end }}
-
</div>
-
</section>
-
{{ end }}
{{ end }}
-
{{ define "knotMember" }}
{{ range .Members }}
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
{{ i "user" "size-4" }}
{{ $user := index $.DidHandleMap . }}
-
<a href="/{{ $user }}">{{ $user }} <span class="ml-2 font-mono text-gray-500">{{.}}</span></a>
</div>
</div>
<div class="ml-2 pl-2 pt-2 border-l border-gray-200 dark:border-gray-700">
{{ $repos := index $.Repos . }}
···
</div>
{{ else }}
<div class="text-gray-500 dark:text-gray-400">
-
No repositories created yet.
</div>
{{ end }}
</div>
</div>
{{ end }}
{{ end }}
···
+
{{ define "title" }}{{ .Registration.Domain }} &middot; knots{{ end }}
{{ define "content" }}
+
<div class="px-6 py-4">
+
<div class="flex justify-between items-center">
+
<h1 class="text-xl font-bold dark:text-white">{{ .Registration.Domain }}</h1>
+
<div id="right-side" class="flex gap-2">
+
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2" }}
+
{{ $isOwner := and .LoggedInUser (eq .LoggedInUser.Did .Registration.ByDid) }}
+
{{ if .Registration.Registered }}
+
<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>
+
{{ if $isOwner }}
{{ template "knots/fragments/addMemberModal" .Registration }}
{{ end }}
+
{{ else }}
+
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>
+
{{ if $isOwner }}
+
{{ block "retryButton" .Registration }} {{ end }}
+
{{ end }}
+
{{ end }}
+
+
{{ if $isOwner }}
+
{{ block "deleteButton" .Registration }} {{ end }}
+
{{ end }}
</div>
</div>
+
<div id="operation-error" class="dark:text-red-400"></div>
+
</div>
+
{{ if .Members }}
+
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">
+
<div class="flex flex-col gap-2">
+
{{ block "member" . }} {{ end }}
+
</div>
+
</section>
+
{{ end }}
{{ end }}
+
+
{{ define "member" }}
{{ range .Members }}
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
{{ i "user" "size-4" }}
{{ $user := index $.DidHandleMap . }}
+
<a href="/{{ $user }}">{{ $user }}</a>
</div>
+
{{ if ne $.LoggedInUser.Did . }}
+
{{ block "removeMemberButton" (list $ . ) }} {{ end }}
+
{{ end }}
</div>
<div class="ml-2 pl-2 pt-2 border-l border-gray-200 dark:border-gray-700">
{{ $repos := index $.Repos . }}
···
</div>
{{ else }}
<div class="text-gray-500 dark:text-gray-400">
+
No repositories configured yet.
</div>
{{ end }}
</div>
</div>
{{ end }}
{{ end }}
+
+
{{ define "deleteButton" }}
+
<button
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+
title="Delete knot"
+
hx-delete="/knots/{{ .Domain }}"
+
hx-swap="outerHTML"
+
hx-confirm="Are you sure you want to delete the knot '{{ .Domain }}'?"
+
hx-headers='{"shouldRedirect": "true"}'
+
>
+
{{ i "trash-2" "w-5 h-5" }}
+
<span class="hidden md:inline">delete</span>
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
{{ end }}
+
+
+
{{ define "retryButton" }}
+
<button
+
class="btn gap-2 group"
+
title="Retry knot verification"
+
hx-post="/knots/{{ .Domain }}/retry"
+
hx-swap="none"
+
hx-headers='{"shouldRefresh": "true"}'
+
>
+
{{ i "rotate-ccw" "w-5 h-5" }}
+
<span class="hidden md:inline">retry</span>
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
{{ end }}
+
+
+
{{ define "removeMemberButton" }}
+
{{ $root := index . 0 }}
+
{{ $member := index . 1 }}
+
<button
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+
title="Remove member"
+
hx-post="/knots/{{ $root.Registration.Domain }}/remove"
+
hx-swap="none"
+
hx-vals='{"member": "{{$member}}" }'
+
hx-confirm="Are you sure you want to remove {{ index $root.DidHandleMap $member }} from this knot?"
+
>
+
{{ i "user-minus" "w-4 h-4" }}
+
remove
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
{{ end }}
+
+
+6 -7
appview/pages/templates/knots/fragments/addMemberModal.html
···
{{ define "knots/fragments/addMemberModal" }}
<button
class="btn gap-2 group"
-
title="Add member to this spindle"
popovertarget="add-member-{{ .Id }}"
popovertargetaction="toggle"
>
···
{{ define "addKnotMemberPopover" }}
<form
-
hx-put="/knots/{{ .Domain }}/member"
hx-indicator="#spinner"
hx-swap="none"
class="flex flex-col gap-2"
···
<label for="member-did-{{ .Id }}" class="uppercase p-0">
ADD MEMBER
</label>
-
<p class="text-sm text-gray-500 dark:text-gray-400">Members can create repositories on this knot.</p>
<input
type="text"
id="member-did-{{ .Id }}"
-
name="subject"
required
placeholder="@foo.bsky.social"
/>
<div class="flex gap-2 pt-2">
-
<button
type="button"
popovertarget="add-member-{{ .Id }}"
popovertargetaction="hide"
···
</div>
<div id="add-member-error-{{ .Id }}" class="text-red-500 dark:text-red-400"></div>
</form>
-
{{ end }}
-
···
{{ define "knots/fragments/addMemberModal" }}
<button
class="btn gap-2 group"
+
title="Add member to this knot"
popovertarget="add-member-{{ .Id }}"
popovertargetaction="toggle"
>
···
{{ define "addKnotMemberPopover" }}
<form
+
hx-post="/knots/{{ .Domain }}/add"
hx-indicator="#spinner"
hx-swap="none"
class="flex flex-col gap-2"
···
<label for="member-did-{{ .Id }}" class="uppercase p-0">
ADD MEMBER
</label>
+
<p class="text-sm text-gray-500 dark:text-gray-400">Members can create repositories and run workflows on this knot.</p>
<input
type="text"
id="member-did-{{ .Id }}"
+
name="member"
required
placeholder="@foo.bsky.social"
/>
<div class="flex gap-2 pt-2">
+
<button
type="button"
popovertarget="add-member-{{ .Id }}"
popovertargetaction="hide"
···
</div>
<div id="add-member-error-{{ .Id }}" class="text-red-500 dark:text-red-400"></div>
</form>
+
{{ end }}
+42 -23
appview/pages/templates/knots/fragments/knotListing.html
···
{{ define "knots/fragments/knotListing" }}
-
<div
-
id="knot-{{.Id}}"
-
hx-swap-oob="true"
-
class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700">
-
{{ block "listLeftSide" . }} {{ end }}
-
{{ block "listRightSide" . }} {{ end }}
</div>
{{ end }}
-
{{ define "listLeftSide" }}
<div class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">
{{ i "hard-drive" "w-4 h-4" }}
-
{{ if .Registered }}
-
<a href="/knots/{{ .Domain }}">
-
{{ .Domain }}
-
</a>
-
{{ else }}
-
{{ .Domain }}
-
{{ end }}
<span class="text-gray-500">
{{ template "repo/fragments/shortTimeAgo" .Created }}
</span>
</div>
{{ end }}
-
{{ define "listRightSide" }}
<div id="right-side" class="flex gap-2">
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm" }}
{{ if .Registered }}
<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>
{{ template "knots/fragments/addMemberModal" . }}
{{ else }}
-
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} pending</span>
-
{{ block "initializeButton" . }} {{ end }}
{{ end }}
</div>
{{ end }}
-
{{ define "initializeButton" }}
<button
-
class="btn dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 flex gap-2 items-center group"
-
hx-post="/knots/{{ .Domain }}/init"
-
hx-swap="none"
>
-
{{ i "square-play" "w-5 h-5" }}
-
<span class="hidden md:inline">initialize</span>
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
</button>
{{ end }}
···
{{ define "knots/fragments/knotListing" }}
+
<div id="knot-{{.Id}}" class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700">
+
{{ block "knotLeftSide" . }} {{ end }}
+
{{ block "knotRightSide" . }} {{ end }}
</div>
{{ end }}
+
{{ define "knotLeftSide" }}
+
{{ if .Registered }}
+
<a href="/knots/{{ .Domain }}" class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">
+
{{ i "hard-drive" "w-4 h-4" }}
+
{{ .Domain }}
+
<span class="text-gray-500">
+
{{ template "repo/fragments/shortTimeAgo" .Created }}
+
</span>
+
</a>
+
{{ else }}
<div class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">
{{ i "hard-drive" "w-4 h-4" }}
+
{{ .Domain }}
<span class="text-gray-500">
{{ template "repo/fragments/shortTimeAgo" .Created }}
</span>
</div>
+
{{ end }}
{{ end }}
+
{{ define "knotRightSide" }}
<div id="right-side" class="flex gap-2">
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm" }}
{{ if .Registered }}
<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>
{{ template "knots/fragments/addMemberModal" . }}
{{ else }}
+
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>
+
{{ block "knotRetryButton" . }} {{ end }}
{{ end }}
+
{{ block "knotDeleteButton" . }} {{ end }}
</div>
{{ end }}
+
{{ define "knotDeleteButton" }}
<button
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+
title="Delete knot"
+
hx-delete="/knots/{{ .Domain }}"
+
hx-swap="outerHTML"
+
hx-target="#knot-{{.Id}}"
+
hx-confirm="Are you sure you want to delete the knot '{{ .Domain }}'?"
>
+
{{ i "trash-2" "w-5 h-5" }}
+
<span class="hidden md:inline">delete</span>
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
</button>
{{ end }}
+
+
{{ define "knotRetryButton" }}
+
<button
+
class="btn gap-2 group"
+
title="Retry knot verification"
+
hx-post="/knots/{{ .Domain }}/retry"
+
hx-swap="none"
+
hx-target="#knot-{{.Id}}"
+
>
+
{{ i "rotate-ccw" "w-5 h-5" }}
+
<span class="hidden md:inline">retry</span>
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
{{ end }}
-18
appview/pages/templates/knots/fragments/knotListingFull.html
···
-
{{ define "knots/fragments/knotListingFull" }}
-
<section
-
id="knot-listing-full"
-
hx-swap-oob="true"
-
class="rounded w-full flex flex-col gap-2">
-
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">your knots</h2>
-
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 w-full">
-
{{ range $knot := .Registrations }}
-
{{ template "knots/fragments/knotListing" . }}
-
{{ else }}
-
<div class="flex items-center justify-center p-2 border-b border-gray-200 dark:border-gray-700 text-gray-500">
-
no knots registered yet
-
</div>
-
{{ end }}
-
</div>
-
<div id="operation-error" class="text-red-500 dark:text-red-400"></div>
-
</section>
-
{{ end }}
···
-10
appview/pages/templates/knots/fragments/secret.html
···
-
{{ define "knots/fragments/secret" }}
-
<div
-
id="secret"
-
hx-swap-oob="true"
-
class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded px-6 py-2 w-full lg:w-3xl">
-
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">generated secret</h2>
-
<p class="pb-2">Configure your knot to use this secret, and then hit initialize.</p>
-
<span class="font-mono overflow-x">{{ .Secret }}</span>
-
</div>
-
{{ end }}
···
+22 -7
appview/pages/templates/knots/index.html
···
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">
<div class="flex flex-col gap-6">
{{ block "about" . }} {{ end }}
-
{{ template "knots/fragments/knotListingFull" . }}
{{ block "register" . }} {{ end }}
</div>
</section>
···
</section>
{{ end }}
{{ define "register" }}
-
<section class="rounded max-w-2xl flex flex-col gap-2">
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">register a knot</h2>
-
<p class="mb-2 dark:text-gray-300">Enter the hostname of your knot to generate a key.</p>
<form
-
hx-post="/knots/key"
-
class="space-y-4"
hx-indicator="#register-button"
hx-swap="none"
>
···
>
<span class="inline-flex items-center gap-2">
{{ i "plus" "w-4 h-4" }}
-
generate
</span>
<span class="pl-2 hidden group-[.htmx-request]:inline">
{{ i "loader-circle" "w-4 h-4 animate-spin" }}
···
<div id="registration-error" class="error dark:text-red-400"></div>
</form>
-
<div id="secret"></div>
</section>
{{ end }}
···
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">
<div class="flex flex-col gap-6">
{{ block "about" . }} {{ end }}
+
{{ block "list" . }} {{ end }}
{{ block "register" . }} {{ end }}
</div>
</section>
···
</section>
{{ end }}
+
{{ define "list" }}
+
<section class="rounded w-full flex flex-col gap-2">
+
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">your knots</h2>
+
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 w-full">
+
{{ range $registration := .Registrations }}
+
{{ template "knots/fragments/knotListing" . }}
+
{{ else }}
+
<div class="flex items-center justify-center p-2 border-b border-gray-200 dark:border-gray-700 text-gray-500">
+
no knots registered yet
+
</div>
+
{{ end }}
+
</div>
+
<div id="operation-error" class="text-red-500 dark:text-red-400"></div>
+
</section>
+
{{ end }}
+
{{ define "register" }}
+
<section class="rounded w-full lg:w-fit flex flex-col gap-2">
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">register a knot</h2>
+
<p class="mb-2 dark:text-gray-300">Enter the hostname of your knot to get started.</p>
<form
+
hx-post="/knots/register"
+
class="max-w-2xl mb-2 space-y-4"
hx-indicator="#register-button"
hx-swap="none"
>
···
>
<span class="inline-flex items-center gap-2">
{{ i "plus" "w-4 h-4" }}
+
register
</span>
<span class="pl-2 hidden group-[.htmx-request]:inline">
{{ i "loader-circle" "w-4 h-4 animate-spin" }}
···
<div id="registration-error" class="error dark:text-red-400"></div>
</form>
</section>
{{ end }}
+2 -2
appview/pages/templates/spindles/fragments/addMemberModal.html
···
id="add-member-{{ .Instance }}"
popover
class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">
-
{{ block "addMemberPopover" . }} {{ end }}
</div>
{{ end }}
-
{{ define "addMemberPopover" }}
<form
hx-post="/spindles/{{ .Instance }}/add"
hx-indicator="#spinner"
···
id="add-member-{{ .Instance }}"
popover
class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">
+
{{ block "addSpindleMemberPopover" . }} {{ end }}
</div>
{{ end }}
+
{{ define "addSpindleMemberPopover" }}
<form
hx-post="/spindles/{{ .Instance }}/add"
hx-indicator="#spinner"
+8 -8
appview/pages/templates/spindles/fragments/spindleListing.html
···
{{ define "spindles/fragments/spindleListing" }}
<div id="spindle-{{.Id}}" class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700">
-
{{ block "leftSide" . }} {{ end }}
-
{{ block "rightSide" . }} {{ end }}
</div>
{{ end }}
-
{{ define "leftSide" }}
{{ if .Verified }}
<a href="/spindles/{{ .Instance }}" class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">
{{ i "hard-drive" "w-4 h-4" }}
···
{{ end }}
{{ end }}
-
{{ define "rightSide" }}
<div id="right-side" class="flex gap-2">
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm" }}
{{ if .Verified }}
···
{{ template "spindles/fragments/addMemberModal" . }}
{{ else }}
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>
-
{{ block "retryButton" . }} {{ end }}
{{ end }}
-
{{ block "deleteButton" . }} {{ end }}
</div>
{{ end }}
-
{{ define "deleteButton" }}
<button
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
title="Delete spindle"
···
{{ end }}
-
{{ define "retryButton" }}
<button
class="btn gap-2 group"
title="Retry spindle verification"
···
{{ define "spindles/fragments/spindleListing" }}
<div id="spindle-{{.Id}}" class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700">
+
{{ block "spindleLeftSide" . }} {{ end }}
+
{{ block "spindleRightSide" . }} {{ end }}
</div>
{{ end }}
+
{{ define "spindleLeftSide" }}
{{ if .Verified }}
<a href="/spindles/{{ .Instance }}" class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">
{{ i "hard-drive" "w-4 h-4" }}
···
{{ end }}
{{ end }}
+
{{ define "spindleRightSide" }}
<div id="right-side" class="flex gap-2">
{{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm" }}
{{ if .Verified }}
···
{{ template "spindles/fragments/addMemberModal" . }}
{{ else }}
<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>
+
{{ block "spindleRetryButton" . }} {{ end }}
{{ end }}
+
{{ block "spindleDeleteButton" . }} {{ end }}
</div>
{{ end }}
+
{{ define "spindleDeleteButton" }}
<button
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
title="Delete spindle"
···
{{ end }}
+
{{ define "spindleRetryButton" }}
<button
class="btn gap-2 group"
title="Retry spindle verification"