tinyrelayfile
tinyrelay/tinyclient/manage.html/

tinyclient/manage.html raw
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="&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>
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="&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>
61</table>
62<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>Revoke</button></rpc-form>{{end}}<a href="/manage/agents?agent={{$pk}}">recent activity</a>{{if ne $state "revoked"}} <a href="/manage/agents?agent={{$pk}}&amp;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&#10;owner pubkey:identifier:read&#10;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&#10;* 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&#10;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}}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs