+34
-34
appview/pages/templates/repo/new.html
+34
-34
appview/pages/templates/repo/new.html
···
2
3
{{ define "content" }}
4
<h1>new repo</h1>
5
-
<form>
6
-
<label for="name">repo name</label>
7
-
<input
8
-
type="text"
9
-
id="name"
10
-
name="name"
11
-
class="px-1 border-2 border-blue-100"
12
-
required
13
-
/>
14
15
-
<fieldset class="border-blue-100 border-2">
16
-
<legend>select a knot:</legend>
17
-
{{ range .Knots }}
18
-
<label>
19
-
<input
20
-
class="px-1 border-2 border-blue-500"
21
-
type="radio"
22
-
name="domain"
23
-
value="{{ . }}"
24
-
/>
25
-
{{ . }} </label
26
-
><br />
27
-
{{ else }}
28
-
<p>no knots available</p>
29
-
{{ end }}
30
</fieldset>
31
32
-
<button
33
-
type="submit"
34
-
hx-post="/repo/new"
35
-
hx-swap="none"
36
-
class="my-2 btn"
37
-
>
38
-
create repo
39
-
</button>
40
</form>
41
-
42
-
<div id="repo" class="error"></div>
43
{{ end }}
···
2
3
{{ define "content" }}
4
<h1>new repo</h1>
5
+
<form hx-post="/repo/new" class="mt-6 space-y-6" hx-swap="none">
6
+
<div class="space-y-2">
7
+
<label for="name" class="block">repo name</label>
8
+
<input
9
+
type="text"
10
+
id="name"
11
+
name="name"
12
+
required
13
+
class="w-full max-w-md"
14
+
/>
15
+
</div>
16
17
+
<fieldset class="space-y-3">
18
+
<legend class="font-medium">select a knot</legend>
19
+
<div class="space-y-2">
20
+
{{ range .Knots }}
21
+
<div>
22
+
<label class="inline-flex items-center">
23
+
<input
24
+
type="radio"
25
+
name="domain"
26
+
value="{{ . }}"
27
+
class="mr-2"
28
+
/>
29
+
<span>{{ . }}</span>
30
+
</label>
31
+
</div>
32
+
{{ else }}
33
+
<p>No knots available</p>
34
+
{{ end }}
35
+
</div>
36
</fieldset>
37
38
+
<div class="space-y-2">
39
+
<button type="submit" class="btn">create repo</button>
40
+
<div id="repo" class="error"></div>
41
+
</div>
42
</form>
43
{{ end }}
+54
-28
appview/pages/templates/settings/keys.html
+54
-28
appview/pages/templates/settings/keys.html
···
3
{{ define "content" }}
4
<h1>settings</h1>
5
6
-
<h2>profile</h2>
7
-
<p><strong>handle:</strong> {{ .LoggedInUser.Handle }}</p>
8
-
<p><strong>did:</strong> {{ .LoggedInUser.Did }}</p>
9
-
<p><strong>pds:</strong> {{ .LoggedInUser.Pds }}</p>
10
11
-
<h2>ssh keys</h2>
12
-
<form hx-put="/settings/keys">
13
-
<label for="name">key name:</label>
14
-
<input type="text" id="name" name="name" required />
15
16
-
<label for="key">pub key:</label>
17
-
<textarea
18
-
id="key"
19
-
name="key"
20
-
placeholder="ssh-rsa AAAAAA..."
21
-
required
22
-
></textarea>
23
24
-
<button class="btn my-2" type="submit">add key</button>
25
-
</form>
26
27
-
<h3>existing keys</h3>
28
-
<ul id="key-list">
29
-
{{ range .PubKeys }}
30
-
<li>
31
-
<strong>{{ .Name }}</strong><br />
32
-
<code>{{ .Key }}</code>
33
-
</li>
34
-
{{ else }}
35
-
<p>no ssh keys added yet</p>
36
-
{{ end }}
37
-
</ul>
38
{{ end }}
···
3
{{ define "content" }}
4
<h1>settings</h1>
5
6
+
<section class="mb-8">
7
+
<h2 class="text-xl mb-4">profile</h2>
8
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4">
9
+
<dt>handle</dt>
10
+
<dd>{{ .LoggedInUser.Handle }}</dd>
11
+
<dt>did</dt>
12
+
<dd>{{ .LoggedInUser.Did }}</dd>
13
+
<dt>pds</dt>
14
+
<dd>{{ .LoggedInUser.Pds }}</dd>
15
+
</dl>
16
+
</section>
17
18
+
<section>
19
+
<h2 class="text-xl mb-4">ssh keys</h2>
20
+
<form
21
+
hx-put="/settings/keys"
22
+
hx-swap="none"
23
+
class="max-w-2xl space-y-4 mb-8"
24
+
>
25
+
<div>
26
+
<label for="name" class="block mb-1">key name</label>
27
+
<input
28
+
type="text"
29
+
id="name"
30
+
name="name"
31
+
required
32
+
class="w-full"
33
+
/>
34
+
</div>
35
36
+
<div>
37
+
<label for="key" class="block mb-1">pub key</label>
38
+
<textarea
39
+
id="key"
40
+
name="key"
41
+
placeholder="ssh-rsa AAAAAA..."
42
+
required
43
+
class="w-full h-24"
44
+
></textarea>
45
+
</div>
46
+
47
+
<button class="btn" type="submit">add key</button>
48
49
+
<div id="settings-keys" class="error"></div>
50
+
</form>
51
52
+
<h3 class="text-lg mb-2">existing keys</h3>
53
+
<ul id="key-list" class="space-y-4">
54
+
{{ range .PubKeys }}
55
+
<li class="p-4 bg-gray-50 rounded">
56
+
<h4 class="font-bold mb-2">{{ .Name }}</h4>
57
+
<code class="block text-sm break-all">{{ .Key }}</code>
58
+
</li>
59
+
{{ else }}
60
+
<p class="text-gray-600">no ssh keys added yet</p>
61
+
{{ end }}
62
+
</ul>
63
+
</section>
64
{{ end }}
+5
-2
appview/state/settings.go
+5
-2
appview/state/settings.go
···
30
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
31
switch r.Method {
32
case http.MethodGet:
33
-
w.Write([]byte("unimplemented"))
34
log.Println("unimplemented")
35
return
36
case http.MethodPut:
···
43
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
44
if err != nil {
45
log.Printf("parsing public key: %s", err)
46
return
47
}
48
49
if err := s.db.AddPublicKey(did, name, key); err != nil {
50
log.Printf("adding public key: %s", err)
51
return
52
}
53
···
66
// invalid record
67
if err != nil {
68
log.Printf("failed to create record: %s", err)
69
return
70
}
71
72
log.Println("created atproto record: ", resp.Uri)
73
-
74
return
75
}
76
}
···
30
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
31
switch r.Method {
32
case http.MethodGet:
33
+
s.pages.Notice(w, "settings-keys", "Unimplemented.")
34
log.Println("unimplemented")
35
return
36
case http.MethodPut:
···
43
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
44
if err != nil {
45
log.Printf("parsing public key: %s", err)
46
+
s.pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.")
47
return
48
}
49
50
if err := s.db.AddPublicKey(did, name, key); err != nil {
51
log.Printf("adding public key: %s", err)
52
+
s.pages.Notice(w, "settings-keys", "Failed to add public key.")
53
return
54
}
55
···
68
// invalid record
69
if err != nil {
70
log.Printf("failed to create record: %s", err)
71
+
s.pages.Notice(w, "settings-keys-bad", "Failed to create record.")
72
return
73
}
74
75
log.Println("created atproto record: ", resp.Uri)
76
+
s.pages.HxLocation(w, "/settings")
77
return
78
}
79
}
+27
-12
input.css
+27
-12
input.css
···
11
h1 {
12
@apply text-2xl;
13
@apply font-sans;
14
-
@apply text-gray-900;
15
@apply py-4;
16
}
17
18
::selection {
19
@apply bg-green-400;
20
-
@apply text-gray-900;
21
@apply bg-opacity-30;
22
}
23
a {
24
-
@apply underline text-blue-600 hover:text-blue-800 visited:text-purple-600;
25
}
26
27
@layer components {
28
.btn {
29
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center
30
justify-center bg-transparent px-2 pb-[0.2rem] text-base
31
-
text-gray-900 before:absolute before:inset-0 before:-z-10
32
-
before:block before:rounded-sm before:border before:border-blue-200
33
-
before:bg-white before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#e5edff]
34
-
before:content-[''] hover:before:border-blue-300
35
-
hover:before:bg-blue-50
36
-
hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#e5edff]
37
focus:outline-none focus-visible:before:outline
38
-
focus-visible:before:outline-4 focus-visible:before:outline-blue-500
39
-
active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)];
40
}
41
}
42
@layer utilities {
43
.error {
44
-
@apply py-1 border-red-400 text-red-600;
45
}
46
}
47
}
···
11
h1 {
12
@apply text-2xl;
13
@apply font-sans;
14
+
@apply text-black;
15
@apply py-4;
16
}
17
18
::selection {
19
@apply bg-green-400;
20
+
@apply text-black;
21
@apply bg-opacity-30;
22
}
23
a {
24
+
@apply underline text-black hover:text-gray-800 visited:text-gray-600;
25
+
}
26
+
27
+
@layer base {
28
+
label {
29
+
@apply block text-sm text-black;
30
+
}
31
+
input {
32
+
@apply bg-white border border-black rounded-sm focus:ring-black p-2;
33
+
}
34
+
textarea {
35
+
@apply bg-white border border-black rounded-sm focus:ring-black p-2;
36
+
}
37
}
38
39
@layer components {
40
.btn {
41
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center
42
justify-center bg-transparent px-2 pb-[0.2rem] text-base
43
+
text-black before:absolute before:inset-0 before:-z-10
44
+
before:block before:rounded-sm before:border before:border-black
45
+
before:bg-white before:shadow-[0_2px_2px_0_rgba(0,0,0,0.1),inset_0_-2px_0_0_#ffffff]
46
+
before:content-[''] hover:before:border-gray-800
47
+
hover:before:bg-gray-50
48
+
hover:before:shadow-[0_2px_2px_0_rgba(0,0,0,0.1),inset_0_-2px_0_0_#ffffff]
49
focus:outline-none focus-visible:before:outline
50
+
focus-visible:before:outline-4 focus-visible:before:outline-black
51
+
active:before:shadow-[inset_0_2px_2px_0_rgba(0,0,0,0.1)];
52
}
53
}
54
@layer utilities {
55
.error {
56
+
@apply py-1 border-black text-black;
57
+
}
58
+
.success {
59
+
@apply py-1 border-black text-black;
60
}
61
}
62
}