Compare commits
1 Commits
73cfcea923
...
2c362d3e83
Author | SHA1 | Date | |
---|---|---|---|
2c362d3e83 |
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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 {
|
||||
|
@ -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
4
app/package-lock.json
generated
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "website",
|
||||
"version": "6.3",
|
||||
"version": "6.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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 };
|
||||
|
@ -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!!"
|
||||
}
|
||||
}
|
||||
|
@ -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!!"
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,3 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow: /doc/
|
||||
Disallow: /api/
|
||||
|
@ -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: "",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user