Compare commits

..

1 Commits

Author SHA1 Message Date
2c362d3e83 Update dependency marked to v15.0.8 2025-04-07 17:03:38 +00:00
22 changed files with 128 additions and 111 deletions

View File

@ -26,10 +26,14 @@ jobs:
- name: Build image
run: |
cd app
docker build --build-arg WEBSITE_REPORT_URL=https://git.ngn.tf/ngn/website/issues/new \
--build-arg WEBSITE_SOURCE_URL=https://git.ngn.tf/ngn/website \
--build-arg WEBSITE_DOC_URL=http://doc:7003 \
--build-arg WEBSITE_API_URL=http://api:7002 \
--build-arg WEBSITE_API_PATH=/api \
docker build --build-arg WEBSITE_REPORT_URL=https://git.ngn.tf/ngn/website/issues/new \
--build-arg WEBSITE_SOURCE_URL=https://git.ngn.tf/ngn/website \
--build-arg WEBSITE_APP_URL_CLEAR=https://ngn.tf \
--build-arg WEBSITE_APP_URL_ONION=http://ngntfwmwovvku6eqi7dzzgzv2wzlvq2cqtqha7ccgzub2xnivsuxnuyd.onion \
--build-arg WEBSITE_APP_URL_I2P=http://ngn.i2p \
--build-arg WEBSITE_API_URL_CLEAR=https://api.ngn.tf \
--build-arg WEBSITE_API_URL_ONION=http://api.ngntfwmwovvku6eqi7dzzgzv2wzlvq2cqtqha7ccgzub2xnivsuxnuyd.onion \
--build-arg WEBSITE_API_URL_I2P=https://api.ngn.i2p \
--build-arg WEBSITE_DOC_URL=http://doc:7003 \
--tag ${{env.REGISTRY}}/${{env.IMAGE}}:latest .
docker push ${{env.REGISTRY}}/${{env.IMAGE}}:latest

View File

@ -37,14 +37,14 @@ func (c *Type) Load() (err error) {
// default options
c.Options = []Option{
{Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes?
{Name: "app_url", Value: "http://localhost:7001/", Type: OPTION_TYPE_URL, Required: true}, // frontend application URL for the website
{Name: "password", Value: "", Type: OPTION_TYPE_STR, Required: true}, // admin password
{Name: "host", Value: "0.0.0.0:7002", Type: OPTION_TYPE_STR, Required: true}, // host the server should listen on
{Name: "ip_header", Value: "X-Real-IP", Type: OPTION_TYPE_STR, Required: false}, // header that should be checked for obtaining the client IP
{Name: "interval", Value: "1h", Type: OPTION_TYPE_STR, Required: false}, // service status check interval
{Name: "timeout", Value: "15s", Type: OPTION_TYPE_STR, Required: false}, // timeout for the service status check
{Name: "limit", Value: "5s", Type: OPTION_TYPE_STR, Required: false}, // if the service responds slower than this limit, it will be marked as "slow"
{Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes?
{Name: "app_url_clear", Value: "http://localhost:7001/", Type: OPTION_TYPE_URL, Required: true}, // frontend application URL for the website
{Name: "password", Value: "", Type: OPTION_TYPE_STR, Required: true}, // admin password
{Name: "host", Value: "0.0.0.0:7002", Type: OPTION_TYPE_STR, Required: true}, // host the server should listen on
{Name: "ip_header", Value: "X-Real-IP", Type: OPTION_TYPE_STR, Required: false}, // header that should be checked for obtaining the client IP
{Name: "interval", Value: "1h", Type: OPTION_TYPE_STR, Required: false}, // service status check interval
{Name: "timeout", Value: "15s", Type: OPTION_TYPE_STR, Required: false}, // timeout for the service status check
{Name: "limit", Value: "5s", Type: OPTION_TYPE_STR, Required: false}, // if the service responds slower than this limit, it will be marked as "slow"
}
c.Count = len(c.Options)

View File

@ -7,7 +7,7 @@ import (
func GET_Index(c *fiber.Ctx) error {
conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url")
app := conf.GetURL("app_url_clear")
// redirect to the API documentation
return c.Redirect(app.JoinPath("/doc/api").String())

View File

@ -40,7 +40,7 @@ func GET_News(c *fiber.Ctx) error {
db := c.Locals("database").(*database.Type)
conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url")
app := conf.GetURL("app_url_clear")
lang := c.Params("lang")
if lang == "" || len(lang) != 2 {

View File

@ -1,17 +1,32 @@
# build the application with node
FROM node:23.11.0 AS build
# app URLs
ARG WEBSITE_APP_URL_CLEAR
ARG WEBSITE_APP_URL_ONION
ARG WEBSITE_APP_URL_I2P
ENV WEBSITE_APP_URL_CLEAR=$WEBSITE_APP_URL_CLEAR
ENV WEBSITE_APP_URL_ONION=$WEBSITE_APP_URL_ONION
ENV WEBSITE_APP_URL_I2P=$WEBSITE_APP_URL_I2P
# API URLs
ARG WEBSITE_API_URL_CLEAR
ARG WEBSITE_API_URL_ONION
ARG WEBSITE_API_URL_I2P
ENV WEBSITE_API_URL_CLEAR=$WEBSITE_API_URL_CLEAR
ENV WEBSITE_API_URL_ONION=$WEBSITE_API_URL_ONION
ENV WEBSITE_API_URL_I2P=$WEBSITE_API_URL_I2P
# other config
ARG WEBSITE_REPORT_URL
ARG WEBSITE_SOURCE_URL
ARG WEBSITE_DOC_URL
ARG WEBSITE_API_URL
ARG WEBSITE_API_PATH
ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL
ENV WEBSITE_SOURCE_URL=$WEBSITE_SOURCE_URL
ENV WEBSITE_DOC_URL=$WEBSITE_DOC_URL
ENV WEBSITE_API_URL=$WEBSITE_API_URL
ENV WEBSITE_API_PATH=$WEBSITE_API_PATH
WORKDIR /app
COPY . /app

4
app/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "website",
"version": "6.3",
"version": "6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "website",
"version": "6.3",
"version": "6.2",
"dependencies": {
"dompurify": "^3.2.3",
"marked": "^15.0.6",

View File

@ -1,6 +1,6 @@
{
"name": "website",
"version": "6.3",
"version": "6.2",
"private": true,
"scripts": {
"dev": "vite dev",

View File

@ -1,14 +1,9 @@
import { browser } from "$app/environment";
import { urljoin } from "$lib/util.js";
import { urljoin, env_url } from "$lib/util.js";
const api_version = "v1";
function api_urljoin(path = null, query = {}) {
let api_url = "";
if (browser) api_url = urljoin(import.meta.env.WEBSITE_API_PATH, api_version);
else api_url = urljoin(import.meta.env.WEBSITE_API_URL, api_version);
let api_url = urljoin(env_url("API"), api_version);
return urljoin(api_url, path, query);
}

View File

@ -1,4 +1,4 @@
import { urljoin } from "$lib/util.js";
import { urljoin, env_url } from "$lib/util.js";
function doc_urljoin(path = null, query = {}) {
return urljoin(import.meta.env.WEBSITE_DOC_URL, path, query);

View File

@ -1,16 +1,14 @@
<script>
import { color, date_from_ts } from "$lib/util.js";
import { app_url, color, date_from_ts } from "$lib/util.js";
import { api_get_metrics } from "$lib/api.js";
import Link from "$lib/link.svelte";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
let show_counter = false,
data = {};
let data = {};
onMount(async () => {
show_counter = true;
data = await api_get_metrics(fetch);
});
</script>
@ -22,28 +20,24 @@
</span>
<span>/</span>
<span>
<Link link="/doc/license" bold={true}>{$_("footer.license")}</Link>
<Link link={app_url("/doc/license")} bold={true}>{$_("footer.license")}</Link>
</span>
<span>/</span>
<span>
<Link link="/doc/privacy" bold={true}>{$_("footer.privacy")}</Link>
<Link link={app_url("/doc/privacy")} bold={true}>{$_("footer.privacy")}</Link>
</span>
</div>
{#if show_counter}
<span class="counter">
{$_("footer.number", {
values: {
total: data.total,
since: date_from_ts(data.since),
},
})}
{#if data.number % 1000 == 0}
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
{/if}
</span>
{:else}
<span class="counter">{$_("footer.js")}</span>
{/if}
<span class="counter">
{$_("footer.number", {
values: {
total: data.total,
since: date_from_ts(data.since),
},
})}
{#if data.number % 1000 == 0}
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
{/if}
</span>
</footer>
<style>

View File

@ -1,5 +1,7 @@
<script>
import { api_urljoin } from "$lib/api.js";
import { app_url } from "$lib/util.js";
export let desc, title;
</script>
@ -14,6 +16,7 @@
<meta property="og:title" content="[ngn.tf] | {title}" />
<meta property="og:description" content={desc} />
<meta property="og:url" content={app_url()} />
<link
rel="alternate"

View File

@ -1,14 +1,12 @@
<script>
import { browser } from "$app/environment";
import { color } from "$lib/util.js";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
export let picture = "";
export let title = "";
let title_cur = title;
let show_animation = false;
let title_cur = "";
function animate(title) {
if (!browser) return;
@ -26,21 +24,13 @@
}
}
onMount(() => {
show_animation = true;
});
$: animate(title);
</script>
<header>
<div>
{#if show_animation}
<h1 class="title" style="color: var(--{color()})">{title_cur}</h1>
<h1 class="cursor" style="color: var(--{color()})">_</h1>
{:else}
<h1 class="title" style="color: var(--{color()})">{title}</h1>
{/if}
<h1 class="title" style="color: var(--{color()})">{title_cur}</h1>
<h1 class="cursor" style="color: var(--{color()})">_</h1>
</div>
<img src="/profile/{picture}.png" alt="" />
</header>

View File

@ -1,9 +1,7 @@
<script>
import { locale_list, locale_select, locale_index } from "$lib/locale.js";
import { onMount } from "svelte";
let len = locale_list.length;
let show = false;
function get_next(indx) {
let new_indx = 0;
@ -17,17 +15,11 @@
function next() {
locale_select(get_next($locale_index).code);
}
onMount(() => {
show = true;
});
</script>
{#if show}
<button on:click={next}>
{get_next($locale_index).icon}
</button>
{/if}
<button on:click={next}>
{get_next($locale_index).icon}
</button>
<style>
button {

View File

@ -1,4 +1,6 @@
import { locale_from_browser } from "$lib/locale.js";
import { browser } from "$app/environment";
import { page } from "$app/state";
const colors = [
"yellow",
@ -23,14 +25,34 @@ function click() {
audio.play();
}
function urljoin(url, path = null) {
function urljoin(url, path = null, query = {}) {
if (undefined === url || null === url) return;
if (url[url.length - 1] != "/") url += "/";
let url_len = url.length;
if (null === path || "" === path) return url;
if (path[0] === "/") return url + path.slice(1);
return url + path;
if (url[url_len - 1] != "/") url += "/";
if (null === path || "" === path) url = new URL(url);
else if (path[0] === "/") url = new URL(path.slice(1), url);
else url = new URL(path, url);
for (let k in query) url.searchParams.append(k, query[k]);
return url.href;
}
function env_url(prefix) {
let host = "";
if (browser) host = window.location.hostname;
if (host.endsWith(".onion")) return import.meta.env["WEBSITE_" + prefix + "_URL_ONION"];
else if (host.endsWith(".i2p")) return import.meta.env["WEBSITE_" + prefix + "_URL_I2P"];
else return import.meta.env["WEBSITE_" + prefix + "_URL_CLEAR"];
}
function app_url(path = null, query = {}) {
return urljoin(env_url("APP"), path, query);
}
function time_from_ts(ts) {
@ -58,4 +80,4 @@ function date_from_ts(ts) {
}).format(new Date(ts * 1000));
}
export { color, click, urljoin, time_from_ts, date_from_ts };
export { color, click, urljoin, env_url, app_url, time_from_ts, date_from_ts };

View File

@ -73,7 +73,6 @@
"license": "License",
"privacy": "Privacy",
"number": "Visited {total} times since {since}",
"wow": "wow!!",
"js": "Enable javascript to display all the elements"
"wow": "wow!!"
}
}

View File

@ -73,7 +73,6 @@
"license": "Lisans",
"privacy": "Gizlilik",
"number": "{since} tarihinden beri {total} kez ziyaret edildi",
"wow": "vay be!!",
"js": "Tüm elementleri görüntelemek için javascript'i açın"
"wow": "vay be!!"
}
}

View File

@ -7,11 +7,9 @@
import { api_urljoin } from "$lib/api.js";
import { locale, _ } from "svelte-i18n";
import { onMount } from "svelte";
let { data } = $props();
let services = $state(data.services);
let show_input = $state(false);
function change(input) {
let value = input.target.value.toLowerCase();
@ -33,10 +31,6 @@
return s.desc[$locale] !== "" && s.desc[$locale] !== null && s.desc[$locale] !== undefined;
});
}
onMount(() => {
show_input = true;
});
</script>
<Head title="services" desc="my self-hosted services and projects" />
@ -47,9 +41,7 @@
{:else}
<main>
<div class="title">
{#if show_input}
<input oninput={change} type="text" placeholder={$_("services.search")} />
{/if}
<input oninput={change} type="text" placeholder={$_("services.search")} />
<div>
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}>{$_("services.feed")}</Link>
</div>

View File

@ -1,3 +1,2 @@
User-Agent: *
Disallow: /doc/
Disallow: /api/

View File

@ -24,9 +24,15 @@ const default_env = {
source_url: "https://git.ngn.tf/ngn/website",
report_url: "https://git.ngn.tf/ngn/website/issues",
doc_url: "http://localhost:7003",
api: {
url: "http://localhost:7002",
path: "http://localhost:7002",
app_url: {
clear: "http://localhost:7001",
onion: "",
i2p: "",
},
api_url: {
clear: "http://localhost:7002",
onion: "",
i2p: "",
},
};

View File

@ -5,11 +5,18 @@ services:
build:
context: ./app
args:
# app URLs
WEBSITE_APP_URL_CLEAR: "http://localhost:7001"
WEBSITE_APP_URL_ONION: ""
WEBSITE_APP_URL_I2P: ""
# API URLs
WEBSITE_API_URL_CLEAR: "http://localhost:7002"
WEBSITE_API_URL_ONION: ""
WEBSITE_API_URL_I2P: ""
# other
WEBSITE_SOURCE_URL: "http://github.com/ngn13/website"
WEBSITE_REPORT_URL: "http://github.com/ngn13/website/issues"
WEBSITE_DOC_URL: "http://doc:7003"
WEBSITE_API_URL: "http://api:7002"
WEBSITE_API_PATH: "http://localhost:7002"
security_opt:
- "no-new-privileges:true"
cap_drop:
@ -37,7 +44,7 @@ services:
- ./data.db:/api/data.db:rw
environment:
WEBSITE_DEBUG: "false"
WEBSITE_APP_URL: "http://localhost:7001"
WEBSITE_APP_URL_CLEAR: "http://localhost:7001/"
WEBSITE_PASSWORD: "change_me"
WEBSITE_HOST: "0.0.0.0:7002"
WEBSITE_IP_HEADER: "X-Real-IP"

View File

@ -1,9 +1,9 @@
My website's API, stores information about my self-hosted services, it also allows me
to publish news and updates about these services using an Atom feed and it keeps track
of visitor metrics.
My website's API, [api.ngn.tf](https://api.ngn.tf), stores information about my
self-hosted services, it also allows me to publish news and updates about these
services using an Atom feed and it keeps track of visitor metrics. The API itself is
written in Go and uses SQLite for storage.
This documentation contains information about all the available API endpoints. All the
endpoints can be accessed using the `/api` route.
This documentation contains information about all the available API endpoints.
## Version 1 Endpoints
Each version 1 endpoint, can be accessed using the `/v1` route.

View File

@ -1,9 +1,9 @@
Websitemin API, self-host edilen servisler hakkında bilgileri tutuyor, bu servisler hakkında
haberleri ve güncellemeleri bir Atom feed'i aracılığı ile paylaşmama izin veriyor ve ziyartçi
metriklerini takip ediyor.
Websitemin API, [api.ngn.tf](https://api.ngn.tf), self-host edilen servisler hakkında bilgileri
tutuyor, bu servisler hakkında haberleri ve güncellemeleri bir Atom feed'i aracılığı ile
paylaşmama izin veriyor ve ziyartçi metriklerini takip ediyor. API'ın kendisi Go ile yazıldı ve
veritabanı olarak SQLite kullanıyor.
Bu dökümentasyon tüm erişime açık API endpoint'leri hakkında bilgiler içeriyor. Tüm endpoint'lere
`/api` yolu ile erişilebilir.
Bu dökümentasyon tüm erişeme açık API endpoint'leri hakkında bilgiler içeriyor.
## Versyion 1 Endpoint'leri
Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir.