Compare commits

..

1 Commits

Author SHA1 Message Date
7b892dcfc0 Update dependency svelte to v5.25.8 2025-04-07 17:03:43 +00:00
22 changed files with 128 additions and 111 deletions

View File

@ -26,10 +26,14 @@ jobs:
- name: Build image - name: Build image
run: | run: |
cd app cd app
docker build --build-arg WEBSITE_REPORT_URL=https://git.ngn.tf/ngn/website/issues/new \ 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_SOURCE_URL=https://git.ngn.tf/ngn/website \
--build-arg WEBSITE_DOC_URL=http://doc:7003 \ --build-arg WEBSITE_APP_URL_CLEAR=https://ngn.tf \
--build-arg WEBSITE_API_URL=http://api:7002 \ --build-arg WEBSITE_APP_URL_ONION=http://ngntfwmwovvku6eqi7dzzgzv2wzlvq2cqtqha7ccgzub2xnivsuxnuyd.onion \
--build-arg WEBSITE_API_PATH=/api \ --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 . --tag ${{env.REGISTRY}}/${{env.IMAGE}}:latest .
docker push ${{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 // default options
c.Options = []Option{ c.Options = []Option{
{Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes? {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: "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: "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: "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: "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: "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: "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: "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) c.Count = len(c.Options)

View File

@ -7,7 +7,7 @@ import (
func GET_Index(c *fiber.Ctx) error { func GET_Index(c *fiber.Ctx) error {
conf := c.Locals("config").(*config.Type) conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url") app := conf.GetURL("app_url_clear")
// redirect to the API documentation // redirect to the API documentation
return c.Redirect(app.JoinPath("/doc/api").String()) 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) db := c.Locals("database").(*database.Type)
conf := c.Locals("config").(*config.Type) conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url") app := conf.GetURL("app_url_clear")
lang := c.Params("lang") lang := c.Params("lang")
if lang == "" || len(lang) != 2 { if lang == "" || len(lang) != 2 {

View File

@ -1,17 +1,32 @@
# build the application with node # build the application with node
FROM node:23.11.0 AS build 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_REPORT_URL
ARG WEBSITE_SOURCE_URL ARG WEBSITE_SOURCE_URL
ARG WEBSITE_DOC_URL ARG WEBSITE_DOC_URL
ARG WEBSITE_API_URL
ARG WEBSITE_API_PATH
ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL
ENV WEBSITE_SOURCE_URL=$WEBSITE_SOURCE_URL ENV WEBSITE_SOURCE_URL=$WEBSITE_SOURCE_URL
ENV WEBSITE_DOC_URL=$WEBSITE_DOC_URL ENV WEBSITE_DOC_URL=$WEBSITE_DOC_URL
ENV WEBSITE_API_URL=$WEBSITE_API_URL
ENV WEBSITE_API_PATH=$WEBSITE_API_PATH
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app

4
app/package-lock.json generated
View File

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

View File

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

View File

@ -1,14 +1,9 @@
import { browser } from "$app/environment"; import { urljoin, env_url } from "$lib/util.js";
import { urljoin } from "$lib/util.js";
const api_version = "v1"; const api_version = "v1";
function api_urljoin(path = null, query = {}) { function api_urljoin(path = null, query = {}) {
let api_url = ""; let api_url = urljoin(env_url("API"), api_version);
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);
return urljoin(api_url, path, query); 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 = {}) { function doc_urljoin(path = null, query = {}) {
return urljoin(import.meta.env.WEBSITE_DOC_URL, path, query); return urljoin(import.meta.env.WEBSITE_DOC_URL, path, query);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
import { locale_from_browser } from "$lib/locale.js"; import { locale_from_browser } from "$lib/locale.js";
import { browser } from "$app/environment";
import { page } from "$app/state";
const colors = [ const colors = [
"yellow", "yellow",
@ -23,14 +25,34 @@ function click() {
audio.play(); audio.play();
} }
function urljoin(url, path = null) { function urljoin(url, path = null, query = {}) {
if (undefined === url || null === url) return; if (undefined === url || null === url) return;
if (url[url.length - 1] != "/") url += "/"; let url_len = url.length;
if (null === path || "" === path) return url; if (url[url_len - 1] != "/") url += "/";
if (path[0] === "/") return url + path.slice(1);
return url + path; 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) { function time_from_ts(ts) {
@ -58,4 +80,4 @@ function date_from_ts(ts) {
}).format(new Date(ts * 1000)); }).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", "license": "License",
"privacy": "Privacy", "privacy": "Privacy",
"number": "Visited {total} times since {since}", "number": "Visited {total} times since {since}",
"wow": "wow!!", "wow": "wow!!"
"js": "Enable javascript to display all the elements"
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -5,11 +5,18 @@ services:
build: build:
context: ./app context: ./app
args: 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_SOURCE_URL: "http://github.com/ngn13/website"
WEBSITE_REPORT_URL: "http://github.com/ngn13/website/issues" WEBSITE_REPORT_URL: "http://github.com/ngn13/website/issues"
WEBSITE_DOC_URL: "http://doc:7003" WEBSITE_DOC_URL: "http://doc:7003"
WEBSITE_API_URL: "http://api:7002"
WEBSITE_API_PATH: "http://localhost:7002"
security_opt: security_opt:
- "no-new-privileges:true" - "no-new-privileges:true"
cap_drop: cap_drop:
@ -37,7 +44,7 @@ services:
- ./data.db:/api/data.db:rw - ./data.db:/api/data.db:rw
environment: environment:
WEBSITE_DEBUG: "false" WEBSITE_DEBUG: "false"
WEBSITE_APP_URL: "http://localhost:7001" WEBSITE_APP_URL_CLEAR: "http://localhost:7001/"
WEBSITE_PASSWORD: "change_me" WEBSITE_PASSWORD: "change_me"
WEBSITE_HOST: "0.0.0.0:7002" WEBSITE_HOST: "0.0.0.0:7002"
WEBSITE_IP_HEADER: "X-Real-IP" 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 My website's API, [api.ngn.tf](https://api.ngn.tf), stores information about my
to publish news and updates about these services using an Atom feed and it keeps track self-hosted services, it also allows me to publish news and updates about these
of visitor metrics. 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 This documentation contains information about all the available API endpoints.
endpoints can be accessed using the `/api` route.
## Version 1 Endpoints ## Version 1 Endpoints
Each version 1 endpoint, can be accessed using the `/v1` route. 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 Websitemin API, [api.ngn.tf](https://api.ngn.tf), self-host edilen servisler hakkında bilgileri
haberleri ve güncellemeleri bir Atom feed'i aracılığı ile paylaşmama izin veriyor ve ziyartçi tutuyor, bu servisler hakkında haberleri ve güncellemeleri bir Atom feed'i aracılığı ile
metriklerini takip ediyor. 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 Bu dökümentasyon tüm erişeme açık API endpoint'leri hakkında bilgiler içeriyor.
`/api` yolu ile erişilebilir.
## Versyion 1 Endpoint'leri ## Versyion 1 Endpoint'leri
Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir. Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir.