Basic add node action implemented

This commit is contained in:
ditatompel 2024-05-04 17:24:47 +07:00
parent 92acb52aac
commit 7cd802e640
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
4 changed files with 158 additions and 28 deletions

View file

@ -1,26 +1,41 @@
<script> <script>
import { applyAction, enhance } from '$app/forms'; import { invalidateAll, goto } from '$app/navigation';
import { slide } from 'svelte/transition'; import { apiUri } from '$lib/utils/common';
import { ProgressBar } from '@skeletonlabs/skeleton'; import { ProgressBar } from '@skeletonlabs/skeleton';
/** @type {import('./$types').PageData} */ /** @type {import('./$types').PageData} */
export let data; export let data;
/** @type {import('./$types').ActionData} */ /**
export let form; * @typedef formResult
* @type {object}
* @property {string} status
* @property {string} message
* @property {null | object} data
*/
/** @type {formResult} */
export let formResult;
let isProcessing = false; let isProcessing = false;
/** @type {import('./$types').SubmitFunction} */ /** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
const handleForm = async () => { async function handleSubmit(event) {
isProcessing = true; isProcessing = true;
return async ({ result }) => { const data = new FormData(event.currentTarget);
isProcessing = false;
if (result.type === 'success' || result.type === 'redirect') { const response = await fetch(event.currentTarget.action, {
close(); method: 'POST',
} body: data
await applyAction(result); });
};
}; formResult = await response.json();
isProcessing = false;
if (formResult.status === 'ok') {
// rerun all `load` functions, following the successful update
await invalidateAll();
goto('/remote-nodes');
}
}
</script> </script>
<header id="hero" class="hero-gradient py-7"> <header id="hero" class="hero-gradient py-7">
@ -37,7 +52,12 @@
<div class="section-container text-center"> <div class="section-container text-center">
<p>Enter your Monero node information below (IPv4 host only):</p> <p>Enter your Monero node information below (IPv4 host only):</p>
<form class="mx-auto w-full max-w-3xl py-2" method="POST" use:enhance={handleForm}> <form
class="mx-auto w-full max-w-3xl py-2"
action={apiUri('/api/v1/nodes')}
method="POST"
on:submit|preventDefault={handleSubmit}
>
<div class="grid grid-cols-1 gap-4 py-6 md:grid-cols-4"> <div class="grid grid-cols-1 gap-4 py-6 md:grid-cols-4">
<label class="label"> <label class="label">
<span>Protocol *</span> <span>Protocol *</span>
@ -50,7 +70,7 @@
<span>Host / IP *</span> <span>Host / IP *</span>
<input <input
class="input variant-form-material" class="input variant-form-material"
name="host" name="hostname"
type="text" type="text"
placeholder="Eg: node.example.com or 172.16.17.18" placeholder="Eg: node.example.com or 172.16.17.18"
disabled={isProcessing} disabled={isProcessing}
@ -74,24 +94,23 @@
<div class="mx-auto w-full max-w-3xl py-2"> <div class="mx-auto w-full max-w-3xl py-2">
{#if !isProcessing} {#if !isProcessing}
{#if form?.status === 'error'} {#if formResult?.status === 'error'}
<div class="alert variant-ghost-error" transition:slide={{ duration: 500 }}> <div class="mx-4 p-4 mb-4 text-sm rounded-lg bg-gray-700 text-red-400" role="alert">
<div class="alert-message"> <span class="font-medium">Error:</span>
<h3 class="h3">Error!</h3> {formResult.message}!
<p>{form.message}!</p>
</div>
</div> </div>
{/if} {/if}
{#if form?.status === 'ok'} {#if formResult?.status === 'ok'}
<div class="alert variant-ghost-success" transition:slide={{ duration: 500 }}> <div class="mx-4 p-4 mb-4 text-sm rounded-lg bg-gray-700 text-green-400" role="alert">
<div class="alert-message"> <span class="font-medium">Success:</span>
<h3 class="h3">Success!</h3> {formResult.message}!
<p>{form.message}!</p>
</div>
</div> </div>
{/if} {/if}
{:else} {:else}
<ProgressBar meter="bg-secondary-500" track="bg-secondary-500/30" value={undefined} /> <ProgressBar meter="bg-secondary-500" track="bg-secondary-500/30" value={undefined} />
<div class="mx-4 p-4 mb-4 text-sm rounded-lg bg-gray-700 text-blue-400" role="alert">
<span class="font-medium">Processing...</span>
</div>
{/if} {/if}
</div> </div>

View file

@ -2,6 +2,7 @@ package handler
import ( import (
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/ditatompel/xmr-nodes/internal/database" "github.com/ditatompel/xmr-nodes/internal/database"
@ -114,6 +115,36 @@ func Prober(c *fiber.Ctx) error {
}) })
} }
func AddNode(c *fiber.Ctx) error {
formPort := c.FormValue("port")
port, err := strconv.Atoi(formPort)
if err != nil {
return c.JSON(fiber.Map{
"status": "error",
"message": "Invalid port number",
"data": nil,
})
}
protocol := c.FormValue("protocol")
hostname := c.FormValue("hostname")
moneroRepo := repo.NewMoneroRepo(database.GetDB())
if err := moneroRepo.Add(protocol, hostname, uint(port)); err != nil {
return c.JSON(fiber.Map{
"status": "error",
"message": err.Error(),
"data": nil,
})
}
return c.JSON(fiber.Map{
"status": "ok",
"message": "Query Ok",
"data": nil,
})
}
func Crons(c *fiber.Ctx) error { func Crons(c *fiber.Ctx) error {
cronRepo := repo.NewCron(database.GetDB()) cronRepo := repo.NewCron(database.GetDB())

View file

@ -14,5 +14,6 @@ func V1Api(app *fiber.App) {
v1.Get("/prober", Prober) v1.Get("/prober", Prober)
v1.Post("/prober", Prober) v1.Post("/prober", Prober)
v1.Post("/nodes", AddNode)
v1.Get("/crons", Crons) v1.Get("/crons", Crons)
} }

79
internal/repo/monero.go Normal file
View file

@ -0,0 +1,79 @@
package repo
import (
"encoding/json"
"errors"
"net"
"strings"
"time"
"github.com/ditatompel/xmr-nodes/internal/database"
)
type MoneroRepository interface {
Add(protocol string, host string, port uint) error
}
type MoneroRepo struct {
db *database.DB
}
func NewMoneroRepo(db *database.DB) MoneroRepository {
return &MoneroRepo{db}
}
func (repo *MoneroRepo) Add(protocol string, hostname string, port uint) error {
if protocol != "http" && protocol != "https" {
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
}
if port > 65535 || port < 1 {
return errors.New("Invalid port number")
}
is_tor := false
if strings.HasSuffix(hostname, ".onion") {
is_tor = true
}
ip := ""
if !is_tor {
hostIps, err := net.LookupIP(hostname)
if err != nil {
return err
}
hostIp := hostIps[0].To4()
if hostIp == nil {
return errors.New("Host IP is not IPv4")
}
if hostIp.IsPrivate() {
return errors.New("IP address is private")
}
if hostIp.IsLoopback() {
return errors.New("IP address is loopback address")
}
ip = hostIp.String()
}
check := `SELECT id FROM tbl_node WHERE protocol = ? AND hostname = ? AND port = ? LIMIT 1`
row, err := repo.db.Query(check, protocol, hostname, port)
if err != nil {
return err
}
defer row.Close()
if row.Next() {
return errors.New("Node already monitored")
}
statusDb, _ := json.Marshal([5]int{2, 2, 2, 2, 2})
query := `INSERT INTO tbl_node (protocol, hostname, port, is_tor, nettype, ip_addr, lat, lon, date_entered, last_checked, last_check_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err = repo.db.Exec(query, protocol, hostname, port, is_tor, "", ip, 0, 0, time.Now().Unix(), 0, string(statusDb))
if err != nil {
return err
}
return nil
}