Compare commits

...

25 commits
v0.2.0 ... main

Author SHA1 Message Date
ditatombot[bot]
0ac3d6837b
Merge pull request #178 from ditatompel/dependabot/go_modules/github.com/a-h/templ-0.3.857
Merge pull request #178

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-04-02 14:53:59 +00:00
9fe1ed5351
build: Using templ v0.3.857 2025-04-02 21:50:43 +07:00
dependabot[bot]
8e32103ae4
build(deps): bump github.com/a-h/templ from 0.2.778 to 0.3.857
Bumps [github.com/a-h/templ](https://github.com/a-h/templ) from 0.2.778 to 0.3.857.
- [Release notes](https://github.com/a-h/templ/releases)
- [Changelog](https://github.com/a-h/templ/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/a-h/templ/compare/v0.2.778...v0.3.857)

---
updated-dependencies:
- dependency-name: github.com/a-h/templ
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 05:57:37 +00:00
ditatombot[bot]
31edbc0ecf
Merge pull request #175 from ditatompel/dependabot/go_modules/golang.org/x/net-0.37.0
Merge pull request #175

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-30 09:46:04 +00:00
866c48e85f
build!: Use minimum Go version 1.23.0 2025-03-30 16:42:19 +07:00
7e66528180
chore: Start v0.2.2 2025-03-30 16:41:12 +07:00
dependabot[bot]
2bc41ed1a8
build(deps): bump golang.org/x/net from 0.35.0 to 0.37.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.37.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.37.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>
2025-03-30 09:35:25 +00:00
ditatombot[bot]
6b4f880dbd
Merge pull request #177 from ditatompel/dependabot/go_modules/github.com/go-sql-driver/mysql-1.9.1
Merge pull request #177

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-30 09:34:17 +00:00
dependabot[bot]
6bee3768f6
build(deps): bump github.com/go-sql-driver/mysql from 1.9.0 to 1.9.1
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 05:48:17 +00:00
ditatombot[bot]
44ac10bb8a
Merge pull request #169 from ditatompel/dependabot/go_modules/github.com/gofiber/fiber/v2-2.52.6
Merge pull request #169

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-16 17:38:13 +00:00
dependabot[bot]
60a4c83011
build(deps): bump github.com/gofiber/fiber/v2 from 2.52.5 to 2.52.6
Bumps [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) from 2.52.5 to 2.52.6.
- [Release notes](https://github.com/gofiber/fiber/releases)
- [Commits](https://github.com/gofiber/fiber/compare/v2.52.5...v2.52.6)

---
updated-dependencies:
- dependency-name: github.com/gofiber/fiber/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-16 17:36:16 +00:00
ditatombot[bot]
e66e087d44
Merge pull request #174 from ditatompel/dependabot/go_modules/github.com/go-sql-driver/mysql-1.9.0
Merge pull request #174

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-16 17:33:00 +00:00
dependabot[bot]
ff682dd0bc
build(deps): bump github.com/go-sql-driver/mysql from 1.8.1 to 1.9.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.8.1...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-16 17:30:46 +00:00
ditatombot[bot]
8a3921edde
Merge pull request #172 from ditatompel/dependabot/go_modules/github.com/spf13/cobra-1.9.1
Merge pull request #172

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-16 17:29:43 +00:00
dependabot[bot]
bdd85078cf
build(deps): bump github.com/spf13/cobra from 1.8.1 to 1.9.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-16 17:26:50 +00:00
ditatombot[bot]
16681f6b51
Merge pull request #173 from ditatompel/dependabot/go_modules/golang.org/x/net-0.35.0
Merge pull request #173

This merge action was created automatically.

Reviewed-by: ditatompel <ditatompel@users.noreply.github.com>
2025-03-16 17:25:45 +00:00
dependabot[bot]
46a29fb9cc
build(deps): bump golang.org/x/net from 0.31.0 to 0.35.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.31.0 to 0.35.0.
- [Commits](https://github.com/golang/net/compare/v0.31.0...v0.35.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>
2025-02-17 05:13:42 +00:00
48a25bece0
feat: Store hashed user IP address when submitting new node
This feature added to help trace spammers. The IP address stored with
one-way hash + salt to maintain user privacy.
2024-11-25 04:51:51 +07:00
d19e5844b0
feat: Added curl example command to Node details modal and page 2024-11-25 01:57:28 +07:00
f1b9ffde33
chore: Modal window is works for all networks 2024-11-23 18:56:41 +07:00
b780783df0
feat: Added more information on monero node details page
These following information added to Monero Node details:

- CORS capable
- Country, City, ASNumber (if any)
- Monitored Since

This commit also add some styling to TOR, I2P information.
2024-11-23 18:49:06 +07:00
84ad053413
Use int64 for DateEntered field
The `DateEntered` value expected to be a timestamp, so using int64
instead of uint make it easier to manipulate time.
2024-11-23 18:31:21 +07:00
9b48c4731a
feat: Added tor hidden service via HTTP header #161
The `Onion-Location` header was added to Nginx example configuration.
2024-11-16 19:01:09 +07:00
a936cb343b
fix(docs): Deadlink GeoIP documentation
Also, this project not using SvelteKit anymore. So, remove the old
SvelteKit information from the README.
2024-11-15 20:14:02 +07:00
76c6a5514d
chore: Start v0.2.1 2024-11-15 17:45:46 +07:00
21 changed files with 1106 additions and 864 deletions

View file

@ -16,6 +16,11 @@ IPV6_CAPABLE=false
# #############
APP_URL="https://xmr.ditatompel.com" # URL where user can access the web UI, don't put trailing slash
# APP_SECRET is random 64-character hex string that give us 32 random bytes.
# For now, this used for ip address salt, but may be useful for another feature
# in the future. You can achieve this using `openssl rand -hex 32`.
APP_SECRET=
# Fiber Config
APP_PREFORK=false
APP_HOST="127.0.0.1"

View file

@ -20,10 +20,10 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
go-version: 1.23.x
- name: Setup templ
run: go install github.com/a-h/templ/cmd/templ@v0.2.778
run: go install github.com/a-h/templ/cmd/templ@v0.3.857
- name: Prepare assets
run: make prepare

View file

@ -22,10 +22,10 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
go-version: 1.23.x
- name: Setup templ
run: go install github.com/a-h/templ/cmd/templ@v0.2.778
run: go install github.com/a-h/templ/cmd/templ@v0.3.857
# Need to build the UI here before build the server binary with go-release-action
- name: Prepare assets

View file

@ -21,10 +21,10 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
go-version: 1.23.x
- name: Setup templ
run: go install github.com/a-h/templ/cmd/templ@v0.2.778
run: go install github.com/a-h/templ/cmd/templ@v0.3.857
- name: Cache Go modules
uses: actions/cache@v3

View file

@ -20,31 +20,31 @@ The **clients** is used to fetch node information given by the server. First,
it will ask the server which node to fetch. Then, it will fetch the information
and report back to the server.
The **server** serves an embedded Svelte static site for the Web UI. It also
serves the `/api` endpoint that is used by the clients and the Web UI itself.
The **server** serves the Web UI and the `/api` endpoint that is used by the
clients.
## Requirements
To build the executable binaries, you need:
- Go >= 1.22
- Bun >= 1.1.26
- [a-h/templ][templ-repo] v0.2.778
- Go >= 1.23
- Bun >= 1.1.26
- [a-h/templ][templ-repo] v0.3.857
> **Note**:
>
> - If you want to contribute to the code, please use exact templ version
> (v0.2.778).
> - If you want to contribute to the code, please use exact templ version
> (v0.3.857).
### Server & Prober requirements
- Linux Machines (AMD64 or ARM64)
- Linux Machines (AMD64 or ARM64)
### Server requirements
- MySQL/MariaDB
- [GeoIP Database][geoip-doc] (optional). Place it to `./assets/geoip`,
see [./internal/ip/geo/geoip.go](./internal/ip/geo/geoip.go).
- MySQL/MariaDB
- [GeoIP Database][geoip-doc] (optional). Place it to `./assets/geoip`,
see [./internal/ip/geo/geoip.go](./internal/ip/geo/geoip.go).
## Installation
@ -81,23 +81,24 @@ See the [Makefile](./Makefile).
## ToDo's
- :white_check_mark: Accept IPv6 nodes.
- :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.
- :white_check_mark: Accept IPv6 nodes.
- :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.
- :white_check_mark: Support Tor hidden service (beta, inform via HTTP header).
## Acknowledgement
The creators and contributors of these projects have provided valuable
resources, which I am grateful for:
- [jtgrassie/monero-pool][jtgrassie-monero-pool]
- [rclone/rclone][rclone]
- [jtgrassie/monero-pool][jtgrassie-monero-pool]
- [rclone/rclone][rclone]
## Similar Projects
- [lalanza808/monero.fail][monerofail-repo]
- [cake-tech/upptime-monerocom][uptime-monerocom-repo]
- [lalanza808/monero.fail][monerofail-repo]
- [cake-tech/upptime-monerocom][uptime-monerocom-repo]
## Donation
@ -121,7 +122,7 @@ Thank you!
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/geolite2-free-geolocation-data/ "GeoLite2 Free 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"
[prober-systemd-timer]: ./deployment/init/xmr-nodes-prober.timer "systemd timer example for prober"

View file

@ -1 +1 @@
v0.2.0
v0.2.2

View file

@ -44,6 +44,9 @@ server {
add_header X-XSS-Protection "1; mode=block";
add_header X-Download-Options noopen;
# Add your onion URL here if you support it
# add_header Onion-Location http://<YOUR-ONION-ADDRESS>.onion$request_uri;
location = /robots.txt {
log_not_found off;
access_log off;

22
go.mod
View file

@ -1,33 +1,35 @@
module github.com/ditatompel/xmr-remote-nodes
go 1.22.2
go 1.23.0
toolchain go1.24.1
require (
github.com/a-h/templ v0.2.778
github.com/go-sql-driver/mysql v1.8.1
github.com/gofiber/fiber/v2 v2.52.5
github.com/a-h/templ v0.3.857
github.com/go-sql-driver/mysql v1.9.1
github.com/gofiber/fiber/v2 v2.52.6
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
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.31.0
github.com/spf13/cobra v1.9.1
golang.org/x/net v0.38.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.6 // indirect
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.27.0 // indirect
golang.org/x/sys v0.31.0 // indirect
)

37
go.sum
View file

@ -1,16 +1,17 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg=
github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -24,8 +25,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -33,8 +34,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
@ -46,10 +47,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -58,12 +59,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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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

@ -13,7 +13,8 @@ type App struct {
LogLevel string
// configuration for server
URL string // URL where user can access the web UI, don't put trailing slash
URL string // URL where user can access the web UI, don't put trailing slash
Secret string // random 64-character hex string that give us 32 random bytes
// fiber specific config
Prefork bool
@ -61,6 +62,7 @@ func LoadApp() {
// server configuration
app.URL = os.Getenv("APP_URL")
app.Secret = os.Getenv("APP_SECRET")
// fiber specific config
app.Host = os.Getenv("APP_HOST")

View file

@ -7,7 +7,7 @@ import (
type migrateFn func(*DB) error
var dbMigrate = [...]migrateFn{v1, v2, v3, v4}
var dbMigrate = [...]migrateFn{v1, v2, v3, v4, v5}
func MigrateDb(db *DB) error {
version := getSchemaVersion(db)
@ -287,3 +287,18 @@ func v4(db *DB) error {
return nil
}
func v5(db *DB) error {
// table: tbl_node
slog.Debug("[DB] Adding additional columns to tbl_node")
_, err := db.Exec(`
ALTER TABLE tbl_node
ADD COLUMN submitter_iphash CHAR(64) NOT NULL DEFAULT ''
COMMENT 'hashed IP address who submitted the node'
AFTER date_entered;`)
if err != nil {
return err
}
return nil
}

View file

@ -66,7 +66,7 @@ func (s *fiberServer) addNodeHandler(c *fiber.Ctx) error {
}
moneroRepo := monero.New()
if err := moneroRepo.Add(f.Protocol, f.Hostname, uint(f.Port)); err != nil {
if err := moneroRepo.Add(c.IP(), s.secret, f.Protocol, f.Hostname, uint(f.Port)); err != nil {
handler := adaptor.HTTPHandler(templ.Handler(views.Alert("error", err.Error())))
return handler(c)
}
@ -354,7 +354,7 @@ func (s *fiberServer) addNodeAPI(c *fiber.Ctx) error {
hostname := c.FormValue("hostname")
moneroRepo := monero.New()
if err := moneroRepo.Add(protocol, hostname, uint(port)); err != nil {
if err := moneroRepo.Add(c.IP(), s.secret, protocol, hostname, uint(port)); err != nil {
return c.JSON(fiber.Map{
"status": "error",
"message": err.Error(),

View file

@ -8,8 +8,9 @@ import (
type fiberServer struct {
*fiber.App
db *database.DB
url string
db *database.DB
url string
secret string
}
// NewServer returns a new fiber server

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.778
// templ: version: v0.3.857
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -29,7 +29,7 @@ func AddNode() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- Hero --><section class=\"relative overflow-hidden pt-6\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!-- Hero --><section class=\"relative overflow-hidden pt-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -37,11 +37,11 @@ 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\"><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 -->")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<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
}
return templ_7745c5c3_Err
return nil
})
}

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.778
// templ: version: v0.3.857
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -46,7 +46,7 @@ func base(m Meta) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\" class=\"dark\"><head><meta charset=\"utf-8\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"dark\"><head><meta charset=\"utf-8\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -59,7 +59,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" — xmr.ditatompel.com</title><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"title\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " — xmr.ditatompel.com</title><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -72,7 +72,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"description\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><meta name=\"description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -85,7 +85,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"keywords\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><meta name=\"keywords\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -98,7 +98,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"robots\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta name=\"robots\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -111,7 +111,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"theme-color\" content=\"#272b31\"><meta name=\"author\" content=\"ditatompel\"><meta property=\"og:site_name\" content=\"xmr.ditatompel.com\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"><meta name=\"theme-color\" content=\"#272b31\"><meta name=\"author\" content=\"ditatompel\"><meta property=\"og:site_name\" content=\"xmr.ditatompel.com\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -124,7 +124,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta property=\"og:locale\" content=\"en_US\"><meta property=\"og:title\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><meta property=\"og:locale\" content=\"en_US\"><meta property=\"og:title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -137,7 +137,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta property=\"og:description\" content=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><meta property=\"og:description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -150,7 +150,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><link rel=\"icon\" href=\"/assets/favicon.ico\"><link href=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"><link rel=\"icon\" href=\"/assets/favicon.ico\"><link href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -163,7 +163,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" rel=\"stylesheet\"><script src=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" rel=\"stylesheet\"><script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -176,7 +176,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script><script src=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"></script><script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -189,7 +189,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script><script src=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"></script><script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -202,7 +202,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script></head><body class=\"bg-neutral-900 text-neutral-400\" hx-boost=\"true\" hx-indicator=\"#hx-indicator-main\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"></script></head><body class=\"bg-neutral-900 text-neutral-400\" hx-boost=\"true\" hx-indicator=\"#hx-indicator-main\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -210,7 +210,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"shrink-0 min-h-screen\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<main class=\"shrink-0 min-h-screen\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -218,7 +218,7 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</main><footer class=\"mt-auto py-3 bg-neutral-800 text-center\"><div class=\"max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8\"><p class=\"text-sm\">XMR Nodes ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main><footer class=\"mt-auto py-3 bg-neutral-800 text-center\"><div class=\"max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8\"><p class=\"text-sm\">XMR Nodes ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -231,11 +231,11 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(", <a href=\"https://github.com/ditatompel/xmr-remote-nodes\" target=\"_blank\" rel=\"noopener\" class=\"external\">source code</a> licensed under <strong>BSD-3-Clause</strong> license.</p></div></footer><div id=\"modal-section\" class=\"hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none\" role=\"dialog\" tabindex=\"-1\" aria-labelledby=\"modal-section-label\"></div></body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ", <a href=\"https://github.com/ditatompel/xmr-remote-nodes\" target=\"_blank\" rel=\"noopener\" class=\"external\">source code</a> licensed under <strong>BSD-3-Clause</strong> license.</p></div></footer><div id=\"modal-section\" class=\"hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none\" role=\"dialog\" tabindex=\"-1\" aria-labelledby=\"modal-section-label\"></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -276,13 +276,13 @@ func BaseLayout(m Meta, cmp templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
templ_7745c5c3_Err = base(m).Render(templ.WithChildren(ctx, templ_7745c5c3_Var16), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -311,7 +311,7 @@ func BlankLayout(cmp templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -336,7 +336,7 @@ func ModalLayout(title string, cmp templ.Component) templ.Component {
templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-0 opacity-0 ease-out transition-all lg:max-w-4xl lg:w-full m-3 lg:mx-auto h-[calc(100%-3.5rem)] min-h-[calc(100%-3.5rem)] flex items-center\"><div class=\"modal-container\"><div class=\"modal-header\"><h3 class=\"font-bold\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-0 opacity-0 ease-out transition-all lg:max-w-4xl lg:w-full m-3 lg:mx-auto h-[calc(100%-3.5rem)] min-h-[calc(100%-3.5rem)] flex items-center\"><div class=\"modal-container\"><div class=\"modal-header\"><h3 class=\"font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -349,7 +349,7 @@ func ModalLayout(title string, cmp templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h3><button type=\"button\" class=\"btn-close\" data-hs-overlay=\"#modal-section\"><span class=\"sr-only\">Close</span> <svg class=\"flex-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 class=\"modal-body\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h3><button type=\"button\" class=\"btn-close\" data-hs-overlay=\"#modal-section\"><span class=\"sr-only\">Close</span> <svg class=\"flex-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 class=\"modal-body\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -357,11 +357,11 @@ func ModalLayout(title string, cmp templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"modal-footer\"><button type=\"button\" class=\"py-2 px-3 inline-flex items-center gap-x-2 bg-neutral-900 text-sm text-white rounded-lg shadow-sm border border-neutral-700 hover:bg-neutral-800\" data-hs-overlay=\"#modal-section\">Close</button></div></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div><div class=\"modal-footer\"><button type=\"button\" class=\"py-2 px-3 inline-flex items-center gap-x-2 bg-neutral-900 text-sm text-white rounded-lg shadow-sm border border-neutral-700 hover:bg-neutral-800\" data-hs-overlay=\"#modal-section\">Close</button></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -386,11 +386,11 @@ func heroGradient() templ.Component {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div aria-hidden=\"true\" class=\"flex absolute -top-96 start-1/2 transform -translate-x-1/2\"><div class=\"bg-gradient-to-r blur-3xl w-[25rem] h-[44rem] rotate-[-60deg] transform -translate-x-[10rem] from-amber-800/30 to-orange-800/40\"></div><div class=\"bg-gradient-to-tl blur-3xl w-[90rem] h-[50rem] rounded-fulls origin-top-left -rotate-12 -translate-x-[15rem] from-orange-900/60 via-orange-900/40 to-amber-900/80\"></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div aria-hidden=\"true\" class=\"flex absolute -top-96 start-1/2 transform -translate-x-1/2\"><div class=\"bg-gradient-to-r blur-3xl w-[25rem] h-[44rem] rotate-[-60deg] transform -translate-x-[10rem] from-amber-800/30 to-orange-800/40\"></div><div class=\"bg-gradient-to-tl blur-3xl w-[90rem] h-[50rem] rounded-fulls origin-top-left -rotate-12 -translate-x-[15rem] from-orange-900/60 via-orange-900/40 to-amber-900/80\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -417,7 +417,7 @@ func Alert(status, message string) templ.Component {
ctx = templ.ClearChildren(ctx)
switch status {
case "success":
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-green-600 text-white rounded-lg p-4\"><strong>Success:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"mt-2 bg-green-600 text-white rounded-lg p-4\"><strong>Success:</strong> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -430,12 +430,12 @@ func Alert(status, message string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "error":
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-red-600 text-white rounded-lg p-4\"><strong>Error:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div class=\"mt-2 bg-red-600 text-white rounded-lg p-4\"><strong>Error:</strong> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -448,12 +448,12 @@ func Alert(status, message string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-2 bg-blue-600 text-white rounded-lg p-4\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"mt-2 bg-blue-600 text-white rounded-lg p-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -466,12 +466,12 @@ func Alert(status, message string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
return nil
})
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.778
// templ: version: v0.3.857
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -37,7 +37,7 @@ func DtRowPerPage(url, hxTarget string, rowsPerPage int, q interface{}) templ.Co
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"max-w-sm space-y-3\"><select name=\"limit\" id=\"dt_limit\" class=\"py-2 px-3 pe-9 block bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-orange-400 focus:ring-orange-400\" autocomplete=\"off\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"max-w-sm space-y-3\"><select name=\"limit\" id=\"dt_limit\" class=\"py-2 px-3 pe-9 block bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-orange-400 focus:ring-orange-400\" autocomplete=\"off\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -50,7 +50,7 @@ func DtRowPerPage(url, hxTarget string, rowsPerPage int, q interface{}) templ.Co
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-trigger=\"change\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" hx-trigger=\"change\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -63,12 +63,12 @@ func DtRowPerPage(url, hxTarget string, rowsPerPage int, q interface{}) templ.Co
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\"><option disabled>CHOOSE</option> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" hx-swap=\"outerHTML\"><option disabled>CHOOSE</option> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, page := range availablePages {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -81,17 +81,17 @@ func DtRowPerPage(url, hxTarget string, rowsPerPage int, q interface{}) templ.Co
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if page == rowsPerPage {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -104,16 +104,16 @@ func DtRowPerPage(url, hxTarget string, rowsPerPage int, q interface{}) templ.Co
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</select></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -138,7 +138,7 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"inline-flex gap-x-2 items-center\"><div>Auto refresh:</div><div class=\"max-w-sm space-y-3\"><select name=\"refresh\" id=\"dt_refresh\" class=\"py-2 px-3 pe-9 block text-sm text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg focus:border-orange-400 focus:ring-orange-400\" autocomplete=\"off\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"inline-flex gap-x-2 items-center\"><div>Auto refresh:</div><div class=\"max-w-sm space-y-3\"><select name=\"refresh\" id=\"dt_refresh\" class=\"py-2 px-3 pe-9 block text-sm text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg focus:border-orange-400 focus:ring-orange-400\" autocomplete=\"off\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -151,7 +151,7 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-trigger=\"change\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" hx-trigger=\"change\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -164,12 +164,12 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\"><option value=\"\">off</option> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" hx-swap=\"outerHTML\"><option value=\"\">off</option> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, v := range refreshIntevals {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -182,17 +182,17 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if v == interval {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -205,17 +205,17 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</select></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if slices.Contains(refreshIntevals, interval) {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -228,7 +228,7 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -241,7 +241,7 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-trigger=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" hx-trigger=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -254,16 +254,16 @@ func DtRefreshInterval(url, hxTarget, interval string, q interface{}) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\"></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" hx-swap=\"outerHTML\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -288,7 +288,7 @@ func DtReload(url, hxTarget string, q interface{}) templ.Component {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button class=\"py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<button class=\"py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -301,7 +301,7 @@ func DtReload(url, hxTarget string, q interface{}) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -314,11 +314,11 @@ func DtReload(url, hxTarget string, q interface{}) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\"><svg class=\"flex-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\" class=\"lucide lucide-refresh-cw\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"></path><path d=\"M21 3v5h-5\"></path><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"></path><path d=\"M8 16H3v5\"></path></svg> Reload</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" hx-swap=\"outerHTML\"><svg class=\"flex-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\" class=\"lucide lucide-refresh-cw\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"></path><path d=\"M21 3v5h-5\"></path><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"></path><path d=\"M8 16H3v5\"></path></svg> Reload</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -353,7 +353,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
}
ctx = templ.ClearChildren(ctx)
if expectedSort == sortBy && sortDir == "asc" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -366,7 +366,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" hx-swap=\"outerHTML\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -379,7 +379,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -392,12 +392,12 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ▾</th>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ▾</th>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if expectedSort == sortBy && sortDir == "desc" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -410,7 +410,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" hx-swap=\"outerHTML\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -423,7 +423,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -436,12 +436,12 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ▴</th>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " ▴</th>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<th scope=\"col\" class=\"cursor-pointer\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -454,7 +454,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" hx-swap=\"outerHTML\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -467,7 +467,7 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -480,12 +480,12 @@ func DtThSort(url, hxTarget, title, expectedSort, sortBy, sortDir string, q inte
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ▴▾</th>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " ▴▾</th>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
return nil
})
}
@ -510,17 +510,17 @@ func DtRowCount(currentPage, rowsPerPage, totalRows int) templ.Component {
templ_7745c5c3_Var27 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><p class=\"text-sm\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div><p class=\"text-sm\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if totalRows <= 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("No entries found")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "No entries found")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -533,12 +533,12 @@ func DtRowCount(currentPage, rowsPerPage, totalRows int) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</b> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</b> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if rowsPerPage*currentPage > totalRows {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("- <b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "- <b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -551,12 +551,12 @@ func DtRowCount(currentPage, rowsPerPage, totalRows int) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("- <b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "- <b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -569,12 +569,12 @@ func DtRowCount(currentPage, rowsPerPage, totalRows int) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <b>/ ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " <b>/ ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -587,16 +587,16 @@ func DtRowCount(currentPage, rowsPerPage, totalRows int) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</b>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}
@ -621,18 +621,18 @@ func DtPagination(url, hxTarget string, q interface{}, p paging.Pagination) temp
templ_7745c5c3_Var32 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><nav class=\"pagination inline-flex gap-x-2\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<div><nav class=\"pagination inline-flex gap-x-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, page := range p.Pages {
if page == -1 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button class=\"cursor-not-allowed\" disabled>...</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<button class=\"cursor-not-allowed\" disabled>...</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if page == p.CurrentPage {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button class=\"active\" disabled>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<button class=\"active\" disabled>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -645,12 +645,12 @@ func DtPagination(url, hxTarget string, q interface{}, p paging.Pagination) temp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<button hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -663,7 +663,7 @@ func DtPagination(url, hxTarget string, q interface{}, p paging.Pagination) temp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-push-url=\"false\" hx-target=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\" hx-push-url=\"false\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -676,7 +676,7 @@ func DtPagination(url, hxTarget string, q interface{}, p paging.Pagination) temp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" hx-swap=\"outerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -689,17 +689,17 @@ func DtPagination(url, hxTarget string, q interface{}, p paging.Pagination) temp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</nav></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</nav></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.778
// templ: version: v0.3.857
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -29,41 +29,41 @@ 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 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=\"/\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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
}
if pageIdentifier == "/" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"active\" aria-current=\"page\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " class=\"active\" aria-current=\"page\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Home</a> <a href=\"/remote-nodes\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ">Home</a> <a href=\"/remote-nodes\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if pageIdentifier == "/remote-nodes" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"active\" aria-current=\"page\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " class=\"active\" aria-current=\"page\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Remote Nodes</a> <a href=\"/add-node\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">Remote Nodes</a> <a href=\"/add-node\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if pageIdentifier == "/add-node" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"active\" aria-current=\"page\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " class=\"active\" aria-current=\"page\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Add Node</a></div></div></nav></header>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ">Add Node</a></div></div></nav></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
return nil
})
}

View file

@ -50,7 +50,7 @@ templ RemoteNodes(data monero.Nodes, countries []monero.Countries, q monero.Quer
<li>Nodes with 0% uptime within 1 month with more than 300 check attempt will be removed. You can always add your node again latter.</li>
<li>You can filter remote node by selecting on <strong>nettype</strong>, <strong>protocol</strong>, <strong>country</strong>, <strong>tor</strong>, and <strong>online status</strong> option.</li>
<li>If you want to add more remote node, you can add them using <a href="/add-node" class="link">/add-node</a> page.</li>
<li>I deliberately cut the long Tor addresses, click the <span class="text-orange-300">👁 torhostname...</span> to see the full Tor address.</li>
<li>I deliberately cut the long Tor and I2P addresses, click the <span class="text-orange-300">👁 hostname...</span> to open more detailed information about the Node.</li>
<li>You can found larger remote nodes database from <a href="https://monero.fail/" target="_blank" rel="noopener" class="external">monero.fail</a>.</li>
<li>If you are developer or power user who like to fetch Monero remote node above in JSON format, you can read <a href="https://insights.ditatompel.com/en/blog/2022/01/public-api-monero-remote-node-list/" class="external">Public API Monero Remote Node List</a> blog post for more detailed information.</li>
</ul>
@ -206,10 +206,16 @@ templ TableNodes(data monero.Nodes, countries []monero.Countries, q monero.Query
@cellHostPort(row.ID, row.Port, row.Hostname, row.IPAddresses, row.IsTor, row.IsI2P, row.IPv6Only)
</td>
<td>
@cellNettype(row.Nettype, row.Height)
@fmtNettype(row.Nettype)
<br/>
{ fmt.Sprintf("%d", row.Height) }
</td>
<td>
@cellProtocol(row.Protocol, row.CORSCapable)
@fmtProtocol(row.Protocol)
if row.CORSCapable {
<br/>
(CORS 💪)
}
</td>
<td>
@cellCountry(row.CountryCode, row.CountryName, row.City, row.ASNName, row.ASN)
@ -259,7 +265,12 @@ templ Node(data monero.Node) {
</dt>
<dd>
<ul>
<li class="uppercase">{ data.Protocol }</li>
<li class="uppercase">
@fmtProtocol(data.Protocol)
if data.CORSCapable {
<span class="ml-2">(CORS 💪)</span>
}
</li>
</ul>
</dd>
</dl>
@ -270,7 +281,14 @@ templ Node(data monero.Node) {
</dt>
<dd>
<ul>
<li class="uppercase">{ data.Nettype }</li>
<li class="uppercase">
if data.IsI2P {
<span class="badge bg-green-600 mr-2">I2P</span>
} else if data.IsTor {
<span class="badge bg-purple-800 mr-2">TOR</span>
}
@fmtNettype(data.Nettype)
</li>
</ul>
</dd>
</dl>
@ -287,6 +305,50 @@ templ Node(data monero.Node) {
</dd>
</dl>
}
if data.CountryCode != "" {
<dl class="flex flex-col sm:flex-row gap-1">
<dt class="min-w-40">
<span class="block text-white text-bold">Country:</span>
</dt>
<dd>
<ul>
<li class="whitespace-break-spaces">
@cellCountry(data.CountryCode, data.CountryName, data.City, data.ASNName, data.ASN)
</li>
</ul>
</dd>
</dl>
}
<dl class="flex flex-col sm:flex-row gap-1">
<dt class="min-w-40">
<span class="block text-white text-bold">Monitored Since:</span>
</dt>
<dd>
<ul>
<li class="whitespace-break-spaces">
{ time.Unix(data.DateEntered, 0).UTC().Format("Jan 2, 2006 15:04 MST") } (about { utils.TimeSince(data.DateEntered) })
</li>
</ul>
</dd>
</dl>
<dl class="flex flex-col sm:flex-row gap-1">
<dt class="min-w-40">
<span class="block text-white text-bold">cURL get_info Eg.:</span>
</dt>
<dd>
<ul>
<li>
<label for="curl-getinfo-eg" class="sr-only">cURL get_info Example</label>
<div class="flex rounded-lg shadow-sm">
<input type="text" id="curl-getinfo-eg" name="stagenet-ssl" class="py-1 px-2 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value={ monero.ParseCURLGetInfo(data) } readonly/>
<button class="clipboard copy-input" data-clipboard-target="#curl-getinfo-eg">
Copy
</button>
</div>
</li>
</ul>
</dd>
</dl>
</div>
}
@ -413,6 +475,26 @@ templ TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagi
</div>
}
templ fmtNettype(nettype string) {
switch nettype {
case "stagenet":
<span class="font-semibold uppercase text-sky-500">{ nettype }</span>
case "testnet":
<span class="font-semibold uppercase text-rose-500">{ nettype }</span>
default:
<span class="font-semibold uppercase text-green-500">{ nettype }</span>
}
}
templ fmtProtocol(protocol string) {
switch protocol {
case "http":
<span class="font-semibold uppercase text-sky-500">{ protocol }</span>
default:
<span class="font-semibold uppercase text-green-500">{ protocol }</span>
}
}
templ cellHostPort(id, port uint, hostname, ips string, isTor, isI2P, ipv6Only bool) {
if isTor {
<button
@ -447,7 +529,19 @@ templ cellHostPort(id, port uint, hostname, ips string, isTor, isI2P, ipv6Only b
.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>
<button
class="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"
>
👁 { ip.FormatHostname(hostname) }
</button>
:<span class="text-indigo-400">{ fmt.Sprintf("%d", port) }</span>
<br/>
<div class="max-w-40 text-ellipsis overflow-x-auto md:overflow-hidden hover:overflow-visible">
<span class="whitespace-break-spaces text-gray-400">{ strings.ReplaceAll(ips, ",", " ") }</span>
@ -458,32 +552,6 @@ templ cellHostPort(id, port uint, hostname, ips string, isTor, isI2P, ipv6Only b
}
}
templ cellNettype(nettype string, height uint) {
switch nettype {
case "stagenet":
<span class="font-semibold uppercase text-sky-500">{ nettype }</span>
case "testnet":
<span class="font-semibold uppercase text-rose-500">{ nettype }</span>
default:
<span class="font-semibold uppercase text-green-500">{ nettype }</span>
}
<br/>
{ fmt.Sprintf("%d", height) }
}
templ cellProtocol(protocol string, cors bool) {
switch protocol {
case "http":
<span class="font-semibold uppercase text-sky-500">{ protocol }</span>
default:
<span class="font-semibold uppercase text-green-500">{ protocol }</span>
}
if cors {
<br/>
(CORS 💪)
}
}
templ cellCountry(cc, countryName, city, asnName string, asn uint) {
if cc != "" {
if city != "" {

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,9 @@
package monero
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -53,7 +55,8 @@ type Node struct {
City string `json:"city" db:"city"`
Latitude float64 `json:"latitude" db:"lat"`
Longitude float64 `json:"longitude" db:"lon"`
DateEntered uint `json:"date_entered,omitempty" db:"date_entered"`
DateEntered int64 `json:"date_entered,omitempty" db:"date_entered"`
SubmitterIPHash string `json:"submitter_iphash,omitempty" db:"submitter_iphash"`
LastChecked int64 `json:"last_checked" db:"last_checked"`
FailedCount uint `json:"failed_count,omitempty" db:"failed_count"`
LastCheckStatus types.JSONText `json:"last_check_statuses" db:"last_check_status"`
@ -176,7 +179,35 @@ func (r *moneroRepo) Nodes(q QueryNodes) (Nodes, error) {
query := fmt.Sprintf(`
SELECT
*
id,
hostname,
ip_addr,
port,
protocol,
is_tor,
is_i2p,
is_available,
nettype,
height,
adjusted_time,
database_size,
difficulty,
version,
uptime,
estimate_fee,
asn,
asn_name,
country,
country_name,
city,
lat,
lon,
date_entered,
last_checked,
last_check_status,
cors_capable,
ipv6_only,
ip_addresses
FROM
tbl_node
%s
@ -190,7 +221,7 @@ func (r *moneroRepo) Nodes(q QueryNodes) (Nodes, error) {
return nodes, err
}
func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
func (r *moneroRepo) Add(submitterIP, salt, protocol, hostname string, port uint) error {
if protocol != "http" && protocol != "https" {
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
}
@ -270,6 +301,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
lat,
lon,
date_entered,
submitter_iphash,
last_checked,
last_check_status,
ip_addresses,
@ -288,6 +320,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
?,
?,
?,
?,
?
)`,
protocol,
@ -300,6 +333,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
0,
0,
time.Now().Unix(),
hashIPWithSalt(submitterIP, salt),
0,
string(statusDb),
ips,
@ -397,6 +431,14 @@ func (r *moneroRepo) Countries() ([]Countries, error) {
return c, err
}
// hashIPWithSalt hashes IP address with salt designed for checksumming, but
// still maintain user privacy, this is NOT cryptographic security.
func hashIPWithSalt(ip, salt string) string {
hasher := sha256.New()
hasher.Write([]byte(salt + ip)) // Combine salt and IP
return hex.EncodeToString(hasher.Sum(nil))
}
// ParseNodeStatuses parses JSONText into [5]int
// Used this to parse last_check_status for templ engine
func ParseNodeStatuses(statuses types.JSONText) [5]int {
@ -407,3 +449,28 @@ func ParseNodeStatuses(statuses types.JSONText) [5]int {
return s
}
// ParseCURLGetInfo generates curl command to get node info from given node
//
// Primarily used for Web UI to display example curl command.
func ParseCURLGetInfo(node Node) string {
d := `'{"jsonrpc":"2.0","id":"0","method":"get_info"}' -H 'Content-Type: application/json'`
if node.IsI2P {
return fmt.Sprintf(
"curl -x socks5h://127.0.0.1:4447 %s://%s:%d/json_rpc -d %s -sL",
node.Protocol, node.Hostname, node.Port, d,
)
}
if node.IsTor {
return fmt.Sprintf(
"curl -x socks5h://127.0.0.1:9050 %s://%s:%d/json_rpc -d %s -sL",
node.Protocol, node.Hostname, node.Port, d,
)
}
return fmt.Sprintf(
"curl %s://%s:%d/json_rpc -d %s -sL",
node.Protocol, node.Hostname, node.Port, d,
)
}