mirror of
https://github.com/ditatompel/xmr-remote-nodes.git
synced 2025-01-08 05:52:10 +07:00
Compare commits
7 commits
15804ee438
...
114078d3a1
Author | SHA1 | Date | |
---|---|---|---|
114078d3a1 | |||
8e79d20b29 | |||
64da0beff9 | |||
5e2ab83295 | |||
e0313bdbe2 | |||
f339bc9c3c | |||
e892733a55 |
16 changed files with 337 additions and 129 deletions
|
@ -8,6 +8,8 @@ SERVER_ENDPOINT="http://127.0.0.1:18901"
|
||||||
API_KEY=
|
API_KEY=
|
||||||
ACCEPT_TOR=false
|
ACCEPT_TOR=false
|
||||||
TOR_SOCKS="127.0.0.1:9050"
|
TOR_SOCKS="127.0.0.1:9050"
|
||||||
|
ACCEPT_I2P=false
|
||||||
|
I2P_SOCKS="127.0.0.1:4447"
|
||||||
IPV6_CAPABLE=false
|
IPV6_CAPABLE=false
|
||||||
|
|
||||||
# Server Config
|
# Server Config
|
||||||
|
|
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
@ -3,6 +3,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- htmx
|
- htmx
|
||||||
|
- i2p-support
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
name: Test
|
name: Test
|
||||||
|
|
|
@ -29,7 +29,7 @@ To build the executable binaries, you need:
|
||||||
|
|
||||||
- Go >= 1.22
|
- Go >= 1.22
|
||||||
- Bun >= 1.1.26
|
- Bun >= 1.1.26
|
||||||
- templ v0.2.778
|
- [a-h/templ][templ-repo] v0.2.778
|
||||||
|
|
||||||
> **Note**:
|
> **Note**:
|
||||||
>
|
>
|
||||||
|
@ -84,6 +84,7 @@ See the [Makefile](./Makefile).
|
||||||
- :white_check_mark: Accept IPv6 nodes.
|
- :white_check_mark: Accept IPv6 nodes.
|
||||||
- :white_check_mark: 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`.
|
- Use Go standard `net/http` instead of `fiber`.
|
||||||
|
- :white_check_mark: Accept I2P nodes.
|
||||||
|
|
||||||
## Acknowledgement
|
## Acknowledgement
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ Thank you!
|
||||||
|
|
||||||
This project is licensed under [BSD-3-Clause](./LICENSE) 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"
|
[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"
|
[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"
|
[prober-systemd-service]: ./deployment/init/xmr-nodes-prober.service "systemd service example for prober"
|
||||||
|
|
|
@ -21,11 +21,12 @@ import (
|
||||||
"golang.org/x/net/proxy"
|
"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 (
|
const (
|
||||||
errNoEndpoint = errProber("no SERVER_ENDPOINT was provided")
|
errNoEndpoint = errProber("no SERVER_ENDPOINT was provided")
|
||||||
errNoTorSocks = errProber("no TOR_SOCKS was provided")
|
errNoTorSocks = errProber("no TOR_SOCKS was provided")
|
||||||
|
errNoI2PSocks = errProber("no I2P_SOCKS was provided")
|
||||||
errNoAPIKey = errProber("no API_KEY was provided")
|
errNoAPIKey = errProber("no API_KEY was provided")
|
||||||
errInvalidCredentials = errProber("invalid API_KEY credentials")
|
errInvalidCredentials = errProber("invalid API_KEY credentials")
|
||||||
)
|
)
|
||||||
|
@ -41,6 +42,8 @@ type proberClient struct {
|
||||||
apiKey string // prober api key
|
apiKey string // prober api key
|
||||||
acceptTor bool // accept tor
|
acceptTor bool // accept tor
|
||||||
torSOCKS string // IP:Port of tor socks
|
torSOCKS string // IP:Port of tor socks
|
||||||
|
acceptI2P bool // accept i2p
|
||||||
|
I2PSOCKS string // IP:Port of i2p socks
|
||||||
acceptIPv6 bool // accept ipv6
|
acceptIPv6 bool // accept ipv6
|
||||||
message string // message to include when reporting back to server
|
message string // message to include when reporting back to server
|
||||||
}
|
}
|
||||||
|
@ -52,6 +55,8 @@ func newProber() *proberClient {
|
||||||
apiKey: cfg.APIKey,
|
apiKey: cfg.APIKey,
|
||||||
acceptTor: cfg.AcceptTor,
|
acceptTor: cfg.AcceptTor,
|
||||||
torSOCKS: cfg.TorSOCKS,
|
torSOCKS: cfg.TorSOCKS,
|
||||||
|
acceptI2P: cfg.AcceptI2P,
|
||||||
|
I2PSOCKS: cfg.I2PSOCKS,
|
||||||
acceptIPv6: cfg.IPv6Capable,
|
acceptIPv6: cfg.IPv6Capable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +72,9 @@ var ProbeCmd = &cobra.Command{
|
||||||
if t, _ := cmd.Flags().GetBool("no-tor"); t {
|
if t, _ := cmd.Flags().GetBool("no-tor"); t {
|
||||||
prober.SetAcceptTor(false)
|
prober.SetAcceptTor(false)
|
||||||
}
|
}
|
||||||
|
if t, _ := cmd.Flags().GetBool("no-i2p"); t {
|
||||||
|
prober.SetAcceptI2P(false)
|
||||||
|
}
|
||||||
|
|
||||||
if err := prober.Run(); err != nil {
|
if err := prober.Run(); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
@ -88,6 +96,10 @@ func (p *proberClient) SetAcceptTor(acceptTor bool) {
|
||||||
p.acceptTor = acceptTor
|
p.acceptTor = acceptTor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *proberClient) SetAcceptI2P(acceptI2P bool) {
|
||||||
|
p.acceptI2P = acceptI2P
|
||||||
|
}
|
||||||
|
|
||||||
func (p *proberClient) SetAcceptIPv6(acceptIPv6 bool) {
|
func (p *proberClient) SetAcceptIPv6(acceptIPv6 bool) {
|
||||||
p.acceptIPv6 = acceptIPv6
|
p.acceptIPv6 = acceptIPv6
|
||||||
}
|
}
|
||||||
|
@ -122,6 +134,9 @@ func (p *proberClient) validateConfig() error {
|
||||||
if p.acceptTor && p.torSOCKS == "" {
|
if p.acceptTor && p.torSOCKS == "" {
|
||||||
return errNoTorSocks
|
return errNoTorSocks
|
||||||
}
|
}
|
||||||
|
if p.acceptI2P && p.I2PSOCKS == "" {
|
||||||
|
return errNoI2PSocks
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -133,6 +148,11 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
|
||||||
acceptTor = 1
|
acceptTor = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acceptI2P := 0
|
||||||
|
if p.acceptI2P {
|
||||||
|
acceptI2P = 1
|
||||||
|
}
|
||||||
|
|
||||||
acceptIPv6 := 0
|
acceptIPv6 := 0
|
||||||
if p.acceptIPv6 {
|
if p.acceptIPv6 {
|
||||||
acceptIPv6 = 1
|
acceptIPv6 = 1
|
||||||
|
@ -140,7 +160,7 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
|
||||||
|
|
||||||
var node monero.Node
|
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))
|
slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, uri, nil)
|
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")
|
req.Header.Set("Origin", "https://xmr.ditatompel.com")
|
||||||
|
|
||||||
var client http.Client
|
var client http.Client
|
||||||
|
var socks5 string
|
||||||
|
|
||||||
if p.acceptTor && node.IsTor {
|
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 {
|
if err != nil {
|
||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
|
@ -268,7 +296,7 @@ func (p *proberClient) fetchNode(node monero.Node) (monero.Node, error) {
|
||||||
node.CORSCapable = true
|
node.CORSCapable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !node.IsTor {
|
if !node.IsTor && !node.IsI2P {
|
||||||
hostIp, err := net.LookupIP(node.Hostname)
|
hostIp, err := net.LookupIP(node.Hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Warning: Could not resolve hostname: " + node.Hostname)
|
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 {
|
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 {
|
if hostIps, err := net.LookupIP(node.Hostname); err == nil {
|
||||||
node.IPv6Only = ip.IsIPv6Only(hostIps)
|
node.IPv6Only = ip.IsIPv6Only(hostIps)
|
||||||
node.IPAddresses = ip.SliceToString(hostIps)
|
node.IPAddresses = ip.SliceToString(hostIps)
|
||||||
|
|
|
@ -29,7 +29,8 @@ func init() {
|
||||||
Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env")
|
Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env")
|
||||||
Root.AddCommand(client.ProbeCmd)
|
Root.AddCommand(client.ProbeCmd)
|
||||||
client.ProbeCmd.Flags().StringP("endpoint", "e", "", "Server endpoint")
|
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() {
|
func initConfig() {
|
||||||
|
|
|
@ -27,6 +27,8 @@ type App struct {
|
||||||
APIKey string
|
APIKey string
|
||||||
AcceptTor bool
|
AcceptTor bool
|
||||||
TorSOCKS string
|
TorSOCKS string
|
||||||
|
AcceptI2P bool
|
||||||
|
I2PSOCKS string
|
||||||
IPv6Capable bool
|
IPv6Capable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,5 +74,7 @@ func LoadApp() {
|
||||||
app.APIKey = os.Getenv("API_KEY")
|
app.APIKey = os.Getenv("API_KEY")
|
||||||
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
|
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
|
||||||
app.TorSOCKS = os.Getenv("TOR_SOCKS")
|
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"))
|
app.IPv6Capable, _ = strconv.ParseBool(os.Getenv("IPV6_CAPABLE"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
type migrateFn func(*DB) error
|
type migrateFn func(*DB) error
|
||||||
|
|
||||||
var dbMigrate = [...]migrateFn{v1, v2, v3}
|
var dbMigrate = [...]migrateFn{v1, v2, v3, v4}
|
||||||
|
|
||||||
func MigrateDb(db *DB) error {
|
func MigrateDb(db *DB) error {
|
||||||
version := getSchemaVersion(db)
|
version := getSchemaVersion(db)
|
||||||
|
@ -272,3 +272,18 @@ func v3(db *DB) error {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -402,10 +402,11 @@ func (s *fiberServer) countriesAPI(c *fiber.Ctx) error {
|
||||||
// This handler should protected by `s.checkProberMW` middleware.
|
// This handler should protected by `s.checkProberMW` middleware.
|
||||||
func (s *fiberServer) giveJobAPI(c *fiber.Ctx) error {
|
func (s *fiberServer) giveJobAPI(c *fiber.Ctx) error {
|
||||||
acceptTor := c.QueryInt("accept_tor", 0)
|
acceptTor := c.QueryInt("accept_tor", 0)
|
||||||
|
acceptI2P := c.QueryInt("accept_i2p", 0)
|
||||||
acceptIPv6 := c.QueryInt("accept_ipv6", 0)
|
acceptIPv6 := c.QueryInt("accept_ipv6", 0)
|
||||||
|
|
||||||
moneroRepo := monero.New()
|
moneroRepo := monero.New()
|
||||||
node, err := moneroRepo.GiveJob(acceptTor, acceptIPv6)
|
node, err := moneroRepo.GiveJob(acceptTor, acceptI2P, acceptIPv6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"status": "error",
|
"status": "error",
|
||||||
|
|
|
@ -7,11 +7,9 @@ templ AddNode() {
|
||||||
<div class="relative z-10">
|
<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="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-10 lg:py-16">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- Title -->
|
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<h1 class="block font-extrabold text-4xl md:text-5xl lg:text-6xl text-neutral-200">Add Monero Node</h1>
|
<h1 class="block font-extrabold text-4xl md:text-5xl lg:text-6xl text-neutral-200">Add Monero Node</h1>
|
||||||
</div>
|
</div>
|
||||||
<!-- End Title -->
|
|
||||||
<div class="mt-5">
|
<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>
|
<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>
|
||||||
|
@ -25,6 +23,7 @@ templ AddNode() {
|
||||||
<div class="mt-2 text-sm">
|
<div class="mt-2 text-sm">
|
||||||
<ul class="list-disc space-y-1 ps-5">
|
<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>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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,9 +31,7 @@ templ AddNode() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<p class="mt-1 text-center">
|
<p class="mt-1 text-center">Enter your Monero node information below:</p>
|
||||||
Enter your Monero node information below (IPv6 host check is experimental):
|
|
||||||
</p>
|
|
||||||
<div class="mt-12">
|
<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()">
|
<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 class="grid grid-cols-1 sm:grid-cols-4 gap-6">
|
||||||
|
|
|
@ -37,7 +37,7 @@ func AddNode() templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var nettypes = []string{"mainnet", "stagenet", "testnet"}
|
var nettypes = []string{"mainnet", "stagenet", "testnet"}
|
||||||
var protocols = []string{"tor", "http", "https"}
|
var protocols = []string{"tor", "i2p", "http", "https"}
|
||||||
|
|
||||||
type nodeStatus struct {
|
type nodeStatus struct {
|
||||||
Code int
|
Code int
|
||||||
|
@ -217,7 +217,7 @@ templ TableNodes(data monero.Nodes, countries []monero.Countries, q monero.Query
|
||||||
for _, row := range data.Items {
|
for _, row := range data.Items {
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
@cellNettype(row.Nettype, row.Height)
|
@cellNettype(row.Nettype, row.Height)
|
||||||
|
@ -427,7 +427,7 @@ templ TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagi
|
||||||
</div>
|
</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 {
|
if isTor {
|
||||||
<button
|
<button
|
||||||
class="max-w-40 truncate text-orange-400 hover:brightness-125"
|
class="max-w-40 truncate text-orange-400 hover:brightness-125"
|
||||||
|
@ -443,7 +443,23 @@ templ cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) {
|
||||||
</button>
|
</button>
|
||||||
<br/>
|
<br/>
|
||||||
.onion:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
|
.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 {
|
} else {
|
||||||
{ ip.FormatHostname(hostname) }:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
|
{ ip.FormatHostname(hostname) }:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var nettypes = []string{"mainnet", "stagenet", "testnet"}
|
var nettypes = []string{"mainnet", "stagenet", "testnet"}
|
||||||
var protocols = []string{"tor", "http", "https"}
|
var protocols = []string{"tor", "i2p", "http", "https"}
|
||||||
|
|
||||||
type nodeStatus struct {
|
type nodeStatus struct {
|
||||||
Code int
|
Code int
|
||||||
|
@ -458,7 +458,7 @@ func TableNodes(data monero.Nodes, countries []monero.Countries, q monero.QueryN
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1085,7 +1085,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) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
@ -1146,17 +1146,61 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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: 450, 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: 458, 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: 461, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var templ_7745c5c3_Var52 string
|
var templ_7745c5c3_Var55 string
|
||||||
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(ip.FormatHostname(hostname))
|
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(ip.FormatHostname(hostname))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 464, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1164,12 +1208,12 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var53 string
|
var templ_7745c5c3_Var56 string
|
||||||
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
|
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", port))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 464, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1177,12 +1221,12 @@ func cellHostPort(id, port uint, hostname, ips string, isTor, ipv6Only bool) tem
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var54 string
|
var templ_7745c5c3_Var57 string
|
||||||
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(ips, ",", " "))
|
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(strings.ReplaceAll(ips, ",", " "))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 467, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1221,9 +1265,9 @@ func cellNettype(nettype string, height uint) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var55 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var58 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var55 == nil {
|
if templ_7745c5c3_Var58 == nil {
|
||||||
templ_7745c5c3_Var55 = templ.NopComponent
|
templ_7745c5c3_Var58 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
switch nettype {
|
switch nettype {
|
||||||
|
@ -1232,12 +1276,12 @@ func cellNettype(nettype string, height uint) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var56 string
|
var templ_7745c5c3_Var59 string
|
||||||
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 478, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1250,12 +1294,12 @@ func cellNettype(nettype string, height uint) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var57 string
|
var templ_7745c5c3_Var60 string
|
||||||
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 480, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1268,12 +1312,12 @@ func cellNettype(nettype string, height uint) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var58 string
|
var templ_7745c5c3_Var61 string
|
||||||
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(nettype)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 482, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1286,12 +1330,12 @@ func cellNettype(nettype string, height uint) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var59 string
|
var templ_7745c5c3_Var62 string
|
||||||
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", height))
|
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", height))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 485, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1315,9 +1359,9 @@ func cellProtocol(protocol string, cors bool) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var60 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var63 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var60 == nil {
|
if templ_7745c5c3_Var63 == nil {
|
||||||
templ_7745c5c3_Var60 = templ.NopComponent
|
templ_7745c5c3_Var63 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
switch protocol {
|
switch protocol {
|
||||||
|
@ -1326,12 +1370,12 @@ func cellProtocol(protocol string, cors bool) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var61 string
|
var templ_7745c5c3_Var64 string
|
||||||
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
|
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 491, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1344,12 +1388,12 @@ func cellProtocol(protocol string, cors bool) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var62 string
|
var templ_7745c5c3_Var65 string
|
||||||
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
|
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(protocol)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 493, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1384,19 +1428,19 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var63 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var66 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var63 == nil {
|
if templ_7745c5c3_Var66 == nil {
|
||||||
templ_7745c5c3_Var63 = templ.NopComponent
|
templ_7745c5c3_Var66 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
if cc != "" {
|
if cc != "" {
|
||||||
if city != "" {
|
if city != "" {
|
||||||
var templ_7745c5c3_Var64 string
|
var templ_7745c5c3_Var67 string
|
||||||
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(city)
|
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(city)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 504, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1409,12 +1453,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var65 string
|
var templ_7745c5c3_Var68 string
|
||||||
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(countryName)
|
templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(countryName)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 506, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1422,12 +1466,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var66 string
|
var templ_7745c5c3_Var69 string
|
||||||
templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/img/cf/%s.svg", strings.ToLower(cc)))
|
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/img/cf/%s.svg", strings.ToLower(cc)))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 507, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1435,12 +1479,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var67 string
|
var templ_7745c5c3_Var70 string
|
||||||
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s Flag", cc))
|
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s Flag", cc))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 507, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1454,8 +1498,8 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var68 templ.SafeURL = templ.URL(fmt.Sprintf("https://www.ditatompel.com/asn/%d", asn))
|
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_Var68)))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var71)))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1463,12 +1507,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var69 string
|
var templ_7745c5c3_Var72 string
|
||||||
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("AS%d", asn))
|
templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("AS%d", asn))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 516, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1476,12 +1520,12 @@ func cellCountry(cc, countryName, city, asnName string, asn uint) templ.Componen
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var70 string
|
var templ_7745c5c3_Var73 string
|
||||||
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(asnName)
|
templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(asnName)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 517, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1510,9 +1554,9 @@ func cellStatuses(isAvailable bool, statuses [5]int) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var71 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var74 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var71 == nil {
|
if templ_7745c5c3_Var74 == nil {
|
||||||
templ_7745c5c3_Var71 = templ.NopComponent
|
templ_7745c5c3_Var74 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
if isAvailable {
|
if isAvailable {
|
||||||
|
@ -1568,9 +1612,9 @@ func cellUptime(uptime float64) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var72 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var75 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var72 == nil {
|
if templ_7745c5c3_Var75 == nil {
|
||||||
templ_7745c5c3_Var72 = templ.NopComponent
|
templ_7745c5c3_Var75 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
if uptime >= 98 {
|
if uptime >= 98 {
|
||||||
|
@ -1578,12 +1622,12 @@ func cellUptime(uptime float64) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var73 string
|
var templ_7745c5c3_Var76 string
|
||||||
templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 541, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1596,12 +1640,12 @@ func cellUptime(uptime float64) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var74 string
|
var templ_7745c5c3_Var77 string
|
||||||
templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 543, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1614,12 +1658,12 @@ func cellUptime(uptime float64) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var75 string
|
var templ_7745c5c3_Var78 string
|
||||||
templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 545, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@ -1632,12 +1676,12 @@ func cellUptime(uptime float64) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var76 string
|
var templ_7745c5c3_Var79 string
|
||||||
templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatFloat(uptime))
|
||||||
if templ_7745c5c3_Err != nil {
|
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: 547, 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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,14 @@ a.external {
|
||||||
@apply link after:content-['_↗'];
|
@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 */
|
||||||
#main-navbar div a {
|
#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 {
|
#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;
|
@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;
|
||||||
|
|
|
@ -35,6 +35,7 @@ type Node struct {
|
||||||
Port uint `json:"port" db:"port"`
|
Port uint `json:"port" db:"port"`
|
||||||
Protocol string `json:"protocol" db:"protocol"`
|
Protocol string `json:"protocol" db:"protocol"`
|
||||||
IsTor bool `json:"is_tor" db:"is_tor"`
|
IsTor bool `json:"is_tor" db:"is_tor"`
|
||||||
|
IsI2P bool `json:"is_i2p" db:"is_i2p"`
|
||||||
IsAvailable bool `json:"is_available" db:"is_available"`
|
IsAvailable bool `json:"is_available" db:"is_available"`
|
||||||
Nettype string `json:"nettype" db:"nettype"`
|
Nettype string `json:"nettype" db:"nettype"`
|
||||||
Height uint `json:"height" db:"height"`
|
Height uint `json:"height" db:"height"`
|
||||||
|
@ -98,13 +99,17 @@ func (q *QueryNodes) toSQL() (args []interface{}, where string) {
|
||||||
wq = append(wq, "nettype = ?")
|
wq = append(wq, "nettype = ?")
|
||||||
args = append(args, q.Nettype)
|
args = append(args, q.Nettype)
|
||||||
}
|
}
|
||||||
if q.Protocol != "any" && slices.Contains([]string{"tor", "http", "https"}, q.Protocol) {
|
if q.Protocol != "any" && slices.Contains([]string{"tor", "i2p", "http", "https"}, q.Protocol) {
|
||||||
if q.Protocol == "tor" {
|
switch q.Protocol {
|
||||||
|
case "i2p":
|
||||||
|
wq = append(wq, "is_i2p = ?")
|
||||||
|
args = append(args, 1)
|
||||||
|
case "tor":
|
||||||
wq = append(wq, "is_tor = ?")
|
wq = append(wq, "is_tor = ?")
|
||||||
args = append(args, 1)
|
args = append(args, 1)
|
||||||
} else {
|
default:
|
||||||
wq = append(wq, "(protocol = ? AND is_tor = ?)")
|
wq = append(wq, "(protocol = ? AND is_tor = ? AND is_i2p = ?)")
|
||||||
args = append(args, q.Protocol, 0)
|
args = append(args, q.Protocol, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q.CC != "any" {
|
if q.CC != "any" {
|
||||||
|
@ -196,14 +201,25 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
|
|
||||||
is_tor := false
|
is_tor := false
|
||||||
if strings.HasSuffix(hostname, ".onion") {
|
if strings.HasSuffix(hostname, ".onion") {
|
||||||
|
if !validTorHostname(hostname) {
|
||||||
|
return errors.New("Invalid TOR v3 .onion hostname")
|
||||||
|
}
|
||||||
is_tor = true
|
is_tor = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_i2p := false
|
||||||
|
if strings.HasSuffix(hostname, ".i2p") {
|
||||||
|
if !validI2PHostname(hostname) {
|
||||||
|
return errors.New("Invalid I2P hostname")
|
||||||
|
}
|
||||||
|
is_i2p = true
|
||||||
|
}
|
||||||
|
|
||||||
ipAddr := ""
|
ipAddr := ""
|
||||||
ips := ""
|
ips := ""
|
||||||
ipv6_only := false
|
ipv6_only := false
|
||||||
|
|
||||||
if !is_tor {
|
if !is_tor && !is_i2p {
|
||||||
hostIps, err := net.LookupIP(hostname)
|
hostIps, err := net.LookupIP(hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -221,10 +237,6 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
|
|
||||||
ipAddr = hostIp.String()
|
ipAddr = hostIp.String()
|
||||||
ips = ip.SliceToString(hostIps)
|
ips = ip.SliceToString(hostIps)
|
||||||
} else {
|
|
||||||
if !validTorHostname(hostname) {
|
|
||||||
return errors.New("Invalid TOR v3 .onion hostname")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
row, err := r.db.Query(`
|
row, err := r.db.Query(`
|
||||||
|
@ -252,6 +264,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
hostname,
|
hostname,
|
||||||
port,
|
port,
|
||||||
is_tor,
|
is_tor,
|
||||||
|
is_i2p,
|
||||||
nettype,
|
nettype,
|
||||||
ip_addr,
|
ip_addr,
|
||||||
lat,
|
lat,
|
||||||
|
@ -274,12 +287,14 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
|
?,
|
||||||
?
|
?
|
||||||
)`,
|
)`,
|
||||||
protocol,
|
protocol,
|
||||||
hostname,
|
hostname,
|
||||||
port,
|
port,
|
||||||
is_tor,
|
is_tor,
|
||||||
|
is_i2p,
|
||||||
"",
|
"",
|
||||||
ipAddr,
|
ipAddr,
|
||||||
0,
|
0,
|
||||||
|
@ -304,6 +319,26 @@ func validTorHostname(hostname string) bool {
|
||||||
return regexp.MustCompile(`^([a-z0-9-]+\.)*[a-z2-7]{56}\.onion$`).MatchString(hostname)
|
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 {
|
func (r *moneroRepo) Delete(id uint) error {
|
||||||
if _, err := r.db.Exec(`DELETE FROM tbl_node WHERE id = ?`, id); err != nil {
|
if _, err := r.db.Exec(`DELETE FROM tbl_node WHERE id = ?`, id); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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.
|
// equalArgs is helper function for testing.
|
||||||
//
|
//
|
||||||
// This returns true if two slices of interface{} are equal.
|
// This returns true if two slices of interface{} are equal.
|
||||||
|
|
|
@ -109,7 +109,7 @@ func (r *moneroRepo) Logs(q QueryLogs) (FetchLogs, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GiveJob returns node that should be probed for the next time
|
// 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{}{}
|
args := []interface{}{}
|
||||||
wq := []string{}
|
wq := []string{}
|
||||||
where := ""
|
where := ""
|
||||||
|
@ -118,6 +118,10 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
|
||||||
wq = append(wq, "is_tor = ?")
|
wq = append(wq, "is_tor = ?")
|
||||||
args = append(args, 0)
|
args = append(args, 0)
|
||||||
}
|
}
|
||||||
|
if acceptI2P != 1 {
|
||||||
|
wq = append(wq, "is_i2p = ?")
|
||||||
|
args = append(args, 0)
|
||||||
|
}
|
||||||
if acceptIPv6 != 1 {
|
if acceptIPv6 != 1 {
|
||||||
wq = append(wq, "ipv6_only = ?")
|
wq = append(wq, "ipv6_only = ?")
|
||||||
args = append(args, 0)
|
args = append(args, 0)
|
||||||
|
@ -136,10 +140,11 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
|
||||||
port,
|
port,
|
||||||
protocol,
|
protocol,
|
||||||
is_tor,
|
is_tor,
|
||||||
|
is_i2p,
|
||||||
last_check_status
|
last_check_status
|
||||||
FROM
|
FROM
|
||||||
tbl_node
|
tbl_node
|
||||||
%s -- where query if any
|
%s
|
||||||
ORDER BY
|
ORDER BY
|
||||||
last_checked ASC
|
last_checked ASC
|
||||||
LIMIT 1`, where)
|
LIMIT 1`, where)
|
||||||
|
@ -149,6 +154,7 @@ func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
|
||||||
&node.Port,
|
&node.Port,
|
||||||
&node.Protocol,
|
&node.Protocol,
|
||||||
&node.IsTor,
|
&node.IsTor,
|
||||||
|
&node.IsI2P,
|
||||||
&node.LastCheckStatus)
|
&node.LastCheckStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return node, err
|
return node, err
|
||||||
|
|
Loading…
Reference in a new issue