Compare commits

...

6 Commits

Author SHA1 Message Date
73cfcea923 Update dependency marked to v15.0.8 2025-04-08 02:03:40 +00:00
ngn
90af3d0500
update the app_url config option
All checks were successful
Build the docker image for the API / build (push) Successful in 2m6s
Build the docker image for the frontend application / build (push) Successful in 37s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 03:07:14 +03:00
ngn
8d16273540
add /api route to the API documentation
All checks were successful
Build the docker image for the doc server / build (push) Successful in 14s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 03:00:02 +03:00
ngn
4fb78c0ab7
update frontend app version
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 35s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:55:58 +03:00
ngn
a57a4955ba
fix internal api url
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:54:23 +03:00
ngn
ccf0d8abf9
make stuff work without js
All checks were successful
Build the docker image for the API / build (push) Successful in 2m7s
Build the docker image for the frontend application / build (push) Successful in 42s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:39:37 +03:00
22 changed files with 114 additions and 131 deletions

View File

@ -26,14 +26,10 @@ 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_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 \
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 \
--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_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"
{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"
}
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_clear")
app := conf.GetURL("app_url")
// 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_clear")
app := conf.GetURL("app_url")
lang := c.Params("lang")
if lang == "" || len(lang) != 2 {

View File

@ -1,32 +1,17 @@
# 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

10
app/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "website",
"version": "6.1",
"version": "6.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "website",
"version": "6.1",
"version": "6.3",
"dependencies": {
"dompurify": "^3.2.3",
"marked": "^15.0.6",
@ -1396,9 +1396,9 @@
}
},
"node_modules/marked": {
"version": "15.0.7",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.7.tgz",
"integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==",
"version": "15.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz",
"integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"

View File

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

View File

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

View File

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

View File

@ -1,14 +1,16 @@
<script>
import { app_url, color, date_from_ts } from "$lib/util.js";
import { 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 data = {};
let show_counter = false,
data = {};
onMount(async () => {
show_counter = true;
data = await api_get_metrics(fetch);
});
</script>
@ -20,24 +22,28 @@
</span>
<span>/</span>
<span>
<Link link={app_url("/doc/license")} bold={true}>{$_("footer.license")}</Link>
<Link link="/doc/license" bold={true}>{$_("footer.license")}</Link>
</span>
<span>/</span>
<span>
<Link link={app_url("/doc/privacy")} bold={true}>{$_("footer.privacy")}</Link>
<Link link="/doc/privacy" bold={true}>{$_("footer.privacy")}</Link>
</span>
</div>
<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>
{#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}
</footer>
<style>

View File

@ -1,7 +1,5 @@
<script>
import { api_urljoin } from "$lib/api.js";
import { app_url } from "$lib/util.js";
export let desc, title;
</script>
@ -16,7 +14,6 @@
<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,12 +1,14 @@
<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 = "";
let title_cur = title;
let show_animation = false;
function animate(title) {
if (!browser) return;
@ -24,13 +26,21 @@
}
}
onMount(() => {
show_animation = true;
});
$: animate(title);
</script>
<header>
<div>
<h1 class="title" style="color: var(--{color()})">{title_cur}</h1>
<h1 class="cursor" style="color: var(--{color()})">_</h1>
{#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}
</div>
<img src="/profile/{picture}.png" alt="" />
</header>

View File

@ -1,7 +1,9 @@
<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;
@ -15,11 +17,17 @@
function next() {
locale_select(get_next($locale_index).code);
}
onMount(() => {
show = true;
});
</script>
<button on:click={next}>
{get_next($locale_index).icon}
</button>
{#if show}
<button on:click={next}>
{get_next($locale_index).icon}
</button>
{/if}
<style>
button {

View File

@ -1,6 +1,4 @@
import { locale_from_browser } from "$lib/locale.js";
import { browser } from "$app/environment";
import { page } from "$app/state";
const colors = [
"yellow",
@ -25,34 +23,14 @@ function click() {
audio.play();
}
function urljoin(url, path = null, query = {}) {
function urljoin(url, path = null) {
if (undefined === url || null === url) return;
let url_len = url.length;
if (url[url.length - 1] != "/") url += "/";
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);
if (null === path || "" === path) return url;
if (path[0] === "/") return url + path.slice(1);
return url + path;
}
function time_from_ts(ts) {
@ -80,4 +58,4 @@ function date_from_ts(ts) {
}).format(new Date(ts * 1000));
}
export { color, click, urljoin, env_url, app_url, time_from_ts, date_from_ts };
export { color, click, urljoin, time_from_ts, date_from_ts };

View File

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

View File

@ -73,6 +73,7 @@
"license": "Lisans",
"privacy": "Gizlilik",
"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,9 +7,11 @@
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();
@ -31,6 +33,10 @@
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" />
@ -41,7 +47,9 @@
{:else}
<main>
<div class="title">
<input oninput={change} type="text" placeholder={$_("services.search")} />
{#if show_input}
<input oninput={change} type="text" placeholder={$_("services.search")} />
{/if}
<div>
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}>{$_("services.feed")}</Link>
</div>

View File

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

View File

@ -24,15 +24,9 @@ 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",
app_url: {
clear: "http://localhost:7001",
onion: "",
i2p: "",
},
api_url: {
clear: "http://localhost:7002",
onion: "",
i2p: "",
api: {
url: "http://localhost:7002",
path: "http://localhost:7002",
},
};

View File

@ -5,18 +5,11 @@ 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:
@ -44,7 +37,7 @@ services:
- ./data.db:/api/data.db:rw
environment:
WEBSITE_DEBUG: "false"
WEBSITE_APP_URL_CLEAR: "http://localhost:7001/"
WEBSITE_APP_URL: "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, [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.
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.
This documentation contains information about all the available API endpoints.
This documentation contains information about all the available API endpoints. All the
endpoints can be accessed using the `/api` route.
## Version 1 Endpoints
Each version 1 endpoint, can be accessed using the `/v1` route.

View File

@ -1,9 +1,9 @@
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.
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.
Bu dökümentasyon tüm erişeme açık API endpoint'leri hakkında bilgiler içeriyor.
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.
## Versyion 1 Endpoint'leri
Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir.