| 1 | {{define "people" }}<section> |
| 2 | <h1>People</h1> |
| 3 | <h3>Members</h3> |
| 4 | <rpc-form method="listmembers" ><button>List members</button></rpc-form> |
| 5 | <rpc-form method="setmember" compose="member" > |
| 6 | <label>Pubkey <input name="member-pubkey" pattern="[0-9a-f]{64}" required></label> |
| 7 | <label>Name <input name="member-name" ></label> |
| 8 | <label>Note <input name="member-note" ></label> |
| 9 | <label>Role <select name="member-role" ><option value="member" >Member</option><option value="moderator" >Moderator</option></select></label> |
| 10 | <button>Save member</button> |
| 11 | </rpc-form> |
| 12 | <rpc-form method="removemember" ><label>Pubkey <input name="param" required></label><button>Remove member</button></rpc-form> |
| 13 | <rpc-form method="removesubtree" ><label>Member pubkey <input name="param" required></label><button>Remove invite subtree</button></rpc-form> |
| 14 | {{if .Review}}<h3>Access requests</h3> |
| 15 | <p><small>People who asked to join without an invite. Approving adds them as members.</small></p> |
| 16 | {{with pendingRequests .Requests}}<table id="requests" ><thead><tr><th>person</th><th>reason</th><th>asked</th><th></th></tr></thead><tbody> |
| 17 | {{range .}}{{$r := asMap .}}{{$pk := str (index $r "pubkey" )}}<tr><td>{{template "nostrName" $pk}}</td><td>{{with str (index $r "reason" )}}{{.}}{{else}}<small>no reason given</small>{{end}}</td><td><time datetime="{{datetime (index $r " requested_at")}}" >{{short (index $r "requested_at" )}}</time></td><td><rpc-form method="approvejoin" refresh><input type="hidden" name="param" value=""{{$pk}}"" ><button>Approve</button></rpc-form><rpc-form method="denyjoin" refresh><input type="hidden" name="param" value=""{{$pk}}"" ><button>Deny</button></rpc-form></td></tr> |
| 18 | {{end}}</tbody></table>{{else}}<p>No pending requests.</p>{{end}} |
| 19 | {{with decidedRequests .Requests}}<ul id="decided" >{{range .}}{{$r := asMap .}}{{$status := str (index $r "status" )}}<li data-state="{{$status}}" >{{template "nostrName" (str (index $r "pubkey" ))}} <small>{{$status}}{{with index $r "decided_at" }}{{if short .}} <time datetime="{{datetime .}}" >{{short .}}</time>{{end}}{{end}}{{with str (index $r "decided_by" )}} by {{template "nostrName" .}}{{end}}</small></li>{{end}}</ul>{{end}} |
| 20 | {{end}}<h3>Invites</h3> |
| 21 | <rpc-form method="createinvite" > |
| 22 | <label>TTL seconds <input name="param" type="number" value="259200" ></label> |
| 23 | <label>Maximum uses <input name="param" type="number" value="1" ></label> |
| 24 | <label>Note <input name="param" ></label> |
| 25 | <button>Create invite</button> |
| 26 | </rpc-form> |
| 27 | <rpc-form method="listinvites" ><button>List invites</button></rpc-form> |
| 28 | <h3>Claims</h3> |
| 29 | <rpc-form method="listclaims" ><button>List claims</button></rpc-form> |
| 30 | <rpc-form method="createclaim" ><label>JSON claim <input name="param" value="{}" ></label><button>Create claim</button></rpc-form> |
| 31 | <rpc-form method="deleteclaim" ><label>Claim code <input name="param" required></label><button>Delete claim</button></rpc-form> |
| 32 | <h3>Lists</h3> |
| 33 | <rpc-form method="listallowedpubkeys" ><button>Allowed pubkeys</button></rpc-form> |
| 34 | <rpc-form method="listbannedpubkeys" ><button>Banned pubkeys</button></rpc-form> |
| 35 | </section> |
| 36 | {{end}} |
| 37 | |
| 38 | {{define "agents" }}<section> |
| 39 | <h1>Agents</h1> |
| 40 | <p>Agents are members with their own keys. Each one acts under a grant you sign, and stays the author of everything it does.</p> |
| 41 | {{template "alert" .Error}} |
| 42 | <table id="rows" ><thead><tr><th>agent</th><th>scope</th><th>owner</th><th>state</th></tr></thead><tbody> |
| 43 | {{range .Feed}}{{$a := asMap .}}{{$pk := str (index $a "pubkey" )}}<tr><td><a href="#agent-{{$pk}}" >{{agentLabel $a}}</a></td><td title="{{agentScope $a}}" >{{agentScope $a}}</td><td>{{template "nostrName" (str (index $a "owner" ))}}</td><td data-state="{{agentState $a}}" >{{agentState $a}}{{with agentSince $a}} | {{.}}{{end}}</td></tr> |
| 44 | {{else}}<tr><td colspan="4" >No agents yet. Sign a grant below to add one.</td></tr> |
| 45 | {{end}}</tbody></table> |
| 46 | {{range .Feed}}{{$a := asMap .}}{{$pk := str (index $a "pubkey" )}}{{$state := agentState $a}}{{$scope := asMap (index $a "scope" )}} |
| 47 | <agent-card id="agent-{{$pk}}" > |
| 48 | <h3>{{agentLabel $a}} <small><span data-state="{{$state}}" >{{$state}}</span> | signed by {{if eq (str (index $a "owner" )) $.Actor}}you{{else}}{{template "nostrName" (str (index $a "owner" ))}}{{end}} | expires {{short (index $a "expires" )}}</small></h3> |
| 49 | <table> |
| 50 | <tr><th>key</th><td><nostr-key hex="{{$pk}}" title="{{$pk}}" >{{$pk}}</nostr-key> <small>click to copy</small></td></tr> |
| 51 | <tr><th>rooms</th><td>{{or (scopeList $a "rooms" ) "none" }}</td></tr> |
| 52 | <tr><th>repositories</th><td>{{range $i, $r := index $scope "repos" }}{{$repo := asMap $r}}{{if $i}}<br>{{end}}<code>{{str (index $repo "identifier" )}}</code> {{str (index $repo "level" )}}, owner {{template "nostrName" (str (index $repo "owner" ))}}{{else}}none{{end}}</td></tr> |
| 53 | <tr><th>wiki</th><td>{{or (str (index $scope "wiki" )) "none" }}</td></tr> |
| 54 | <tr><th>sites</th><td>{{range $i, $s := index $scope "sites" }}{{$site := asMap $s}}{{if $i}}<br>{{end}}<code>{{str (index $site "label" )}}</code>{{with index $site "ttl" }} {{str .}} days{{end}}{{if index $site "encrypted" }} encrypted{{end}}{{else}}none{{end}}</td></tr> |
| 55 | <tr><th>kinds</th><td>{{or (scopeList $a "kinds" ) "profile and relay list only" }}</td></tr> |
| 56 | <tr><th>rate</th><td>{{str (index $scope "rate" )}} events per minute</td></tr> |
| 57 | <tr><th>expires</th><td><time datetime="{{datetime (index $a " expires")}}" >{{when (index $a "expires" )}}</time></td></tr> |
| 58 | <tr><th>owner</th><td>{{template "nostrName" (str (index $a "owner" ))}}</td></tr> |
| 59 | {{with index $a "lastEvent" }}{{if when .}}<tr><th>last event</th><td><time datetime="{{datetime .}}" >{{when .}}</time></td></tr>{{end}}{{end}} |
| 60 | <tr><th>callbacks</th><td>{{range $c := callbacksFor $.Callbacks $pk}}{{$cm := asMap $c}}{{$cs := callbackState $c}}{{$cid := str (index $cm "id" )}}<agent-callback data-state="{{$cs}}" ><code>{{str (index $cm "host" )}}</code> kinds {{callbackKinds $c}} | <span data-state="{{$cs}}" title="{{str (index $cm " lastStatus")}}" >{{$cs}}</span>{{with index $cm "lastDelivery" }}{{if when .}} | delivered <time datetime="{{datetime .}}" >{{when .}}</time>{{end}}{{end}} {{if eq $cs "paused" }}<rpc-form method="resumecallback" refresh><input type="hidden" name="param" value=""{{$cid}}"" ><button>Resume</button></rpc-form>{{else}}<rpc-form method="pausecallback" refresh><input type="hidden" name="param" value=""{{$cid}}"" ><button>Pause</button></rpc-form>{{end}}<rpc-form method="removecallback" refresh><input type="hidden" name="param" value=""{{$cid}}"" ><button>Remove</button></rpc-form></agent-callback>{{else}}none{{end}}</td></tr> |
| 61 | </table> |
| 62 | <p>{{if eq $state "paused" }}<rpc-form method="resumeagent" refresh><input type="hidden" name="param" value=""{{$pk}}"" ><button>Resume</button></rpc-form>{{else if eq $state "active" }}<rpc-form method="pauseagent" refresh><input type="hidden" name="param" value=""{{$pk}}"" ><button>Pause</button></rpc-form>{{end}}{{if ne $state "revoked" }}<rpc-form method="revokeagent" refresh><input type="hidden" name="param" value=""{{$pk}}"" ><button>Revoke</button></rpc-form>{{end}}<a href="/manage/agents?agent={{$pk}}" >recent activity</a>{{if ne $state "revoked" }} <a href="/manage/agents?agent={{$pk}}&edit={{$pk}}#grant" >edit</a>{{end}}</p> |
| 63 | </agent-card> |
| 64 | {{end}}{{with asMap .Event}}{{$agent := asMap (index . "agent" )}} |
| 65 | <h3>Recent activity of {{agentLabel $agent}}</h3> |
| 66 | {{$events := index . "events" }}{{if $events}}<ol id="events" > |
| 67 | {{range $events}}<li>{{template "nostrEvent" .}}</li> |
| 68 | {{end}}</ol> |
| 69 | {{else}}<p>No events from this agent yet.</p> |
| 70 | {{end}}{{end}} |
| 71 | {{$edit := agentByKey .Feed (.Query.Get "edit" )}}{{$es := asMap (index (asMap $edit) "scope" )}}<h3 id="grant" >{{if $edit}}Replace the grant for {{agentLabel $edit}}{{else}}New agent{{end}}</h3> |
| 72 | <p>{{if $edit}}The form holds the current grant. Signing it publishes a new kind 30392 event for the same key, which replaces the old grant at once.{{else}}The grant is a kind 30392 event signed by your key. The agent's own key signs everything it publishes.{{end}}</p> |
| 73 | <agent-grant> |
| 74 | <label>Name <input name="name" maxlength="64" required placeholder="release-notes" {{with $edit}} value="{{agentLabel .}}" {{end}}></label> |
| 75 | <label>Key <select name="key" ><option value="generate" >generate here, show once</option><option value="paste" {{if $edit}} selected{{end}}>paste a public key</option></select></label> |
| 76 | <label>Public key <input name="pubkey" pattern="[0-9a-f]{64}" placeholder="64 hex characters" {{with $edit}} value="{{str (index (asMap .) " pubkey")}}" {{end}}></label> |
| 77 | <label>Rooms <input name="rooms" placeholder="build, agents" {{with $edit}} value="{{scopeList . " rooms"}}" {{end}}></label> |
| 78 | <label>Repositories <textarea name="repos" placeholder="owner pubkey:identifier:propose owner pubkey:identifier:read owner pubkey:identifier:maintain" >{{with $edit}}{{repoLines .}}{{end}}</textarea></label> |
| 79 | <label>Wiki <select name="wiki" ><option value="" >none</option><option value="propose" {{if eq (str (index $es "wiki" )) "propose" }} selected{{end}}>propose</option><option value="edit" {{if eq (str (index $es "wiki" )) "edit" }} selected{{end}}>edit</option></select></label> |
| 80 | <label>Sites <textarea name="sites" placeholder="npub1... ttl=30 encrypted * ttl=7" >{{with $edit}}{{siteLines .}}{{end}}</textarea></label> |
| 81 | <label>Kinds <input name="kinds" placeholder="1, 1111, 1621" {{with $edit}} value="{{scopeList . " kinds"}}" {{end}}></label> |
| 82 | <label>Rate per minute <input name="rate" type="number" min="1" max="600" value="{{if $edit}}{{str (index $es " rate")}}{{else}}60{{end}}" ></label> |
| 83 | <label>Expires <input name="expires" type="date" value="{{if $edit}}{{dateOf (index (asMap $edit) " expires")}}{{else}}{{dateAfter 90}}{{end}}" required></label> |
| 84 | <button>{{if $edit}}Sign the replacement{{else}}Sign grant{{end}}</button> |
| 85 | <noscript><p>Signing a grant needs JavaScript and a connected signer. You can also publish the kind 30392 event from any client.</p></noscript> |
| 86 | </agent-grant> |
| 87 | </section> |
| 88 | {{end}} |
| 89 | |
| 90 | {{define "moderation" }}<section> |
| 91 | <h1>Moderation</h1> |
| 92 | <h3>Reports</h3> |
| 93 | <rpc-form method="listreports" ><button>List reports</button></rpc-form> |
| 94 | <rpc-form method="resolvereport" > |
| 95 | <label>Report ID <input name="param" required></label> |
| 96 | <label>JSON resolution <input name="param" value="{}" ></label> |
| 97 | <button>Resolve report</button> |
| 98 | </rpc-form> |
| 99 | <h3>Events</h3> |
| 100 | <rpc-form method="listrecentevents" ><button>Recent events</button></rpc-form> |
| 101 | <rpc-form method="searchevents" ><label>JSON filter <input name="param" value="{}" ></label><button>Search events</button></rpc-form> |
| 102 | <rpc-form method="banevent" ><label>Event ID <input name="param" required></label><button>Hide event</button></rpc-form> |
| 103 | <rpc-form method="allowevent" ><label>Event ID <input name="param" required></label><button>Allow event</button></rpc-form> |
| 104 | <rpc-form method="deleteevent" ><label>Event ID <input name="param" required></label><button>Delete event</button></rpc-form> |
| 105 | <h3>Pins</h3> |
| 106 | <rpc-form method="listpins" ><button>List pins</button></rpc-form> |
| 107 | <rpc-form method="pinevent" ><label>Event ID or address <input name="param" required></label><button>Pin</button></rpc-form> |
| 108 | <rpc-form method="unpinevent" ><label>Event ID or address <input name="param" required></label><button>Unpin</button></rpc-form> |
| 109 | <h3>Pubkeys</h3> |
| 110 | <rpc-form method="banpubkey" ><label>Pubkey <input name="param" required></label><button>Ban</button></rpc-form> |
| 111 | </section> |
| 112 | {{end}} |
| 113 | |
| 114 | {{define "rules" }}<section> |
| 115 | <h1>Rules</h1> |
| 116 | <h3>Policy</h3> |
| 117 | <rpc-form method="getpolicy" ><button>Load policy</button></rpc-form> |
| 118 | <rpc-form method="setpolicy" compose="memberInvites" > |
| 119 | <label>Member invite depth <input name="member-invite-depth" type="number" min="0" max="100" value="0" ></label> |
| 120 | <label>Live invites per member <input name="member-invite-quota" type="number" min="0" max="1000" value="0" ></label> |
| 121 | <button>Save member invites</button> |
| 122 | </rpc-form> |
| 123 | <rpc-form method="setpolicy" ><label>JSON policy patch <textarea name="param" >{}</textarea></label><button>Apply policy</button></rpc-form> |
| 124 | <h3>Kinds</h3> |
| 125 | <rpc-form method="allowkind" ><label>Kind <input name="param" type="number" required></label><button>Allow kind</button></rpc-form> |
| 126 | <rpc-form method="disallowkind" ><label>Kind <input name="param" type="number" required></label><button>Block kind</button></rpc-form> |
| 127 | <h3>Retention</h3> |
| 128 | <rpc-form method="setretention" > |
| 129 | <label>Kind or null <input name="param" value="null" required></label> |
| 130 | <label>Keep days <input name="param" type="number" min="0" required></label> |
| 131 | <button>Save retention</button> |
| 132 | </rpc-form> |
| 133 | </section> |
| 134 | {{end}} |
| 135 | |
| 136 | {{define "identity" }}<section> |
| 137 | <h1>Identity</h1> |
| 138 | <h3>Profile</h3> |
| 139 | <rpc-form method="changerelayname" ><label>Name <input name="param" value="{{.Policy.Name}}" ></label><button>Save name</button></rpc-form> |
| 140 | <rpc-form method="changerelaydescription" ><label>Description <textarea name="param" >{{.Policy.Description}}</textarea></label><button>Save description</button></rpc-form> |
| 141 | <rpc-form method="changerelayicon" ><label>Icon URL <input name="param" type="url" maxlength="2000" value="{{.Policy.Icon}}" placeholder="https://…/icon.png" ></label><button>Save icon</button></rpc-form> |
| 142 | <h3>Directory listing</h3> |
| 143 | <rpc-form method="setpolicy" compose="identity" > |
| 144 | <label>Contact <input name="identity-contact" value="{{.Policy.Contact}}" type="email" placeholder="you@example.com" ></label> |
| 145 | <label>Banner URL <input name="identity-banner" value="{{.Policy.Banner}}" type="url" placeholder="https://…/banner.png" ></label> |
| 146 | <label>Posting policy URL <input name="identity-posting" value="{{.Policy.PostingPolicy}}" type="url" placeholder="https://…/posting" ></label> |
| 147 | <label>Privacy policy URL <input name="identity-privacy" value="{{.Policy.PrivacyPolicy}}" type="url" placeholder="https://…/privacy" ></label> |
| 148 | <label>Tags <input name="identity-tags" value="{{join .Policy.Tags " , "}}" placeholder="bitcoin, cats" ></label> |
| 149 | <label>Languages <input name="identity-languages" value="{{join .Policy.LanguageTags " , "}}" placeholder="en, pt-BR" ></label> |
| 150 | <label>Countries <input name="identity-countries" value="{{join .Policy.RelayCountries " , "}}" placeholder="US, DE" ></label> |
| 151 | <button>Save directory identity</button> |
| 152 | </rpc-form> |
| 153 | <h3>Ownership</h3> |
| 154 | <rpc-form method="transferowner" ><label>New owner pubkey <input name="param" required></label><button>Transfer ownership</button></rpc-form> |
| 155 | </section> |
| 156 | {{end}} |
| 157 | |
| 158 | {{define "connect" }}<section> |
| 159 | <h1>Connect</h1> |
| 160 | {{if .Private}}<p>This private relay requires current membership. Connect a signer to sign in.</p> |
| 161 | {{end}}<h3>Connections</h3> |
| 162 | <p>The cards on the home page, in order. Remove what you will not use; add more from the catalog. Cards whose feature is off stay hidden.</p> |
| 163 | <connect-list> |
| 164 | <table id="rows" ><thead><tr><th>card</th><th>app</th><th>audience</th><th></th></tr></thead><tbody><tr><td colspan="4" >Loading…</td></tr></tbody></table> |
| 165 | <p><label>Add a card <select name="template" ><option value="" >choose one</option></select></label> <button type="button" name="add" >add</button></p> |
| 166 | <output role="status" ></output> |
| 167 | </connect-list> |
| 168 | <details><summary>Edit as JSON</summary> |
| 169 | <rpc-form method="setconnections" ><label>Connections <textarea name="param" >[]</textarea></label><button>Save connections</button></rpc-form> |
| 170 | </details> |
| 171 | <h3>Publish signed relay records</h3> |
| 172 | <p>These actions open the configured signer and publish owner-authored Nostr records.</p> |
| 173 | <private-services pubkey="{{.Actor}}" > |
| 174 | <h3>Private Git relay list</h3> |
| 175 | <p>GRASP-08 stores private relay URLs in your encrypted kind 10318 list. The relay URLs and any list entries are encrypted to your signer with NIP-44.</p> |
| 176 | <form><label>Private relay URLs, one per line <textarea name="lines" placeholder="wss://private-relay.example" ></textarea></label><button disabled>Publish encrypted list</button> <button type="button" data-decrypt hidden>Decrypt to edit</button></form> |
| 177 | <noscript><p>Connect a signer and enable JavaScript to encrypt your private service list.</p></noscript> |
| 178 | <output role="status" ></output> |
| 179 | </private-services> |
| 180 | <publish-list kind="10317" tag="g" scheme="ws" > |
| 181 | <label>GRASP relay URLs, one per line <textarea name="lines" placeholder="wss://relay.example wss://backup.example" ></textarea></label> |
| 182 | <button>Publish GRASP list</button> |
| 183 | </publish-list> |
| 184 | <publish-list kind="10063" tag="server" > |
| 185 | <label>Blossom server URLs, one per line <textarea name="lines" ></textarea></label> |
| 186 | <button>Publish server list</button> |
| 187 | </publish-list> |
| 188 | {{if not .Actor}}<h3>Sign in</h3> |
| 189 | {{template "signerControls" .}} |
| 190 | {{end}}</section> |
| 191 | {{end}} |
| 192 | |
| 193 | {{define "data" }}<section> |
| 194 | <h1>Data</h1> |
| 195 | <h3>Files</h3> |
| 196 | <signed-form action="/upload" method="PUT" ><label>Upload file <input type="file" name="file" required></label><button>Upload</button></signed-form> |
| 197 | <rpc-form method="listblobs" ><button>List blobs</button></rpc-form> |
| 198 | <rpc-form method="deleteblob" ><label>Blob hash <input name="param" required></label><button>Delete blob</button></rpc-form> |
| 199 | <rpc-form method="listsites" ><button>List sites</button></rpc-form> |
| 200 | <h3>Storage</h3> |
| 201 | <rpc-form method="storagestats" ><button>Storage stats</button></rpc-form> |
| 202 | <rpc-form method="gitstorage" > |
| 203 | <label>Repository owner <input name="param" required pattern="[0-9a-fA-F]{64}" placeholder="64 hex pubkey" ></label> |
| 204 | <label>Repository identifier <input name="param" required maxlength="256" placeholder="repository name" ></label> |
| 205 | <button>Git storage status</button> |
| 206 | </rpc-form> |
| 207 | <h3>Dumps</h3> |
| 208 | <rpc-form method="listdumps" ><button>List dumps</button></rpc-form> |
| 209 | <rpc-form method="dumpnow" ><button>Write dump</button></rpc-form> |
| 210 | <signed-form action="/dumps/" method="GET" download><label>Dump name <input name="name" required></label><button>Download dump</button></signed-form> |
| 211 | <rpc-form method="deletedump" ><label>Dump name <input name="param" required></label><button>Delete dump</button></rpc-form> |
| 212 | <h3>Backups</h3> |
| 213 | <rpc-form method="listbackups" ><button>List backups</button></rpc-form> |
| 214 | <rpc-form method="backupnow" ><button>Create backup</button></rpc-form> |
| 215 | <signed-form action="/backups/" method="GET" download><label>Backup name <input name="name" required></label><button>Download backup</button></signed-form> |
| 216 | <signed-form action="/backups/preview" method="POST" ><label>Backup archive <input type="file" name="file" accept=".backup.json,application/json" required></label><button>Preview backup</button></signed-form> |
| 217 | <signed-form action="/backups/restore" method="POST" ><label>Backup archive <input type="file" name="file" accept=".backup.json,application/json" required></label><button>Restore backup</button></signed-form> |
| 218 | <rpc-form method="deletebackup" ><label>Backup name <input name="param" required></label><button>Delete backup</button></rpc-form> |
| 219 | <h3>Import</h3> |
| 220 | <signed-form action="/import" method="PUT" ><label>Event JSONL <input type="file" name="file" accept=".jsonl,.json,application/json" required></label><button>Import events</button></signed-form> |
| 221 | </section> |
| 222 | {{end}} |
| 223 | |
| 224 | {{define "sync" }}<section> |
| 225 | <h1>Sync and delivery</h1> |
| 226 | <h3>Jobs</h3> |
| 227 | <p><button id="watch-jobs" fx-action="/manage/jobs/status" fx-method="get" fx-trigger="click" fx-target="#job-status" fx-swap="innerHTML" >Watch job status</button></p> |
| 228 | <scroll-box><table id="job-status" ><tbody><tr><td>Job status is idle.</td></tr></tbody></table></scroll-box> |
| 229 | <rpc-form method="listjobs" ><button>List jobs</button></rpc-form> |
| 230 | <rpc-form method="addjob" ><label>JSON job <input name="param" value="{}" ></label><button>Add job</button></rpc-form> |
| 231 | <rpc-form method="runjob" ><label>Job ID <input name="param" required></label><button>Run job</button></rpc-form> |
| 232 | <rpc-form method="removejob" ><label>Job ID <input name="param" required></label><button>Remove job</button></rpc-form> |
| 233 | <h3>Pull and delivery</h3> |
| 234 | <rpc-form method="pullfrom" > |
| 235 | <label>Relay URL <input name="param" type="url" required></label> |
| 236 | <label>JSON filter <input name="param" value="{}" ></label> |
| 237 | <button>Add pull source</button> |
| 238 | </rpc-form> |
| 239 | <rpc-form method="pullstatus" ><button>Pull status</button></rpc-form> |
| 240 | <rpc-form method="deliverystatus" ><button>Delivery status</button></rpc-form> |
| 241 | <rpc-form method="backfill" ><label>JSON backfill plan <input name="param" value="{}" ></label><button>Backfill</button></rpc-form> |
| 242 | </section> |
| 243 | {{end}} |
| 244 | |
| 245 | {{define "views" }}<section> |
| 246 | <h1>Views</h1> |
| 247 | <rpc-form method="listviews" ><button>List views</button></rpc-form> |
| 248 | <rpc-form method="publishview" ><label>View name <input name="param" required></label><button>Publish view</button></rpc-form> |
| 249 | {{if .Owner}}<h3>Block transforms</h3> |
| 250 | <p>Block transforms render fenced code as images. Choose the event kinds and languages to send to your rendering service.</p> |
| 251 | {{if .CustomViews}}<table id="custom-views" > |
| 252 | <thead><tr><th>name</th><th>kinds</th><th>languages</th><th>trigger</th><th>audience</th><th>state</th><th>last run</th><th></th></tr></thead> |
| 253 | <tbody>{{range .CustomViews}}{{$v := asMap .}}<tr id="view-{{str (index $v " name")}}" > |
| 254 | <td><code>{{str (index $v "name" )}}</code></td> |
| 255 | <td>{{viewKinds $v}}</td> |
| 256 | <td>{{viewLanguages $v}}</td> |
| 257 | <td>{{str (index $v "trigger" )}}</td> |
| 258 | <td>{{str (index $v "audience" )}}</td> |
| 259 | <td data-state="{{viewState $v}}" >{{viewState $v}}{{with str (index $v "lastStatus" )}} | {{.}}{{end}}</td> |
| 260 | <td>{{with index $v "lastRun" }}{{when .}}{{else}}never{{end}}</td> |
| 261 | <td>{{if eq (viewState $v) "paused" }}<rpc-form method="resumecustomview" refresh><input type="hidden" name="param" value="{{str (index $v " name")}}" ><button>Resume</button></rpc-form>{{else}}<rpc-form method="pausecustomview" refresh><input type="hidden" name="param" value="{{str (index $v " name")}}" ><button>Pause</button></rpc-form><rpc-form method="runcustomview" refresh><input type="hidden" name="param" value="{{str (index $v " name")}}" ><button>Run now</button></rpc-form><rpc-form method="runcustomview" refresh><input type="hidden" name="param" value='{"name":"{{str (index $v "name")}}","refresh":true}' ><button>Rebuild</button></rpc-form>{{end}}<rpc-form method="removecustomview" refresh><input type="hidden" name="param" value="{{str (index $v " name")}}" ><button>Remove</button></rpc-form></td> |
| 262 | </tr>{{end}}</tbody> |
| 263 | </table>{{else}}<p id="custom-views-empty" >No custom views yet.</p>{{end}} |
| 264 | <view-form method="addcustomview" > |
| 265 | <label>Name <input name="view-name" pattern="[a-z0-9-]{1,32}" placeholder="diagrams" required></label> |
| 266 | <label>Kinds <input name="view-kinds" placeholder="1, 30023, 30618" required></label> |
| 267 | <label>Languages <input name="view-languages" placeholder="mermaid, dot" required></label> |
| 268 | <label>Transform URL <input name="view-transform" type="url" placeholder="https://render.example/transform/blocks" required></label> |
| 269 | <label>Trigger <select name="view-trigger" ><option value="write" >write</option><option value="hourly" >hourly</option></select></label> |
| 270 | <label>Audience <select name="view-audience" ><option value="public" >public</option><option value="members" >members</option></select></label> |
| 271 | <label>Max artifact bytes <input name="view-max-bytes" type="number" min="1" max="4194304" placeholder="1048576" ></label> |
| 272 | <label>Secret <input name="view-secret" minlength="16" maxlength="128" placeholder="generated when empty" ></label> |
| 273 | <button>Add view</button> |
| 274 | </view-form>{{end}} |
| 275 | </section> |
| 276 | {{end}} |
| 277 | |
| 278 | {{define "health" }}<section> |
| 279 | <h1>Health</h1> |
| 280 | <table id="facts" > |
| 281 | <tr><th>relay</th><td><code>{{or .Version "dev" }}</code>{{with .Revision}} <small>{{.}}</small>{{end}}</td></tr> |
| 282 | <tr><th>scripts</th><td><code>{{scriptsVersion}}</code></td></tr> |
| 283 | <tr><th>browser tools</th><td><span id="webmcp-status" role="status" >Needs JavaScript to report.</span></td></tr> |
| 284 | </table> |
| 285 | <details><summary>About browser tools</summary><p>Browser agents use WebMCP tools registered on every page; Chrome's Application panel lists them and their calls. In Chrome, open <code>chrome://flags/#enable-webmcp-testing</code>, enable WebMCP for testing and relaunch. Your usual pages and signed forms work in other browsers.</p></details> |
| 286 | <script>(() => { |
| 287 | const update = () => { |
| 288 | const state = window.tinyWebMCP; |
| 289 | const node = document.getElementById("webmcp-status" ); |
| 290 | if (!node) return; |
| 291 | node.textContent = !state?.supported ? "WebMCP is unavailable in this browser." |
| 292 | : state.errors.length ? "Some tools could not register: " + state.errors.join("; " ) |
| 293 | : "ready, " + state.registered.length + " tools registered" + (state.lastOperation ? ", last: " + state.lastOperation : "" ); |
| 294 | }; |
| 295 | window.addEventListener("tiny:webmcp" , update); |
| 296 | update(); |
| 297 | window.tinyWebMCP?.ready.then(update); |
| 298 | })();</script> |
| 299 | <h3>Service</h3> |
| 300 | {{if .Error}}{{template "alert" .Error}}{{else}}{{template "jsonView" .Event}}{{end}} |
| 301 | <p><a href="/manage/sync" >Jobs and delivery</a> | <a href="/manage/data" >Storage and backups</a></p> |
| 302 | <h3>Reports</h3> |
| 303 | <rpc-form method="stats" ><button>Relay stats</button></rpc-form> |
| 304 | <rpc-form method="listaudit" ><label>Before sequence <input name="param" type="number" value="0" ></label><button>Audit log</button></rpc-form> |
| 305 | <rpc-form method="supportedmethods" ><button>Supported methods</button></rpc-form> |
| 306 | </section> |
| 307 | {{end}} |
| 308 | |
| 309 | {{define "owner" }}<section> |
| 310 | <h1>Owner operations</h1> |
| 311 | <p>Destructive actions ask the signer. The browser never receives or submits a private key.</p> |
| 312 | <h3>Configuration</h3> |
| 313 | <rpc-form method="exportconfig" ><button>Export configuration</button></rpc-form> |
| 314 | <rpc-form method="planconfig" ><label>Config JSON <textarea name="param" >{}</textarea></label><button>Preview configuration</button></rpc-form> |
| 315 | <rpc-form method="importconfig" ><label>Config JSON <textarea name="param" >{}</textarea></label><button>Apply configuration</button></rpc-form> |
| 316 | <rpc-form method="forkrelay" ><label>Fork options JSON <textarea name="param" >{}</textarea></label><button>Fork relay</button></rpc-form> |
| 317 | <h3>Succession</h3> |
| 318 | <rpc-form method="setsuccession" compose="succession" > |
| 319 | <label>Heir pubkey <input name="succession-heir" pattern="[0-9a-f]{64}" required></label> |
| 320 | <label>Hand over after days <select name="succession-days" ><option value="90" >90</option><option value="180" selected>180</option><option value="365" >365</option></select></label> |
| 321 | <button>Set heir</button> |
| 322 | </rpc-form> |
| 323 | <rpc-form method="clearsuccession" ><button>Clear heir</button></rpc-form> |
| 324 | <h3>Delete</h3> |
| 325 | <rpc-form method="deleterelay" ><label>Type relay name to confirm <input name="param" placeholder="{{.Slug}}" required></label><button>Delete relay</button></rpc-form> |
| 326 | <h3>Any management method</h3> |
| 327 | <p>Every retained NIP-86 method remains callable through the signed form bridge. Dedicated tabs supply guided fields for the common ones.</p> |
| 328 | <rpc-form json-params> |
| 329 | <label>Method <select name="method" >{{range .Methods}}<option>{{.}}</option>{{end}}</select></label> |
| 330 | <label>JSON parameters <input name="param" value="[]" ></label> |
| 331 | <button>Run method</button> |
| 332 | </rpc-form> |
| 333 | </section> |
| 334 | {{end}} |