From a8c94ca0aabb8e5e38ae5aac90e810bd89e8b2e2 Mon Sep 17 00:00:00 2001 From: Christian Ditaputratama <ditatompel@gmail.com> Date: Mon, 4 Nov 2024 23:53:09 +0700 Subject: [PATCH] feat!: Added Add node form and action --- internal/handler/response.go | 25 +++++++ internal/handler/routes.go | 3 +- internal/handler/views/add_node.templ | 56 +++++++++++++++- internal/handler/views/add_node_templ.go | 2 +- internal/handler/views/home.templ | 2 +- internal/handler/views/home_templ.go | 2 +- internal/handler/views/layout.templ | 11 ++++ internal/handler/views/layout_templ.go | 83 +++++++++++++++++++++++- 8 files changed, 178 insertions(+), 6 deletions(-) diff --git a/internal/handler/response.go b/internal/handler/response.go index d4cd8f9..228bb27 100644 --- a/internal/handler/response.go +++ b/internal/handler/response.go @@ -33,6 +33,29 @@ func (s *fiberServer) homeHandler(c *fiber.Ctx) error { // Render Add Node Page func (s *fiberServer) addNodeHandler(c *fiber.Ctx) error { + switch c.Method() { + case fiber.MethodPut: + type formData struct { + Protocol string `form:"protocol"` + Hostname string `form:"hostname"` + Port int `form:"port"` + } + var f formData + + if err := c.BodyParser(&f); err != nil { + handler := adaptor.HTTPHandler(templ.Handler(views.Alert("error", "Cannot parse the request body"))) + return handler(c) + } + + moneroRepo := monero.New() + if err := moneroRepo.Add(f.Protocol, f.Hostname, uint(f.Port)); err != nil { + handler := adaptor.HTTPHandler(templ.Handler(views.Alert("error", err.Error()))) + return handler(c) + } + + handler := adaptor.HTTPHandler(templ.Handler(views.Alert("success", "Node added successfully"))) + return handler(c) + } p := views.Meta{ Title: "Add Monero Node", Description: "You can use this page to add known remote node to the system so my bots can monitor it.", @@ -256,6 +279,8 @@ func ProbeLogs(c *fiber.Ctx) error { } // Handles `POST /nodes` request to add a new node +// +// Deprecated: AddNode is deprecated, use s.addNodeHandler with put method instead func AddNode(c *fiber.Ctx) error { formPort := c.FormValue("port") port, err := strconv.Atoi(formPort) diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 9f8f7c8..5038b09 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -5,13 +5,14 @@ func (s *fiberServer) Routes() { s.App.Get("/remote-nodes", s.remoteNodesHandler) s.App.Get("/remote-nodes/id/:id", s.nodeHandler) s.App.Get("/add-node", s.addNodeHandler) + s.App.Put("/add-node", s.addNodeHandler) // V1 API routes v1 := s.App.Group("/api/v1") // these routes are public, they don't require a prober api key v1.Get("/nodes", Nodes) - v1.Post("/nodes", AddNode) + v1.Post("/nodes", AddNode) // old add node form action endpoint. Deprecated: Use PUT /add-node instead v1.Get("/nodes/id/:id", Node) v1.Get("/nodes/logs", ProbeLogs) v1.Get("/fees", NetFees) diff --git a/internal/handler/views/add_node.templ b/internal/handler/views/add_node.templ index f38396b..208d892 100644 --- a/internal/handler/views/add_node.templ +++ b/internal/handler/views/add_node.templ @@ -15,7 +15,61 @@ templ AddNode() { <div class="mt-5"> <p class="text-lg text-neutral-300">You can use this page to add known remote node to the system so my bots can monitor it.</p> </div> - <hr class="mt-6"/> + </div> + <hr class="my-6 border-orange-400 mx-auto max-w-3xl"/> + <div class="max-w-4xl mx-auto px-4"> + <div class="p-4 bg-blue-800/10 border border-blue-900 text-sm text-white rounded-lg" role="alert" tabindex="-1" aria-labelledby="add-node-notice"> + <div class="flex"> + <div class="ms-4"> + <h2 id="add-node-notice" class="text-xl font-bold text-center">Important Note</h2> + <div class="mt-2 text-sm"> + <ul class="list-disc space-y-1 ps-5"> + <li>As an administrator of this instance, I have full rights to delete, and blacklist any submitted node with or without providing any reason.</li> + </ul> + </div> + </div> + </div> + </div> + </div> + <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> + <p class="mt-1 text-center"> + Enter your Monero node information below (IPv6 host check is experimental): + </p> + <div class="mt-12"> + <form method="put" hx-swap="transition:true" hx-target="#form-result" hx-disabled-elt=".form" hx-on::after-request="this.reset()"> + <div class="grid grid-cols-1 sm:grid-cols-4 gap-6"> + <div> + <label for="protocol" class="block text-neutral-200">Protocol *</label> + <select + id="protocol" + name="protocol" + class="frameless form" + autocomplete="off" + > + <option value="http">HTTP</option> + <option value="https">HTTPS</option> + </select> + </div> + <div class="md:col-span-2"> + <label for="hostname" class="block text-neutral-200">Host / IP *</label> + <input type="text" name="hostname" id="hostname" class="frameless form" autocomplete="off" placeholder="Eg: node.example.com or 172.16.17.18" required/> + </div> + <div> + <label for="port" class="block text-neutral-200">Port *</label> + <input type="text" name="port" id="port" class="frameless form" autocomplete="off" placeholder="Eg: 18081" required/> + </div> + </div> + <div class="mt-6 grid"> + <button type="submit" class="form w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-bold rounded-lg border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none disabled:opacity-60 disabled:pointer-events-none">Submit</button> + </div> + </form> + <div id="form-result" class="max-w-4xl mx-auto my-6"></div> + <div class="mt-3 text-center"> + <p class="text-sm text-gray-500 dark:text-neutral-500"> + Existing remote nodes can be found in <a href="/remote-nodes">/remote-nodes</a> page. + </p> + </div> + </div> </div> </div> </div> diff --git a/internal/handler/views/add_node_templ.go b/internal/handler/views/add_node_templ.go index b0dd376..439942a 100644 --- a/internal/handler/views/add_node_templ.go +++ b/internal/handler/views/add_node_templ.go @@ -37,7 +37,7 @@ func AddNode() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"relative z-10\"><div class=\"max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-10 lg:py-16\"><div class=\"text-center\"><!-- Title --><div class=\"mt-5\"><h1 class=\"block font-extrabold text-4xl md:text-5xl lg:text-6xl text-neutral-200\">Add Monero Node</h1></div><!-- End Title --><div class=\"mt-5\"><p class=\"text-lg text-neutral-300\">You can use this page to add known remote node to the system so my bots can monitor it.</p></div><hr class=\"mt-6\"></div></div></div></section><!-- End Hero -->") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"relative z-10\"><div class=\"max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-10 lg:py-16\"><div class=\"text-center\"><!-- Title --><div class=\"mt-5\"><h1 class=\"block font-extrabold text-4xl md:text-5xl lg:text-6xl text-neutral-200\">Add Monero Node</h1></div><!-- End Title --><div class=\"mt-5\"><p class=\"text-lg text-neutral-300\">You can use this page to add known remote node to the system so my bots can monitor it.</p></div></div><hr class=\"my-6 border-orange-400 mx-auto max-w-3xl\"><div class=\"max-w-4xl mx-auto px-4\"><div class=\"p-4 bg-blue-800/10 border border-blue-900 text-sm text-white rounded-lg\" role=\"alert\" tabindex=\"-1\" aria-labelledby=\"add-node-notice\"><div class=\"flex\"><div class=\"ms-4\"><h2 id=\"add-node-notice\" class=\"text-xl font-bold text-center\">Important Note</h2><div class=\"mt-2 text-sm\"><ul class=\"list-disc space-y-1 ps-5\"><li>As an administrator of this instance, I have full rights to delete, and blacklist any submitted node with or without providing any reason.</li></ul></div></div></div></div></div><div class=\"max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6\"><p class=\"mt-1 text-center\">Enter your Monero node information below (IPv6 host check is experimental):</p><div class=\"mt-12\"><form method=\"put\" hx-swap=\"transition:true\" hx-target=\"#form-result\" hx-disabled-elt=\".form\" hx-on::after-request=\"this.reset()\"><div class=\"grid grid-cols-1 sm:grid-cols-4 gap-6\"><div><label for=\"protocol\" class=\"block text-neutral-200\">Protocol *</label> <select id=\"protocol\" name=\"protocol\" class=\"frameless form\" autocomplete=\"off\"><option value=\"http\">HTTP</option> <option value=\"https\">HTTPS</option></select></div><div class=\"md:col-span-2\"><label for=\"hostname\" class=\"block text-neutral-200\">Host / IP *</label> <input type=\"text\" name=\"hostname\" id=\"hostname\" class=\"frameless form\" autocomplete=\"off\" placeholder=\"Eg: node.example.com or 172.16.17.18\" required></div><div><label for=\"port\" class=\"block text-neutral-200\">Port *</label> <input type=\"text\" name=\"port\" id=\"port\" class=\"frameless form\" autocomplete=\"off\" placeholder=\"Eg: 18081\" required></div></div><div class=\"mt-6 grid\"><button type=\"submit\" class=\"form w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-bold rounded-lg border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none disabled:opacity-60 disabled:pointer-events-none\">Submit</button></div></form><div id=\"form-result\" class=\"max-w-4xl mx-auto my-6\"></div><div class=\"mt-3 text-center\"><p class=\"text-sm text-gray-500 dark:text-neutral-500\">Existing remote nodes can be found in <a href=\"/remote-nodes\">/remote-nodes</a> page.</p></div></div></div></div></div></section><!-- End Hero -->") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/handler/views/home.templ b/internal/handler/views/home.templ index 2da6b86..245fc2d 100644 --- a/internal/handler/views/home.templ +++ b/internal/handler/views/home.templ @@ -61,7 +61,7 @@ templ Home() { </div> </div> </a> - <a href="/remote-nodes" class="group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125"> + <a href="/add-node" class="group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125"> <div class="p-4 md:p-5"> <div class="grow"> <p class="font-semibold text-orange-400">Add Node</p> diff --git a/internal/handler/views/home_templ.go b/internal/handler/views/home_templ.go index df2c594..3073544 100644 --- a/internal/handler/views/home_templ.go +++ b/internal/handler/views/home_templ.go @@ -82,7 +82,7 @@ func Home() templ.Component { return templ_7745c5c3_Err } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("etc; can be an other good reference for you.</p></div></div></div></section><!-- End Hero --><!-- Alt Nav Section --><section class=\"max-w-5xl px-4 pb-10 sm:px-6 lg:px-8 mx-auto\"><div class=\"grid sm:grid-cols-2 gap-3 sm:gap-6\"><a href=\"/remote-nodes\" class=\"group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125\"><div class=\"p-4 md:p-5\"><div class=\"grow\"><p class=\"font-semibold text-orange-400\">Remote Nodes</p><p class=\"text-sm\">List of submitted Monero remote nodes you can use when you <strong>cannot</strong> run your own node.</p></div></div></a> <a href=\"/remote-nodes\" class=\"group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125\"><div class=\"p-4 md:p-5\"><div class=\"grow\"><p class=\"font-semibold text-orange-400\">Add Node</p><p class=\"text-sm\">Add your Monero public node to be monitored and see how it performs.</p></div></div></a></div></section><!-- End Alt Nav Section --><!-- My Public Nodes Section --><section id=\"my-nodes\" class=\"bg-neutral-800\"><div class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto\"><div class=\"max-w-2xl text-center mx-auto mb-8 prose prose-invert\"><p>You can find few resources I provide related to Monero below:</p></div><!-- Grid --><div class=\"grid gap-2 sm:grid-cols-2 sm:gap-3\"><!-- Card --><div class=\"group flex flex-col text-center gap-2\"><h2 class=\"font-semibold text-neutral-200 text-3xl\">My Stagenet Public Node</h2><p>Stagenet is what you need to learn Monero safely. Stagenet is technically equivalent to mainnet, both in terms of features and consensus rules.</p><div><label for=\"stagenet-p2p\" class=\"sr-only\">Stagenet P2P</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">P2P</span></span> <input type=\"text\" id=\"stagenet-p2p\" name=\"stagenet-p2p\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:38080\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-p2p\">Copy</button></div></div><div><label for=\"stagenet-rpc\" class=\"sr-only\">Stagenet RPC</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC</span></span> <input type=\"text\" id=\"stagenet-rpc\" name=\"stagenet-rpc\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:38089\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-rpc\">Copy</button></div></div><div><label for=\"stagenet-ssl\" class=\"sr-only\">Stagenet RPC SSL</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC SSL</span></span> <input type=\"text\" id=\"stagenet-ssl\" name=\"stagenet-ssl\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:443\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-ssl\">Copy</button></div></div></div><!-- Card --><div class=\"group flex flex-col text-center gap-2\"><h2 class=\"font-semibold text-neutral-200 text-3xl\">My Testnet Public Node</h2><p>Testnet is the <em>\"experimental\"</em> network and blockchain where things get released long before mainnet. As a normal user, use mainnet instead.</p><div><label for=\"testnet-p2p\" class=\"sr-only\">Testnet P2P</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">P2P</span></span> <input type=\"text\" id=\"testnet-p2p\" name=\"testnet-p2p\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:28080\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-p2p\">Copy</button></div></div><div><label for=\"testnet-rpc\" class=\"sr-only\">Testnet RPC</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC</span></span> <input type=\"text\" id=\"testnet-rpc\" name=\"testnet-rpc\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:28089\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-rpc\">Copy</button></div></div><div><label for=\"testnet-ssl\" class=\"sr-only\">Testnet RPC SSL</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC SSL</span></span> <input type=\"text\" id=\"testnet-ssl\" name=\"testnet-ssl\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:443\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-ssl\">Copy</button></div></div></div></div></div></section><!-- End My Public Nodes Section --><!-- Quote Section --><section class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto\"><blockquote class=\"relative\"><svg class=\"absolute -top-6 -start-8 size-16 text-neutral-700\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z\" fill=\"currentColor\"></path></svg><div class=\"relative z-10 text-center\"><p class=\"text-xl text-white md:text-2xl md:leading-normal\"><em>Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction.</em></p><p class=\"my-2\"><strong>Eric Hughes</strong> in <a href=\"https://www.activism.net/cypherpunk/manifesto.html\" class=\"external\" target=\"_blank\" rel=\"noopener\"><cite title=\"Source Title\">A Cypherpunk's Manifesto</cite></a>.</p></div></blockquote></section><!-- End Quote Section --><hr class=\"border-orange-400 mx-auto max-w-3xl\"><!-- Monero Donation Section --><section id=\"monero-donation\" class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto text-center\"><div class=\"mx-auto flex w-full max-w-4xl flex-col md:flex-row items-center gap-2 md:gap-10\"><div class=\"md:basis-3/4\"><p>If you find this project useful, please consider making a donation to help cover the ongoing expenses. Your contribution will go towards ensuring the continued availability of <strong>this website</strong>, my <strong>stagenet</strong> and <strong>testnet</strong> public nodes.</p><label for=\"donate\" class=\"block text-sm text-white font-medium my-2\">XMR Donation address:</label> <textarea id=\"donate\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-neutral-500 focus:ring-neutral-600\" rows=\"3\" readonly>8BWYe6GzbNKbxe3D8mPkfFMQA2rViaZJFhWShhZTjJCNG6EZHkXRZCKHiuKmwwe4DXDYF8KKcbGkvNYaiRG3sNt7JhnVp7D</textarea> <button type=\"button\" class=\"clipboard mt-2 py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none disabled:bg-green-600 disabled:opacity-60 disabled:pointer-events-none\" data-clipboard-target=\"#donate\" data-success-text=\"Donation Address Copied! 🤩\">Copy Donation Address</button></div><div class=\"md:basis-1/4\"><img src=\"/assets/img/monerotip.png\" class=\"w-full pb-2\" alt=\"ditatompel's monero address\"><p>Thank you so much! It means a lot to me. 🥰</p></div></div></section>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("etc; can be an other good reference for you.</p></div></div></div></section><!-- End Hero --><!-- Alt Nav Section --><section class=\"max-w-5xl px-4 pb-10 sm:px-6 lg:px-8 mx-auto\"><div class=\"grid sm:grid-cols-2 gap-3 sm:gap-6\"><a href=\"/remote-nodes\" class=\"group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125\"><div class=\"p-4 md:p-5\"><div class=\"grow\"><p class=\"font-semibold text-orange-400\">Remote Nodes</p><p class=\"text-sm\">List of submitted Monero remote nodes you can use when you <strong>cannot</strong> run your own node.</p></div></div></a> <a href=\"/add-node\" class=\"group flex flex-col text-center bg-neutral-900 border border-orange-400 shadow-sm rounded-xl transition hover:shadow-md hover:brightness-125\"><div class=\"p-4 md:p-5\"><div class=\"grow\"><p class=\"font-semibold text-orange-400\">Add Node</p><p class=\"text-sm\">Add your Monero public node to be monitored and see how it performs.</p></div></div></a></div></section><!-- End Alt Nav Section --><!-- My Public Nodes Section --><section id=\"my-nodes\" class=\"bg-neutral-800\"><div class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto\"><div class=\"max-w-2xl text-center mx-auto mb-8 prose prose-invert\"><p>You can find few resources I provide related to Monero below:</p></div><!-- Grid --><div class=\"grid gap-2 sm:grid-cols-2 sm:gap-3\"><!-- Card --><div class=\"group flex flex-col text-center gap-2\"><h2 class=\"font-semibold text-neutral-200 text-3xl\">My Stagenet Public Node</h2><p>Stagenet is what you need to learn Monero safely. Stagenet is technically equivalent to mainnet, both in terms of features and consensus rules.</p><div><label for=\"stagenet-p2p\" class=\"sr-only\">Stagenet P2P</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">P2P</span></span> <input type=\"text\" id=\"stagenet-p2p\" name=\"stagenet-p2p\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:38080\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-p2p\">Copy</button></div></div><div><label for=\"stagenet-rpc\" class=\"sr-only\">Stagenet RPC</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC</span></span> <input type=\"text\" id=\"stagenet-rpc\" name=\"stagenet-rpc\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:38089\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-rpc\">Copy</button></div></div><div><label for=\"stagenet-ssl\" class=\"sr-only\">Stagenet RPC SSL</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC SSL</span></span> <input type=\"text\" id=\"stagenet-ssl\" name=\"stagenet-ssl\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"stagenet.xmr.ditatompel.com:443\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#stagenet-ssl\">Copy</button></div></div></div><!-- Card --><div class=\"group flex flex-col text-center gap-2\"><h2 class=\"font-semibold text-neutral-200 text-3xl\">My Testnet Public Node</h2><p>Testnet is the <em>\"experimental\"</em> network and blockchain where things get released long before mainnet. As a normal user, use mainnet instead.</p><div><label for=\"testnet-p2p\" class=\"sr-only\">Testnet P2P</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">P2P</span></span> <input type=\"text\" id=\"testnet-p2p\" name=\"testnet-p2p\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:28080\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-p2p\">Copy</button></div></div><div><label for=\"testnet-rpc\" class=\"sr-only\">Testnet RPC</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC</span></span> <input type=\"text\" id=\"testnet-rpc\" name=\"testnet-rpc\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:28089\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-rpc\">Copy</button></div></div><div><label for=\"testnet-ssl\" class=\"sr-only\">Testnet RPC SSL</label><div class=\"flex rounded-lg shadow-sm\"><span class=\"px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-neutral-700 bg-neutral-700 text-sm\"><span class=\"text-sm text-neutral-400\">RPC SSL</span></span> <input type=\"text\" id=\"testnet-ssl\" name=\"testnet-ssl\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500\" value=\"testnet.xmr.ditatompel.com:443\" readonly> <button class=\"clipboard copy-input\" data-clipboard-target=\"#testnet-ssl\">Copy</button></div></div></div></div></div></section><!-- End My Public Nodes Section --><!-- Quote Section --><section class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto\"><blockquote class=\"relative\"><svg class=\"absolute -top-6 -start-8 size-16 text-neutral-700\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z\" fill=\"currentColor\"></path></svg><div class=\"relative z-10 text-center\"><p class=\"text-xl text-white md:text-2xl md:leading-normal\"><em>Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction.</em></p><p class=\"my-2\"><strong>Eric Hughes</strong> in <a href=\"https://www.activism.net/cypherpunk/manifesto.html\" class=\"external\" target=\"_blank\" rel=\"noopener\"><cite title=\"Source Title\">A Cypherpunk's Manifesto</cite></a>.</p></div></blockquote></section><!-- End Quote Section --><hr class=\"border-orange-400 mx-auto max-w-3xl\"><!-- Monero Donation Section --><section id=\"monero-donation\" class=\"max-w-5xl px-4 py-10 sm:px-6 lg:px-8 mx-auto text-center\"><div class=\"mx-auto flex w-full max-w-4xl flex-col md:flex-row items-center gap-2 md:gap-10\"><div class=\"md:basis-3/4\"><p>If you find this project useful, please consider making a donation to help cover the ongoing expenses. Your contribution will go towards ensuring the continued availability of <strong>this website</strong>, my <strong>stagenet</strong> and <strong>testnet</strong> public nodes.</p><label for=\"donate\" class=\"block text-sm text-white font-medium my-2\">XMR Donation address:</label> <textarea id=\"donate\" class=\"py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-neutral-500 focus:ring-neutral-600\" rows=\"3\" readonly>8BWYe6GzbNKbxe3D8mPkfFMQA2rViaZJFhWShhZTjJCNG6EZHkXRZCKHiuKmwwe4DXDYF8KKcbGkvNYaiRG3sNt7JhnVp7D</textarea> <button type=\"button\" class=\"clipboard mt-2 py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none disabled:bg-green-600 disabled:opacity-60 disabled:pointer-events-none\" data-clipboard-target=\"#donate\" data-success-text=\"Donation Address Copied! 🤩\">Copy Donation Address</button></div><div class=\"md:basis-1/4\"><img src=\"/assets/img/monerotip.png\" class=\"w-full pb-2\" alt=\"ditatompel's monero address\"><p>Thank you so much! It means a lot to me. 🥰</p></div></div></section>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/handler/views/layout.templ b/internal/handler/views/layout.templ index 9ad2814..d30413e 100644 --- a/internal/handler/views/layout.templ +++ b/internal/handler/views/layout.templ @@ -96,3 +96,14 @@ templ heroGradient() { <div class="bg-gradient-to-tl blur-3xl w-[90rem] h-[50rem] rounded-fulls origin-top-left -rotate-12 -translate-x-[15rem] from-orange-900/60 via-orange-900/40 to-amber-900/80"></div> </div> } + +templ Alert(status, message string) { + switch status { + case "success": + <div class="mt-2 bg-green-600 text-white rounded-lg p-4"><strong>Success:</strong> { message }</div> + case "error": + <div class="mt-2 bg-red-600 text-white rounded-lg p-4"><strong>Error:</strong> { message }</div> + default: + <div class="mt-2 bg-blue-600 text-white rounded-lg p-4">{ message }</div> + } +} diff --git a/internal/handler/views/layout_templ.go b/internal/handler/views/layout_templ.go index b1edcab..59c8c49 100644 --- a/internal/handler/views/layout_templ.go +++ b/internal/handler/views/layout_templ.go @@ -231,7 +231,7 @@ func base(m Meta) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(", <a href=\"https://github.com/ditatompel/xmr-remote-nodes\" target=\"_blank\" rel=\"noopener\" class=\"external\">source code</a> licensed under <strong>BSD-3-Clause</strong> license.</p></div></footer></body><div id=\"modal-section\" class=\"hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none\" role=\"dialog\" tabindex=\"-1\" aria-labelledby=\"modal-section-label\"></div></html>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(", <a href=\"https://github.com/ditatompel/xmr-remote-nodes\" target=\"_blank\" rel=\"noopener\" class=\"external\">source code</a> licensed under <strong>BSD-3-Clause</strong> license.</p></div></footer><div id=\"modal-section\" class=\"hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none\" role=\"dialog\" tabindex=\"-1\" aria-labelledby=\"modal-section-label\"></div></body></html>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -394,4 +394,85 @@ func heroGradient() templ.Component { }) } +func Alert(status, message string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var21 := templ.GetChildren(ctx) + if templ_7745c5c3_Var21 == nil { + templ_7745c5c3_Var21 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch status { + case "success": + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-green-600 text-white rounded-lg p-4\"><strong>Success:</strong> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 103, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "error": + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-red-600 text-white rounded-lg p-4\"><strong>Error:</strong> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 105, Col: 91} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-blue-600 text-white rounded-lg p-4\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 107, Col: 68} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return templ_7745c5c3_Err + }) +} + var _ = templruntime.GeneratedTemplate