mirror of
https://github.com/ditatompel/xmr-remote-nodes.git
synced 2024-11-17 01:17:37 +00:00
Simple display probe logs
This commit is contained in:
parent
33aae21237
commit
8f5f972faf
8 changed files with 449 additions and 3 deletions
|
@ -293,8 +293,9 @@
|
||||||
is_tor={row.is_tor}
|
is_tor={row.is_tor}
|
||||||
hostname={row.hostname}
|
hostname={row.hostname}
|
||||||
port={row.port}
|
port={row.port}
|
||||||
/></td
|
/>
|
||||||
>
|
<a class="anchor" href="/remote-nodes/logs/?node_id={row.id}">[Logs]</a>
|
||||||
|
</td>
|
||||||
<td><NetTypeCell nettype={row.nettype} height={row.height} /></td>
|
<td><NetTypeCell nettype={row.nettype} height={row.height} /></td>
|
||||||
<td><ProtocolCell protocol={row.protocol} cors={row.cors} /></td>
|
<td><ProtocolCell protocol={row.protocol} cors={row.cors} /></td>
|
||||||
<td
|
<td
|
||||||
|
|
28
frontend/src/routes/(front)/remote-nodes/logs/+page.js
Normal file
28
frontend/src/routes/(front)/remote-nodes/logs/+page.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ data }) {
|
||||||
|
/* prettier-ignore */
|
||||||
|
const metaDefaults = {
|
||||||
|
title: 'Probe Logs',
|
||||||
|
description: 'Monero is private, decentralized cryptocurrency that keeps your finances confidential and secure.',
|
||||||
|
keywords: 'monero,xmr,monero node,xmrnode,cryptocurrency'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
title: metaDefaults.title,
|
||||||
|
description: metaDefaults.description,
|
||||||
|
keywords: metaDefaults.keywords,
|
||||||
|
image:
|
||||||
|
'https://vcl-og-img.ditatompel.com/' + encodeURIComponent(metaDefaults.title) + '.png?md=0',
|
||||||
|
// Article
|
||||||
|
article: { publishTime: '', modifiedTime: '', author: '' },
|
||||||
|
// Twitter
|
||||||
|
twitter: {
|
||||||
|
title: metaDefaults.title,
|
||||||
|
description: metaDefaults.description,
|
||||||
|
image: metaDefaults.image
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
262
frontend/src/routes/(front)/remote-nodes/logs/+page.svelte
Normal file
262
frontend/src/routes/(front)/remote-nodes/logs/+page.svelte
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
<script>
|
||||||
|
import { DataHandler } from '@vincjo/datatables/remote';
|
||||||
|
import { format, formatDistance } from 'date-fns';
|
||||||
|
import { loadData, formatBytes } from './api-handler';
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import {
|
||||||
|
DtSrRowsPerPage,
|
||||||
|
DtSrThSort,
|
||||||
|
DtSrThFilter,
|
||||||
|
DtSrRowCount,
|
||||||
|
DtSrPagination
|
||||||
|
} from '$lib/components/datatables/server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @param {number} p
|
||||||
|
*/
|
||||||
|
function maxPrecision(n, p) {
|
||||||
|
return parseFloat(n.toFixed(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} h
|
||||||
|
*/
|
||||||
|
function formatHashes(h) {
|
||||||
|
if (h < 1e-12) return '0 H';
|
||||||
|
else if (h < 1e-9) return maxPrecision(h * 1e12, 0) + ' pH';
|
||||||
|
else if (h < 1e-6) return maxPrecision(h * 1e9, 0) + ' nH';
|
||||||
|
else if (h < 1e-3) return maxPrecision(h * 1e6, 0) + ' μH';
|
||||||
|
else if (h < 1) return maxPrecision(h * 1e3, 0) + ' mH';
|
||||||
|
else if (h < 1e3) return h + ' H';
|
||||||
|
else if (h < 1e6) return maxPrecision(h * 1e-3, 2) + ' KH';
|
||||||
|
else if (h < 1e9) return maxPrecision(h * 1e-6, 2) + ' MH';
|
||||||
|
else return maxPrecision(h * 1e-9, 2) + ' GH';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number | null } runtime */
|
||||||
|
function parseRuntime(runtime) {
|
||||||
|
return runtime === null ? '' : runtime.toLocaleString(undefined) + 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
let pageId = '0';
|
||||||
|
let filterProberId = 0;
|
||||||
|
let filterStatus = -1;
|
||||||
|
|
||||||
|
const handler = new DataHandler([], { rowsPerPage: 10, totalRows: 0 });
|
||||||
|
let rows = handler.getRows();
|
||||||
|
|
||||||
|
const reloadData = () => {
|
||||||
|
handler.invalidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {number | undefined} */
|
||||||
|
let intervalId;
|
||||||
|
let intervalValue = 0;
|
||||||
|
|
||||||
|
const intervalOptions = [
|
||||||
|
{ value: 0, label: 'No' },
|
||||||
|
{ value: 5, label: '5s' },
|
||||||
|
{ value: 10, label: '10s' },
|
||||||
|
{ value: 30, label: '30s' },
|
||||||
|
{ value: 60, label: '1m' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const startInterval = () => {
|
||||||
|
const seconds = intervalValue;
|
||||||
|
if (isNaN(seconds) || seconds < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!intervalOptions.some((option) => option.value === seconds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds > 0) {
|
||||||
|
reloadData();
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
reloadData();
|
||||||
|
}, seconds * 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$: startInterval(); // Automatically start the interval on change
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(intervalId); // Clear the interval when the component is destroyed
|
||||||
|
});
|
||||||
|
onMount(() => {
|
||||||
|
pageId = new URLSearchParams(window.location.search).get('node_id') || '0';
|
||||||
|
handler.filter(pageId, 'node_id');
|
||||||
|
handler.onChange((state) => loadData(state));
|
||||||
|
handler.invalidate();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header id="hero" class="hero-gradient py-7">
|
||||||
|
<div class="section-container text-center">
|
||||||
|
<h1 class="h1 pb-2 font-extrabold">{data.meta.title}</h1>
|
||||||
|
<p class="mx-auto max-w-3xl">
|
||||||
|
<strong>Monero remote node</strong> is a device on the internet running the Monero software with
|
||||||
|
full copy of the Monero blockchain that doesn't run on the same local machine where the Monero
|
||||||
|
wallet is located.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto w-full max-w-3xl px-20">
|
||||||
|
<hr class="!border-primary-400-500-token !border-t-4 !border-double" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="introduction ">
|
||||||
|
<div class="section-container text-center !max-w-4xl">
|
||||||
|
<p>
|
||||||
|
Remote node can be used by people who, for their own reasons (usually because of hardware
|
||||||
|
requirements, disk space, or technical abilities), cannot/don't want to run their own node and
|
||||||
|
prefer to relay on one publicly available on the Monero network.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Using an open node will allow to make a transaction instantaneously, without the need to
|
||||||
|
download the blockchain and sync to the Monero network first, but at the cost of the control
|
||||||
|
over your privacy. the <strong>Monero community suggests to always run your own node</strong> to
|
||||||
|
obtain the maximum possible privacy and to help decentralize the network.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="monero-remote-node">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="space-y-2 overflow-x-auto">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<DtSrRowsPerPage {handler} />
|
||||||
|
<div class="invisible flex place-items-center md:visible">
|
||||||
|
<label for="autoRefreshInterval">Auto Refresh:</label>
|
||||||
|
<select
|
||||||
|
class="select ml-2"
|
||||||
|
id="autoRefreshInterval"
|
||||||
|
bind:value={intervalValue}
|
||||||
|
on:change={startInterval}
|
||||||
|
>
|
||||||
|
{#each intervalOptions as { value, label }}
|
||||||
|
<option {value}>{label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex place-items-center">
|
||||||
|
<button
|
||||||
|
id="reloadDt"
|
||||||
|
name="reloadDt"
|
||||||
|
class="variant-filled-primary btn"
|
||||||
|
on:click={reloadData}>Reload</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-hover table-compact w-full table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#ID</th>
|
||||||
|
<th><label for="prober_id">Prober</label></th>
|
||||||
|
<th><label for="status">Status</label></th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Adjusted Time</th>
|
||||||
|
<th>DB Size</th>
|
||||||
|
<th>Difficulty</th>
|
||||||
|
<DtSrThSort {handler} orderBy="estimate_fee">Est. Fee</DtSrThSort>
|
||||||
|
<DtSrThSort {handler} orderBy="date_checked">Date Checked</DtSrThSort>
|
||||||
|
<DtSrThSort {handler} orderBy="fetch_runtime">Runtime</DtSrThSort>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">
|
||||||
|
<select
|
||||||
|
id="prober_id"
|
||||||
|
name="prober_id"
|
||||||
|
class="select variant-form-material"
|
||||||
|
bind:value={filterProberId}
|
||||||
|
on:change={() => {
|
||||||
|
handler.filter(filterProberId, 'prober_id');
|
||||||
|
handler.invalidate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={0}>Any</option>
|
||||||
|
</select>
|
||||||
|
</th>
|
||||||
|
<th colspan="2">
|
||||||
|
<select
|
||||||
|
id="status"
|
||||||
|
name="status"
|
||||||
|
class="select variant-form-material"
|
||||||
|
bind:value={filterStatus}
|
||||||
|
on:change={() => {
|
||||||
|
handler.filter(filterStatus, 'status');
|
||||||
|
handler.invalidate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={-1}>Any</option>
|
||||||
|
<option value="1">Online</option>
|
||||||
|
<option value="0">Offline</option>
|
||||||
|
</select>
|
||||||
|
</th>
|
||||||
|
<DtSrThFilter
|
||||||
|
{handler}
|
||||||
|
filterBy="failed_reason"
|
||||||
|
placeholder="Filter reason"
|
||||||
|
colspan={6}
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each $rows as row (row.id)}
|
||||||
|
<tr>
|
||||||
|
<td>{row.id}</td>
|
||||||
|
<td>{row.prober_id}</td>
|
||||||
|
<td>{row.status === 1 ? 'OK' : 'ERR'}</td>
|
||||||
|
{#if row.status !== 1}
|
||||||
|
<td colspan="5">{row.failed_reason ?? ''}</td>
|
||||||
|
{:else}
|
||||||
|
<td class="text-right">{row.height.toLocaleString(undefined)}</td>
|
||||||
|
<td>{format(row.adjusted_time * 1000, 'yyyy-MM-dd HH:mm')}</td>
|
||||||
|
<td class="text-right">{formatBytes(row.database_size, 2)}</td>
|
||||||
|
<td class="text-right">{formatHashes(row.difficulty)}</td>
|
||||||
|
<td class="text-right">{row.estimate_fee.toLocaleString(undefined)}</td>
|
||||||
|
{/if}
|
||||||
|
<td>
|
||||||
|
{format(row.date_checked * 1000, 'PP HH:mm')}<br />
|
||||||
|
{formatDistance(row.date_checked * 1000, new Date(), { addSuffix: true })}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">{parseRuntime(row.fetch_runtime)}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="flex justify-between mb-2">
|
||||||
|
<DtSrRowCount {handler} />
|
||||||
|
<DtSrPagination {handler} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.section-container {
|
||||||
|
@apply mx-auto w-full max-w-7xl p-4;
|
||||||
|
}
|
||||||
|
/* Hero Gradient */
|
||||||
|
/* prettier-ignore */
|
||||||
|
.hero-gradient {
|
||||||
|
background-image:
|
||||||
|
radial-gradient(at 0% 0%, rgba(242, 104, 34, .4) 0px, transparent 50%),
|
||||||
|
radial-gradient(at 98% 1%, rgba(var(--color-warning-900) / 0.33) 0px, transparent 50%);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
td:nth-child(1) {
|
||||||
|
@apply max-w-20;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>
|
38
frontend/src/routes/(front)/remote-nodes/logs/api-handler.js
Normal file
38
frontend/src/routes/(front)/remote-nodes/logs/api-handler.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { apiUri } from '$lib/utils/common';
|
||||||
|
|
||||||
|
/** @param {import('@vincjo/datatables/remote/state')} state */
|
||||||
|
export const loadData = async (state) => {
|
||||||
|
const response = await fetch(apiUri(`/api/v1/nodes/logs?${getParams(state)}`));
|
||||||
|
const json = await response.json();
|
||||||
|
state.setTotalRows(json.data.total_rows ?? 0);
|
||||||
|
return json.data.items ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} bytes
|
||||||
|
* @param {number} decimals
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const formatBytes = (bytes, decimals = 2) => {
|
||||||
|
if (!+bytes) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getParams = ({ pageNumber, rowsPerPage, sort, filters }) => {
|
||||||
|
let params = `page=${pageNumber}&limit=${rowsPerPage}`;
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
params += `&sort_by=${sort.orderBy}&sort_direction=${sort.direction}`;
|
||||||
|
}
|
||||||
|
if (filters) {
|
||||||
|
params += filters.map(({ filterBy, value }) => `&${filterBy}=${value}`).join('');
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
|
@ -146,6 +146,32 @@ func MoneroNodes(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProbeLogs(c *fiber.Ctx) error {
|
||||||
|
moneroRepo := repo.NewMoneroRepo(database.GetDB())
|
||||||
|
query := repo.MoneroLogQueryParams{
|
||||||
|
RowsPerPage: c.QueryInt("limit", 10),
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
SortBy: c.Query("sort_by", "id"),
|
||||||
|
SortDirection: c.Query("sort_direction", "desc"),
|
||||||
|
NodeId: c.QueryInt("node_id", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := moneroRepo.Logs(query)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"status": "error",
|
||||||
|
"message": err.Error(),
|
||||||
|
"data": nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"status": "ok",
|
||||||
|
"message": "Success",
|
||||||
|
"data": logs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func AddNode(c *fiber.Ctx) error {
|
func AddNode(c *fiber.Ctx) error {
|
||||||
formPort := c.FormValue("port")
|
formPort := c.FormValue("port")
|
||||||
port, err := strconv.Atoi(formPort)
|
port, err := strconv.Atoi(formPort)
|
||||||
|
|
|
@ -16,6 +16,7 @@ func V1Api(app *fiber.App) {
|
||||||
v1.Post("/prober", Prober)
|
v1.Post("/prober", Prober)
|
||||||
v1.Get("/nodes", MoneroNodes)
|
v1.Get("/nodes", MoneroNodes)
|
||||||
v1.Post("/nodes", AddNode)
|
v1.Post("/nodes", AddNode)
|
||||||
|
v1.Get("/nodes/logs", ProbeLogs)
|
||||||
v1.Get("/fees", NetFee)
|
v1.Get("/fees", NetFee)
|
||||||
v1.Get("/countries", Countries)
|
v1.Get("/countries", Countries)
|
||||||
v1.Get("/job", CheckProber, GiveJob)
|
v1.Get("/job", CheckProber, GiveJob)
|
||||||
|
|
|
@ -22,6 +22,7 @@ type MoneroRepository interface {
|
||||||
ProcessJob(report ProbeReport, proberId int64) error
|
ProcessJob(report ProbeReport, proberId int64) error
|
||||||
NetFee() []NetFee
|
NetFee() []NetFee
|
||||||
Countries() ([]MoneroCountries, error)
|
Countries() ([]MoneroCountries, error)
|
||||||
|
Logs(q MoneroLogQueryParams) (MoneroNodeFetchLogs, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MoneroRepo struct {
|
type MoneroRepo struct {
|
||||||
|
@ -173,6 +174,95 @@ func (repo *MoneroRepo) Nodes(q MoneroQueryParams) (MoneroNodes, error) {
|
||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MoneroLogQueryParams struct {
|
||||||
|
NodeId int // 0 fpr all, >0 for specific node
|
||||||
|
WorkerId int // 0 for all, >0 for specific worker
|
||||||
|
Status int // -1 for all, 0 for failed, 1 for success
|
||||||
|
FailReason string // empty for all, if not empty, will be used as search from failed_reaso
|
||||||
|
|
||||||
|
RowsPerPage int
|
||||||
|
Page int
|
||||||
|
SortBy string
|
||||||
|
SortDirection string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProbeLog struct {
|
||||||
|
Id int `db:"id" json:"id,omitempty"`
|
||||||
|
NodeId int `db:"node_id" json:"node_id"`
|
||||||
|
ProberId int `db:"prober_id" json:"prober_id"`
|
||||||
|
Status int `db:"is_available" json:"status"`
|
||||||
|
Height int `db:"height" json:"height"`
|
||||||
|
AdjustedTime int `db:"adjusted_time" json:"adjusted_time"`
|
||||||
|
DatabaseSize int `db:"database_size" json:"database_size"`
|
||||||
|
Difficulty int `db:"difficulty" json:"difficulty"`
|
||||||
|
EstimateFee int `db:"estimate_fee" json:"estimate_fee"`
|
||||||
|
DateChecked int `db:"date_checked" json:"date_checked"`
|
||||||
|
FailedReason string `db:"failed_reason" json:"failed_reason"`
|
||||||
|
FetchRuntime float64 `db:"fetch_runtime" json:"fetch_runtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoneroNodeFetchLogs struct {
|
||||||
|
TotalRows int `json:"total_rows"`
|
||||||
|
RowsPerPage int `json:"rows_per_page"`
|
||||||
|
Items []*ProbeLog `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MoneroRepo) Logs(q MoneroLogQueryParams) (MoneroNodeFetchLogs, error) {
|
||||||
|
queryParams := []interface{}{}
|
||||||
|
whereQueries := []string{}
|
||||||
|
where := ""
|
||||||
|
|
||||||
|
if q.NodeId != 0 {
|
||||||
|
whereQueries = append(whereQueries, "node_id = ?")
|
||||||
|
queryParams = append(queryParams, q.NodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(whereQueries) > 0 {
|
||||||
|
where = "WHERE " + strings.Join(whereQueries, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLogs := MoneroNodeFetchLogs{}
|
||||||
|
|
||||||
|
queryTotalRows := fmt.Sprintf("SELECT COUNT(id) FROM tbl_probe_log %s", where)
|
||||||
|
err := repo.db.QueryRow(queryTotalRows, queryParams...).Scan(&fetchLogs.TotalRows)
|
||||||
|
if err != nil {
|
||||||
|
return fetchLogs, err
|
||||||
|
}
|
||||||
|
queryParams = append(queryParams, q.RowsPerPage, (q.Page-1)*q.RowsPerPage)
|
||||||
|
|
||||||
|
allowedSort := []string{"date_checked", "fetch_runtime"}
|
||||||
|
sortBy := "id"
|
||||||
|
if slices.Contains(allowedSort, q.SortBy) {
|
||||||
|
sortBy = q.SortBy
|
||||||
|
}
|
||||||
|
sortDirection := "DESC"
|
||||||
|
if q.SortDirection == "asc" {
|
||||||
|
sortDirection = "ASC"
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("SELECT id, node_id, prober_id, is_available, height, adjusted_time, database_size, difficulty, estimate_fee, date_checked, failed_reason, fetch_runtime FROM tbl_probe_log %s ORDER BY %s %s LIMIT ? OFFSET ?", where, sortBy, sortDirection)
|
||||||
|
|
||||||
|
row, err := repo.db.Query(query, queryParams...)
|
||||||
|
if err != nil {
|
||||||
|
return fetchLogs, err
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
|
fetchLogs.RowsPerPage = q.RowsPerPage
|
||||||
|
|
||||||
|
for row.Next() {
|
||||||
|
probeLog := ProbeLog{}
|
||||||
|
err = row.Scan(&probeLog.Id, &probeLog.NodeId, &probeLog.ProberId, &probeLog.Status, &probeLog.Height, &probeLog.AdjustedTime, &probeLog.DatabaseSize, &probeLog.Difficulty, &probeLog.EstimateFee, &probeLog.DateChecked, &probeLog.FailedReason, &probeLog.FetchRuntime)
|
||||||
|
if err != nil {
|
||||||
|
return fetchLogs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLogs.Items = append(fetchLogs.Items, &probeLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchLogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *MoneroRepo) Add(protocol string, hostname string, port uint) error {
|
func (repo *MoneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
if protocol != "http" && protocol != "https" {
|
if protocol != "http" && protocol != "https" {
|
||||||
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
|
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
|
||||||
|
|
|
@ -87,7 +87,7 @@ CREATE TABLE `tbl_probe_log` (
|
||||||
`estimate_fee` int(9) unsigned NOT NULL DEFAULT 0,
|
`estimate_fee` int(9) unsigned NOT NULL DEFAULT 0,
|
||||||
`date_checked` bigint(20) unsigned NOT NULL DEFAULT 0,
|
`date_checked` bigint(20) unsigned NOT NULL DEFAULT 0,
|
||||||
`failed_reason` text NOT NULL DEFAULT '',
|
`failed_reason` text NOT NULL DEFAULT '',
|
||||||
`fetch_runtime` float(5,2) unsigned DEFAULT NULL,
|
`fetch_runtime` float(5,2) unsigned NOT NULL DEFAULT 0.00,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `node_id` (`node_id`)
|
KEY `node_id` (`node_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
Loading…
Reference in a new issue