upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-03 16:15:55 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-03 16:15:55 +0000
commitdd1b44132199aa72c2b699e1160fbe6b885f0ef6 (patch)
treea4f3ea50770f8ef22d5f5140e79bbfddec563b0d
parent2591dbf7498e2300a42928ac9fbec454732e66f0 (diff)
landing page: display relay inforamtion document
-rw-r--r--src/http/landing.rs133
-rw-r--r--src/http/nip11.rs8
-rw-r--r--templates/landing.html59
3 files changed, 189 insertions, 11 deletions
diff --git a/src/http/landing.rs b/src/http/landing.rs
index c76a7e6..8ab4a68 100644
--- a/src/http/landing.rs
+++ b/src/http/landing.rs
@@ -398,7 +398,7 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String {
398 if let Some(meta) = metadata.get(nip) { 398 if let Some(meta) = metadata.get(nip) {
399 html.push_str(&format!( 399 html.push_str(&format!(
400 r#"<div class="card"> 400 r#"<div class="card">
401 <div class="card-title"><span class="badge">NIP-{:02}</span> {}</div> 401 <div class="card-title"><span class="badge badge-nip">NIP-{:02}</span> {}</div>
402 <div class="card-desc">{}</div> 402 <div class="card-desc">{}</div>
403 </div>"#, 403 </div>"#,
404 nip, meta.title, meta.description 404 nip, meta.title, meta.description
@@ -407,7 +407,7 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String {
407 // Fallback for unknown NIPs - still show them 407 // Fallback for unknown NIPs - still show them
408 html.push_str(&format!( 408 html.push_str(&format!(
409 r#"<div class="card"> 409 r#"<div class="card">
410 <div class="card-title"><span class="badge">NIP-{:02}</span></div> 410 <div class="card-title"><span class="badge badge-nip">NIP-{:02}</span></div>
411 </div>"#, 411 </div>"#,
412 nip 412 nip
413 )); 413 ));
@@ -418,6 +418,129 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String {
418 html 418 html
419} 419}
420 420
421/// Escape HTML special characters
422fn escape_html(s: &str) -> String {
423 s.replace('&', "&amp;")
424 .replace('<', "&lt;")
425 .replace('>', "&gt;")
426 .replace('"', "&quot;")
427 .replace('\'', "&#39;")
428}
429
430/// Generate table rows for NIP-11 relay information
431fn generate_relay_info_rows(nip11: &RelayInformationDocument) -> String {
432 let mut html = String::new();
433
434 // Follow NIP-11 document order:
435 // name
436 html.push_str(&format!(
437 r#"<tr><th>name</th><td>{}</td></tr>"#,
438 escape_html(&nip11.name)
439 ));
440 html.push('\n');
441
442 // description
443 html.push_str(&format!(
444 r#"<tr><th>description</th><td>{}</td></tr>"#,
445 escape_html(&nip11.description)
446 ));
447 html.push('\n');
448
449 // pubkey (if present)
450 if let Some(ref pubkey) = nip11.pubkey {
451 html.push_str(&format!(
452 r#"<tr><th>pubkey</th><td><code>{}</code></td></tr>"#,
453 escape_html(pubkey)
454 ));
455 html.push('\n');
456 }
457
458 // contact (if present)
459 if let Some(ref contact) = nip11.contact {
460 html.push_str(&format!(
461 r#"<tr><th>contact</th><td>{}</td></tr>"#,
462 escape_html(contact)
463 ));
464 html.push('\n');
465 }
466
467 // supported_nips
468 let nips_str = nip11
469 .supported_nips
470 .iter()
471 .map(|n| n.to_string())
472 .collect::<Vec<_>>()
473 .join(", ");
474 html.push_str(&format!(
475 r#"<tr><th>supported_nips</th><td>{}</td></tr>"#,
476 nips_str
477 ));
478 html.push('\n');
479
480 // software
481 html.push_str(&format!(
482 r#"<tr><th>software</th><td><a href="{}">{}</a></td></tr>"#,
483 escape_html(&nip11.software),
484 escape_html(&nip11.software)
485 ));
486 html.push('\n');
487
488 // version
489 html.push_str(&format!(
490 r#"<tr><th>version</th><td>{}</td></tr>"#,
491 escape_html(&nip11.version)
492 ));
493 html.push('\n');
494
495 // icon (if present)
496 if let Some(ref icon) = nip11.icon {
497 html.push_str(&format!(
498 r#"<tr><th>icon</th><td><a href="{}">{}</a></td></tr>"#,
499 escape_html(icon),
500 escape_html(icon)
501 ));
502 html.push('\n');
503 }
504
505 // GRASP-01 Extensions:
506 // supported_grasps
507 let grasps_str = nip11.supported_grasps.join(", ");
508 html.push_str(&format!(
509 r#"<tr><th>supported_grasps</th><td>{}</td></tr>"#,
510 escape_html(&grasps_str)
511 ));
512 html.push('\n');
513
514 // repo_acceptance_criteria
515 html.push_str(&format!(
516 r#"<tr><th>repo_acceptance_criteria</th><td>{}</td></tr>"#,
517 escape_html(&nip11.repo_acceptance_criteria)
518 ));
519 html.push('\n');
520
521 // curation (if present)
522 if let Some(ref curation) = nip11.curation {
523 html.push_str(&format!(
524 r#"<tr><th>curation</th><td>{}</td></tr>"#,
525 escape_html(curation)
526 ));
527 html.push('\n');
528 }
529
530 html
531}
532
533/// Generate the relay icon HTML for the header
534fn generate_relay_icon_html(nip11: &RelayInformationDocument) -> String {
535 match &nip11.icon {
536 Some(icon_url) => format!(
537 r#"<img src="{}" alt="Relay Icon" class="relay-icon">"#,
538 escape_html(icon_url)
539 ),
540 None => String::new(),
541 }
542}
543
421/// Generate the HTML landing page 544/// Generate the HTML landing page
422pub fn get_html(config: &Config) -> String { 545pub fn get_html(config: &Config) -> String {
423 // Get NIP-11 document for supported NIPs and GRASPs 546 // Get NIP-11 document for supported NIPs and GRASPs
@@ -430,6 +553,8 @@ pub fn get_html(config: &Config) -> String {
430 let hero_tags = generate_hero_tags(&nip11); 553 let hero_tags = generate_hero_tags(&nip11);
431 let grasp_cards = generate_grasp_cards(&nip11); 554 let grasp_cards = generate_grasp_cards(&nip11);
432 let nip_cards = generate_nip_cards(&nip11); 555 let nip_cards = generate_nip_cards(&nip11);
556 let relay_info_rows = generate_relay_info_rows(&nip11);
557 let relay_icon = generate_relay_icon_html(&nip11);
433 558
434 format!( 559 format!(
435 include_str!("../../templates/landing.html"), 560 include_str!("../../templates/landing.html"),
@@ -437,12 +562,16 @@ pub fn get_html(config: &Config) -> String {
437 relay_name = config.relay_name(), 562 relay_name = config.relay_name(),
438 relay_description = config.relay_description, 563 relay_description = config.relay_description,
439 version = get_version(), 564 version = get_version(),
565 nip11_version = escape_html(&nip11.version),
440 curation = curation, 566 curation = curation,
567 repo_acceptance_criteria = escape_html(&nip11.repo_acceptance_criteria),
441 theme_toggle = get_theme_toggle_html(), 568 theme_toggle = get_theme_toggle_html(),
442 theme_script = get_theme_script(), 569 theme_script = get_theme_script(),
443 hero_tags = hero_tags, 570 hero_tags = hero_tags,
444 grasp_cards = grasp_cards, 571 grasp_cards = grasp_cards,
445 nip_cards = nip_cards, 572 nip_cards = nip_cards,
573 relay_info_rows = relay_info_rows,
574 relay_icon = relay_icon,
446 ) 575 )
447} 576}
448 577
diff --git a/src/http/nip11.rs b/src/http/nip11.rs
index 2a37385..1ed80de 100644
--- a/src/http/nip11.rs
+++ b/src/http/nip11.rs
@@ -76,11 +76,7 @@ impl RelayInformationDocument {
76 76
77 // GRASP-01 Extensions 77 // GRASP-01 Extensions
78 supported_grasps: vec!["GRASP-01".to_string()], 78 supported_grasps: vec!["GRASP-01".to_string()],
79 repo_acceptance_criteria: format!( 79 repo_acceptance_criteria: "None".to_string(),
80 "Repositories must list this relay ({}) in both 'clone' and 'relays' tags of kind 30617 announcements. \
81 All other events must reference accepted repositories or accepted events.",
82 config.domain
83 ),
84 curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy 80 curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy
85 } 81 }
86 } 82 }
@@ -118,7 +114,7 @@ mod tests {
118 assert!(doc.supported_nips.contains(&34)); 114 assert!(doc.supported_nips.contains(&34));
119 assert!(doc.supported_nips.contains(&77)); 115 assert!(doc.supported_nips.contains(&77));
120 assert_eq!(doc.supported_grasps, vec!["GRASP-01"]); 116 assert_eq!(doc.supported_grasps, vec!["GRASP-01"]);
121 assert!(doc.repo_acceptance_criteria.contains("relay.example.com")); 117 assert!(doc.repo_acceptance_criteria.contains("None"));
122 assert!(doc.curation.is_none()); 118 assert!(doc.curation.is_none());
123 assert_eq!( 119 assert_eq!(
124 doc.icon, 120 doc.icon,
diff --git a/templates/landing.html b/templates/landing.html
index 6dc2833..41bb1e5 100644
--- a/templates/landing.html
+++ b/templates/landing.html
@@ -27,9 +27,13 @@
27 gap: 16px; 27 gap: 16px;
28 margin-bottom: 24px; 28 margin-bottom: 24px;
29 }} 29 }}
30 .logo {{ 30 .relay-icon {{
31 width: 48px; 31 width: 48px;
32 height: 48px; 32 height: 48px;
33 max-width: 48px;
34 max-height: 48px;
35 object-fit: contain;
36 border-radius: 8px;
33 }} 37 }}
34 h1 {{ 38 h1 {{
35 font-size: 2rem; 39 font-size: 2rem;
@@ -123,6 +127,9 @@
123 font-weight: 500; 127 font-weight: 500;
124 margin-right: 8px; 128 margin-right: 8px;
125 }} 129 }}
130 .badge-nip {{
131 background: var(--success);
132 }}
126 code {{ padding: 2px 8px; }} 133 code {{ padding: 2px 8px; }}
127 .tag-container {{ 134 .tag-container {{
128 margin: 16px 0; 135 margin: 16px 0;
@@ -169,6 +176,37 @@
169 background: var(--brand-light); 176 background: var(--brand-light);
170 text-decoration: none; 177 text-decoration: none;
171 }} 178 }}
179 /* Relay Information Document table styles */
180 .info-table {{
181 width: 100%;
182 border-collapse: collapse;
183 font-size: 0.875rem;
184 }}
185 .info-table th {{
186 text-align: left;
187 padding: 8px 12px;
188 color: var(--text-muted);
189 font-weight: 500;
190 width: 140px;
191 vertical-align: top;
192 border-bottom: 1px solid var(--border);
193 }}
194 .info-table td {{
195 padding: 8px 12px;
196 color: var(--text);
197 border-bottom: 1px solid var(--border);
198 word-break: break-all;
199 }}
200 .info-table tr:last-child th,
201 .info-table tr:last-child td {{
202 border-bottom: none;
203 }}
204 .info-table a {{
205 color: var(--brand-light);
206 }}
207 .info-table code {{
208 font-size: 0.75rem;
209 }}
172 .footer {{ margin-top: 64px; }} 210 .footer {{ margin-top: 64px; }}
173 .footer-separator {{ margin: 0 0.5em; opacity: 0.5; }} 211 .footer-separator {{ margin: 0 0.5em; opacity: 0.5; }}
174 </style> 212 </style>
@@ -178,6 +216,7 @@
178 <header class="hero"> 216 <header class="hero">
179 <div class="hero-content"> 217 <div class="hero-content">
180 <div class="header"> 218 <div class="header">
219 {relay_icon}
181 <h1>{relay_name}</h1> 220 <h1>{relay_name}</h1>
182 </div> 221 </div>
183 222
@@ -187,11 +226,16 @@
187 Browse Repositories on GitWorkshop.dev &rarr; 226 Browse Repositories on GitWorkshop.dev &rarr;
188 </a> 227 </a>
189 228
190 <div class="tag-container" style="margin-bottom: 12px;"> 229 <div class="tag-container" style="margin-bottom: 8px;">
191 <span class="tag-label">Curation:</span> 230 <span class="tag-label">Curation:</span>
192 <span class="tag-label">{curation}</span> 231 <span class="tag-label">{curation}</span>
193 </div> 232 </div>
194 233
234 <div class="tag-container" style="margin-bottom: 12px;">
235 <span class="tag-label">Repo Acceptance Criteria:</span>
236 <span class="tag-label" style="max-width: 600px; text-align: center;">{repo_acceptance_criteria}</span>
237 </div>
238
195 <div class="tag-container" style="margin-top: 12px;"> 239 <div class="tag-container" style="margin-top: 12px;">
196 <span class="tag-label">Supports:</span> 240 <span class="tag-label">Supports:</span>
197 {hero_tags} 241 {hero_tags}
@@ -205,7 +249,7 @@
205 <path d="M10.6731 30.6348C8.83687 30.6346 7.34885 29.1458 7.34885 27.3096C7.34891 26.2473 7.84783 25.303 8.62326 24.6943C8.21265 23.3055 7.86571 22.049 7.45334 20.6758C6.90247 18.8412 7.4492 16.8197 8.93576 15.5605L15.7512 9.78906C15.6931 9.54286 15.6614 9.28642 15.6613 9.02246C15.6613 7.51617 16.6628 6.24465 18.0363 5.83594L18.0363 -1.11215e-06C18.511 0.000462658 18.4612 0.000975391 18.9856 0.000975533C19.5102 0.000975578 19.5802 -1.11589e-06 19.9367 -9.46012e-07L19.9367 5.83594C21.3097 6.24503 22.3108 7.5166 22.3108 9.02246C22.3107 9.29118 22.2792 9.55249 22.219 9.80273L29.0783 15.6123C30.5229 16.8359 31.1022 18.8013 30.5539 20.6133L29.3254 24.6758C30.1142 25.2837 30.6232 26.2367 30.6233 27.3096C30.6233 29.1459 29.1344 30.6348 27.2981 30.6348C25.4619 30.6346 23.9738 29.1458 23.9738 27.3096C23.974 25.4734 25.4619 23.9846 27.2981 23.9844C27.3814 23.9844 27.4643 23.9891 27.5461 23.9951L28.7356 20.0625C29.0645 18.9753 28.7166 17.7966 27.8498 17.0625L21.2424 11.4648C20.8746 11.8048 20.4294 12.0622 19.9367 12.209L19.9367 18.9258C21.0425 19.3175 21.836 20.3694 21.8362 21.6094C21.8362 23.1834 20.5596 24.46 18.9856 24.46C17.4117 24.4598 16.136 23.1833 16.136 21.6094C16.1361 20.3689 16.93 19.3172 18.0363 18.9258L18.0363 12.21C17.5395 12.0622 17.0916 11.801 16.7219 11.457L10.1643 17.0107C9.27919 17.7605 8.93068 18.9867 9.27365 20.1289C9.68708 21.5056 10.0175 22.7009 10.3986 23.998C10.4892 23.9906 10.5806 23.9844 10.6731 23.9844C12.5093 23.9844 13.9981 25.4733 13.9983 27.3096C13.9983 29.1459 12.5094 30.6348 10.6731 30.6348Z" fill="white"/> 249 <path d="M10.6731 30.6348C8.83687 30.6346 7.34885 29.1458 7.34885 27.3096C7.34891 26.2473 7.84783 25.303 8.62326 24.6943C8.21265 23.3055 7.86571 22.049 7.45334 20.6758C6.90247 18.8412 7.4492 16.8197 8.93576 15.5605L15.7512 9.78906C15.6931 9.54286 15.6614 9.28642 15.6613 9.02246C15.6613 7.51617 16.6628 6.24465 18.0363 5.83594L18.0363 -1.11215e-06C18.511 0.000462658 18.4612 0.000975391 18.9856 0.000975533C19.5102 0.000975578 19.5802 -1.11589e-06 19.9367 -9.46012e-07L19.9367 5.83594C21.3097 6.24503 22.3108 7.5166 22.3108 9.02246C22.3107 9.29118 22.2792 9.55249 22.219 9.80273L29.0783 15.6123C30.5229 16.8359 31.1022 18.8013 30.5539 20.6133L29.3254 24.6758C30.1142 25.2837 30.6232 26.2367 30.6233 27.3096C30.6233 29.1459 29.1344 30.6348 27.2981 30.6348C25.4619 30.6346 23.9738 29.1458 23.9738 27.3096C23.974 25.4734 25.4619 23.9846 27.2981 23.9844C27.3814 23.9844 27.4643 23.9891 27.5461 23.9951L28.7356 20.0625C29.0645 18.9753 28.7166 17.7966 27.8498 17.0625L21.2424 11.4648C20.8746 11.8048 20.4294 12.0622 19.9367 12.209L19.9367 18.9258C21.0425 19.3175 21.836 20.3694 21.8362 21.6094C21.8362 23.1834 20.5596 24.46 18.9856 24.46C17.4117 24.4598 16.136 23.1833 16.136 21.6094C16.1361 20.3689 16.93 19.3172 18.0363 18.9258L18.0363 12.21C17.5395 12.0622 17.0916 11.801 16.7219 11.457L10.1643 17.0107C9.27919 17.7605 8.93068 18.9867 9.27365 20.1289C9.68708 21.5056 10.0175 22.7009 10.3986 23.998C10.4892 23.9906 10.5806 23.9844 10.6731 23.9844C12.5093 23.9844 13.9981 25.4733 13.9983 27.3096C13.9983 29.1459 12.5094 30.6348 10.6731 30.6348Z" fill="white"/>
206 </svg> 250 </svg>
207 <div class="software-content"> 251 <div class="software-content">
208 <h3 class="software-heading"><a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp">Grasp Server</a> Powered by <a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp">ngit-grasp</a></h3> 252 <h3 class="software-heading"><a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp">Grasp Server</a> Powered by <a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp">ngit-grasp</a> ({nip11_version})</h3>
209 <p class="software-desc">Git hosting distributed across relays using Nostr for authorization. <a href="https://ngit.dev/grasp">Find out more...</a></p> 253 <p class="software-desc">Git hosting distributed across relays using Nostr for authorization. <a href="https://ngit.dev/grasp">Find out more...</a></p>
210 </div> 254 </div>
211 </div> 255 </div>
@@ -234,6 +278,15 @@
234 {nip_cards} 278 {nip_cards}
235 </div> 279 </div>
236 280
281 <div class="section">
282 <div class="section-title">Relay Information Document</div>
283 <div class="card">
284 <table class="info-table">
285 {relay_info_rows}
286 </table>
287 </div>
288 </div>
289
237 <div class="footer"> 290 <div class="footer">
238 <span id="footer-domain"></span><span class="footer-separator">•</span>powered by <a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp"><strong>ngit-grasp</strong></a><span class="footer-separator">•</span>{version}<span class="footer-separator">•</span>MIT Licensed 291 <span id="footer-domain"></span><span class="footer-separator">•</span>powered by <a href="https://gitworkshop.dev/danconwaydev.com/ngit-grasp"><strong>ngit-grasp</strong></a><span class="footer-separator">•</span>{version}<span class="footer-separator">•</span>MIT Licensed
239 </div> 292 </div>