clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

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