feat!: Remove old frontend codes

This commit is contained in:
Christian Ditaputratama 2024-11-03 16:33:15 +07:00
parent 204865e50d
commit e524c2686d
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
78 changed files with 0 additions and 6137 deletions

10
frontend/.gitignore vendored
View file

@ -1,10 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,4 +0,0 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,8 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -1,9 +0,0 @@
# UI
The UI is generated and embedded when the Go project is built. See
[./frontend/embed.go](https://github.com/ditatompel/xmr-remote-nodes/blob/main/frontend/embed.go#L10-L13).
> **NOTE**:
>
> Since this project will not using Svelte anymore, anything under this
> directory will soon be removed.

View file

@ -1,21 +0,0 @@
package frontend
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:generate npm ci
//go:generate npm run build
//go:embed build/*
var f embed.FS
func SvelteKitHandler() http.FileSystem {
build, err := fs.Sub(f, "build")
if err != nil {
log.Fatal(err)
}
return http.FS(build)
}

View file

@ -1,23 +0,0 @@
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
js.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
{
ignores: ['build/', '.svelte-kit/', 'dist/']
}
];

View file

@ -1,18 +0,0 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

File diff suppressed because it is too large Load diff

View file

@ -1,39 +0,0 @@
{
"name": "xmr-nodes-frontend",
"version": "v0.1.3",
"private": true,
"scripts": {
"dev": "VITE_API_URL=http://127.0.0.1:18901 vite dev --host 127.0.0.1",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@floating-ui/dom": "^1.6.11",
"@skeletonlabs/skeleton": "^2.10.3",
"@skeletonlabs/tw-plugin": "^0.4.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.7.3",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/forms": "^0.5.9",
"@types/eslint": "^9.6.1",
"@vincjo/datatables": "^1.14.10",
"autoprefixer": "^10.4.20",
"date-fns": "^4.1.0",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.0",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^5.1.3",
"svelte-check": "^4.0.5",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"vite": "^5.4.8"
},
"type": "module"
}

View file

@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View file

@ -1,25 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
html,
body {
@apply h-full;
}
p {
@apply mb-2;
}
.link {
@apply text-primary-800 dark:text-primary-500 hover:brightness-110;
}
a.external {
@apply link after:content-['_↗'];
}
.section-container {
@apply mx-auto w-full max-w-7xl p-4;
}
.hero-gradient {
background-image: radial-gradient(at 0% 0%, rgba(242, 104, 34, 0.4) 0px, transparent 50%),
radial-gradient(at 98% 1%, rgba(var(--color-warning-900) / 0.33) 0px, transparent 50%);
}

34
frontend/src/app.d.ts vendored
View file

@ -1,34 +0,0 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
interface ImportMetaEnv {
VITE_API_URL: string;
}
interface MoneroNode {
id: number;
hostname: string;
ip: string;
port: number;
protocol: string;
is_tor: boolean;
is_available: boolean;
nettype: string;
ip_addresses: string;
}
interface ApiResponse {
status: string;
message: string;
data: null | object | object[];
}
}
export {};

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="false" data-theme="skeleton">
<div style="display: contents" class="h-full">%sveltekit.body%</div>
</body>
</html>

View file

@ -1,14 +0,0 @@
<script>
import { version } from '$app/environment';
</script>
<div class="flex w-full items-end border-t border-surface-500/10 bg-surface-50 dark:bg-surface-900">
<footer class="w-full">
<div class="bg-surface-500/5">
<div class="container mx-auto px-5 py-4">
<!-- prettier-ignore -->
<p class="text-center text-sm">XMR Nodes {version}, <a href="https://github.com/ditatompel/xmr-remote-nodes" target="_blank" rel="noopener" class="external">source code</a> licensed under <strong>GLWTPL</strong>.</p>
</div>
</div>
</footer>
</div>

View file

@ -1,11 +0,0 @@
<!-- prettier-ignore -->
<section id="site-news" class="mx-auto w-full max-w-4xl px-4 pb-7">
<div class="alert variant-ghost-secondary shadow-xl">
<div class="alert-message">
<h2 class="h3 text-center">Quick Info</h2>
<p>
On November 7th, 2024, localmonero.co website will be taken down. If you have an account and a balance, withdraw your funds prior to this date. For more information, visit <a href="https://localmonero.co/blog/announcements/winding-down" class="external" target="_blank" rel="noopener">localmonero.co announcement page</a>.
</p>
</div>
</div>
</section>

View file

@ -1,58 +0,0 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
let intervalId: number | undefined;
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) {
handler.invalidate();
intervalId = setInterval(() => {
handler.invalidate();
}, seconds * 1000);
}
};
$: startInterval();
onDestroy(() => {
clearInterval(intervalId);
});
</script>
<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>

View file

@ -1,66 +0,0 @@
<script lang="ts">
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
const pageNumber = handler.getPageNumber();
const pageCount = handler.getPageCount();
const pages = handler.getPages({ ellipsis: true });
const setPage = (value: 'previous' | 'next' | number) => {
handler.setPage(value);
handler.invalidate();
};
</script>
<section class={$$props.class ?? ''}>
{#if $pages === undefined}
<button type="button" class="sm-btn" on:click={() => setPage('previous')}> &#10094; </button>
<button class="mx-4">page <b>{$pageNumber}</b></button>
<button type="button" class="sm-btn" on:click={() => setPage('next')}>&#10095;</button>
{:else}
<div class="lg:hidden">
<button type="button" class="sm-btn" on:click={() => setPage('previous')}> &#10094; </button>
<button class="mx-4">page <b>{$pageNumber}</b></button>
<button
class="sm-btn"
class:disabled={$pageNumber === $pageCount}
on:click={() => setPage('next')}
>
&#10095;
</button>
</div>
<div class="btn-group variant-ghost-surface hidden lg:block">
<button
type="button"
class="hover:variant-soft-secondary"
class:disabled={$pageNumber === 1}
on:click={() => setPage('previous')}>&#10094;</button
>
{#each $pages as page}<button
type="button"
class="hover:variant-filled-secondary"
class:!variant-filled-primary={$pageNumber === page}
class:ellipse={page === null}
on:click={() => setPage(page)}>{page ?? '...'}</button
>{/each}
<button
type="button"
class="hover:variant-soft-secondary"
class:disabled={$pageNumber === $pageCount}
on:click={() => setPage('next')}
>
&#10095;
</button>
</div>
{/if}
</section>
<style lang="postcss">
.disabled {
@apply cursor-not-allowed;
}
</style>

View file

@ -1,22 +0,0 @@
<script lang="ts">
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
const rowCount = handler.getRowCount();
</script>
{#if $rowCount === undefined}
<div></div>
{:else}
<div class={$$props.class ?? 'mr-6 leading-8 lg:leading-10'}>
{#if $rowCount.total > 0}
<b>{$rowCount.start}</b>
- <b>{$rowCount.end}</b>
/ <b>{$rowCount.total}</b>
{:else}
No entries found
{/if}
</div>
{/if}

View file

@ -1,33 +0,0 @@
<script lang="ts">
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
export let options = [5, 10, 20, 50, 100];
export let labelId = 'rowsPerPage';
const rowsPerPage = handler.getRowsPerPage();
const setRowsPerPage = () => {
handler.setPage(1);
handler.invalidate();
};
</script>
<div class="flex place-items-center">
<label for={labelId}>Show</label>
<select
class="select ml-2"
id={labelId}
name="rowsPerPage"
bind:value={$rowsPerPage}
on:change={setRowsPerPage}
>
{#each options as option}
<option value={option}>
{option}
</option>
{/each}
</select>
</div>

View file

@ -1,23 +0,0 @@
<script lang="ts">
import type { DataHandler } from '@vincjo/datatables/remote';
export let handler: DataHandler;
let value: string;
let timeout: any;
const search = () => {
handler.search(value);
clearTimeout(timeout);
timeout = setTimeout(() => {
handler.invalidate();
}, 400);
};
</script>
<input
class="input-variant-secondary input w-36 sm:w-64"
type="search"
name="tableGlobalSearch"
placeholder="Search..."
bind:value
on:input={search}
/>

View file

@ -1,35 +0,0 @@
<script lang="ts">
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
export let filterBy: keyof T;
/** @type {string} */
export let placeholder: string = 'Filter';
/** @type {number} */
export let colspan: number = 1;
let value: string = '';
let timeout: any;
const filter = () => {
handler.filter(value, filterBy);
clearTimeout(timeout);
timeout = setTimeout(() => {
handler.invalidate();
}, 400);
};
</script>
<th {colspan}>
<input
class="input variant-form-material h-8 w-full text-sm"
type="text"
{placeholder}
bind:value
on:input={filter}
/>
</th>

View file

@ -1,18 +0,0 @@
<script lang="ts">
import type { DataHandler, Row } from '@vincjo/datatables/remote';
type T = $$Generic<Row>;
export let handler: DataHandler<T>;
export let orderBy: keyof T;
const update = () => {
handler.sort(orderBy);
handler.invalidate();
};
</script>
<th on:click={update} class="cursor-pointer select-none p-2 px-5">
<div class="flex h-full items-center justify-start gap-x-2">
<slot /> ↕️
</div>
</th>

View file

@ -1,7 +0,0 @@
export { default as DtSrPagination } from './DtSrPagination.svelte';
export { default as DtSrRowCount } from './DtSrRowCount.svelte';
export { default as DtSrRowsPerPage } from './DtSrRowsPerPage.svelte';
export { default as DtSrSearch } from './DtSrSearch.svelte';
export { default as DtSrThFilter } from './DtSrThFilter.svelte';
export { default as DtSrThSort } from './DtSrThSort.svelte';
export { default as DtSrAutoRefresh } from './DtSrAutoRefresh.svelte';

View file

@ -1,10 +0,0 @@
<script lang="ts">
export let estimate_fee: number;
export let majority_fee: number;
</script>
{#if estimate_fee !== majority_fee}
<span class="text-orange-800 dark:text-orange-300">{estimate_fee}<br />(CAUTION!)</span>
{:else}
{estimate_fee}
{/if}

View file

@ -1,20 +0,0 @@
<script lang="ts">
import { getDistinct } from '$lib/utils/arrays';
export let filterValue; //: Writable<string>;
export let preFilteredValues; //: Readable<unknown[]>;
$: uniqueValues = getDistinct($preFilteredValues);
</script>
<div class="pt-2">
<select name="filterAnonymity" class="select" bind:value={$filterValue} on:click|stopPropagation>
<option value={undefined}>All</option>
{#each uniqueValues as value}
{#if value === true}
<option {value}>TOR</option>
{:else}
<option {value}>CLEARNET</option>
{/if}
{/each}
</select>
</div>

View file

@ -1,22 +0,0 @@
<script>
import { getDistinct } from '$lib/utils/arrays';
/** @type {string} */
export let filterName;
export let filterValue; //: Writable<string>;
export let preFilteredValues; //: Readable<unknown[]>;
$: uniqueValues = getDistinct($preFilteredValues);
</script>
<div class="pt-2">
<select name={filterName} class="select" bind:value={$filterValue} on:click|stopPropagation>
<option value={undefined}>All</option>
{#each uniqueValues as value}
{#if value === ''}
<option {value}>UNKNOWN</option>
{:else}
<option {value}>{value.toUpperCase()}</option>
{/if}
{/each}
</select>
</div>

View file

@ -1,20 +0,0 @@
<script lang="ts">
import { getDistinct } from '$lib/utils/arrays';
export let filterValue; //: Writable<string>;
export let preFilteredValues; //: Readable<unknown[]>;
$: uniqueValues = getDistinct($preFilteredValues);
</script>
<div class="pt-2">
<select name="filterStatus" class="select" bind:value={$filterValue} on:click|stopPropagation>
<option value={undefined}>All</option>
{#each uniqueValues as value}
{#if value === true}
<option {value}>ONLINE</option>
{:else}
<option {value}>OFFLINE</option>
{/if}
{/each}
</select>
</div>

View file

@ -1,20 +0,0 @@
<script lang="ts">
export let is_available: boolean;
export let statuses: number[];
</script>
{#if is_available}
<span class="font-semibold text-green-800 dark:text-green-500">Online</span>
{:else}
<span class="text-rose-800 dark:text-rose-400">Offline</span>
{/if}
<br />
{#each statuses as status}
{#if status === 1}
<span class="text-success-700 dark:text-success-400 mr-1"></span>
{:else if status === 0}
<span class="text-error-700 dark:text-error-400 mr-1"></span>
{:else}
<span class="text-surface-400 dark:text-surface-600 mr-1"></span>
{/if}
{/each}

View file

@ -1,13 +0,0 @@
<script lang="ts">
export let uptime: number;
</script>
{#if uptime >= 98}
<span class="text-green-800 dark:text-green-500">{uptime}%</span>
{:else if uptime < 98 && uptime >= 80}
<span class="text-sky-800 dark:text-sky-500">{uptime}%</span>
{:else if uptime < 80 && uptime > 75}
<span class="text-orange-800 dark:text-orange-300">{uptime}%</span>
{:else}
<span class="text-rose-800 dark:text-rose-400">{uptime}%</span>
{/if}

View file

@ -1,10 +0,0 @@
export { default as CountryCellWithAsn } from './CountryCellWithAsn.svelte';
export { default as EstimateFeeCell } from './EstimateFeeCell.svelte';
export { default as HostPortCell } from './HostPortCell.svelte';
export { default as NetTypeCell } from './NetTypeCell.svelte';
export { default as ProtocolCell } from './ProtocolCell.svelte';
export { default as SelectAnonymityFilter } from './SelectAnonymityFilter.svelte';
export { default as SelectFilter } from './SelectFilter.svelte';
export { default as SelectStatusFilter } from './SelectStatusFilter.svelte';
export { default as StatusCell } from './StatusCell.svelte';
export { default as UptimeCell } from './UptimeCell.svelte';

View file

@ -1,21 +0,0 @@
<script>
import { page } from '$app/stores';
import { adminNavs } from './navs';
import { getDrawerStore } from '@skeletonlabs/skeleton';
const drawerStore = getDrawerStore();
$: style = (/** @type {string} */ href) =>
$page.url.pathname.startsWith(href) ? 'bg-primary-500' : '';
</script>
<nav class="list-nav p-4">
<ul>
{#each adminNavs as nav}
<li>
<a href={nav.path} class={style(nav.path)} on:click={() => drawerStore.close()}
>{nav.name}</a
>
</li>
{/each}
</ul>
</nav>

View file

@ -1,72 +0,0 @@
<script>
import { invalidateAll, goto } from '$app/navigation';
import { LightSwitch, getDrawerStore } from '@skeletonlabs/skeleton';
import { apiUri } from '$lib/utils/common';
const drawerStore = getDrawerStore();
/** @type {ApiResponse} */
let formResult;
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleLogout(event) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify(Object.fromEntries(data))
});
formResult = await response.json();
if (formResult.status === 'ok') {
await invalidateAll();
goto('/login/');
}
}
</script>
<nav class="bg-surface-100-800-token fixed top-0 z-30 w-full shadow-2xl">
<div class="px-3 py-2 lg:px-5 lg:pl-3">
<div class="flex items-center justify-between">
<div class="flex items-center justify-start rtl:justify-end">
<button
class="btn btn-sm inline-flex items-center md:hidden"
aria-label="Mobile Drawer Button"
on:click={() => drawerStore.open({})}
>
<span>
<svg viewBox="0 0 100 80" class="fill-token h-4 w-4">
<rect width="100" height="20" />
<rect y="30" width="100" height="20" />
<rect y="60" width="100" height="20" />
</svg>
</span>
</button>
<a href="/app/prober/" class="ms-2 flex md:me-24" aria-label="title">
<span class="hidden self-center whitespace-nowrap text-2xl font-semibold lg:block"
>XMR Nodes</span
>
</a>
</div>
<div class="flex items-center">
<div class="ms-3 flex items-center space-x-4">
<LightSwitch />
<form
action={apiUri('/auth/logout')}
method="POST"
on:submit|preventDefault={handleLogout}
>
<input type="hidden" name="logout" value="logout" />
<button type="submit" class="btn btn-sm variant-filled-error" role="menuitem">
Sign out
</button>
</form>
</div>
</div>
</div>
</div>
</nav>

View file

@ -1,34 +0,0 @@
<script>
import { page } from '$app/stores';
import { adminNavs } from './navs';
</script>
<aside
id="logo-sidebar"
class="bg-surface-100-800-token fixed left-0 top-0 z-20 h-screen w-64 -translate-x-full pt-20 shadow-2xl transition-transform sm:translate-x-0"
aria-label="Sidebar"
>
<div class="h-full overflow-y-auto px-3 pb-4">
<ul class="space-y-2 font-medium list-none" data-sveltekit-preload-data="false">
{#each adminNavs as nav}
<li>
<a
href={nav.path}
class={$page.url.pathname.startsWith(nav.path) ? 'active' : 'nav-link'}
>
<span class="ms-3">{nav.name}</span>
</a>
</li>
{/each}
</ul>
</div>
</aside>
<style lang="postcss">
.active {
@apply flex items-center rounded-lg bg-primary-500 p-2;
}
.nav-link {
@apply flex items-center rounded-lg p-2 hover:bg-secondary-500 hover:text-white;
}
</style>

View file

@ -1,63 +0,0 @@
<script>
import { page } from '$app/stores';
import { navs } from './navs';
import { LightSwitch, getDrawerStore } from '@skeletonlabs/skeleton';
const drawerStore = getDrawerStore();
</script>
<nav class="fixed w-full z-20 top-0 start-0 bg-surface-100-800-token shadow-2xl">
<div class="mx-auto flex max-w-screen-xl flex-wrap items-center justify-between px-4 py-1">
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse" aria-label="xmr nodes">
<span class="self-center whitespace-nowrap text-2xl font-semibold lg:block">XMR Nodes</span>
</a>
<div class="flex items-center space-x-1 md:order-2 md:space-x-0 rtl:space-x-reverse">
<LightSwitch />
<button
class="btn btn-sm mr-4 md:hidden"
aria-label="Mobile Drawer Button"
on:click={() => drawerStore.open({})}
>
<span>
<svg viewBox="0 0 100 80" class="fill-token h-4 w-4">
<rect width="100" height="20" />
<rect y="30" width="100" height="20" />
<rect y="60" width="100" height="20" />
</svg>
</span>
</button>
</div>
<div class="hidden w-full items-center justify-between md:order-1 md:flex md:w-auto">
<ul
class="flex flex-row space-x-1 rounded-lg bg-white p-0 dark:bg-gray-900 rtl:space-x-reverse"
>
<li>
<a
href="/"
class={$page.url.pathname === '/' ? 'active' : 'nav-link'}
aria-current={$page.url.pathname === '/' ? 'page' : undefined}>Home</a
>
</li>
{#each navs as nav}
<li>
<a
href={nav.path}
class={$page.url.pathname.startsWith(nav.path) ? 'active' : 'nav-link'}
>
{nav.name}
</a>
</li>
{/each}
</ul>
</div>
</div>
</nav>
<style lang="postcss">
.active {
@apply block rounded bg-primary-500 p-2 text-black;
}
.nav-link {
@apply block rounded hover:bg-secondary-500 md:p-2 hover:text-white;
}
</style>

View file

@ -1,28 +0,0 @@
<script>
import { page } from '$app/stores';
import { navs } from './navs';
import { getDrawerStore } from '@skeletonlabs/skeleton';
const drawerStore = getDrawerStore();
$: classes = (/** @type {string} */ href) =>
$page.url.pathname.startsWith(href) ? 'bg-primary-500' : '';
</script>
<nav class="list-nav p-4">
<ul>
<li>
<a
href="/"
class={$page.url.pathname === '/' ? 'bg-primary-500' : ''}
on:click={() => drawerStore.close()}>Home</a
>
</li>
{#each navs as nav}
<li>
<a href={nav.path} class={classes(nav.path)} on:click={() => drawerStore.close()}
>{nav.name}</a
>
</li>
{/each}
</ul>
</nav>

View file

@ -1,5 +0,0 @@
export { default as MainNav } from './MainNav.svelte';
export { default as MobileDrawer } from './MobileDrawer.svelte';
export { default as AdminNav } from './AdminNav.svelte';
export { default as AdminSidebar } from './AdminSidebar.svelte';
export { default as AdminMobileDrawer } from './AdminMobileDrawer.svelte';

View file

@ -1,9 +0,0 @@
export const adminNavs = [
{ name: 'Prober', path: '/app/prober/' },
{ name: 'Crons', path: '/app/crons/' }
];
export const navs = [
{ name: 'Remote Nodes', path: '/remote-nodes/' },
{ name: 'Add Node', path: '/add-node/' }
];

View file

@ -1,10 +0,0 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 512 512"
><path
d="M512 256C512 114.6 397.4 0 256 0S0 114.6 0 256C0 376 82.7 476.8 194.2 504.5V334.2H141.4V256h52.8V222.3c0-87.1 39.4-127.5 125-127.5c16.2 0 44.2 3.2 55.7 6.4V172c-6-.6-16.5-1-29.6-1c-42 0-58.2 15.9-58.2 57.2V256h83.6l-14.4 78.2H287V510.1C413.8 494.8 512 386.9 512 256h0z"
/></svg
>

Before

Width:  |  Height:  |  Size: 582 B

View file

@ -1,10 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 24 24"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
></path>
</svg>

Before

Width:  |  Height:  |  Size: 880 B

View file

@ -1,10 +0,0 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 496 512"
><path
d="M352 384h108.4C417 455.9 338.1 504 248 504S79 455.9 35.6 384H144V256.2L248 361l104-105v128zM88 336V128l159.4 159.4L408 128v208h74.8c8.5-25.1 13.2-52 13.2-80C496 119 385 8 248 8S0 119 0 256c0 28 4.6 54.9 13.2 80H88z"
/></svg
>

Before

Width:  |  Height:  |  Size: 528 B

View file

@ -1,10 +0,0 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 512 512"
><path
d="M373 138.6c-25.2 0-46.3-17.5-51.9-41l0 0c-30.6 4.3-54.2 30.7-54.2 62.4l0 .2c47.4 1.8 90.6 15.1 124.9 36.3c12.6-9.7 28.4-15.5 45.5-15.5c41.3 0 74.7 33.4 74.7 74.7c0 29.8-17.4 55.5-42.7 67.5c-2.4 86.8-97 156.6-213.2 156.6S45.5 410.1 43 323.4C17.6 311.5 0 285.7 0 255.7c0-41.3 33.4-74.7 74.7-74.7c17.2 0 33 5.8 45.7 15.6c34-21.1 76.8-34.4 123.7-36.4l0-.3c0-44.3 33.7-80.9 76.8-85.5C325.8 50.2 347.2 32 373 32c29.4 0 53.3 23.9 53.3 53.3s-23.9 53.3-53.3 53.3zM157.5 255.3c-20.9 0-38.9 20.8-40.2 47.9s17.1 38.1 38 38.1s36.6-9.8 37.8-36.9s-14.7-49.1-35.7-49.1zM395 303.1c-1.2-27.1-19.2-47.9-40.2-47.9s-36.9 22-35.7 49.1c1.2 27.1 16.9 36.9 37.8 36.9s39.3-11 38-38.1zm-60.1 70.8c1.5-3.6-1-7.7-4.9-8.1c-23-2.3-47.9-3.6-73.8-3.6s-50.8 1.3-73.8 3.6c-3.9 .4-6.4 4.5-4.9 8.1c12.9 30.8 43.3 52.4 78.7 52.4s65.8-21.6 78.7-52.4z"
/></svg
>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,10 +0,0 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 496 512"
><path
d="M248 8C111 8 0 119 0 256S111 504 248 504 496 393 496 256 385 8 248 8zM363 176.7c-3.7 39.2-19.9 134.4-28.1 178.3-3.5 18.6-10.3 24.8-16.9 25.4-14.4 1.3-25.3-9.5-39.3-18.7-21.8-14.3-34.2-23.2-55.3-37.2-24.5-16.1-8.6-25 5.3-39.5 3.7-3.8 67.1-61.5 68.3-66.7 .2-.7 .3-3.1-1.2-4.4s-3.6-.8-5.1-.5q-3.3 .7-104.6 69.1-14.8 10.2-26.9 9.9c-8.9-.2-25.9-5-38.6-9.1-15.5-5-27.9-7.7-26.8-16.3q.8-6.7 18.5-13.7 108.4-47.2 144.6-62.3c68.9-28.6 83.2-33.6 92.5-33.8 2.1 0 6.6 .5 9.6 2.9a10.5 10.5 0 0 1 3.5 6.7A43.8 43.8 0 0 1 363 176.7z"
/></svg
>

Before

Width:  |  Height:  |  Size: 831 B

View file

@ -1,10 +0,0 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<svg
xmlns="http://www.w3.org/2000/svg"
fill={`${$$props.fill ?? 'currentColor'}`}
class={`${$$props.class}`}
viewBox="0 0 512 512"
><path
d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"
/></svg
>

Before

Width:  |  Height:  |  Size: 470 B

View file

@ -1,6 +0,0 @@
export { default as IcnGitHub } from './IcnGitHub.svelte';
export { default as IcnMonero } from './IcnMonero.svelte';
export { default as IcnReddit } from './IcnReddit.svelte';
export { default as IcnTwitter } from './IcnTwitter.svelte';
export { default as IcnFacebook } from './IcnFacebook.svelte';
export { default as IcnTelegram } from './IcnTelegram.svelte';

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -1,17 +0,0 @@
export const getDistinct = (items) => {
return Array.from(getCounter(items).keys());
};
export const getDuplicates = (items) => {
return Array.from(getCounter(items).entries())
.filter(([, count]) => count !== 1)
.map(([key]) => key);
};
export const getCounter = (items) => {
const result = new Map();
items.forEach((item) => {
result.set(item, (result.get(item) ?? 0) + 1);
});
return result;
};

View file

@ -1,4 +0,0 @@
/** @param {string} path */
export const apiUri = (path) => {
return `${import.meta.env.VITE_API_URL || ''}${path}`;
};

View file

@ -1,76 +0,0 @@
/**
* Modifies the input string based on whether it is an IPv6 address.
* If the input is an IPv6 address, it wraps it in square brackets `[ ]`.
* Otherwise, it returns the input string as-is (for domain names or
* IPv4 addresses). AND I'M SORRY USING REGEX FOR THIS!
*
* @param {string} hostname
* @returns {string} - The modified string, IPv6 addresses wrapped in `[ ]`.
*/
export const formatHostname = (hostname) => {
// const ipv6Pattern = /^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/; // full
// pattern for both full and compressed IPv6 addresses.
// source: https://regex101.com/library/cP9mH9?filterFlavors=dotnet&filterFlavors=javascript&orderBy=RELEVANCE&search=ip
// This may be incorrect, but let's assume it's correct. xD
const ipv6Pattern =
/^(([0-9A-Fa-f]{1,4}:){7})([0-9A-Fa-f]{1,4})$|(([0-9A-Fa-f]{1,4}:){1,6}:)(([0-9A-Fa-f]{1,4}:){0,4})([0-9A-Fa-f]{1,4})$/;
if (ipv6Pattern.test(hostname)) {
return `[${hostname}]`;
}
return hostname;
};
/**
* @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]}`;
};
/**
* Returns a number with a maximum precision.
*
* This function was copied from jtgrassie/monero-pool project.
* Source: https://github.com/jtgrassie/monero-pool/blob/master/src/webui-embed.html
*
* Copyright (c) 2018, The Monero Project
*
* @param {number} n
* @param {number} p
*/
const maxPrecision = (n, p) => {
return parseFloat(n.toFixed(p));
};
/**
* Formats a hash value (h) into human readable format.
*
* This function was copied from jtgrassie/monero-pool project.
* Source: https://github.com/jtgrassie/monero-pool/blob/master/src/webui-embed.html
*
* Copyright (c) 2018, The Monero Project
*
* @param {number} h
*/
export const 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';
};

View file

@ -1,2 +0,0 @@
export const prerender = true;
export const trailingSlash = 'always';

View file

@ -1,85 +0,0 @@
<script>
import '../app.css';
import { page } from '$app/stores';
import {
Toast,
Modal,
Drawer,
ProgressBar,
initializeStores,
storePopup
} from '@skeletonlabs/skeleton';
import { beforeNavigate, afterNavigate } from '$app/navigation';
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { MainNav, MobileDrawer } from '$lib/components/navigation';
import Footer from '$lib/components/Footer.svelte';
initializeStores();
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
let isLoading = false;
beforeNavigate(() => (isLoading = true));
afterNavigate(() => {
isLoading = false;
});
/* prettier-ignore */
const meta = {
title: 'Monero Remote Node',
description: 'A website that helps you monitor your favourite Monero remote nodes, a device on the internet running the Monero software with copy of the Monero blockchain.',
keywords: 'monero,monero,xmr,monero node,xmrnode,cryptocurrency,monero remote node,monero testnet,monero stagenet'
};
page.subscribe((page) => {
if (typeof page.data.meta === 'object') {
meta.title = page.data.meta.title ?? meta.title;
meta.description = page.data.meta.description ?? meta.description;
meta.keywords = page.data.meta.keywords ?? meta.description;
}
});
</script>
<svelte:head>
<title>{meta.title} — xmr.ditatompel.com</title>
<!-- Meta Tags -->
<meta name="title" content="{meta.title} — xmr.ditatompel.com" />
<meta name="description" content={meta.description} />
<meta name="keywords" content={meta.keywords} />
<meta name="theme-color" content="#272b31" />
<meta name="author" content="ditatompel" />
<!-- Open Graph - https://ogp.me/ -->
<meta property="og:site_name" content="xmr.ditatompel.com" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://xmr.ditatompel.com{$page.url.pathname}" />
<meta property="og:locale" content="en_US" />
<meta property="og:title" content="{meta.title} — xmr.ditatompel.com" />
<meta property="og:description" content={meta.description} />
</svelte:head>
<Modal />
<Toast />
{#if isLoading}
<ProgressBar
class="fixed top-0 z-50"
height="h-1"
track="bg-opacity-100"
meter="bg-gradient-to-br from-purple-600 via-pink-600 to-blue-600"
/>
{/if}
<Drawer>
<h2 class="p-4">Navigation</h2>
<hr />
<MobileDrawer />
<hr />
</Drawer>
<MainNav />
<div class="pt-10 md:pt-12 min-h-screen">
<slot />
</div>
<Footer />

View file

@ -1,37 +0,0 @@
/** @type {import('./$types').PageLoad} */
export async function load() {
return {
meta: {
title: 'Monero Remote Node',
description:
'A website that helps you monitor your favourite Monero remote nodes, but YOU BETTER RUN AND USE YOUR OWN NODE.',
keywords:
'monero,monero,xmr,monero node,xmrnode,cryptocurrency,monero remote node,monero testnet,monero stagenet'
},
links: [
{ text: 'moneroworld.com', uri: 'https://moneroworld.com' },
{ text: 'monero.how', uri: 'https://www.monero.how' },
{ text: 'monero.observer', uri: 'https://www.monero.observer' },
{ text: 'revuo-xmr.com', uri: 'https://revuo-xmr.com' },
{ text: 'themonoeromoon.com', uri: 'https://www.themoneromoon.com' },
{ text: 'monerotopia.com', uri: 'https://monerotopia.com' },
{ text: 'sethforprivacy.com', uri: 'https://sethforprivacy.com' }
],
stagenet: [
{ label: 'P2P', value: 'stagenet.xmr.ditatompel.com:38080', key: 'snetP2P' },
{ label: 'RPC', value: 'stagenet.xmr.ditatompel.com:38089', key: 'snetRPC' },
{ label: 'RPC SSL', value: 'stagenet.xmr.ditatompel.com:443', key: 'snetSSL' }
],
testnet: [
{ label: 'P2P', value: 'testnet.xmr.ditatompel.com:28080', key: 'tnetP2P' },
{ label: 'RPC', value: 'testnet.xmr.ditatompel.com:28089', key: 'tnetRPC' },
{ label: 'RPC SSL', value: 'testnet.xmr.ditatompel.com:443', key: 'tnetSSL' }
],
donation: {
// You change donation address and qr image below if you run your own "instance"
address:
'8BWYe6GzbNKbxe3D8mPkfFMQA2rViaZJFhWShhZTjJCNG6EZHkXRZCKHiuKmwwe4DXDYF8KKcbGkvNYaiRG3sNt7JhnVp7D',
qr: '/img/monerotip.png'
}
};
}

View file

@ -1,187 +0,0 @@
<script>
import { clipboard } from '@skeletonlabs/skeleton';
import {
IcnGitHub,
IcnMonero,
IcnReddit,
IcnTwitter,
IcnFacebook,
IcnTelegram
} from '$lib/components/svg';
import News from '$lib/components/News.svelte';
/** @type {import('./$types').PageData} */
export let data;
let donationCopied = false;
/** @param {Event & { target: HTMLInputElement }} e */
function copyHandler(e) {
e.target.disabled = true;
e.target.innerText = 'Copied 👍';
setTimeout(() => {
e.target.innerText = 'Copy';
e.target.disabled = false;
}, 1000);
}
function copyDonationAddr() {
donationCopied = true;
setTimeout(() => {
donationCopied = false;
}, 2500);
}
</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>{data.meta.description}</p>
<!-- prettier-ignore -->
<div class="pt-2">
<a href="https://www.getmonero.org" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnMonero class="h-4 w-4" /></span>
<span>GetMonero.org</span>
</a>
<a href="https://github.com/monero-project" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnGitHub fill="currentColor" class="h-4 w-4" /></span>
<span>monero-project</span>
</a>
<a href="https://www.reddit.com/r/Monero/" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnReddit fill="currentColor" class="h-4 w-4" /></span>
<span>r/Monero</span>
</a>
<a href="https://twitter.com/monero" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnTwitter fill="currentColor" class="h-4 w-4" /></span>
<span>@monero</span>
</a>
<a href="https://www.facebook.com/monerocurrency/" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnFacebook fill="currentColor" class="h-4 w-4" /></span>
<span>monerocurrency</span>
</a>
<a href="https://telegram.me/monero" class="variant-ghost chip mt-2 hover:variant-filled" target="_blank" rel="noopener">
<span><IcnTelegram fill="currentColor" class="h-4 w-4" /></span>
<span>monero</span>
</a>
</div>
</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">
<p>If you're new to Monero, the official links above is a perfect place to start.</p>
<p class="py-2">
Of course, there are lots of personal and community sites which generally discusses a lot
about Monero, such as
{#each data.links as link}
<a href={link.uri} class="external" target="_blank" rel="noopener">{link.text}</a>,&nbsp;
{/each} etc; can be an other good reference for you.
</p>
<p>You can find few resources I provide related to Monero below:</p>
</div>
<!-- prettier-ignore -->
<div class="section-container text-token grid grid-cols-1 gap-2 md:grid-cols-3">
<a class="card card-hover overflow-hidden py-2 text-center" href="/remote-nodes/">
<h2 class="h2 font-bold">Remote Nodes</h2>
<div class="space-y-4 p-4">
<p>List of submitted Monero remote nodes you can use when you <strong>cannot</strong> run your own node.</p>
</div>
</a>
<a class="card card-hover overflow-hidden py-2 text-center" href="/add-node/">
<h2 class="h2 font-bold">Add Node</h2>
<div class="space-y-4 p-4">
<p>Add your Monero public node to be monitored and see how it performs.</p>
</div>
</a>
<a class="card card-hover overflow-hidden py-2 text-center" href="https://monitor.ditatompel.com/d/xmr_metrics/monero-metrics?orgId=2" target="_blank" rel="noopener" >
<h2 class="h2 font-bold">Metrics</h2>
<div class="space-y-4 p-4">
<p>Collection of my Monero metrics (GitHub repository, blockchain, market, P2Pool) presented through Grafana. ↗</p>
</div>
</a>
</div>
</section>
<News />
<section id="my-monero-public-nodes" class="bg-surface-100-800-token">
<div class="section-container text-token grid grid-cols-1 gap-10 md:grid-cols-2">
<div class="text-center">
<h2 class="h2 pb-2 font-bold">My Stagenet Public Node</h2>
<p>
Stagenet is what you need to learn Monero safely. Stagenet is technically equivalent to
mainnet, both in terms of features and consensus rules.
</p>
{#each data.stagenet as { label, value, key }}
<div class="input-group input-group-divider my-2 grid-cols-[auto_1fr_auto]">
<div class="input-group-shim"><label for={key}>{label}</label></div>
<input class="text-center" type="text" id={key} name={key} {value} data-clipboard={key} />
<button
class="variant-filled-secondary"
use:clipboard={{ input: key }}
on:click={copyHandler}>Copy</button
>
</div>
{/each}
</div>
<div class="text-center">
<h2 class="h2 pb-2 font-bold">My Testnet Public Node</h2>
<p>
Testnet is the <em>"experimental"</em> network and blockchain where things get released long
before mainnet. As a normal user, use mainnet instead.
</p>
{#each data.testnet as { label, value, key }}
<div class="input-group input-group-divider my-2 grid-cols-[auto_1fr_auto]">
<div class="input-group-shim"><label for={key}>{label}</label></div>
<input class="text-center" type="text" id={key} name={key} {value} data-clipboard={key} />
<button
class="variant-filled-secondary"
use:clipboard={{ input: key }}
on:click={copyHandler}>Copy</button
>
</div>
{/each}
</div>
</div>
</section>
<section id="privacy-quote">
<div class="text-token mx-auto w-full max-w-4xl py-4 text-center">
<!-- prettier-ignore -->
<blockquote class="blockquote">
<p class="text-3xl">
Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction.
</p>
<p class="my-2">
<strong>Eric Hughes</strong> in <a href="https://www.activism.net/cypherpunk/manifesto.html" class="external" target="_blank" rel="noopener"><cite title="Source Title">A Cypherpunk's Manifesto</cite></a>.
</p>
</blockquote>
</div>
</section>
<section id="monero-donation" class="section-container text-token text-center">
<div class="mx-auto flex w-full max-w-4xl flex-row items-center gap-10">
<div class="md:basis-3/4">
<label for="donate">If you like to buy me a coffee, here is my Monero address:</label>
<textarea class="textarea my-2" id="donate" name="donate" data-clipboard="donate" readonly
>{data.donation.address}</textarea
>
<button
class="variant-filled-success btn"
use:clipboard={{ input: 'donate' }}
disabled={donationCopied}
on:click={copyDonationAddr}
>{donationCopied ? 'Donation Address Copied! 🤩' : 'Copy Donation Address'}</button
>
</div>
<div class="md:basis-1/4">
<img src={data.donation.qr} alt="ditatompel's monero address" />
<p>Thank you so much! It means a lot to me. 🥰</p>
</div>
</div>
</section>

View file

@ -1,12 +0,0 @@
/** @type {import('./$types').PageLoad} */
export async function load() {
/* prettier-ignore */
return {
meta: {
title: 'Add Monero Node',
description:
'You can use this page to add known remote node to the system so my bots can monitor it.',
keywords: 'monero,monero node,monero public node,monero wallet,list monero node,monero node monitoring'
}
};
}

View file

@ -1,129 +0,0 @@
<script>
import { invalidateAll, goto } from '$app/navigation';
import { apiUri } from '$lib/utils/common';
import { ProgressBar } from '@skeletonlabs/skeleton';
/** @type {import('./$types').PageData} */
export let data;
/** @type {ApiResponse} */
export let formResult;
let isProcessing = false;
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
isProcessing = true;
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
formResult = await response.json();
isProcessing = false;
if (formResult.status === 'ok') {
await invalidateAll();
goto('/remote-nodes');
}
}
</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>{data.meta.description}</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="page-info" class="mx-auto w-full max-w-4xl px-4 pb-7">
<div class="alert card shadow-xl">
<div class="alert-message">
<h2 class="h3 text-center">Important Note</h2>
<ul class="list-inside list-disc">
<li>
As an administrator of this instance, I have full rights to delete, and blacklist any
submitted node with or without providing any reason.
</li>
</ul>
</div>
</div>
</section>
<section id="form-add-monero-node">
<div class="section-container text-center">
<p>Enter your Monero node information below (IPv6 host check is experimental):</p>
<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">
<label class="label">
<span>Protocol *</span>
<select name="protocol" class="select variant-form-material" disabled={isProcessing}>
<option value="http">HTTP or TOR</option>
<option value="https">HTTPS</option>
</select>
</label>
<label class="label md:col-span-2">
<span>Host / IP *</span>
<input
class="input variant-form-material"
name="hostname"
type="text"
required
placeholder="Eg: node.example.com or 172.16.17.18"
disabled={isProcessing}
/>
</label>
<label class="label">
<span>Port *</span>
<input
class="input variant-form-material"
name="port"
type="number"
required
placeholder="Eg: 18081"
disabled={isProcessing}
/>
</label>
</div>
<button class="variant-filled-success btn" disabled={isProcessing}
>{isProcessing ? 'Processing...' : 'Submit'}</button
>
</form>
<div class="mx-auto w-full max-w-3xl py-2">
{#if !isProcessing}
{#if formResult?.status === 'error'}
<div class="mx-4 p-4 mb-4 text-sm rounded-lg bg-gray-700 text-red-400" role="alert">
<span class="font-medium">Error:</span>
{formResult.message}!
</div>
{/if}
{#if formResult?.status === 'ok'}
<div class="mx-4 p-4 mb-4 text-sm rounded-lg bg-gray-700 text-green-400" role="alert">
<span class="font-medium">Success:</span>
{formResult.message}!
</div>
{/if}
{:else}
<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}
</div>
<p>
Here you can find list of <a class="anchor" href="/remote-nodes/">Monero Remote Node</a>.
</p>
</div>
</section>

View file

@ -1,11 +0,0 @@
/** @type {import('./$types').PageLoad} */
export async function load() {
return {
// prettier-ignore
meta: {
title: 'Public Monero Remote Nodes List',
description: "Although it's possible to use these existing public Monero nodes, you're MUST RUN AND USE YOUR OWN NODE!",
keywords: 'monero remote nodes,public monero nodes,monero public nodes,monero wallet,tor monero node,monero cors rpc'
}
};
}

View file

@ -1,343 +0,0 @@
<script>
import { DataHandler } from '@vincjo/datatables/remote';
import { format, formatDistance } from 'date-fns';
import { loadData, loadFees, loadCountries } from './api-handler';
import { onMount } from 'svelte';
import {
DtSrRowsPerPage,
DtSrThSort,
DtSrThFilter,
DtSrRowCount,
DtSrPagination,
DtSrAutoRefresh
} from '$lib/components/datatables/server';
import {
HostPortCell,
NetTypeCell,
ProtocolCell,
CountryCellWithAsn,
StatusCell,
UptimeCell,
EstimateFeeCell
} from '$lib/components/datatables/xmr';
import News from '$lib/components/News.svelte';
export let data;
let filterNettype = 'any';
let filterProtocol = 'any';
let filterCc = 'any';
let filterStatus = -1;
let checkboxCors = false;
/** @type {{total_nodes: number, cc: string, name: string}[]} */
let countries = [];
let fees = [];
const handler = new DataHandler([], { rowsPerPage: 10, totalRows: 0 });
let rows = handler.getRows();
/** @type {Object.<string, number>} */
let majorityFee;
onMount(() => {
loadFees().then((data) => {
fees = data;
majorityFee = fees.reduce(
/**
* @param {Object.<string, number>} o
* @param {{ nettype: string, estimate_fee: number }} key
* @returns {Object.<string, number>}
*/
(o, key) => ({
...o,
[key.nettype]: key.estimate_fee
}),
{}
);
handler.onChange((state) => loadData(state));
handler.invalidate();
});
loadCountries().then((data) => {
countries = data;
});
});
</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>
<!-- prettier-ignore -->
<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>
<!-- prettier-ignore -->
<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 <span class="font-extrabold text-2xl underline decoration-double decoration-2 decoration-pink-500">always run and use your own node</span></strong> to obtain the maximum possible privacy and to help decentralize the network.</p>
</div>
</section>
<News />
<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">
<DtSrAutoRefresh {handler} />
</div>
<div class="flex place-items-center">
<button
id="reloadDt"
name="reloadDt"
class="variant-filled-primary btn"
on:click={() => handler.invalidate()}>Reload</button
>
</div>
</div>
<table class="table table-hover table-compact w-full table-auto">
<thead>
<tr>
<th>Host:Port</th>
<th>Nettype</th>
<th>Protocol</th>
<th>Country</th>
<th>Status</th>
<th>Est. Fee</th>
<DtSrThSort {handler} orderBy="uptime">Uptime</DtSrThSort>
<DtSrThSort {handler} orderBy="last_checked">Check</DtSrThSort>
</tr>
<tr>
<DtSrThFilter {handler} filterBy="host" placeholder="Filter Host / IP" />
<th>
<select
id="nettype"
name="nettype"
class="select variant-form-material"
bind:value={filterNettype}
on:change={() => {
handler.filter(filterNettype, 'nettype');
handler.invalidate();
}}
>
<option value="any">Any</option>
<option value="mainnet">MAINNET</option>
<option value="stagenet">STAGENET</option>
<option value="testnet">TESTNET</option>
</select>
</th>
<th>
<select
id="protocol"
name="protocol"
class="select variant-form-material"
bind:value={filterProtocol}
on:change={() => {
handler.filter(filterProtocol, 'protocol');
handler.invalidate();
}}
>
<option value="any">Any</option>
<option value="tor">TOR</option>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</th>
<th>
<select
id="cc"
name="cc"
class="select variant-form-material"
bind:value={filterCc}
on:change={() => {
handler.filter(filterCc, 'cc');
handler.invalidate();
}}
>
<option value="any">Any</option>
{#each countries as country}
{#if country.cc === ''}
<option value="UNKNOWN">UNKNOWN ({country.total_nodes})</option>
{:else}
<option value={country.cc}
>{country.name === '' ? country.cc : country.name} ({country.total_nodes})</option
>
{/if}
{/each}
</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="0">Offline</option>
<option value="1">Online</option>
</select>
</th>
<th colspan="2">
<label for="cors" class="flex items-center justify-center space-x-2">
<input
id="cors"
name="cors"
class="checkbox"
type="checkbox"
bind:checked={checkboxCors}
on:change={() => {
handler.filter(checkboxCors === true ? 1 : -1, 'cors');
handler.invalidate();
}}
/>
<p>CORS</p>
</label>
</th>
</tr>
</thead>
<tbody>
{#each $rows as row (row.id)}
<tr>
<td
><HostPortCell
ip_addresses={row.ip_addresses}
is_tor={row.is_tor}
hostname={row.hostname}
port={row.port}
ipv6_only={row.ipv6_only}
/>
</td>
<td><NetTypeCell nettype={row.nettype} height={row.height} /></td>
<td><ProtocolCell protocol={row.protocol} cors={row.cors} /></td>
<td
><CountryCellWithAsn
cc={row.cc}
country_name={row.country_name}
city={row.city}
asn={row.asn}
asn_name={row.asn_name}
/></td
>
<td
><StatusCell
is_available={row.is_available}
statuses={row.last_check_statuses}
/></td
>
<td>
<EstimateFeeCell
estimate_fee={row.estimate_fee}
majority_fee={majorityFee[row.nettype]}
/>
</td>
<td
><UptimeCell uptime={row.uptime} /><br />
<a
class="anchor !text-purple-800 dark:!text-purple-400"
href="/remote-nodes/logs/?node_id={row.id}">[Logs]</a
>
</td>
<td>
{format(row.last_checked * 1000, 'PP HH:mm')}<br />
{formatDistance(row.last_checked * 1000, new Date(), { addSuffix: true })}
</td>
</tr>
{/each}
</tbody>
</table>
<div class="flex justify-between mb-2">
<DtSrRowCount {handler} />
<DtSrPagination {handler} />
</div>
</div>
</div>
</section>
<section id="page-info" class="mx-auto w-full max-w-4xl px-4 pb-7">
<div class="alert card shadow-xl">
<div class="alert-message">
<h2 class="h3">Info</h2>
<ul class="list-inside list-disc">
<li>
If you find any remote nodes that are strange or suspicious, please <a
class="external"
href="https://github.com/ditatompel/xmr-remote-nodes/issues"
target="_blank"
rel="noopener">open an issue on GitHub</a
> for removal.
</li>
<li>
Uptime percentage calculated is the <strong>last 1 month</strong> uptime.
</li>
<li>
<strong>Est. Fee</strong> here is just fee estimation / byte from
<code class="code text-rose-900 font-bold">get_fee_estimate</code> RPC call method.
</li>
<li>
Malicious actors who running remote nodes <a
class="link"
href="/img/node-tx-fee.jpg"
rel="noopener">still can return high fee only if you about to create a transactions</a
>.
</li>
<li>
<strong
class="font-extrabold text-2xl underline decoration-double decoration-2 decoration-pink-500"
>The best and safest way is running your own node!</strong
>
</li>
<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 know one or more remote node that we don't currently monitor, please add them using <a
href="/add-node">this form</a
>.
</li>
<li>
I deliberately cut the long Tor addresses, click the <span
class="text-orange-800 dark:text-orange-300">👁 torhostname...</span
> to see the full Tor address.
</li>
<li>
You can found larger remote nodes database from <a
class="external"
href="https://monero.fail/"
role="button"
target="_blank"
rel="noopener">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
class="external"
href="https://insights.ditatompel.com/en/blog/2022/01/public-api-monero-remote-node-list/"
>Public API Monero Remote Node List</a
> blog post for more detailed information.
</li>
</ul>
</div>
</div>
</section>

View file

@ -1,37 +0,0 @@
import { apiUri } from '$lib/utils/common';
/**
* @typedef {import('@vincjo/datatables/remote').State} State
* @param {State} state - The state object from the data table.
*/
export const loadData = async (state) => {
const response = await fetch(apiUri(`/api/v1/nodes?${getParams(state)}`));
const json = await response.json();
state.setTotalRows(json.data.total_rows ?? 0);
return json.data.items ?? [];
};
export const loadCountries = async () => {
const response = await fetch(apiUri('/api/v1/countries'));
const json = await response.json();
return json.data ?? [];
};
export const loadFees = async () => {
const response = await fetch(apiUri('/api/v1/fees'));
const json = await response.json();
return json.data ?? [];
};
/** @param {State} state - The state object from the data table. */
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;
};

View file

@ -1,11 +0,0 @@
/** @type {import('./$types').PageLoad} */
export async function load() {
/* prettier-ignore */
return {
meta: {
title: 'Probe Logs',
description: 'Monero RPC response frpm monitored remote nodes',
keywords: 'monero log,monero node log,monitoring monero log,monero,xmr,monero node,xmrnode,cryptocurrency'
}
};
}

View file

@ -1,178 +0,0 @@
<script>
import { DataHandler } from '@vincjo/datatables/remote';
import { format, formatDistance } from 'date-fns';
import { loadData, loadNodeInfo } from './api-handler';
import { onMount } from 'svelte';
import { formatHostname, formatHashes, formatBytes } from '$lib/utils/strings';
import {
DtSrRowsPerPage,
DtSrThSort,
DtSrThFilter,
DtSrRowCount,
DtSrPagination,
DtSrAutoRefresh
} from '$lib/components/datatables/server';
/** @param {number | null } runtime */
function parseRuntime(runtime) {
return runtime === null ? '' : runtime.toLocaleString(undefined) + 's';
}
export let data;
let pageId = '0';
let filterStatus = -1;
/** @type {MoneroNode | null} */
let nodeInfo;
const handler = new DataHandler([], { rowsPerPage: 10, totalRows: 0 });
let rows = handler.getRows();
onMount(() => {
pageId = new URLSearchParams(window.location.search).get('node_id') || '0';
loadNodeInfo(pageId).then((data) => {
nodeInfo = data;
});
handler.filter(pageId, 'node_id');
handler.onChange((state) => loadData(state));
handler.invalidate();
});
</script>
<header id="hero" class="hero-gradient py-7">
<div class="card text-token mx-auto flex w-fit justify-center p-4">
<ol class="breadcrumb">
<li class="crumb"><a class="link underline" href="/remote-nodes">Remote Nodes</a></li>
<li class="crumb-separator" aria-hidden="true">/</li>
<li>Logs</li>
</ol>
</div>
<div class="section-container text-center">
<h1 class="h1 pb-2 font-extrabold">{data.meta.title}</h1>
</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>
{#if nodeInfo === undefined}
<div class="section-container mx-auto w-full max-w-3xl text-center">
<p>Loading...</p>
</div>
{:else if nodeInfo === null}
<div class="section-container mx-auto w-full max-w-3xl text-center">
<p>Node ID does not exist</p>
</div>
{:else}
<div class="section-container">
<div class="table-container mx-auto w-full max-w-3xl">
<table class="table">
<tbody>
<tr>
<td class="font-bold">Hostname:Port</td>
<td>{formatHostname(nodeInfo?.hostname)}:{nodeInfo?.port}</td>
</tr>
<tr>
<td class="font-bold">Public IP</td>
<td>{nodeInfo?.ip_addresses.replace(/,/g, ', ')}</td>
</tr>
<tr>
<td class="font-bold">Net Type</td>
<td>{nodeInfo?.nettype.toUpperCase()}</td>
</tr></tbody
>
</table>
</div>
</div>
<section id="node-logs">
<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">
<DtSrAutoRefresh {handler} />
</div>
<div class="flex place-items-center">
<button
id="reloadDt"
name="reloadDt"
class="variant-filled-primary btn"
on:click={() => handler.invalidate()}>Reload</button
>
</div>
</div>
<table class="table table-hover table-compact w-full table-auto">
<thead>
<tr>
<th>#ID</th>
<th>Prober ID</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="3">
<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={7}
/>
</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>
{/if}

View file

@ -1,32 +0,0 @@
import { apiUri } from '$lib/utils/common';
/**
* @typedef {import('@vincjo/datatables/remote').State} State
* @param {State} state - The state object from the data table.
*/
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 {string} nodeId */
export const loadNodeInfo = async (nodeId) => {
const response = await fetch(apiUri(`/api/v1/nodes/id/${nodeId}`));
const json = await response.json();
return json.data;
};
/** @param {State} state - The state object from the data table. */
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;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -1,66 +0,0 @@
{
"name": "xmr.ditatompel.com",
"short_name": "xmr-remote-nodes",
"start_url": "/",
"display": "standalone",
"background_color": "#272b31",
"theme_color": "#272b31",
"description": "Monero Remote Nodes",
"icons": [
{
"src": "/img/icon/android-icon-36x36.png",
"sizes": "36x36",
"type": "image/png",
"purpose": "any",
"density": "0.75"
},
{
"src": "/img/icon/android-icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "any",
"density": "1.0"
},
{
"src": "/img/icon/android-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any",
"density": "1.5"
},
{
"src": "/img/icon/android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any",
"density": "2.0"
},
{
"src": "/img/icon/android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any",
"density": "3.0"
},
{
"src": "/img/icon/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any",
"density": "4.0"
},
{
"src": "/img/icon/android-icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any",
"density": "4.0"
},
{
"src": "/img/icon/maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View file

@ -1,2 +0,0 @@
User-agent: *
Allow: /

View file

@ -1,58 +0,0 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import * as child_process from 'node:child_process';
import { readFileSync } from 'fs';
import { join } from 'path';
// Helper function to execute shell commands
function execSync(cmd) {
return child_process.execSync(cmd).toString().trim();
}
// Read version from package.json
const packageJsonPath = join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const VERSION = packageJson.version;
// Retrieve current branch
const BRANCH = execSync('git rev-parse --abbrev-ref HEAD');
// Retrieve current tag if it exists
const RELEASE_TAG = execSync('git tag -l --points-at HEAD');
// Generate version suffix
const commitCount = execSync('git rev-list --count HEAD');
const shortCommitHash = execSync('git show --no-patch --no-notes --pretty="%h" HEAD');
const VERSION_SUFFIX = `-beta.${commitCount}.${shortCommitHash}`;
// Determine branch-specific values
let TAG_BRANCH = `.${BRANCH}`;
if (BRANCH === 'HEAD' || BRANCH === 'main') {
TAG_BRANCH = '';
}
// Determine final tag
let TAG = `${VERSION}${VERSION_SUFFIX}${TAG_BRANCH}`;
if (RELEASE_TAG) {
TAG = RELEASE_TAG;
}
console.log('Building with tag', TAG);
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
version: {
name: TAG
},
// paths: {
// base: '/'
// },
// trailingSlash: 'always',
adapter: adapter()
}
};
export default config;

View file

@ -1,23 +0,0 @@
import { join } from 'path';
import { skeleton } from '@skeletonlabs/tw-plugin';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class',
content: [
'./src/**/*.{html,js,svelte,ts}',
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
],
theme: {
extend: {}
},
plugins: [
forms,
skeleton({
themes: {
preset: ['skeleton']
}
})
]
};

View file

@ -1,7 +0,0 @@
// @ts-check
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});

View file

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB