From f26227c7a89b4d0ac5f902bdf7580ee2f42a6fa6 Mon Sep 17 00:00:00 2001 From: Archben Date: Tue, 10 Mar 2026 05:41:27 +0000 Subject: [PATCH] Updates --- src/components/general/admin/hero.tsx | 2 - .../(partials)/cluster-server-views.tsx | 41 +++++++++ .../service/(partials)/cluster-server.tsx | 31 ++++--- .../services/service/(partials)/cluster.tsx | 27 +++--- .../services/service/(sections)/clusters.tsx | 2 +- src/data/app-data.ts | 2 + src/nginx/conf.d/default.conf | 38 ++++++++ src/nginx/default-nginx.conf | 83 +++++++++++++++++ src/nginx/default-server.conf | 91 +++++++++++++++++++ src/nginx/nginx.conf | 34 +++++++ src/styles/globals.css | 4 + src/types/index.ts | 2 + src/utils/grab-next-available-port.ts | 44 +++++++++ twui | 2 +- 14 files changed, 374 insertions(+), 29 deletions(-) create mode 100644 src/components/pages/admin/services/service/(partials)/cluster-server-views.tsx create mode 100644 src/nginx/conf.d/default.conf create mode 100644 src/nginx/default-nginx.conf create mode 100644 src/nginx/default-server.conf create mode 100644 src/nginx/nginx.conf create mode 100644 src/utils/grab-next-available-port.ts diff --git a/src/components/general/admin/hero.tsx b/src/components/general/admin/hero.tsx index b38e820..3ae2699 100644 --- a/src/components/general/admin/hero.tsx +++ b/src/components/general/admin/hero.tsx @@ -15,8 +15,6 @@ type Props = { export default function AdminHero({ title, ctas, description }: Props) { const { pageProps } = useContext(AppContext); - console.log("pageProps.pageUrl", pageProps.pageUrl); - return ( diff --git a/src/components/pages/admin/services/service/(partials)/cluster-server-views.tsx b/src/components/pages/admin/services/service/(partials)/cluster-server-views.tsx new file mode 100644 index 0000000..b9d71a7 --- /dev/null +++ b/src/components/pages/admin/services/service/(partials)/cluster-server-views.tsx @@ -0,0 +1,41 @@ +import Stack from "@/twui/components/layout/Stack"; +import { useContext, useEffect, useRef } from "react"; +import { AppContext } from "@/src/pages/_app"; +import { + NormalizedServerObject, + ParsedDeploymentServiceConfig, + WebSocketDataType, +} from "@/src/types"; +import useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver"; +import Center from "@/twui/components/layout/Center"; +import Loading from "@/twui/components/elements/Loading"; +import useWebSocketEventHandler from "@/twui/components/hooks/useWebSocketEventHandler"; + +type Props = { + service: ParsedDeploymentServiceConfig; + server: NormalizedServerObject; +}; + +export default function ServiceClusterServerViews({ service, server }: Props) { + const { pageProps, ws } = useContext(AppContext); + + const { data } = useWebSocketEventHandler(); + + useEffect(() => { + if (!ws?.socket) { + return; + } + + ws.sendData({ + event: "client:ping", + server, + service, + }); + }, [ws]); + + useEffect(() => { + console.log("data", data); + }, [data]); + + return ; +} diff --git a/src/components/pages/admin/services/service/(partials)/cluster-server.tsx b/src/components/pages/admin/services/service/(partials)/cluster-server.tsx index 7bfc3e9..0e039f2 100644 --- a/src/components/pages/admin/services/service/(partials)/cluster-server.tsx +++ b/src/components/pages/admin/services/service/(partials)/cluster-server.tsx @@ -1,10 +1,14 @@ import Stack from "@/twui/components/layout/Stack"; -import { useContext } from "react"; +import { useContext, useRef } from "react"; import { AppContext } from "@/src/pages/_app"; import { NormalizedServerObject, ParsedDeploymentServiceConfig, } from "@/src/types"; +import useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver"; +import Center from "@/twui/components/layout/Center"; +import Loading from "@/twui/components/elements/Loading"; +import ServiceClusterServerViews from "./cluster-server-views"; type Props = { service: ParsedDeploymentServiceConfig; @@ -13,20 +17,23 @@ type Props = { export default function ServiceClusterServer({ service, server }: Props) { const { pageProps } = useContext(AppContext); - const { deployment, children_services } = pageProps; - const all_services = [service, ...(children_services || [])]; - - const deployment_name = deployment?.deployment_name; - const service_name = service?.service_name; - - const cluster_servers = service.servers; + const elementRef = useRef(undefined); + const { isIntersecting } = useIntersectionObserver({ elementRef }); return ( - - {cluster_servers?.map((server, index) => { - return null; - })} + + + {server.private_ip} + +
+ {isIntersecting ? ( + + ) : ( +
+ +
+ )}
); } diff --git a/src/components/pages/admin/services/service/(partials)/cluster.tsx b/src/components/pages/admin/services/service/(partials)/cluster.tsx index 175445c..0d19bfd 100644 --- a/src/components/pages/admin/services/service/(partials)/cluster.tsx +++ b/src/components/pages/admin/services/service/(partials)/cluster.tsx @@ -4,6 +4,7 @@ import { AppContext } from "@/src/pages/_app"; import { ParsedDeploymentServiceConfig } from "@/src/types"; import Row from "@/twui/components/layout/Row"; import H2 from "@/twui/components/layout/H2"; +import ServiceClusterServer from "./cluster-server"; type Props = { service: ParsedDeploymentServiceConfig; @@ -12,25 +13,25 @@ type Props = { export default function ServiceCluster({ service, cluster_index }: Props) { const { pageProps } = useContext(AppContext); - const { deployment, children_services } = pageProps; - - const all_services = [service, ...(children_services || [])]; - - const deployment_name = deployment?.deployment_name; - const service_name = service?.service_name; - const cluster_servers = service.servers; return ( - +

Cluster #{cluster_index}

- - {cluster_servers?.map((server, index) => { - return {server.private_ip}; - })} -
+
+ + {cluster_servers?.map((server, index) => { + return ( + + ); + })} +
); } diff --git a/src/components/pages/admin/services/service/(sections)/clusters.tsx b/src/components/pages/admin/services/service/(sections)/clusters.tsx index 4d421c5..67f32db 100644 --- a/src/components/pages/admin/services/service/(sections)/clusters.tsx +++ b/src/components/pages/admin/services/service/(sections)/clusters.tsx @@ -10,7 +10,7 @@ export default function ServiceClusters() { const all_services = [service, ...(children_services || [])]; return ( - + {all_services.map((srv, index) => { if (!srv) return null; return ( diff --git a/src/data/app-data.ts b/src/data/app-data.ts index 98fa511..d3969fc 100644 --- a/src/data/app-data.ts +++ b/src/data/app-data.ts @@ -20,4 +20,6 @@ export const AppData = { AuthCookieName: `x-turboci-admin-auth-key`, AuthCSRFCookieName: `x-turboci-admin-csrf`, CookieExpirationTime: 1000 * 60 * 60 * 24 * 7, // One Week + + DynamicPortStart: 4700, } as const; diff --git a/src/nginx/conf.d/default.conf b/src/nginx/conf.d/default.conf new file mode 100644 index 0000000..fd70929 --- /dev/null +++ b/src/nginx/conf.d/default.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name _; + + client_max_body_size 20M; + + # Restrict port range to 4700 and above + location ^/ttyd/(?(?:470[0-9]|47[1-9]\d|4[89]\d{2}|[5-9]\d{3}|[1-9]\d{4,}))(/.*)? { + proxy_pass http://localhost:$port$2; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } + + location /ws { + proxy_pass http://localhost:3773; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_connect_timeout 60s; + + proxy_buffering off; + keepalive_timeout 75s; + + tcp_nodelay on; + } + + location / { + proxy_pass http://localhost:3772; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } +} \ No newline at end of file diff --git a/src/nginx/default-nginx.conf b/src/nginx/default-nginx.conf new file mode 100644 index 0000000..bc94482 --- /dev/null +++ b/src/nginx/default-nginx.conf @@ -0,0 +1,83 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # SSL Settings + ## + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} + + +#mail { +# # See sample authentication script at: +# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript +# +# # auth_http localhost/auth.php; +# # pop3_capabilities "TOP" "USER"; +# # imap_capabilities "IMAP4rev1" "UIDPLUS"; +# +# server { +# listen localhost:110; +# protocol pop3; +# proxy on; +# } +# +# server { +# listen localhost:143; +# protocol imap; +# proxy on; +# } +#} \ No newline at end of file diff --git a/src/nginx/default-server.conf b/src/nginx/default-server.conf new file mode 100644 index 0000000..a53ec0f --- /dev/null +++ b/src/nginx/default-server.conf @@ -0,0 +1,91 @@ +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# https://www.nginx.com/resources/wiki/start/ +# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ +# https://wiki.debian.org/Nginx/DirectoryStructure +# +# In most cases, administrators will remove this file from sites-enabled/ and +# leave it as reference inside of sites-available where it will continue to be +# updated by the nginx packaging team. +# +# This file will automatically load configuration files provided by other +# applications, such as Drupal or Wordpress. These applications will be made +# available underneath a path with that package name, such as /drupal8. +# +# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. +## + +# Default server configuration +# +server { + listen 80 default_server; + listen [::]:80 default_server; + + # SSL configuration + # + # listen 443 ssl default_server; + # listen [::]:443 ssl default_server; + # + # Note: You should disable gzip for SSL traffic. + # See: https://bugs.debian.org/773332 + # + # Read up on ssl_ciphers to ensure a secure configuration. + # See: https://bugs.debian.org/765782 + # + # Self signed certs generated by the ssl-cert package + # Don't use them in a production server! + # + # include snippets/snakeoil.conf; + + root /var/www/html; + + # Add index.php to the list if you are using PHP + index index.html index.htm index.nginx-debian.html; + + server_name _; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ =404; + } + + # pass PHP scripts to FastCGI server + # + #location ~ \.php$ { + # include snippets/fastcgi-php.conf; + # + # # With php-fpm (or other unix sockets): + # fastcgi_pass unix:/run/php/php7.4-fpm.sock; + # # With php-cgi (or other tcp sockets): + # fastcgi_pass 127.0.0.1:9000; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} + + +# Virtual Host configuration for example.com +# +# You can move that to a different file under sites-available/ and symlink that +# to sites-enabled/ to enable it. +# +#server { +# listen 80; +# listen [::]:80; +# +# server_name example.com; +# +# root /var/www/example.com; +# index index.html; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} \ No newline at end of file diff --git a/src/nginx/nginx.conf b/src/nginx/nginx.conf new file mode 100644 index 0000000..8d1971e --- /dev/null +++ b/src/nginx/nginx.conf @@ -0,0 +1,34 @@ +user www-data; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + + keepalive_timeout 65; + + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + + gzip on; + + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css index 335809a..55ceebd 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -115,3 +115,7 @@ code { #admin-main h2 { @apply text-xl font-semibold; } + +hr { + @apply border-foreground-light/10 dark:border-foreground-dark/10; +} diff --git a/src/types/index.ts b/src/types/index.ts index 1d89b3e..8da55dc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -231,6 +231,8 @@ export const WebSocketEvents = [ export type WebSocketDataType = { event: (typeof WebSocketEvents)[number]; message?: string; + service?: ParsedDeploymentServiceConfig; + server?: NormalizedServerObject; }; export type WebSocketType = { diff --git a/src/utils/grab-next-available-port.ts b/src/utils/grab-next-available-port.ts new file mode 100644 index 0000000..14d39c8 --- /dev/null +++ b/src/utils/grab-next-available-port.ts @@ -0,0 +1,44 @@ +import { dlopen, ptr, FFIType } from "bun:ffi"; +import { AppData } from "../data/app-data"; + +const libc = dlopen("libc.so.6", { + socket: { + args: [FFIType.i32, FFIType.i32, FFIType.i32], + returns: FFIType.i32, + }, + bind: { + args: [FFIType.i32, FFIType.ptr, FFIType.i32], + returns: FFIType.i32, + }, + close: { args: [FFIType.i32], returns: FFIType.i32 }, +}); + +const AF_INET = 2; +const SOCK_STREAM = 1; +const INADDR_ANY = 0; + +function makeSockaddr(port: number): Buffer { + const buf = Buffer.alloc(16); + buf.writeUInt16LE(AF_INET, 0); + buf.writeUInt16BE(port, 2); + buf.writeUInt32BE(INADDR_ANY, 4); + return buf; +} + +export default function getNextAvailablePort( + startPort = AppData["DynamicPortStart"], + maxPort = 65535, +): number { + for (let port = startPort; port <= maxPort; port++) { + const fd = libc.symbols.socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) continue; + + const addr = makeSockaddr(port); + const result = libc.symbols.bind(fd, ptr(addr), addr.byteLength); + libc.symbols.close(fd); + + if (result === 0) return port; + } + + throw new Error(`No available port found in range ${startPort}-${maxPort}`); +} diff --git a/twui b/twui index 9f8527f..2c10142 160000 --- a/twui +++ b/twui @@ -1 +1 @@ -Subproject commit 9f8527fc4d851c1fecd6600bd60c490de998676f +Subproject commit 2c101420ccc7dc1c56eb7bb7c41f2cca83af334a