1- <!DOCTYPE html>
2- < html >
3- < head >
4- < title >
5- {{if .IsNew}}Add New Client{{else}}Edit Client{{end}} - Tailscale OIDC Identity Provider
6- </ title >
7- < link rel ="stylesheet " type ="text/css " href ="/style.css " />
8- < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
9- </ head >
10-
11- < body >
12- {{template "header"}}
13-
14- < main >
15- < div class ="form-container ">
1+ {{define "content"}}
2+ < main >
3+ < div class ="form-container ">
164 < div class ="form-header ">
17- < h2 >
18- {{if .IsNew}}Add New OIDC Client{{else}}Edit OIDC Client{{end}}
19- </ h2 >
20- < a href ="/ " class ="btn btn-secondary "> ← Back to Clients</ a >
5+ < h2 >
6+ {{if .IsNew}}Add New OIDC Client{{else}}Edit OIDC Client{{end}}
7+ </ h2 >
8+ < a href ="/ " class ="btn btn-secondary "> ← Back to Clients</ a >
219 </ div >
2210
2311 {{if .Success}}
2412 < div class ="alert alert-success ">
25- {{.Success}}
13+ {{.Success}}
2614 </ div >
2715 {{end}}
2816
2917 {{if .Error}}
3018 < div class ="alert alert-error ">
31- {{.Error}}
19+ {{.Error}}
3220 </ div >
3321 {{end}}
3422
3523 {{if and .Secret .IsNew}}
3624 < div class ="client-info ">
37- < h3 > Client Created Successfully!</ h3 >
38- < p class ="warning "> ⚠️ Save both the Client ID and Secret now! The secret will not be shown again.</ p >
39-
40- < div class ="form-group ">
41- < label > Client ID</ label >
42- < div class ="secret-field ">
43- < input type ="text " value ="{{.ID}} " readonly class ="secret-input " id ="client-id ">
44- < button type ="button " onclick ="copyClientId(event) " class ="btn btn-secondary btn-small "> Copy</ button >
25+ < h3 > Client Created Successfully!</ h3 >
26+ < p class ="warning "> ⚠️ Save both the Client ID and Secret now! The secret will not be shown again.</ p >
27+
28+ < div class ="form-group ">
29+ < label > Client ID</ label >
30+ < div class ="secret-field ">
31+ < input type ="text " value ="{{.ID}} " readonly class ="secret-input " aria-label ="Client ID ">
32+ < button type ="button " onclick ="copyValue(this.previousElementSibling, this) " class ="btn btn-secondary btn-small "> Copy</ button >
33+ </ div >
4534 </ div >
46- </ div >
47-
48- < div class =" form-group " >
49- < label > Client Secret </ label >
50- < div class ="secret-field ">
51- < input type ="text " value =" {{.Secret}} " readonly class ="secret-input " id =" client-secret " >
52- < button type =" button " onclick =" copySecret(event) " class =" btn btn-secondary btn-small " > Copy </ button >
35+
36+ < div class =" form-group " >
37+ < label > Client Secret </ label >
38+ < div class =" secret-field " >
39+ < input type =" text " value =" {{.Secret}} " readonly class ="secret-input " aria-label =" Client Secret ">
40+ < button type ="button " onclick =" copyValue(this.previousElementSibling, this) " class ="btn btn-secondary btn-small " > Copy </ button >
41+ </ div >
5342 </ div >
54- </ div >
5543 </ div >
5644 {{end}}
5745
5846 {{if and .Secret .IsEdit}}
5947 < div class ="secret-display ">
60- < h3 > New Client Secret</ h3 >
61- < p class ="warning "> ⚠️ Save this secret now! It will not be shown again.</ p >
62- < div class ="secret-field ">
63- < input type ="text " value ="{{.Secret}} " readonly class ="secret-input " id =" client-secret ">
64- < button type ="button " onclick ="copySecret(event ) " class ="btn btn-secondary btn-small "> Copy</ button >
65- </ div >
48+ < h3 > New Client Secret</ h3 >
49+ < p class ="warning "> ⚠️ Save this secret now! It will not be shown again.</ p >
50+ < div class ="secret-field ">
51+ < input type ="text " value ="{{.Secret}} " readonly class ="secret-input " aria-label =" Client Secret ">
52+ < button type ="button " onclick ="copyValue(this.previousElementSibling, this ) " class ="btn btn-secondary btn-small "> Copy</ button >
53+ </ div >
6654 </ div >
6755 {{end}}
6856
6957 < form method ="POST " class ="client-form ">
70- < div class ="form-group ">
71- < label for ="name "> Client Name</ label >
72- < input
73- type ="text "
74- id ="name "
75- name ="name "
76- value ="{{.Name}} "
77- placeholder ="e.g., My Application "
78- class ="form-input "
79- >
80- < div class ="form-help ">
81- A descriptive name for this OIDC client (optional).
82- </ div >
83- </ div >
84-
85- < div class ="form-group ">
86- < label for ="redirect_uris "> Redirect URIs < span class ="required "> *</ span > </ label >
87- < textarea
88- id ="redirect_uris "
89- name ="redirect_uris "
90- placeholder ="https://example.com/auth/callback https://example.com/oauth/callback "
91- class ="form-input "
92- rows ="4 "
93- required
94- > {{joinRedirectURIs .RedirectURIs}}</ textarea >
95- < div class ="form-help ">
96- Enter one redirect URI per line. Users will be redirected to one of these URLs after authentication.
58+ < div class ="form-group ">
59+ < label for ="name "> Client Name</ label >
60+ < input
61+ type ="text "
62+ id ="name "
63+ name ="name "
64+ value ="{{.Name}} "
65+ placeholder ="e.g., My Application "
66+ class ="form-input "
67+ >
68+ < div class ="form-help ">
69+ A descriptive name for this OIDC client (optional).
70+ </ div >
9771 </ div >
98- </ div >
99-
100- {{if .IsEdit}}
101- < div class ="form-group ">
102- < label > Client ID</ label >
103- < input
104- type ="text "
105- value ="{{.ID}} "
106- readonly
107- class ="form-input form-input-readonly "
108- >
109- < div class ="form-help ">
110- The client ID cannot be changed.
72+
73+ < div class ="form-group ">
74+ < label for ="redirect_uris "> Redirect URIs < span class ="required "> *</ span > </ label >
75+ < textarea
76+ id ="redirect_uris "
77+ name ="redirect_uris "
78+ placeholder ="https://example.com/auth/callback https://example.com/oauth/callback "
79+ class ="form-input "
80+ rows ="4 "
81+ required
82+ > {{joinRedirectURIs .RedirectURIs}}</ textarea >
83+ < div class ="form-help ">
84+ Enter one redirect URI per line. Users will be redirected to one of these URLs after authentication.
85+ </ div >
11186 </ div >
112- </ div >
113- {{end}}
114-
115- < div class ="form-actions ">
116- < button type ="submit " class ="btn btn-primary ">
117- {{if .IsNew}}Create Client{{else}}Update Client{{end}}
118- </ button >
119-
87+
12088 {{if .IsEdit}}
121- < button type ="submit " name ="action " value ="regenerate_secret " class ="btn btn-warning "
122- onclick ="return confirm('Are you sure you want to regenerate the client secret? The old secret will stop working immediately.') ">
123- Regenerate Secret
124- </ button >
125-
126- < button type ="submit " name ="action " value ="delete " class ="btn btn-danger "
127- onclick ="return confirm('Are you sure you want to delete this client? This cannot be undone.') ">
128- Delete Client
129- </ button >
89+ < div class ="form-group ">
90+ < label > Client ID</ label >
91+ < input
92+ type ="text "
93+ value ="{{.ID}} "
94+ readonly
95+ class ="form-input form-input-readonly "
96+ >
97+ < div class ="form-help ">
98+ The client ID cannot be changed.
99+ </ div >
100+ </ div >
130101 {{end}}
131- </ div >
102+
103+ < div class ="form-actions ">
104+ < button type ="submit " class ="btn btn-primary ">
105+ {{if .IsNew}}Create Client{{else}}Update Client{{end}}
106+ </ button >
107+
108+ {{if .IsEdit}}
109+ < button type ="submit " name ="action " value ="regenerate_secret " class ="btn btn-warning "
110+ onclick ="return confirm('Are you sure you want to regenerate the client secret? The old secret will stop working immediately.') ">
111+ Regenerate Secret
112+ </ button >
113+
114+ < button type ="submit " name ="action " value ="delete " class ="btn btn-danger "
115+ onclick ="return confirm('Are you sure you want to delete this client? This cannot be undone.') ">
116+ Delete Client
117+ </ button >
118+ {{end}}
119+ </ div >
132120 </ form >
133121
134122 {{if .IsEdit}}
135123 < div class ="client-info ">
136- < h3 > Client Information</ h3 >
137- < dl >
138- < dt > Client ID</ dt >
139- < dd > < code > {{.ID}}</ code > </ dd >
140- < dt > Secret Status</ dt >
141- < dd >
142- {{if .HasSecret}}
143- < span class ="status-active "> Secret configured</ span >
144- {{else}}
145- < span class ="status-inactive "> No secret</ span >
146- {{end}}
147- </ dd >
148- </ dl >
124+ < h3 > Client Information</ h3 >
125+ < dl >
126+ < dt > Client ID</ dt >
127+ < dd > < code > {{.ID}}</ code > </ dd >
128+ < dt > Secret Status</ dt >
129+ < dd >
130+ {{if .HasSecret}}
131+ < span class ="status-active "> Secret configured</ span >
132+ {{else}}
133+ < span class ="status-inactive "> No secret</ span >
134+ {{end}}
135+ </ dd >
136+ </ dl >
149137 </ div >
150138 {{end}}
151- </ div >
152- </ main >
153-
154- < script >
155- function copySecret ( event ) {
156- const secretInput = document . getElementById ( 'client-secret' ) ;
157- secretInput . select ( ) ;
158- secretInput . setSelectionRange ( 0 , 99999 ) ; // For mobile devices
159-
160- navigator . clipboard . writeText ( secretInput . value ) . then ( function ( ) {
161- const button = event . target ;
162- const originalText = button . textContent ;
163- button . textContent = 'Copied!' ;
164- button . classList . add ( 'btn-success' ) ;
165-
166- setTimeout ( function ( ) {
167- button . textContent = originalText ;
168- button . classList . remove ( 'btn-success' ) ;
169- } , 2000 ) ;
170- } ) . catch ( function ( err ) {
171- console . error ( 'Failed to copy: ' , err ) ;
172- alert ( 'Failed to copy to clipboard. Please copy manually.' ) ;
173- } ) ;
174- }
175-
176- function copyClientId ( event ) {
177- const clientIdInput = document . getElementById ( 'client-id' ) ;
178- clientIdInput . select ( ) ;
179- clientIdInput . setSelectionRange ( 0 , 99999 ) ; // For mobile devices
180-
181- navigator . clipboard . writeText ( clientIdInput . value ) . then ( function ( ) {
182- const button = event . target ;
183- const originalText = button . textContent ;
184- button . textContent = 'Copied!' ;
185- button . classList . add ( 'btn-success' ) ;
186-
187- setTimeout ( function ( ) {
188- button . textContent = originalText ;
189- button . classList . remove ( 'btn-success' ) ;
190- } , 2000 ) ;
191- } ) . catch ( function ( err ) {
192- console . error ( 'Failed to copy: ' , err ) ;
193- alert ( 'Failed to copy to clipboard. Please copy manually.' ) ;
139+ </ div >
140+ </ main >
141+
142+ < script >
143+ function copyValue ( input , button ) {
144+ input . select ( ) ;
145+ input . setSelectionRange ( 0 , 99999 ) ; // For mobile devices
146+
147+ navigator . clipboard . writeText ( input . value ) . then ( function ( ) {
148+ const originalText = button . textContent ;
149+ button . textContent = 'Copied!' ;
150+ button . classList . add ( 'btn-success' ) ;
151+
152+ setTimeout ( function ( ) {
153+ button . textContent = originalText ;
154+ button . classList . remove ( 'btn-success' ) ;
155+ } , 2000 ) ;
156+ } ) . catch ( function ( ) {
157+ alert ( 'Failed to copy to clipboard. Please copy manually.' ) ;
194158 } ) ;
195- }
196- </ script >
197- </ body >
198- </ html >
159+ }
160+ </ script >
161+ {{end}}
0 commit comments