Compare commits

...

15 commits

Author SHA1 Message Date
dependabot[bot]
abf18a0c28
Merge 18362765b9 into 76c6a5514d 2024-11-15 18:02:54 +07:00
76c6a5514d
chore: Start v0.2.1 2024-11-15 17:45:46 +07:00
56b445d0aa
chore: Moving local vars from templ file to vars.go 2024-11-15 16:36:16 +07:00
ecfee86b2c
style: Changed hr color to orange 2024-11-15 16:15:28 +07:00
ce02334669
style: Added box shadow to navbar for better visibility 2024-11-15 15:52:06 +07:00
ditatombot[bot]
8c48e4749b
Merge pull request #159 from ditatompel/dependabot/go_modules/golang.org/x/net-0.31.0
Merge pull request #159

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2024-11-15 08:15:27 +00:00
dependabot[bot]
eb6d709bbb
build(deps): bump golang.org/x/net from 0.30.0 to 0.31.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/net/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 05:35:58 +00:00
114078d3a1
fix(style): Unreadable navbar link color on light desktop 2024-11-08 17:36:19 +07:00
8e79d20b29
chore: Added a-h/templ link
Also mark "Accept I2P nodes" in ToDo's list as complete.
2024-11-08 17:17:31 +07:00
64da0beff9
style: Added badge for tor and i2p network #148 2024-11-08 03:21:42 +07:00
5e2ab83295
feat: Accept i2p naming service hostname #148
Please note that this naming service validation only validates simple
length and allowed characters. Advanced validation such as
internationalized domain name (IDN) is not implemented.

To minimize abuse, I also set minimum length of submitted i2p naming
service address to 5 characters. If someone have an address of 4
characters or less, let them open an issue or create a pull request.
2024-11-08 00:23:07 +07:00
e0313bdbe2
test: Added validI2PHostname unit test #148
Todo: Validate new format and allow naming service hostnames
2024-11-07 21:35:45 +07:00
f339bc9c3c
feat: Added remote nodes i2p filter #148 2024-11-07 20:52:38 +07:00
e892733a55
feat: Added i2p support #148
For now, only p32 address is supported.

ToDo: Accept i2p naming service from addressbook subscriptions
ToDo: Imporve i2p UI display and add i2p filter
2024-11-07 20:26:49 +07:00
15804ee438
chore: Update readme: Still show old license
Also mark migrating from Stelte to Templ + HTMX done in ToDo's section
2024-11-07 04:40:59 +07:00
22 changed files with 417 additions and 219 deletions

View file

@ -8,6 +8,8 @@ SERVER_ENDPOINT="http://127.0.0.1:18901"
API_KEY=
ACCEPT_TOR=false
TOR_SOCKS="127.0.0.1:9050"
ACCEPT_I2P=false
I2P_SOCKS="127.0.0.1:4447"
IPV6_CAPABLE=false
# Server Config

View file

@ -3,6 +3,7 @@ on:
branches:
- main
- htmx
- i2p-support
pull_request:
name: Test

View file

@ -29,14 +29,12 @@ To build the executable binaries, you need:
- Go >= 1.22
- Bun >= 1.1.26
- templ v0.2.778
- [a-h/templ][templ-repo] v0.2.778
> **Note**:
>
> - If you want to contribute to the code, please use exact templ version
> (v0.2.778).
> - The UI is using Preline UI that uses [Lucide Icons][lucide-icons], use
> that for SVG icons.
### Server & Prober requirements
@ -84,8 +82,9 @@ See the [Makefile](./Makefile).
## ToDo's
- :white_check_mark: Accept IPv6 nodes.
- Use `a-h/templ` and `HTMX` instead of `Svelte`.
- :white_check_mark: Use `a-h/templ` and `HTMX` instead of `Svelte`.
- Use Go standard `net/http` instead of `fiber`.
- :white_check_mark: Accept I2P nodes.
## Acknowledgement
@ -119,8 +118,9 @@ Thank you!
## License
This project is licensed under [GLWTPL](./LICENSE).
This project is licensed under [BSD-3-Clause](./LICENSE) license.
[templ-repo]: https://github.com/a-h/templ "a-h/templ GitHub repository"
[geoip-doc]: https://dev.maxmind.com/geoip/geoip2/geolite2/ "GeoIP documentation"
[server-systemd-service]: ./deployment/init/xmr-nodes-server.service "systemd service example for server"
[prober-systemd-service]: ./deployment/init/xmr-nodes-prober.service "systemd service example for prober"

View file

@ -1 +1 @@
v0.2.0
v0.2.1

View file

@ -21,11 +21,12 @@ import (
"golang.org/x/net/proxy"
)
const RPCUserAgent = "ditatombot/0.0.1 (Monero RPC Monitoring; https://github.com/ditatompel/xmr-remote-nodes)"
const RPCUserAgent = "ditatombot/0.0.2 (Monero RPC Monitoring; https://github.com/ditatompel/xmr-remote-nodes)"
const (
errNoEndpoint = errProber("no SERVER_ENDPOINT was provided")
errNoTorSocks = errProber("no TOR_SOCKS was provided")
errNoI2PSocks = errProber("no I2P_SOCKS was provided")
errNoAPIKey = errProber("no API_KEY was provided")
errInvalidCredentials = errProber("invalid API_KEY credentials")
)
@ -41,6 +42,8 @@ type proberClient struct {
apiKey string // prober api key
acceptTor bool // accept tor
torSOCKS string // IP:Port of tor socks
acceptI2P bool // accept i2p
I2PSOCKS string // IP:Port of i2p socks
acceptIPv6 bool // accept ipv6
message string // message to include when reporting back to server
}
@ -52,6 +55,8 @@ func newProber() *proberClient {
apiKey: cfg.APIKey,
acceptTor: cfg.AcceptTor,
torSOCKS: cfg.TorSOCKS,
acceptI2P: cfg.AcceptI2P,
I2PSOCKS: cfg.I2PSOCKS,
acceptIPv6: cfg.IPv6Capable,
}
}
@ -67,6 +72,9 @@ var ProbeCmd = &cobra.Command{
if t, _ := cmd.Flags().GetBool("no-tor"); t {
prober.SetAcceptTor(false)
}
if t, _ := cmd.Flags().GetBool("no-i2p"); t {
prober.SetAcceptI2P(false)
}
if err := prober.Run(); err != nil {
switch err.(type) {
@ -88,6 +96,10 @@ func (p *proberClient) SetAcceptTor(acceptTor bool) {
p.acceptTor = acceptTor
}
func (p *proberClient) SetAcceptI2P(acceptI2P bool) {
p.acceptI2P = acceptI2P
}
func (p *proberClient) SetAcceptIPv6(acceptIPv6 bool) {
p.acceptIPv6 = acceptIPv6
}
@ -122,6 +134,9 @@ func (p *proberClient) validateConfig() error {
if p.acceptTor && p.torSOCKS == "" {
return errNoTorSocks
}
if p.acceptI2P && p.I2PSOCKS == "" {
return errNoI2PSocks
}
return nil
}
@ -133,6 +148,11 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
acceptTor = 1
}
acceptI2P := 0
if p.acceptI2P {
acceptI2P = 1
}
acceptIPv6 := 0
if p.acceptIPv6 {
acceptIPv6 = 1
@ -140,7 +160,7 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
var node monero.Node
uri := fmt.Sprintf("%s/api/v1/job?accept_tor=%d&accept_ipv6=%d", p.endpoint, acceptTor, acceptIPv6)
uri := fmt.Sprintf("%s/api/v1/job?accept_tor=%d&accept_i2p=%d&accept_ipv6=%d", p.endpoint, acceptTor, acceptI2P, acceptIPv6)
slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri))
req, err := http.NewRequest(http.MethodGet, uri, nil)
@ -198,8 +218,16 @@ func (p *proberClient) fetchNode(node monero.Node) (monero.Node, error) {
req.Header.Set("Origin", "https://xmr.ditatompel.com")
var client http.Client
var socks5 string
if p.acceptTor && node.IsTor {
dialer, err := proxy.SOCKS5("tcp", p.torSOCKS, nil, proxy.Direct)
socks5 = p.torSOCKS
} else if p.acceptI2P && node.IsI2P {
socks5 = p.I2PSOCKS
}
if socks5 != "" {
dialer, err := proxy.SOCKS5("tcp", socks5, nil, proxy.Direct)
if err != nil {
return node, err
}
@ -268,7 +296,7 @@ func (p *proberClient) fetchNode(node monero.Node) (monero.Node, error) {
node.CORSCapable = true
}
if !node.IsTor {
if !node.IsTor && !node.IsI2P {
hostIp, err := net.LookupIP(node.Hostname)
if err != nil {
fmt.Println("Warning: Could not resolve hostname: " + node.Hostname)
@ -335,7 +363,7 @@ func (p *proberClient) fetchFee(client http.Client, endpoint string) (uint, erro
}
func (p *proberClient) reportResult(node monero.Node, tookTime float64) error {
if !node.IsTor {
if !node.IsTor && !node.IsI2P {
if hostIps, err := net.LookupIP(node.Hostname); err == nil {
node.IPv6Only = ip.IsIPv6Only(hostIps)
node.IPAddresses = ip.SliceToString(hostIps)

View file

@ -29,7 +29,8 @@ func init() {
Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env")
Root.AddCommand(client.ProbeCmd)
client.ProbeCmd.Flags().StringP("endpoint", "e", "", "Server endpoint")
client.ProbeCmd.Flags().Bool("no-tor", false, "Only probe clearnet nodes")
client.ProbeCmd.Flags().Bool("no-tor", false, "Do not probe tor nodes")
client.ProbeCmd.Flags().Bool("no-i2p", false, "Do not probe i2p nodes")
}
func initConfig() {

4
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/joho/godotenv v1.5.1
github.com/oschwald/geoip2-golang v1.11.0
github.com/spf13/cobra v1.8.1
golang.org/x/net v0.30.0
golang.org/x/net v0.31.0
)
require (
@ -29,5 +29,5 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/sys v0.27.0 // indirect
)

8
go.sum
View file

@ -58,12 +58,12 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -27,6 +27,8 @@ type App struct {
APIKey string
AcceptTor bool
TorSOCKS string
AcceptI2P bool
I2PSOCKS string
IPv6Capable bool
}
@ -72,5 +74,7 @@ func LoadApp() {
app.APIKey = os.Getenv("API_KEY")
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
app.TorSOCKS = os.Getenv("TOR_SOCKS")
app.AcceptI2P, _ = strconv.ParseBool(os.Getenv("ACCEPT_I2P"))
app.I2PSOCKS = os.Getenv("I2P_SOCKS")
app.IPv6Capable, _ = strconv.ParseBool(os.Getenv("IPV6_CAPABLE"))
}

View file

@ -7,7 +7,7 @@ import (
type migrateFn func(*DB) error
var dbMigrate = [...]migrateFn{v1, v2, v3}
var dbMigrate = [...]migrateFn{v1, v2, v3, v4}
func MigrateDb(db *DB) error {
version := getSchemaVersion(db)
@ -272,3 +272,18 @@ func v3(db *DB) error {
return nil
}
func v4(db *DB) error {
slog.Debug("[DB] Migrating database schema version 4")
// table: tbl_node
slog.Debug("[DB] Adding additional columns to tbl_node")
_, err := db.Exec(`
ALTER TABLE tbl_node
ADD COLUMN is_i2p TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER is_tor;`)
if err != nil {
return err
}
return nil
}

View file

@ -402,10 +402,11 @@ func (s *fiberServer) countriesAPI(c *fiber.Ctx) error {
// This handler should protected by `s.checkProberMW` middleware.
func (s *fiberServer) giveJobAPI(c *fiber.Ctx) error {
acceptTor := c.QueryInt("accept_tor", 0)
acceptI2P := c.QueryInt("accept_i2p", 0)
acceptIPv6 := c.QueryInt("accept_ipv6", 0)
moneroRepo := monero.New()
node, err := moneroRepo.GiveJob(acceptTor, acceptIPv6)
node, err := moneroRepo.GiveJob(acceptTor, acceptI2P, acceptIPv6)
if err != nil {
return c.JSON(fiber.Map{
"status": "error",

View file

@ -7,11 +7,9 @@ templ AddNode() {
<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>
@ -25,6 +23,7 @@ templ AddNode() {
<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>
<li>I2P nodes monitoring is beta.</li>
</ul>
</div>
</div>
@ -32,9 +31,7 @@ templ AddNode() {
</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>
<p class="mt-1 text-center">Enter your Monero node information below:</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">

View file

@ -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></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\" class=\"link\">/remote-nodes</a> page.</p></div></div></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\"><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><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><li>I2P nodes monitoring is beta.</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:</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\" class=\"link\">/remote-nodes</a> page.</p></div></div></div></div></div></section><!-- End Hero -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -1,12 +1,12 @@
package views
templ navbar(pageIdentifier string) {
<header class="fixed top-4 inset-x-0 flex flex-wrap md:justify-start md:flex-nowrap z-50 w-full before:absolute before:inset-0 before:max-w-7xl before:mx-2 before:lg:mx-auto before:rounded-[26px] before:bg-neutral-800/70 before:backdrop-blur-md">
<header class="fixed top-4 inset-x-0 flex flex-wrap z-50 w-full md:justify-start md:flex-nowrap before:absolute before:inset-0 before:max-w-7xl before:mx-2 before:lg:mx-auto before:rounded-[26px] before:bg-neutral-800/70 before:backdrop-blur-sm before:shadow-md before:shadow-orange-400/40">
<nav class="relative max-w-7xl w-full py-2.5 px-5 md:flex md:items-center md:justify-between md:py-0 mx-2 lg:mx-auto">
<div class="flex items-center justify-between">
<div class="flex-none inline-block">
<a class="text-xl font-semibold text-white focus:outline-none" href="/" aria-label="XMR Nodes">XMR Nodes</a>
<div id="hx-indicator-main" class="htmx-indicator animate-spin ml-2 inline-block size-4 border-[3px] border-current border-t-transparent text-blue-500 rounded-full" role="status" aria-label="loading indicator">
<div id="hx-indicator-main" class="htmx-indicator animate-spin ml-2 inline-block size-4 border-[3px] border-current border-t-transparent text-orange-400 rounded-full" role="status" aria-label="loading indicator">
<span class="sr-only">Loading...</span>
</div>
</div>
@ -17,7 +17,6 @@ templ navbar(pageIdentifier string) {
</button>
</div>
</div>
<!-- Collapse -->
<div id="main-navbar" class="hs-collapse hidden overflow-hidden transition-all duration-300 basis-full grow md:block" aria-labelledby="main-navbar-collapse">
<div class="flex flex-col md:flex-row md:items-center md:justify-end gap-2 md:gap-3 mt-3 md:mt-0 py-2 md:py-0 md:ps-7">
<a
@ -43,7 +42,6 @@ templ navbar(pageIdentifier string) {
>Add Node</a>
</div>
</div>
<!-- End Collapse -->
</nav>
</header>
}

View file

@ -29,7 +29,7 @@ func navbar(pageIdentifier string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"fixed top-4 inset-x-0 flex flex-wrap md:justify-start md:flex-nowrap z-50 w-full before:absolute before:inset-0 before:max-w-7xl before:mx-2 before:lg:mx-auto before:rounded-[26px] before:bg-neutral-800/70 before:backdrop-blur-md\"><nav class=\"relative max-w-7xl w-full py-2.5 px-5 md:flex md:items-center md:justify-between md:py-0 mx-2 lg:mx-auto\"><div class=\"flex items-center justify-between\"><div class=\"flex-none inline-block\"><a class=\"text-xl font-semibold text-white focus:outline-none\" href=\"/\" aria-label=\"XMR Nodes\">XMR Nodes</a><div id=\"hx-indicator-main\" class=\"htmx-indicator animate-spin ml-2 inline-block size-4 border-[3px] border-current border-t-transparent text-blue-500 rounded-full\" role=\"status\" aria-label=\"loading indicator\"><span class=\"sr-only\">Loading...</span></div></div><div class=\"md:hidden\"><button type=\"button\" class=\"hs-collapse-toggle size-8 flex justify-center items-center text-sm font-semibold rounded-full bg-neutral-800 text-white disabled:opacity-50 disabled:pointer-events-none\" id=\"hs-navbar-floating-dark-collapse\" aria-expanded=\"false\" aria-controls=\"hs-navbar-floating-dark\" aria-label=\"Toggle navigation\" data-hs-collapse=\"#main-navbar\"><svg class=\"hs-collapse-open:hidden shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line><line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line><line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line></svg> <svg class=\"hs-collapse-open:block hidden shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"></path><path d=\"m6 6 12 12\"></path></svg></button></div></div><!-- Collapse --><div id=\"main-navbar\" class=\"hs-collapse hidden overflow-hidden transition-all duration-300 basis-full grow md:block\" aria-labelledby=\"main-navbar-collapse\"><div class=\"flex flex-col md:flex-row md:items-center md:justify-end gap-2 md:gap-3 mt-3 md:mt-0 py-2 md:py-0 md:ps-7\"><a href=\"/\"")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"fixed top-4 inset-x-0 flex flex-wrap z-50 w-full md:justify-start md:flex-nowrap before:absolute before:inset-0 before:max-w-7xl before:mx-2 before:lg:mx-auto before:rounded-[26px] before:bg-neutral-800/70 before:backdrop-blur-sm before:shadow-md before:shadow-orange-400/40\"><nav class=\"relative max-w-7xl w-full py-2.5 px-5 md:flex md:items-center md:justify-between md:py-0 mx-2 lg:mx-auto\"><div class=\"flex items-center justify-between\"><div class=\"flex-none inline-block\"><a class=\"text-xl font-semibold text-white focus:outline-none\" href=\"/\" aria-label=\"XMR Nodes\">XMR Nodes</a><div id=\"hx-indicator-main\" class=\"htmx-indicator animate-spin ml-2 inline-block size-4 border-[3px] border-current border-t-transparent text-orange-400 rounded-full\" role=\"status\" aria-label=\"loading indicator\"><span class=\"sr-only\">Loading...</span></div></div><div class=\"md:hidden\"><button type=\"button\" class=\"hs-collapse-toggle size-8 flex justify-center items-center text-sm font-semibold rounded-full bg-neutral-800 text-white disabled:opacity-50 disabled:pointer-events-none\" id=\"hs-navbar-floating-dark-collapse\" aria-expanded=\"false\" aria-controls=\"hs-navbar-floating-dark\" aria-label=\"Toggle navigation\" data-hs-collapse=\"#main-navbar\"><svg class=\"hs-collapse-open:hidden shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line><line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line><line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line></svg> <svg class=\"hs-collapse-open:block hidden shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"></path><path d=\"m6 6 12 12\"></path></svg></button></div></div><div id=\"main-navbar\" class=\"hs-collapse hidden overflow-hidden transition-all duration-300 basis-full grow md:block\" aria-labelledby=\"main-navbar-collapse\"><div class=\"flex flex-col md:flex-row md:items-center md:justify-end gap-2 md:gap-3 mt-3 md:mt-0 py-2 md:py-0 md:ps-7\"><a href=\"/\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -59,7 +59,7 @@ func navbar(pageIdentifier string) templ.Component {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Add Node</a></div></div><!-- End Collapse --></nav></header>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Add Node</a></div></div></nav></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -10,20 +10,6 @@ import (
"time"
)
var nettypes = []string{"mainnet", "stagenet", "testnet"}
var protocols = []string{"tor", "http", "https"}
type nodeStatus struct {
Code int
Text string
}
var nodeStatuses = []nodeStatus{
{-1, "ANY"},
{1, "Online"},
{0, "Offline"},
}
templ RemoteNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryNodes, p paging.Pagination) {
<!-- Hero -->
<section class="relative overflow-hidden pt-6">
@ -217,7 +203,7 @@ templ TableNodes(data monero.Nodes, countries []monero.Countries, q monero.Query
for _, row := range data.Items {
<tr>
<td>
@cellHostPort(row.ID, row.Port, row.Hostname, row.IPAddresses, row.IsTor, row.IPv6Only)
@cellHostPort(row.ID, row.Port, row.Hostname, row.IPAddresses, row.IsTor, row.IsI2P, row.IPv6Only)
</td>
<td>
@cellNettype(row.Nettype, row.Height)
@ -316,7 +302,7 @@ templ NodeDetails(data monero.Node, logs monero.FetchLogs, q monero.QueryLogs, p
Monero Node #{ fmt.Sprintf("%d", data.ID) }
</h1>
</div>
<hr class="mt-6"/>
<hr class="mt-6 border-orange-400"/>
</div>
<div class="max-w-3xl mx-auto mt-8">
@Node(data)
@ -427,7 +413,7 @@ templ TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagi
</div>
}
templ cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) {
templ cellHostPort(id, port uint, hostname, ips string, isTor, isI2P, ipv6Only bool) {
if isTor {
<button
class="max-w-40 truncate text-orange-400 hover:brightness-125"
@ -443,7 +429,23 @@ templ cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) {
</button>
<br/>
.onion:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
<span class="text-neutral-400">(TOR)</span>
<span class="badge bg-purple-800">TOR</span>
} else if isI2P {
<button
class="max-w-40 truncate text-orange-400 hover:brightness-125"
hx-get={ fmt.Sprintf("/remote-nodes/id/%d", id) }
hx-push-url="false"
hx-target="#modal-section"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="modal-section"
data-hs-overlay="#modal-section"
>
👁 { hostname }
</button>
<br/>
.i2p:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
<span class="badge bg-green-600">I2P</span>
} else {
{ ip.FormatHostname(hostname) }:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
<br/>

View file

@ -18,20 +18,6 @@ import (
"time"
)
var nettypes = []string{"mainnet", "stagenet", "testnet"}
var protocols = []string{"tor", "http", "https"}
type nodeStatus struct {
Code int
Text string
}
var nodeStatuses = []nodeStatus{
{-1, "ANY"},
{1, "Online"},
{0, "Offline"},
}
func RemoteNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryNodes, p paging.Pagination) 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
@ -141,7 +127,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", q.Host))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 110, Col: 41}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 96, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@ -154,7 +140,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"host"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 114, Col: 96}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 100, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -167,7 +153,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"nettype"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 127, Col: 99}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 113, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@ -185,7 +171,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", nettype))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 135, Col: 51}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 121, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@ -208,7 +194,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 135, Col: 98}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 121, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@ -226,7 +212,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"protocol"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 145, Col: 100}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 131, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -244,7 +230,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", protocol))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 153, Col: 52}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 139, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@ -267,7 +253,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 153, Col: 102}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 139, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
@ -285,7 +271,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"cc"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 163, Col: 94}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 149, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@ -314,7 +300,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("UNKNOWN (%d)", country.TotalNodes))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 172, Col: 115}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 158, Col: 115}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@ -332,7 +318,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", country.CC))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 174, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 160, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@ -355,7 +341,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s (%d)", country.Name, country.TotalNodes))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 174, Col: 150}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 160, Col: 150}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@ -374,7 +360,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"status"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 185, Col: 98}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 171, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@ -392,7 +378,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", status.Code))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 192, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 178, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@ -415,7 +401,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(status.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 192, Col: 109}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 178, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@ -443,7 +429,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", "/remote-nodes", paging.EncodedQuery(q, []string{"cors"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 204, Col: 97}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 190, Col: 97}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
@ -458,7 +444,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = cellHostPort(row.ID, row.Port, row.Hostname, row.IPAddresses, row.IsTor, row.IPv6Only).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = cellHostPort(row.ID, row.Port, row.Hostname, row.IPAddresses, row.IsTor, row.IsI2P, row.IPv6Only).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -501,7 +487,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.EstimateFee))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 234, Col: 66}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 220, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
@ -531,7 +517,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(time.Unix(row.LastChecked, 0).UTC().Format("Jan 2, 2006 15:04 MST"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 240, Col: 86}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 226, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
@ -544,7 +530,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(utils.TimeSince(row.LastChecked))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 240, Col: 123}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 226, Col: 123}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
@ -603,7 +589,7 @@ func Node(data monero.Node) templ.Component {
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%d", data.Hostname, data.Port))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 262, Col: 54}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 248, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
@ -616,7 +602,7 @@ func Node(data monero.Node) templ.Component {
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%d", data.Hostname, data.Port))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 265, Col: 320}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 251, Col: 320}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@ -629,7 +615,7 @@ func Node(data monero.Node) templ.Component {
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(data.Protocol)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 276, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 262, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
@ -647,7 +633,7 @@ func Node(data monero.Node) templ.Component {
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(data.Nettype)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 287, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 273, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
@ -666,7 +652,7 @@ func Node(data monero.Node) templ.Component {
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(data.IPAddresses, ",", ", "))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 299, Col: 91}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 285, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
@ -721,13 +707,13 @@ func NodeDetails(data monero.Node, logs monero.FetchLogs, q monero.QueryLogs, p
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 316, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 302, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div><hr class=\"mt-6\"></div><div class=\"max-w-3xl mx-auto mt-8\">")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div><hr class=\"mt-6 border-orange-400\"></div><div class=\"max-w-3xl mx-auto mt-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -819,7 +805,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", hxPath, paging.EncodedQuery(q, []string{"status"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 371, Col: 89}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 357, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
@ -837,7 +823,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", status.Code))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 378, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 364, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
@ -860,7 +846,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(status.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 378, Col: 109}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 364, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
@ -878,7 +864,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", q.FailedReason))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 387, Col: 49}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 373, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
@ -891,7 +877,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s?%s", hxPath, paging.EncodedQuery(q, []string{"failed_reason"})))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 391, Col: 96}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 377, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
if templ_7745c5c3_Err != nil {
@ -909,7 +895,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var37 string
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 403, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 389, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
@ -922,7 +908,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var38 string
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.ProberID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 404, Col: 44}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 390, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
if templ_7745c5c3_Err != nil {
@ -940,7 +926,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.Height))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 407, Col: 62}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 393, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
@ -953,7 +939,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(time.Unix(row.AdjustedTime, 0).UTC().Format("Jan 2, 2006 15:04 MST"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 408, Col: 82}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 394, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
if templ_7745c5c3_Err != nil {
@ -966,7 +952,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var41 string
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatBytes(row.DatabaseSize, 0))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 409, Col: 52}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 395, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
if templ_7745c5c3_Err != nil {
@ -979,7 +965,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var42 string
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatHashes(float64(row.Difficulty)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 410, Col: 57}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 396, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
if templ_7745c5c3_Err != nil {
@ -992,7 +978,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var43 string
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.EstimateFee))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 411, Col: 67}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 397, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
if templ_7745c5c3_Err != nil {
@ -1010,7 +996,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var44 string
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(row.FailedReason)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 414, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 400, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
if templ_7745c5c3_Err != nil {
@ -1028,7 +1014,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var45 string
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(time.Unix(row.DateChecked, 0).UTC().Format("Jan 2, 2006 15:04 MST"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 416, Col: 86}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 402, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
if templ_7745c5c3_Err != nil {
@ -1041,7 +1027,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var46 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(utils.TimeSince(row.DateChecked))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 416, Col: 123}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 402, Col: 123}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
if templ_7745c5c3_Err != nil {
@ -1054,7 +1040,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
var templ_7745c5c3_Var47 string
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(row.FetchRuntime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 417, Col: 67}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 403, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
if templ_7745c5c3_Err != nil {
@ -1085,7 +1071,7 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin
})
}
func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) templ.Component {
func cellHostPort(id, port uint, hostname, ips string, isTor, isI2P, ipv6Only bool) 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 {
@ -1114,7 +1100,7 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
var templ_7745c5c3_Var49 string
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/remote-nodes/id/%d", id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 434, Col: 50}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 420, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil {
@ -1127,7 +1113,7 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
var templ_7745c5c3_Var50 string
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(hostname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 442, Col: 18}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 428, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil {
@ -1140,23 +1126,67 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
var templ_7745c5c3_Var51 string
templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 445, Col: 64}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 431, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span class=\"text-neutral-400\">(TOR)</span>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span class=\"badge bg-purple-800\">TOR</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if isI2P {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button class=\"max-w-40 truncate text-orange-400 hover:brightness-125\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var52 string
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/remote-nodes/id/%d", id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 436, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-push-url=\"false\" hx-target=\"#modal-section\" aria-haspopup=\"dialog\" aria-expanded=\"false\" aria-controls=\"modal-section\" data-hs-overlay=\"#modal-section\">👁 ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var53 string
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(hostname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 444, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button><br>.i2p:<span class=\"text-indigo-400\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var54 string
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 447, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span class=\"badge bg-green-600\">I2P</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var52 string
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(ip.FormatHostname(hostname))
var templ_7745c5c3_Var55 string
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(ip.FormatHostname(hostname))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 448, Col: 31}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 450, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1164,12 +1194,12 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var53 string
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
var templ_7745c5c3_Var56 string
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 448, Col: 89}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 450, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1177,12 +1207,12 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var54 string
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(ips, ",", " "))
var templ_7745c5c3_Var57 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(ips, ",", " "))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 451, Col: 90}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 453, Col: 90}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1221,9 +1251,9 @@ func cellNettype(nettype string, height uint) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var55 := templ.GetChildren(ctx)
if templ_7745c5c3_Var55 == nil {
templ_7745c5c3_Var55 = templ.NopComponent
templ_7745c5c3_Var58 := templ.GetChildren(ctx)
if templ_7745c5c3_Var58 == nil {
templ_7745c5c3_Var58 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch nettype {
@ -1232,12 +1262,12 @@ func cellNettype(nettype string, height uint) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var56 string
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
var templ_7745c5c3_Var59 string
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 462, Col: 63}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 464, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1250,12 +1280,12 @@ func cellNettype(nettype string, height uint) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var57 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
var templ_7745c5c3_Var60 string
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 464, Col: 64}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 466, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1268,12 +1298,12 @@ func cellNettype(nettype string, height uint) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var58 string
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
var templ_7745c5c3_Var61 string
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 466, Col: 65}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 468, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1286,12 +1316,12 @@ func cellNettype(nettype string, height uint) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var59 string
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", height))
var templ_7745c5c3_Var62 string
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", height))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 469, Col: 28}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 471, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1315,9 +1345,9 @@ func cellProtocol(protocol string, cors bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var60 := templ.GetChildren(ctx)
if templ_7745c5c3_Var60 == nil {
templ_7745c5c3_Var60 = templ.NopComponent
templ_7745c5c3_Var63 := templ.GetChildren(ctx)
if templ_7745c5c3_Var63 == nil {
templ_7745c5c3_Var63 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch protocol {
@ -1326,12 +1356,12 @@ func cellProtocol(protocol string, cors bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var61 string
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
var templ_7745c5c3_Var64 string
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 475, Col: 64}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 477, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1344,12 +1374,12 @@ func cellProtocol(protocol string, cors bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var62 string
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
var templ_7745c5c3_Var65 string
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 477, Col: 66}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 479, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1384,19 +1414,19 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var63 := templ.GetChildren(ctx)
if templ_7745c5c3_Var63 == nil {
templ_7745c5c3_Var63 = templ.NopComponent
templ_7745c5c3_Var66 := templ.GetChildren(ctx)
if templ_7745c5c3_Var66 == nil {
templ_7745c5c3_Var66 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if cc != "" {
if city != "" {
var templ_7745c5c3_Var64 string
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(city)
var templ_7745c5c3_Var67 string
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(city)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 488, Col: 9}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 490, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1409,12 +1439,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var65 string
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(countryName)
var templ_7745c5c3_Var68 string
templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(countryName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 490, Col: 15}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 492, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1422,12 +1452,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var66 string
templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/img/cf/%s.svg", strings.ToLower(cc)))
var templ_7745c5c3_Var69 string
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/img/cf/%s.svg", strings.ToLower(cc)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 491, Col: 91}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 493, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1435,12 +1465,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var67 string
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s Flag", cc))
var templ_7745c5c3_Var70 string
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s Flag", cc))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 491, Col: 126}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 493, Col: 126}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1454,8 +1484,8 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var68 templ.SafeURL = templ.URL(fmt.Sprintf("https://www.ditatompel.com/asn/%d", asn))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var68)))
var templ_7745c5c3_Var71 templ.SafeURL = templ.URL(fmt.Sprintf("https://www.ditatompel.com/asn/%d", asn))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var71)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1463,12 +1493,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var69 string
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("AS%d", asn))
var templ_7745c5c3_Var72 string
templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("AS%d", asn))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 500, Col: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 502, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1476,12 +1506,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var70 string
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(asnName)
var templ_7745c5c3_Var73 string
templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(asnName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 501, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 503, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1510,9 +1540,9 @@ func cellStatuses(isAvailable bool, statuses [5]int) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var71 := templ.GetChildren(ctx)
if templ_7745c5c3_Var71 == nil {
templ_7745c5c3_Var71 = templ.NopComponent
templ_7745c5c3_Var74 := templ.GetChildren(ctx)
if templ_7745c5c3_Var74 == nil {
templ_7745c5c3_Var74 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if isAvailable {
@ -1568,9 +1598,9 @@ func cellUptime(uptime float64) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var72 := templ.GetChildren(ctx)
if templ_7745c5c3_Var72 == nil {
templ_7745c5c3_Var72 = templ.NopComponent
templ_7745c5c3_Var75 := templ.GetChildren(ctx)
if templ_7745c5c3_Var75 == nil {
templ_7745c5c3_Var75 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if uptime >= 98 {
@ -1578,12 +1608,12 @@ func cellUptime(uptime float64) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var73 string
templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
var templ_7745c5c3_Var76 string
templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 525, Col: 58}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 527, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1596,12 +1626,12 @@ func cellUptime(uptime float64) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var74 string
templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
var templ_7745c5c3_Var77 string
templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 527, Col: 56}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 529, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1614,12 +1644,12 @@ func cellUptime(uptime float64) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var75 string
templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
var templ_7745c5c3_Var78 string
templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 529, Col: 59}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 531, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -1632,12 +1662,12 @@ func cellUptime(uptime float64) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var76 string
templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
var templ_7745c5c3_Var79 string
templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 531, Col: 57}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 533, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -9,9 +9,14 @@ a.external {
@apply link after:content-['_↗'];
}
/* badge */
.badge {
@apply inline-flex items-center gap-x-1.5 px-2 rounded-md text-sm font-bold text-white;
}
/* main navbar */
#main-navbar div a {
@apply py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-transparent text-gray-500 hover:text-gray-800 focus:outline-none dark:text-neutral-400 dark:hover:text-neutral-200;
@apply py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-transparent text-neutral-400 hover:text-neutral-200 focus:outline-none;
}
#main-navbar div a.active {
@apply py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-orange-400 font-medium text-neutral-200 focus:outline-none;

View file

@ -5,6 +5,7 @@ type link struct {
URI string
}
// Community reference links that are displayed on the home page
var communityLinks = []link{
{Text: "moneroworld.com", URI: "https://moneroworld.com"},
{Text: "monero.how", URI: "https://www.monero.how"},
@ -15,4 +16,25 @@ var communityLinks = []link{
{Text: "sethforprivacy.com", URI: "https://sethforprivacy.com"},
}
var refreshIntevals = []string{"5s", "10s", "30s", "1m"}
type nodeStatus struct {
Code int
Text string
}
// nodeStatuses is a list of status and their text representation in the UI
//
// The "Status" filter select option in the UI is populated from this list.
var nodeStatuses = []nodeStatus{
{-1, "ANY"},
{1, "Online"},
{0, "Offline"},
}
// refreshIntevals, nettypes, and protocols are used to populate the refresh
// interval, Monero network types, and protocols filter select options in the
// UI
var (
refreshIntevals = []string{"5s", "10s", "30s", "1m"}
nettypes = []string{"mainnet", "stagenet", "testnet"}
protocols = []string{"tor", "i2p", "http", "https"}
)

View file

@ -35,6 +35,7 @@ type Node struct {
Port uint `json:"port" db:"port"`
Protocol string `json:"protocol" db:"protocol"`
IsTor bool `json:"is_tor" db:"is_tor"`
IsI2P bool `json:"is_i2p" db:"is_i2p"`
IsAvailable bool `json:"is_available" db:"is_available"`
Nettype string `json:"nettype" db:"nettype"`
Height uint `json:"height" db:"height"`
@ -98,13 +99,17 @@ func (q *QueryNodes) toSQL() (args []interface{}, where string) {
wq = append(wq, "nettype = ?")
args = append(args, q.Nettype)
}
if q.Protocol != "any" && slices.Contains([]string{"tor", "http", "https"}, q.Protocol) {
if q.Protocol == "tor" {
if q.Protocol != "any" && slices.Contains([]string{"tor", "i2p", "http", "https"}, q.Protocol) {
switch q.Protocol {
case "i2p":
wq = append(wq, "is_i2p = ?")
args = append(args, 1)
case "tor":
wq = append(wq, "is_tor = ?")
args = append(args, 1)
} else {
wq = append(wq, "(protocol = ? AND is_tor = ?)")
args = append(args, q.Protocol, 0)
default:
wq = append(wq, "(protocol = ? AND is_tor = ? AND is_i2p = ?)")
args = append(args, q.Protocol, 0, 0)
}
}
if q.CC != "any" {
@ -196,14 +201,25 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
is_tor := false
if strings.HasSuffix(hostname, ".onion") {
if !validTorHostname(hostname) {
return errors.New("Invalid TOR v3 .onion hostname")
}
is_tor = true
}
is_i2p := false
if strings.HasSuffix(hostname, ".i2p") {
if !validI2PHostname(hostname) {
return errors.New("Invalid I2P hostname")
}
is_i2p = true
}
ipAddr := ""
ips := ""
ipv6_only := false
if !is_tor {
if !is_tor && !is_i2p {
hostIps, err := net.LookupIP(hostname)
if err != nil {
return err
@ -221,10 +237,6 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
ipAddr = hostIp.String()
ips = ip.SliceToString(hostIps)
} else {
if !validTorHostname(hostname) {
return errors.New("Invalid TOR v3 .onion hostname")
}
}
row, err := r.db.Query(`
@ -252,6 +264,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
hostname,
port,
is_tor,
is_i2p,
nettype,
ip_addr,
lat,
@ -274,12 +287,14 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
?,
?,
?,
?,
?
)`,
protocol,
hostname,
port,
is_tor,
is_i2p,
"",
ipAddr,
0,
@ -304,6 +319,26 @@ func validTorHostname(hostname string) bool {
return regexp.MustCompile(`^([a-z0-9-]+\.)*[a-z2-7]{56}\.onion$`).MatchString(hostname)
}
// validI2PHostname checks if a given hostname is a valid b32 or naming service
// I2P address
//
// Old b32 addresses are always {52 chars}.b32.i2p and new ones are
// {56+ chars}.b32.i2p. Since I don't know if there is a length limit of new
// b32 addresses, this function allows up to 63 characters.
//
// For naming service, I2P addresses are up to 67 characters, including the
// '.i2p' part. Please note that this naming service validation only validates
// simple length and allowed characters. Advanced validation such as
// internationalized domain name (IDN) is not implemented.
//
// Ref: https://geti2p.net/spec/b32encrypted and https://geti2p.net/en/docs/naming
func validI2PHostname(hostname string) bool {
// To minimize abuse, I set minimum length of submitted i2p naming service
// address to 5 characters. If someone have an address of 4 characters or
// less, let them open an issue or create a pull request.
return regexp.MustCompile(`^([a-z2-7]{52,63}\.b32|[a-z0-9-]{5,63})\.i2p$`).MatchString(hostname)
}
func (r *moneroRepo) Delete(id uint) error {
if _, err := r.db.Exec(`DELETE FROM tbl_node WHERE id = ?`, id); err != nil {
return err

View file

@ -177,6 +177,57 @@ func Benchmark_validTorHostname(b *testing.B) {
}
}
// Single test:
// go test -race ./internal/monero -run=TestValidI2PHostname -v
func TestValidI2PHostname(t *testing.T) {
tests := []struct {
name string
host string
wantValid bool
}{
{
name: "Empty host",
host: "",
wantValid: false,
},
{
name: "Valid b32 i2p host (old format)",
host: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst234567.b32.i2p",
wantValid: true,
},
{
name: "Invalid b32 i2p host (old format)",
host: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst123456.b32.i2p",
wantValid: false,
},
{
name: "Valid naming service i2p host",
host: "i2p-projekt.i2p",
wantValid: true,
},
{
name: "clearnet domain",
host: "test.com",
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if r := validI2PHostname(tt.host); r != tt.wantValid {
t.Errorf("ValidTorHostname() error = %v, wantValid %v", r, tt.wantValid)
}
})
}
}
// Single bench test:
// go test ./internal/monero -bench validI2PHostname -benchmem -run=^$ -v
func Benchmark_validI2PHostname(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = validTorHostname("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst234567.b32.i2p")
}
}
// equalArgs is helper function for testing.
//
// This returns true if two slices of interface{} are equal.

View file

@ -109,7 +109,7 @@ func (r *moneroRepo) Logs(q QueryLogs) (FetchLogs, error) {
}
// GiveJob returns node that should be probed for the next time
func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
func (r *moneroRepo) GiveJob(acceptTor, acceptI2P, acceptIPv6 int) (Node, error) {
args := []interface{}{}
wq := []string{}
where := ""
@ -118,6 +118,10 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
wq = append(wq, "is_tor = ?")
args = append(args, 0)
}
if acceptI2P != 1 {
wq = append(wq, "is_i2p = ?")
args = append(args, 0)
}
if acceptIPv6 != 1 {
wq = append(wq, "ipv6_only = ?")
args = append(args, 0)
@ -136,10 +140,11 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
port,
protocol,
is_tor,
is_i2p,
last_check_status
FROM
tbl_node
%s -- where query if any
%s
ORDER BY
last_checked ASC
LIMIT 1`, where)
@ -149,6 +154,7 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
&node.Port,
&node.Protocol,
&node.IsTor,
&node.IsI2P,
&node.LastCheckStatus)
if err != nil {
return node, err