diff options
author | Alessio Vanni <vannilla@firemail.cc> | 2021-05-02 19:02:26 +0200 |
---|---|---|
committer | Alessio Vanni <vannilla@firemail.cc> | 2021-05-07 22:38:02 +0200 |
commit | 2bca4006c49c7b3875034f6fd6530cbcfe20bcb1 (patch) | |
tree | 391f0b63a81385b8ad7fabd7cb120c4de9a86ef6 /contrib | |
parent | a5082240f035f1851715771b0265e25088bb687c (diff) | |
download | gnunet-2bca4006c49c7b3875034f6fd6530cbcfe20bcb1.tar.gz gnunet-2bca4006c49c7b3875034f6fd6530cbcfe20bcb1.zip |
[FCFSD] Provide a better user experience
The motivations behind these changes are as following.
To begin with, at the most superficial level, the form is given a better
appearance, instead of some plain XHTML. Additionally, the served pages can
be substituted with something else by using an entry in the configuration
value, altough with some limitations.
The page listing all the registered zones has been removed in favour of a
search function. A configuration entry could've been used to let service
operators choose between showing the full listing or not, but at the same
time, being presented with a (possibly) giant list of names is not that great
from a usability point of view. Having a search function is, at the very
least, faster than having to wait for the full list to be displayed before
being able to use the user agent's page search feature.
Other than the above, people registering names with the service might not want
to be known by everyone. Even though checking if a certain name or key was
registered already can be known simply by querying the service, it's not
straightforward to associate a name with a specific key (or viceversa).
Last but not least, the service was restructured to be more "route-oriented"
instead of the traditional (X)HTML document format. The main purpose of this
change is to decouple usage of the service from the tools used to access it.
With a traditional document, users are pretty much forced to use a web
browsers as data submission is carried through the standard HTML form
handling. Now, it is possible to access the service using any tool capable of
speaking HTTP, regardless of wether it's a web browser, cURL or even a custom
tool specific for this service.
Another advantage of this approach is that it allows adding "layers" to the
service, for example an authentication check before letting users register a
name. As long as the layer immediately on top of the service is able to send
some JSON using HTTP, there is no need to have users access the service
itself: just put a "proxy" inbetween and run the service locally, while the
proxy handles other administrative tasks before a name can be registered.
By using layers, the service can keep being small feature-wise (i.e. provide
only searching and registering), while everything else is provided by other
applications, including access through protocols other than HTTP.
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/Makefile.am | 3 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-forbidden.html | 11 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-index.html | 345 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-notfound.html | 11 |
4 files changed, 370 insertions, 0 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 331620586..f42fb684d 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am | |||
@@ -11,6 +11,9 @@ dist_pkgdata_DATA = \ | |||
11 | gns/def.tex \ | 11 | gns/def.tex \ |
12 | gns/gns-form-fields.xml \ | 12 | gns/gns-form-fields.xml \ |
13 | gns/gns-form.xslt \ | 13 | gns/gns-form.xslt \ |
14 | fcfsd/fcfsd-index.html \ | ||
15 | fcfsd/fcfsd-notfound.html \ | ||
16 | fcfsd/fcfsd-forbidden.html \ | ||
14 | branding/logo/gnunet-logo.pdf \ | 17 | branding/logo/gnunet-logo.pdf \ |
15 | branding/logo/gnunet-logo.png \ | 18 | branding/logo/gnunet-logo.png \ |
16 | branding/logo/gnunet-logo-color.png \ | 19 | branding/logo/gnunet-logo-color.png \ |
diff --git a/contrib/fcfsd/fcfsd-forbidden.html b/contrib/fcfsd/fcfsd-forbidden.html new file mode 100644 index 000000000..57ebb4c61 --- /dev/null +++ b/contrib/fcfsd/fcfsd-forbidden.html | |||
@@ -0,0 +1,11 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | <head> | ||
4 | <meta charset="utf-8"/> | ||
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
6 | <title>Forbidden - GNUnet FCFS Authority Name Registration Service</title> | ||
7 | </head> | ||
8 | <body> | ||
9 | <h1>You can not access this resource.</h1> | ||
10 | </body> | ||
11 | </html> | ||
diff --git a/contrib/fcfsd/fcfsd-index.html b/contrib/fcfsd/fcfsd-index.html new file mode 100644 index 000000000..3fa71d7c8 --- /dev/null +++ b/contrib/fcfsd/fcfsd-index.html | |||
@@ -0,0 +1,345 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | <head> | ||
4 | <meta charset="utf-8"/> | ||
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
6 | <title>GNUnet FCFS Authority Name Registration Service</title> | ||
7 | <style> | ||
8 | html { | ||
9 | box-sizing: border-box; | ||
10 | font-family: sans-serif; | ||
11 | } | ||
12 | |||
13 | *, *:before, *:after { | ||
14 | box-sizing: inherit; | ||
15 | } | ||
16 | |||
17 | header { | ||
18 | width: 800px; | ||
19 | margin: 0 auto; | ||
20 | } | ||
21 | |||
22 | main { | ||
23 | width: 800px; | ||
24 | margin: 0 auto; | ||
25 | } | ||
26 | |||
27 | section h4 { | ||
28 | text-align: center; | ||
29 | width: 100%; | ||
30 | } | ||
31 | |||
32 | section input { | ||
33 | width: 100%; | ||
34 | padding: 8px 17px; | ||
35 | font-size: 1rem; | ||
36 | border: 1px solid #aaa; | ||
37 | border-radius: 7px; | ||
38 | background-color: white; | ||
39 | margin-bottom: 7px; | ||
40 | } | ||
41 | |||
42 | section input:focus { | ||
43 | box-shadow: 0px 0px 5px 3px lightblue; | ||
44 | } | ||
45 | |||
46 | section button { | ||
47 | font-size: 1rem; | ||
48 | font-weight: bold; | ||
49 | background-color: #8b008b; | ||
50 | color: white; | ||
51 | border: none; | ||
52 | padding: 7px; | ||
53 | } | ||
54 | |||
55 | section button:hover { | ||
56 | background-color: #bf00bf; | ||
57 | } | ||
58 | |||
59 | section button:disabled { | ||
60 | background-color: gray; | ||
61 | } | ||
62 | |||
63 | section h3 { | ||
64 | text-align: center; | ||
65 | width: 100%; | ||
66 | } | ||
67 | |||
68 | section small { | ||
69 | display: block; | ||
70 | margin-bottom: 5px; | ||
71 | } | ||
72 | |||
73 | .error-message { | ||
74 | color: red; | ||
75 | } | ||
76 | |||
77 | .success-message { | ||
78 | color: green; | ||
79 | } | ||
80 | |||
81 | @media screen and (max-width: 991px) { | ||
82 | header, main { | ||
83 | width: 100%; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | footer { | ||
88 | margin-top: 30px; | ||
89 | text-align: center; | ||
90 | } | ||
91 | |||
92 | nav { | ||
93 | border-bottom: 1px solid black; | ||
94 | } | ||
95 | |||
96 | nav button { | ||
97 | font-size: 1rem; | ||
98 | font-weight: bold; | ||
99 | background-color: #ccc; | ||
100 | border: 1px solid black; | ||
101 | border-bottom: none; | ||
102 | border-top-right-radius: 7px; | ||
103 | border-top-left-radius: 7px; | ||
104 | padding: 7px; | ||
105 | } | ||
106 | |||
107 | nav button:hover { | ||
108 | background-color: #f0f0f0; | ||
109 | cursor: pointer; | ||
110 | } | ||
111 | |||
112 | nav button.selected { | ||
113 | background-color: #f0f0f0; | ||
114 | } | ||
115 | </style> | ||
116 | </head> | ||
117 | <body> | ||
118 | <header> | ||
119 | <h1>Name Registration Service</h1> | ||
120 | <p>Here you can register a name for your zone as part of this service's | ||
121 | delegated names.</p> | ||
122 | <p>The registration is based on a <em>First Come First Served</em> | ||
123 | policy, meaning a name is given to the first user requesting it.</p> | ||
124 | <p>Use the search bar below to see if your desired name is available and | ||
125 | then use the form to submit your registration request.</p> | ||
126 | </header> | ||
127 | <main> | ||
128 | <div class="form-container"> | ||
129 | <nav> | ||
130 | <button id="tab-search">Search</button> | ||
131 | <button id="tab-register">Register</button> | ||
132 | </nav> | ||
133 | <section id="search-form"> | ||
134 | <h4>Is your name available?</h4> | ||
135 | <h3 id="search-result-message"></h3> | ||
136 | <input id="search-name" | ||
137 | name="search-name" | ||
138 | type="text" | ||
139 | placeholder="Your name..." | ||
140 | autocomplete="name" | ||
141 | maxlength="63" | ||
142 | minlength="1"> | ||
143 | <small class="error-message" id="search-name-error"></small> | ||
144 | <button>Search</button> | ||
145 | </section> | ||
146 | <section id="submit-form"> | ||
147 | <h4>Submit a registration request</h4> | ||
148 | <h3 id="submit-result-message"></h3> | ||
149 | <input id="register-name" | ||
150 | name="register-name" | ||
151 | type="text" | ||
152 | placeholder="Your name..." | ||
153 | autocomplete="off" | ||
154 | maxlength="63" | ||
155 | minlength="1"> | ||
156 | <input id="register-value" | ||
157 | name="register-value" | ||
158 | type="text" | ||
159 | placeholder="Your zone key..." | ||
160 | autocomplete="off" | ||
161 | minlength="1"> | ||
162 | <small class="error-message" id="submit-error"></small> | ||
163 | <button>Submit</button> | ||
164 | </section> | ||
165 | </div> | ||
166 | </main> | ||
167 | <footer> | ||
168 | <a href="https://gnunet.org">GNUnet homepage</a> | ||
169 | </footer> | ||
170 | <script> | ||
171 | const buttons = document.querySelectorAll('nav button'); | ||
172 | for (let i=0; i<buttons.length; ++i) { | ||
173 | buttons[i].onclick = function (e) { | ||
174 | let selected = document.querySelector('nav button.selected'); | ||
175 | if (selected) { | ||
176 | selected.classList.toggle('selected'); | ||
177 | } | ||
178 | e.target.classList.toggle('selected'); | ||
179 | |||
180 | let show = ''; | ||
181 | let hide = ''; | ||
182 | if (e.target.id === 'tab-search') { | ||
183 | show = 'search-form'; | ||
184 | hide = 'submit-form'; | ||
185 | } else { | ||
186 | show = 'submit-form'; | ||
187 | hide = 'search-form' | ||
188 | } | ||
189 | |||
190 | document.getElementById(hide).style.display = 'none'; | ||
191 | document.getElementById(show).style.display = 'block'; | ||
192 | }; | ||
193 | } | ||
194 | |||
195 | buttons[0].click({target: buttons[0]}); | ||
196 | |||
197 | const searchbutton = document.querySelector('#search-form button'); | ||
198 | const submitbutton = document.querySelector('#submit-form button'); | ||
199 | |||
200 | document.getElementById('search-name').onkeydown = function (e) { | ||
201 | if (e.key !== 'Enter') { | ||
202 | return; | ||
203 | } | ||
204 | |||
205 | searchbutton.click(); | ||
206 | }; | ||
207 | |||
208 | for (let n of ['register-name', 'register-value']) { | ||
209 | document.getElementById(n).onkeydown = function (e) { | ||
210 | if (e.key !== 'Enter') { | ||
211 | return; | ||
212 | } | ||
213 | |||
214 | submitbutton.click(); | ||
215 | }; | ||
216 | } | ||
217 | |||
218 | searchbutton.onclick = function (e) { | ||
219 | const searchname = document.getElementById('search-name'); | ||
220 | const errormsg = document.getElementById('search-name-error'); | ||
221 | const resultmsg = document.getElementById('search-result-message'); | ||
222 | |||
223 | if (0 === searchname.value.length) { | ||
224 | errormsg.innerText = 'The field can not be empty'; | ||
225 | searchname.setCustomValidity('The field can not be empty'); | ||
226 | return; | ||
227 | } | ||
228 | |||
229 | if (-1 !== searchname.value.indexOf('.')) { | ||
230 | errormsg.innerText = 'The name can not contain dots'; | ||
231 | searchname.setCustomValidity('The name can not contain dots'); | ||
232 | return; | ||
233 | } | ||
234 | |||
235 | searchname.setCustomValidity(''); | ||
236 | errormsg.innerText = ''; | ||
237 | |||
238 | const name = searchname.value.toLowerCase(); | ||
239 | |||
240 | searchbutton.disabled = true; | ||
241 | submitbutton.disabled = true; | ||
242 | |||
243 | fetch(`/search?name=${name}`) | ||
244 | .then(function (response) { | ||
245 | if (!response.ok) { | ||
246 | throw 'error'; | ||
247 | } | ||
248 | |||
249 | return response.json() | ||
250 | }) | ||
251 | .then(function (data) { | ||
252 | if ("true" === data.free) { | ||
253 | resultmsg.innerText = `'${name}' is available!`; | ||
254 | resultmsg.classList.add('success-message'); | ||
255 | resultmsg.classList.remove('error-message'); | ||
256 | } else { | ||
257 | resultmsg.innerText = `'${name}' is not available`; | ||
258 | resultmsg.classList.remove('success-message'); | ||
259 | resultmsg.classList.add('error-message'); | ||
260 | } | ||
261 | searchbutton.disabled = false; | ||
262 | submitbutton.disabled = false; | ||
263 | }) | ||
264 | .catch(function (error) { | ||
265 | resultmsg.innerText = 'An error occurred while processing your query'; | ||
266 | resultmsg.classList.remove('success-message'); | ||
267 | resultmsg.classList.add('error-message'); | ||
268 | console.error(error); | ||
269 | searchbutton.disabled = false; | ||
270 | submitbutton.disabled = false; | ||
271 | }); | ||
272 | }; | ||
273 | |||
274 | submitbutton.onclick = function (e) { | ||
275 | const registername = document.getElementById('register-name'); | ||
276 | const registervalue = document.getElementById('register-value'); | ||
277 | const errormsg = document.getElementById('submit-error'); | ||
278 | const resultmsg = document.getElementById('submit-result-message'); | ||
279 | |||
280 | let errors = 0; | ||
281 | let errs = []; | ||
282 | |||
283 | if (0 === registername.value.length) { | ||
284 | errs.push('The name field can not be empty'); | ||
285 | registername.setCustomValidity('The name field can not be empty'); | ||
286 | ++errors; | ||
287 | } | ||
288 | if (-1 !== registername.value.indexOf('.')) { | ||
289 | errs.push('The name can not contain dots'); | ||
290 | registername.setCustomValidity('The name can not contain dots'); | ||
291 | ++errors; | ||
292 | } | ||
293 | if (0 === registervalue.value.length) { | ||
294 | errs.push('The value field can not be empty'); | ||
295 | registervalue.setCustomValidity('The value field can not be empty'); | ||
296 | ++errors; | ||
297 | } | ||
298 | |||
299 | if (0 < errors) { | ||
300 | errormsg.innerHTML = 'The form contains invalid values:'; | ||
301 | for (let e of errs) { | ||
302 | errormsg.innerHTML += '<br/>' + e; | ||
303 | } | ||
304 | return; | ||
305 | } | ||
306 | |||
307 | searchbutton.disabled = true; | ||
308 | submitbutton.disabled = true; | ||
309 | |||
310 | fetch('/register', { | ||
311 | method: 'POST', | ||
312 | cache: 'no-cache', | ||
313 | headers: { | ||
314 | 'Content-Type': 'application/json', | ||
315 | }, | ||
316 | body: JSON.stringify({ | ||
317 | name: registername.value, | ||
318 | key: registervalue.value, | ||
319 | }), | ||
320 | }).then(function (response) { | ||
321 | return response.json(); | ||
322 | }).then(function (data) { | ||
323 | if (data.error === "false") { | ||
324 | resultmsg.innerText = `'${registername.value}' was registered successfully!`; | ||
325 | resultmsg.classList.add('success-message'); | ||
326 | resultmsg.classList.remove('error-message'); | ||
327 | } else { | ||
328 | resultmsg.innerText = `'${registername.value}' could not be registered! (${data.message})`; | ||
329 | resultmsg.classList.remove('success-message'); | ||
330 | resultmsg.classList.add('error-message'); | ||
331 | } | ||
332 | searchbutton.disabled = false; | ||
333 | submitbutton.disabled = false; | ||
334 | }).catch(function (error) { | ||
335 | resultmsg.innerText = 'An error occurred while processing your query'; | ||
336 | resultmsg.classList.remove('success-message'); | ||
337 | resultmsg.classList.add('error-message'); | ||
338 | console.error(error); | ||
339 | searchbutton.disabled = false; | ||
340 | submitbutton.disabled = false; | ||
341 | }); | ||
342 | }; | ||
343 | </script> | ||
344 | </body> | ||
345 | </html> | ||
diff --git a/contrib/fcfsd/fcfsd-notfound.html b/contrib/fcfsd/fcfsd-notfound.html new file mode 100644 index 000000000..676bf4a9a --- /dev/null +++ b/contrib/fcfsd/fcfsd-notfound.html | |||
@@ -0,0 +1,11 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | <head> | ||
4 | <meta charset="utf-8"/> | ||
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
6 | <title>Not Found - GNUnet FCFS Authority Name Registration Service</title> | ||
7 | </head> | ||
8 | <body> | ||
9 | <h1>The requested resource could not be found</h1> | ||
10 | </body> | ||
11 | </html> | ||