diff --git a/apps/authentik/docker-compose.myl b/apps/authentik/docker-compose.myl new file mode 100644 index 0000000..620e542 --- /dev/null +++ b/apps/authentik/docker-compose.myl @@ -0,0 +1,56 @@ +services: + postgresql: + image: postgres:16-alpine + container_name: authentik-postgres + restart: unless-stopped + environment: + POSTGRES_PASSWORD: ${PG_PASS} + POSTGRES_USER: authentik + POSTGRES_DB: authentik + volumes: + - ./postgres:/var/lib/postgresql/data + + redis: + image: redis:alpine + container_name: authentik-redis + restart: unless-stopped + + authentik: + image: ghcr.io/goauthentik/server:latest + container_name: authentik + restart: unless-stopped + command: server + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + volumes: + - ./media:/media + - ./custom-templates:/templates + ports: + - "9001:9000" + depends_on: + - postgresql + - redis + + authentik-worker: + image: ghcr.io/goauthentik/server:latest + container_name: authentik-worker + restart: unless-stopped + command: worker + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + volumes: + - ./media:/media + - ./custom-templates:/templates + depends_on: + - postgresql + - redis diff --git a/apps/authentik/docker-compose.yml b/apps/authentik/docker-compose.yml new file mode 100644 index 0000000..d8f7fbe --- /dev/null +++ b/apps/authentik/docker-compose.yml @@ -0,0 +1,57 @@ +services: + postgresql: + image: postgres:16-alpine + container_name: authentik-postgres + restart: unless-stopped + environment: + POSTGRES_PASSWORD: ${PG_PASS} + POSTGRES_USER: authentik + POSTGRES_DB: authentik + volumes: + - ./postgres:/var/lib/postgresql/data + + redis: + image: redis:alpine + container_name: authentik-redis + restart: unless-stopped + + authentik: + image: ghcr.io/goauthentik/server:latest + container_name: authentik + restart: unless-stopped + command: server + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + volumes: + - ./media:/media + - ./custom-templates:/templates + ports: + - "9001:9000" + depends_on: + - postgresql + - redis + + authentik-worker: + image: ghcr.io/goauthentik/server:latest + container_name: authentik-worker + restart: unless-stopped + command: worker + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + volumes: + - ./media:/media + - ./custom-templates:/templates + depends_on: + - postgresql + - redis + diff --git a/apps/bookstack/bookstack/.migrations b/apps/bookstack/bookstack/.migrations new file mode 100644 index 0000000..588a613 --- /dev/null +++ b/apps/bookstack/bookstack/.migrations @@ -0,0 +1,2 @@ +01-nginx-site-confs-default +02-default-location diff --git a/apps/bookstack/bookstack/keys/cert.crt b/apps/bookstack/bookstack/keys/cert.crt new file mode 100644 index 0000000..8f3059f --- /dev/null +++ b/apps/bookstack/bookstack/keys/cert.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIUVt05s9wgylfcEPx3fQDn2e4dF3owDQYJKoZIhvcNAQEL +BQAwaDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhDYXJsc2Jh +ZDEXMBUGA1UECgwOTGludXhzZXJ2ZXIuaW8xFDASBgNVBAsMC0xTSU8gU2VydmVy +MQowCAYDVQQDDAEqMB4XDTI2MDYwNTAwNDczMloXDTM2MDYwMjAwNDczMlowaDEL +MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhDYXJsc2JhZDEXMBUG +A1UECgwOTGludXhzZXJ2ZXIuaW8xFDASBgNVBAsMC0xTSU8gU2VydmVyMQowCAYD +VQQDDAEqMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAooBdmf0Nmewk +YIreTSqKgHJpj+C5uiYflmiQ7TBNrMCyeg7VrkDKlAIbrMsWDbdxbJ3gIWX/+WL9 +iFG3SVwLwj3OLAdaPhLU8vodrjMkxkNFHk7CFNG53sEOU1WOskdwK3xtWUx3F6CD +tBJwWyIepdsiXiFoug6kgKZ7r7Koraqp7fW36iNztvW+V2DakF6F4ufSduzq1zTZ +mp+woGVPUVcI2UPoOuKLQqIt93GmHbmFqw1AKKZkbaoTxJHVnz56YfjmMn/ls+8s +ovLX8wR9zSp+ExwitrbD//zyWYt7GWmDZIuSB0pqb/ofXDSijiDiobM5UJ6bygv1 +BAXXbyg0pwIDAQABo2kwZzAdBgNVHQ4EFgQUSWIeem3I7aV7kjCN9t2xKz9ayBEw +HwYDVR0jBBgwFoAUSWIeem3I7aV7kjCN9t2xKz9ayBEwDwYDVR0TAQH/BAUwAwEB +/zAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAIsbdBRB +sENi2gz8zRqL9oEwiZ6n02mvd/uYh0ReBxp5AAkRJ0v1vqhMtCi26lHA2FoUdUFS +aOKitgpXZn3oP5SqBVfxsE9WUTBP544H3lsUKnsQl06dBpKQCmXrnVedM6ktb33P +EpppqudyS+y+mNVLi9lM4bMqGxQRdze2y4p9+qNYHREczkPgMlEgujOKcd533YJ/ +EbrwKgvYBWQeR0Rl0YnGS3j/mFXYYfsg4jpxHezX5tZRWT7FTtV8GpcchR97qvZH +Ax/cOIYmWF0KIkiW8qTmiMowwm2pEQLxOOxaLwPsICk6jf9kvPeHdu1+aMfvuZhc +MbvugyYpqDKGRCg= +-----END CERTIFICATE----- diff --git a/apps/bookstack/bookstack/keys/cert.key b/apps/bookstack/bookstack/keys/cert.key new file mode 100644 index 0000000..f8af6a4 --- /dev/null +++ b/apps/bookstack/bookstack/keys/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCigF2Z/Q2Z7CRg +it5NKoqAcmmP4Lm6Jh+WaJDtME2swLJ6DtWuQMqUAhusyxYNt3FsneAhZf/5Yv2I +UbdJXAvCPc4sB1o+EtTy+h2uMyTGQ0UeTsIU0bnewQ5TVY6yR3ArfG1ZTHcXoIO0 +EnBbIh6l2yJeIWi6DqSApnuvsqitqqnt9bfqI3O29b5XYNqQXoXi59J27OrXNNma +n7CgZU9RVwjZQ+g64otCoi33caYduYWrDUAopmRtqhPEkdWfPnph+OYyf+Wz7yyi +8tfzBH3NKn4THCK2tsP//PJZi3sZaYNki5IHSmpv+h9cNKKOIOKhszlQnpvKC/UE +BddvKDSnAgMBAAECggEAFP1Bmc8+v3/p3vJw7kStaRPeILBlLO8Zq2LMPWa80bB+ +HRfnb798qwtuqa39pj3oj6AAwC+dlYe8uavcYcRa3HcBN1ynwlbKXKwDw2lluZoc +xmJj3S6NtID5KbhmWw6xJVzH/v3KcCnSeSLckljg3olkcgYcsdOMWfWnCjWoZe3t +jZqwhHKn7tLUKvkcbsILGp5iM0Ff5fJeqfnCMOOAgpFpGdsLT0Ro3Hl3RGOGx29z +TBey3I2pKyaK4sC+Z444lmvPlSNA/+hXmn0CxncG1d6KqPSdVd4+rK3ubJLDM0l0 +wzbzUFeCBWdWORpgJ5JirGD4Z24pU8g2zO75Rx3fiQKBgQDWa4Y0rheR3CPTE/g9 +p8lT8RRGBxge50ldbw7au3+zt89AKVbZ0+PnllWgjgL/qx4bWvOJp9q9WUODhjVO +Qd+B3vlWtlLzBcs53KbVf49E6Ag4g3KvcJ4R7dHLlBNkvcqBwnHyR8WuBDNTWbr2 +Yy6r5P6SXIB27W6ex1KyfCslKwKBgQDCA3D9ppWX9Wj6miJ/0cvJUSKL7wMu+Hhl +JTt8sL0KfuOzLU4/5jtkWXSxCqEWzGteWc7s5rIi/NILBJRxGPG7T/e6R3+n+iiu +T7qgoihl5gecw7sK3PzEAcJMd9TwCiD8Wcy7gGiRz/0Ajqju6fB7i5KqoYodqnTq +a1cM7ySodQKBgQC7e9klRvQk/a/1aIiuoH9RfoKTmLBmlSV5JRp/92J56ka1e3AN +l1C3tqO4d3P3yc/Ra3125+ZDmkGGR1tkygR8slKil1mAVZiVR3I5TAgh4CEQCR/G +d1o/owrGTvuGIs1nGHY5urgGqHWYc+Ueeyrb8qcFowxQ8NrAythsaFXxcQKBgQC/ +qPKoQTaqxW8NkdLe/nwYxqQgJN+6OQ+Gq/9WMKqvgaajTPBuQ50Mhyq18tAsW4j9 +zi6S7VuxIJzG8aFLEN9MsbUCOrurT398o5q0MT1DXLjMbreKBcFWSH6PWBntf7QS +VwvfdvzWfudq84ODcWt2QO2EzsxIfim9ooh+aIiIOQKBgErUJXO3Z6YqxpHjZka9 +0zXRZaUHBTTTQTy014VUT69bKKgwYvaecKZlzgzlzj4wEAZuNmgWQfinGEfUezu8 +VwL+a0BsWnQDMAK96FWGFfui55DmXp8Wo+pzIrSR7O0+GPnSr6B6RPjwEuFKziWX +v4HTdlayWFSvB+uArMUKowFP +-----END PRIVATE KEY----- diff --git a/apps/bookstack/bookstack/nginx/resolver.conf b/apps/bookstack/bookstack/nginx/resolver.conf new file mode 100644 index 0000000..1ae22e7 --- /dev/null +++ b/apps/bookstack/bookstack/nginx/resolver.conf @@ -0,0 +1,3 @@ +# This file is auto-generated only on first start, based on the container's /etc/resolv.conf file. Feel free to modify it as you wish. + +resolver 127.0.0.11 valid=30s; diff --git a/apps/bookstack/bookstack/nginx/site-confs/default.conf b/apps/bookstack/bookstack/nginx/site-confs/default.conf new file mode 100644 index 0000000..610e920 --- /dev/null +++ b/apps/bookstack/bookstack/nginx/site-confs/default.conf @@ -0,0 +1,46 @@ +## Version 2025/12/26 - Changelog: https://github.com/linuxserver/docker-baseimage-alpine-nginx/commits/3.23/root/defaults/nginx/site-confs/default.conf.sample + +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + listen 443 quic reuseport default_server; + listen [::]:443 quic reuseport default_server; + + server_name _; + + include /config/nginx/ssl.conf; + + set $root /app/www/public; + if (!-d /app/www/public) { + set $root /config/www; + } + root $root; + index index.html index.htm index.php; + + location / { + # enable for basic auth + #auth_basic "Restricted"; + #auth_basic_user_file /config/nginx/.htpasswd; + + try_files $uri $uri/ /index.html /index.htm /index.php$is_args$args; + } + + location ~ ^(.+\.php)(.*)$ { + # enable the next two lines for http auth + #auth_basic "Restricted"; + #auth_basic_user_file /config/nginx/.htpasswd; + + fastcgi_split_path_info ^(.+\.php)(.*)$; + if (!-f $document_root$fastcgi_script_name) { return 404; } + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + include /etc/nginx/fastcgi_params; + } + + # deny access to .htaccess/.htpasswd files + location ~ /\.ht { + deny all; + } +} diff --git a/apps/bookstack/bookstack/nginx/ssl.conf b/apps/bookstack/bookstack/nginx/ssl.conf new file mode 100644 index 0000000..b03cd69 --- /dev/null +++ b/apps/bookstack/bookstack/nginx/ssl.conf @@ -0,0 +1,36 @@ +## Version 2026/05/04 - Changelog: https://github.com/linuxserver/docker-baseimage-alpine-nginx/commits/3.23/root/defaults/nginx/ssl.conf.sample + +ssl_certificate /config/keys/cert.crt; +ssl_certificate_key /config/keys/cert.key; + +# HSTS (ngx_http_headers_module is required) (63072000 seconds) +#add_header Strict-Transport-Security "max-age=63072000" always; + +### Mozilla SSL Configuration Generator +# generated 2026-05-04, Mozilla Guideline v6.0, nginx 1.28.3, OpenSSL 3.5.6, intermediate config, HSTS +# https://ssl-config.mozilla.org/#server=nginx&version=1.28.3&config=intermediate&openssl=3.5.6&hsts&guideline=6.0 +# intermediate configuration +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; +ssl_prefer_server_ciphers off; + +# see also ssl_session_ticket_key alternative to stateful session cache +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; # about 40000 sessions + +### Mozilla Practical security implementation +# https://developer.mozilla.org/en-US/docs/Web/Security +#add_header Access-Control-Allow-Origin $http_origin always; +#add_header Content-Security-Policy "upgrade-insecure-requests; base-uri 'self'; form-action 'self'; frame-ancestors 'self';" always; +#add_header Cross-Origin-Resource-Policy "same-origin" always; +#add_header Referrer-Policy "same-origin" always; +#add_header X-Content-Type-Options "nosniff" always; +#add_header X-Frame-Options "SAMEORIGIN" always; + +### Optional additional headers +#add_header Alt-Svc 'h3=":443"' always; +#add_header Cache-Control "no-transform" always; +#add_header Permissions-Policy "interest-cohort=()" always; +#add_header X-UA-Compatible "IE=Edge" always; +#add_header X-XSS-Protection "1; mode=block" always; \ No newline at end of file diff --git a/apps/bookstack/bookstack/nginx/worker_processes.conf b/apps/bookstack/bookstack/nginx/worker_processes.conf new file mode 100644 index 0000000..7a2f19f --- /dev/null +++ b/apps/bookstack/bookstack/nginx/worker_processes.conf @@ -0,0 +1,3 @@ +# This file is auto-generated only on first start, based on the cpu cores detected. Feel free to change it to any other number or to auto to let nginx handle it automatically. + +worker_processes 8; diff --git a/apps/bookstack/bookstack/php/php-local.ini b/apps/bookstack/bookstack/php/php-local.ini new file mode 100644 index 0000000..24bc478 --- /dev/null +++ b/apps/bookstack/bookstack/php/php-local.ini @@ -0,0 +1,3 @@ +; Edit this file to override php.ini directives + +date.timezone = America/Chicago diff --git a/apps/bookstack/bookstack/php/www2.conf b/apps/bookstack/bookstack/php/www2.conf new file mode 100644 index 0000000..13fc59d --- /dev/null +++ b/apps/bookstack/bookstack/php/www2.conf @@ -0,0 +1,5 @@ +; Edit this file to override www.conf and php-fpm.conf directives and restart the container + +; Pool name +[www] + diff --git a/apps/bookstack/bookstack/www/index.html b/apps/bookstack/bookstack/www/index.html new file mode 100644 index 0000000..8351def --- /dev/null +++ b/apps/bookstack/bookstack/www/index.html @@ -0,0 +1,34 @@ + + + Welcome to our server + + + +
+

Welcome to our server

+

The website is currently being setup under this address.

+

For help and support, please contact: me@example.com

+
+ + diff --git a/apps/bookstack/db/mariadb_upgrade_info b/apps/bookstack/db/mariadb_upgrade_info new file mode 100644 index 0000000..84e9b65 --- /dev/null +++ b/apps/bookstack/db/mariadb_upgrade_info @@ -0,0 +1 @@ +11.8.8-MariaDB \ No newline at end of file diff --git a/apps/bookstack/docker-compose.yml b/apps/bookstack/docker-compose.yml new file mode 100644 index 0000000..c1204cf --- /dev/null +++ b/apps/bookstack/docker-compose.yml @@ -0,0 +1,33 @@ +services: + bookstack: + image: lscr.io/linuxserver/bookstack:latest + container_name: bookstack + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Chicago + - APP_URL=http://192.168.1.205:6875 + - DB_HOST=bookstack-db + - DB_PORT=3306 + - DB_USERNAME=bookstack + - DB_PASSWORD=bookstackpassword + - DB_DATABASE=bookstackapp + volumes: + - ./bookstack:/config + ports: + - "6875:80" + depends_on: + - bookstack-db + + bookstack-db: + image: mariadb:11 + container_name: bookstack-db + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=supersecretrootpassword + - MYSQL_DATABASE=bookstackapp + - MYSQL_USER=bookstack + - MYSQL_PASSWORD=bookstackpassword + volumes: + - ./db:/var/lib/mysql diff --git a/apps/cloudflared/docker-compose.yml b/apps/cloudflared/docker-compose.yml new file mode 100644 index 0000000..42d21d4 --- /dev/null +++ b/apps/cloudflared/docker-compose.yml @@ -0,0 +1,15 @@ +services: + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflared + restart: unless-stopped + command: tunnel --no-autoupdate run + environment: + - TUNNEL_TOKEN=eyJhIjoiZDBiYjc2NzMzMzNmY2Q3OTQ2MjI5NTZmMTY2MmY3ODUiLCJ0IjoiNWU2MGVhOGUtYTU0My00OWI2LWJhYjUtMzI1ZjM5NDQxZTAwIiwicyI6IkJyR3NreXdTSEFjQllobk9IcWFBZWJhT2djRUU0cjVSMXcwKzVGeTkrUHc9In0= + networks: + - default + - kitestacks + +networks: + kitestacks: + external: true diff --git a/apps/cloudflared/docker-compose.yml.backup-before-token-rotate-20260608 b/apps/cloudflared/docker-compose.yml.backup-before-token-rotate-20260608 new file mode 100644 index 0000000..173eb7e --- /dev/null +++ b/apps/cloudflared/docker-compose.yml.backup-before-token-rotate-20260608 @@ -0,0 +1,9 @@ +services: + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflared + restart: unless-stopped + command: tunnel --no-autoupdate run + environment: + - TUNNEL_TOKEN=eyJhIjoiZDBiYjc2NzMzMzNmY2Q3OTQ2MjI5NTZmMTY2MmY3ODUiLCJ0IjoiNWU2MGVhOGUtYTU0My00OWI2LWJhYjUtMzI1ZjM5NDQxZTAwIiwicyI6Ik1ESTBPVEV5WVRNdFl6WmlOaTAwWTJNeUxUa3pPRE10T1RRME1tTmlOV1ZsTVRZNCJ9 + diff --git a/apps/forgejo/configmap.yaml b/apps/forgejo/configmap.yaml new file mode 100644 index 0000000..3dac912 --- /dev/null +++ b/apps/forgejo/configmap.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kitestacks-scripts + namespace: monitoring +data: + kitestacks_check.py: | + # Paste the full contents of your kitestacks_check.py script here + # Example: + # import requests + # from datetime import datetime + # ... diff --git a/apps/forgejo/cronjob.yaml b/apps/forgejo/cronjob.yaml new file mode 100644 index 0000000..86d3fc1 --- /dev/null +++ b/apps/forgejo/cronjob.yaml @@ -0,0 +1,24 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: kitestacks-widget-check + namespace: monitoring +spec: + schedule: "0 * * * *" # every hour + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + containers: + - name: kitestacks-check + image: python:3.12-slim + command: ["python3", "/scripts/kitestacks_check.py"] + volumeMounts: + - name: scripts + mountPath: /scripts + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: kitestacks-scripts diff --git a/apps/forgejo/docker-compose.yml b/apps/forgejo/docker-compose.yml new file mode 100644 index 0000000..16def87 --- /dev/null +++ b/apps/forgejo/docker-compose.yml @@ -0,0 +1,17 @@ +services: + forgejo: + image: codeberg.org/forgejo/forgejo:11 + container_name: forgejo + restart: unless-stopped + ports: + - "3006:3000" + - "2222:22" + environment: + - USER_UID=1000 + - USER_GID=1000 + - FORGEJO__server__DOMAIN=192.168.1.205 + - FORGEJO__server__ROOT_URL=http://192.168.1.205:3006 + - FORGEJO__server__SSH_DOMAIN=192.168.1.205 + - FORGEJO__server__SSH_PORT=2222 + volumes: + - ./data:/data diff --git a/apps/forgejo/gitrepository.yaml b/apps/forgejo/gitrepository.yaml new file mode 100644 index 0000000..e037088 --- /dev/null +++ b/apps/forgejo/gitrepository.yaml @@ -0,0 +1,12 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: GitRepository +metadata: + name: kitestacks-cron + namespace: flux-system +spec: + interval: 1m + url: https://gitforge.kitestacks.com/kenpat/kitestacks-homelab.git + ref: + branch: master + secretRef: + name: forgejo-access # Only needed if repo is private diff --git a/apps/forgejo/kitestacks_check.py b/apps/forgejo/kitestacks_check.py new file mode 100644 index 0000000..347002a --- /dev/null +++ b/apps/forgejo/kitestacks_check.py @@ -0,0 +1,17 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: kitestacks-widget-check + namespace: monitoring +spec: + schedule: "0 * * * *" # every hour + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + containers: + - name: kitestacks-check + image: yourdockerhubuser/kitestacks-check:latest + imagePullPolicy: Always + restartPolicy: OnFailure diff --git a/apps/forgejo/uptime-kuma/-n b/apps/forgejo/uptime-kuma/-n new file mode 100644 index 0000000..e69de29 diff --git a/apps/forgejo/uptime-kuma/:31333 b/apps/forgejo/uptime-kuma/:31333 new file mode 100644 index 0000000..e69de29 diff --git a/apps/forgejo/uptime-kuma/NodePort b/apps/forgejo/uptime-kuma/NodePort new file mode 100644 index 0000000..e69de29 diff --git a/apps/forgejo/uptime-kuma/configmap.yaml b/apps/forgejo/uptime-kuma/configmap.yaml new file mode 100644 index 0000000..45ccf6e --- /dev/null +++ b/apps/forgejo/uptime-kuma/configmap.yaml @@ -0,0 +1,147 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kitestacks-monitors + namespace: monitoring +data: + monitors.json: [ + { + "name": "Open Web UI", + "type": "http", + "method": "GET", + "url": "https://www.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Auth", + "type": "http", + "method": "GET", + "url": "https://auth.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Kavita", + "type": "http", + "method": "GET", + "url": "http://kavita.kitestacks.com:5000", + "interval": 60, + "retries": 3 + }, + { + "name": "Tasks", + "type": "http", + "method": "GET", + "url": "http://tasks.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "AI", + "type": "http", + "method": "GET", + "url": "http://ai.kitestacks.com:3100", + "interval": 60, + "retries": 3 + }, + { + "name": "Forgejo", + "type": "http", + "method": "GET", + "url": "http://gitforge.kitestacks.com:3006", + "interval": 60, + "retries": 3 + }, + { + "name": "Linkding", + "type": "http", + "method": "GET", + "url": "http://links.kitestacks.com:9005", + "interval": 60, + "retries": 3 + }, + { + "name": "Grafana", + "type": "http", + "method": "GET", + "url": "http://grafana.kitestacks.com:3150", + "interval": 60, + "retries": 3 + }, + { + "name": "OpenProject", + "type": "http", + "method": "GET", + "url": "http://openproject.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "Prometheus", + "type": "tcp", + "host": "prometheus.kitestacks.com", + "port": 9090, + "interval": 60, + "retries": 3 + }, + { + "name": "Node Exporter", + "type": "tcp", + "host": "node-exporter.kitestacks.com", + "port": 9100, + "interval": 60, + "retries": 3 + }, + { + "name": "Pixel 4", + "type": "ping", + "host": "192.168.1.201", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14", + "type": "ping", + "host": "192.168.1.205", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14s", + "type": "ping", + "host": "192.168.1.206", + "interval": 60, + "retries": 3 + }, + { + "name": "Gaming Desktop", + "type": "ping", + "host": "192.168.1.207", + "interval": 60, + "retries": 3 + }, + { + "name": "Tesla Model Y", + "type": "ping", + "host": "192.168.1.208", + "interval": 60, + "retries": 3 + }, + { + "name": "Mom's HP Laptop", + "type": "ping", + "host": "192.168.1.209", + "interval": 60, + "retries": 3 + }, + { + "name": "Uptime Kuma", + "type": "http", + "method": "GET", + "url": "https://status.kitestacks.com", + "interval": 60, + "retries": 3 + } +] + diff --git a/apps/forgejo/uptime-kuma/gitrepository.yaml b/apps/forgejo/uptime-kuma/gitrepository.yaml new file mode 100644 index 0000000..a7ba006 --- /dev/null +++ b/apps/forgejo/uptime-kuma/gitrepository.yaml @@ -0,0 +1,13 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: GitRepository +metadata: + name: uptime-kuma-repo + namespace: flux-system +spec: + interval: 1m + url: https://gitforge.kitestacks.com/kenpat/docker + ref: + branch: master + secretRef: + name: forgejo-access + diff --git a/apps/forgejo/uptime-kuma/kitestacks-cron.yaml b/apps/forgejo/uptime-kuma/kitestacks-cron.yaml new file mode 100644 index 0000000..c343ad7 --- /dev/null +++ b/apps/forgejo/uptime-kuma/kitestacks-cron.yaml @@ -0,0 +1,172 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kitestacks-monitors + namespace: monitoring +data: + monitors.json: | + [ + { + "name": "Open Web UI", + "type": "http", + "method": "GET", + "url": "https://www.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Auth", + "type": "http", + "method": "GET", + "url": "https://auth.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Kavita", + "type": "http", + "method": "GET", + "url": "http://kavita.kitestacks.com:5000", + "interval": 60, + "retries": 3 + }, + { + "name": "Tasks", + "type": "http", + "method": "GET", + "url": "http://tasks.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "AI", + "type": "http", + "method": "GET", + "url": "http://ai.kitestacks.com:3100", + "interval": 60, + "retries": 3 + }, + { + "name": "Forgejo", + "type": "http", + "method": "GET", + "url": "http://gitforge.kitestacks.com:3006", + "interval": 60, + "retries": 3 + }, + { + "name": "Linkding", + "type": "http", + "method": "GET", + "url": "http://links.kitestacks.com:9005", + "interval": 60, + "retries": 3 + }, + { + "name": "Grafana", + "type": "http", + "method": "GET", + "url": "http://grafana.kitestacks.com:3150", + "interval": 60, + "retries": 3 + }, + { + "name": "OpenProject", + "type": "http", + "method": "GET", + "url": "http://openproject.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "Prometheus", + "type": "tcp", + "host": "prometheus.kitestacks.com", + "port": 9090, + "interval": 60, + "retries": 3 + }, + { + "name": "Node Exporter", + "type": "tcp", + "host": "node-exporter.kitestacks.com", + "port": 9100, + "interval": 60, + "retries": 3 + }, + { + "name": "Pixel 4", + "type": "ping", + "host": "192.168.1.201", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14", + "type": "ping", + "host": "192.168.1.205", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14s", + "type": "ping", + "host": "192.168.1.206", + "interval": 60, + "retries": 3 + }, + { + "name": "Gaming Desktop", + "type": "ping", + "host": "192.168.1.207", + "interval": 60, + "retries": 3 + }, + { + "name": "Tesla Model Y", + "type": "ping", + "host": "192.168.1.208", + "interval": 60, + "retries": 3 + }, + { + "name": "Mom's HP Laptop", + "type": "ping", + "host": "192.168.1.209", + "interval": 60, + "retries": 3 + }, + { + "name": "Uptime Kuma", + "type": "http", + "method": "GET", + "url": "https://status.kitestacks.com", + "interval": 60, + "retries": 3 + } + ] + +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: kitestacks-monitors-check + namespace: monitoring +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: monitor-check + image: python:3.12-slim + command: ["python3", "/scripts/kitestacks_check.py"] + volumeMounts: + - name: monitors + mountPath: /scripts + restartPolicy: OnFailure + volumes: + - name: monitors + configMap: + name: kitestacks-monitors diff --git a/apps/forgejo/uptime-kuma/kitestacks-monitors.json b/apps/forgejo/uptime-kuma/kitestacks-monitors.json new file mode 100644 index 0000000..bd9750d --- /dev/null +++ b/apps/forgejo/uptime-kuma/kitestacks-monitors.json @@ -0,0 +1,140 @@ +[ + { + "name": "Open Web UI", + "type": "http", + "method": "GET", + "url": "https://www.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Auth", + "type": "http", + "method": "GET", + "url": "https://auth.kitestacks.com", + "interval": 60, + "retries": 3 + }, + { + "name": "Kavita", + "type": "http", + "method": "GET", + "url": "http://kavita.kitestacks.com:5000", + "interval": 60, + "retries": 3 + }, + { + "name": "Tasks", + "type": "http", + "method": "GET", + "url": "http://tasks.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "AI", + "type": "http", + "method": "GET", + "url": "http://ai.kitestacks.com:3100", + "interval": 60, + "retries": 3 + }, + { + "name": "Forgejo", + "type": "http", + "method": "GET", + "url": "http://gitforge.kitestacks.com:3006", + "interval": 60, + "retries": 3 + }, + { + "name": "Linkding", + "type": "http", + "method": "GET", + "url": "http://links.kitestacks.com:9005", + "interval": 60, + "retries": 3 + }, + { + "name": "Grafana", + "type": "http", + "method": "GET", + "url": "http://grafana.kitestacks.com:3150", + "interval": 60, + "retries": 3 + }, + { + "name": "OpenProject", + "type": "http", + "method": "GET", + "url": "http://openproject.kitestacks.com:8080", + "interval": 60, + "retries": 3 + }, + { + "name": "Prometheus", + "type": "tcp", + "host": "prometheus.kitestacks.com", + "port": 9090, + "interval": 60, + "retries": 3 + }, + { + "name": "Node Exporter", + "type": "tcp", + "host": "node-exporter.kitestacks.com", + "port": 9100, + "interval": 60, + "retries": 3 + }, + { + "name": "Pixel 4", + "type": "ping", + "host": "192.168.1.201", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14", + "type": "ping", + "host": "192.168.1.205", + "interval": 60, + "retries": 3 + }, + { + "name": "Lenovo T14s", + "type": "ping", + "host": "192.168.1.206", + "interval": 60, + "retries": 3 + }, + { + "name": "Gaming Desktop", + "type": "ping", + "host": "192.168.1.207", + "interval": 60, + "retries": 3 + }, + { + "name": "Tesla Model Y", + "type": "ping", + "host": "192.168.1.208", + "interval": 60, + "retries": 3 + }, + { + "name": "Mom's HP Laptop", + "type": "ping", + "host": "192.168.1.209", + "interval": 60, + "retries": 3 + }, + { + "name": "Uptime Kuma", + "type": "http", + "method": "GET", + "url": "https://status.kitestacks.com", + "interval": 60, + "retries": 3 + } +] diff --git a/apps/forgejo/uptime-kuma/scripts/kitestacks_uptimecheck.py b/apps/forgejo/uptime-kuma/scripts/kitestacks_uptimecheck.py new file mode 100755 index 0000000..bffd082 --- /dev/null +++ b/apps/forgejo/uptime-kuma/scripts/kitestacks_uptimecheck.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import json +import requests +import subprocess + +# Load the monitors JSON +with open("/scripts/kitestacks-monitors.json", "r") as f: + monitors = json.load(f) + +for monitor in monitors: + name = monitor.get("name") + mtype = monitor.get("type") + + try: + if mtype == "http": + url = monitor.get("url") + method = monitor.get("method", "GET").upper() + resp = requests.request(method, url, timeout=10, verify=False) + print(f"[{name}] HTTP {method} {url} -> Status {resp.status_code}") + elif mtype == "tcp": + host = monitor.get("host") + port = monitor.get("port") + result = subprocess.run(["nc", "-zvw3", host, str(port)], capture_output=True) + print(f"[{name}] TCP {host}:{port} -> Returncode {result.returncode}") + elif mtype == "ping": + host = monitor.get("host") + result = subprocess.run(["ping", "-c", "1", host], capture_output=True) + print(f"[{name}] Ping {host} -> Returncode {result.returncode}") + else: + print(f"[{name}] Unknown type: {mtype}") + except Exception as e: + print(f"[{name}] Error: {e}") diff --git a/apps/forgejo/uptime-kuma/uptime-kuma b/apps/forgejo/uptime-kuma/uptime-kuma new file mode 100644 index 0000000..e69de29 diff --git a/apps/forgejo/uptime-kuma/uptime-kuma-deployment.yaml b/apps/forgejo/uptime-kuma/uptime-kuma-deployment.yaml new file mode 100644 index 0000000..f1459bc --- /dev/null +++ b/apps/forgejo/uptime-kuma/uptime-kuma-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: uptime-kuma-pvc + namespace: monitoring +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: uptime-kuma + namespace: monitoring +spec: + replicas: 1 + selector: + matchLabels: + app: uptime-kuma + template: + metadata: + labels: + app: uptime-kuma + spec: + containers: + - name: uptime-kuma + image: louislam/uptime-kuma:latest + ports: + - containerPort: 3001 + volumeMounts: + - name: kuma-data + mountPath: /app/data + volumes: + - name: kuma-data + persistentVolumeClaim: + claimName: uptime-kuma-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: uptime-kuma + namespace: monitoring +spec: + type: NodePort + selector: + app: uptime-kuma + ports: + - protocol: TCP + port: 3001 + targetPort: 3001 + diff --git a/apps/forgejo/uptime-kuma/uptime-kuma-ingress.yaml b/apps/forgejo/uptime-kuma/uptime-kuma-ingress.yaml new file mode 100644 index 0000000..76e19fa --- /dev/null +++ b/apps/forgejo/uptime-kuma/uptime-kuma-ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: uptime-kuma-ingress + namespace: monitoring + annotations: + kubernetes.io/ingress.class: nginx # or your ingress controller name + cert-manager.io/cluster-issuer: letsencrypt-prod # if using cert-manager for TLS +spec: + rules: + - host: status.kitestacks.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: uptime-kuma + port: + number: 3001 + tls: + - hosts: + - status.kitestacks.com + secretName: uptime-kuma-tls diff --git a/apps/grafana/docker-compose.yml b/apps/grafana/docker-compose.yml new file mode 100644 index 0000000..7ffe207 --- /dev/null +++ b/apps/grafana/docker-compose.yml @@ -0,0 +1,7 @@ +services: + grafana: + image: grafana/grafana-oss + container_name: grafana + ports: + - "3150:3000" # host:container + restart: unless-stopped diff --git a/apps/grafana/grafana-networkpolicy.yaml b/apps/grafana/grafana-networkpolicy.yaml new file mode 100644 index 0000000..4026235 --- /dev/null +++ b/apps/grafana/grafana-networkpolicy.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-grafana-ingress + namespace: monitoring +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: grafana + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: 192.168.1.0/24 # replace with your LAN subnet + ports: + - protocol: TCP + port: 3000 + diff --git a/apps/grafana/test-autosync.txt b/apps/grafana/test-autosync.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/Dockerfile b/apps/homepage-archived-2026-06-07/Dockerfile new file mode 100644 index 0000000..7cbd9d9 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/Dockerfile @@ -0,0 +1,13 @@ +# Use Node.js Alpine base +FROM node:20-alpine + +WORKDIR /app + +# Copy all homepage files +COPY . /app + +# Expose port for the homepage +EXPOSE 8080 + +# Start the homepage (adjust if your entry point is different) +CMD ["node", "server.js"] diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/bookmarks.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/bookmarks.yaml new file mode 100644 index 0000000..5404130 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/bookmarks.yaml @@ -0,0 +1,6 @@ +- Social: + - Discord: + - abbr: + href: https://discord.gg/QbdveTb6Kw + + diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/custom.css b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/custom.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/custom.js b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/custom.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/docker.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/docker.yaml new file mode 100644 index 0000000..2f4c4e3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/docker.yaml @@ -0,0 +1,10 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/docker/ + +# my-docker: +# host: 127.0.0.1 +# port: 2375 + +# my-docker: +# socket: /var/run/docker.sock diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/kubernetes.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/kubernetes.yaml new file mode 100644 index 0000000..aca6e82 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/kubernetes.yaml @@ -0,0 +1,2 @@ +--- +# sample kubernetes config diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/proxmox.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/proxmox.yaml new file mode 100644 index 0000000..90aacd7 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/proxmox.yaml @@ -0,0 +1,5 @@ +--- +# pve: +# url: https://proxmox.host.or.ip:8006 +# token: username@pam!Token ID +# secret: secret diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services-live.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services-live.yaml new file mode 100644 index 0000000..20b02ee --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services-live.yaml @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Linkding: + icon: linkding.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services.yaml new file mode 100644 index 0000000..f40945d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/services.yaml @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Shaarli: + icon: shaarli.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings-live.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings-live.yaml new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings-live.yaml @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings.yaml new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/settings.yaml @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets-live.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets-live.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets-live.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets.yaml b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/backups/config-before-test-work-2026-06-06-2102/widgets.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/config-test/bookmarks.yaml b/apps/homepage-archived-2026-06-07/config-test/bookmarks.yaml new file mode 100644 index 0000000..5404130 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/bookmarks.yaml @@ -0,0 +1,6 @@ +- Social: + - Discord: + - abbr: + href: https://discord.gg/QbdveTb6Kw + + diff --git a/apps/homepage-archived-2026-06-07/config-test/custom.css b/apps/homepage-archived-2026-06-07/config-test/custom.css new file mode 100644 index 0000000..93c08f7 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/custom.css @@ -0,0 +1,91 @@ +/* ========================================================== + KITESTACKS CYBERPUNK TEST THEME + TEST ONLY + ========================================================== */ + +/* Background */ +body { + background: url("/images/cyberpunk-bg.png") center center fixed !important; + background-size: cover !important; +} + +/* Remove old injected banner */ +body::before, +body::after { + display: none !important; + content: none !important; +} + +/* Dark overlay for readability */ +body::selection { + background: rgba(56,189,248,0.35); +} + +/* Main containers */ +div[class*="service"], +div[class*="widget"], +.card, +.service-card { + background: rgba(7,15,30,0.72) !important; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + + border: 1px solid rgba(56,189,248,0.25) !important; + + border-radius: 18px !important; + + box-shadow: + 0 0 15px rgba(56,189,248,0.08), + 0 0 30px rgba(56,189,248,0.05) !important; +} + +/* Section headers */ +h2 { + color: #dbeafe !important; + font-weight: 700 !important; + letter-spacing: 0.03em !important; + + text-shadow: + 0 0 8px rgba(56,189,248,0.45); +} + +/* Search box */ +input[type="text"] { + background: rgba(5,10,20,0.75) !important; + border: 1px solid rgba(56,189,248,0.35) !important; + border-radius: 14px !important; +} + +/* Widget row */ +div[class*="widget"] { + border-radius: 18px !important; +} + +/* Hover effects */ +a:hover .service-card, +.service-card:hover, +.card:hover { + transform: translateY(-2px); + + box-shadow: + 0 0 15px rgba(56,189,248,0.25), + 0 0 35px rgba(56,189,248,0.18) !important; + + transition: all .2s ease; +} + +/* Footer */ +footer { + backdrop-filter: blur(8px); +} + +/* Slightly dim the background for readability */ +body::after { + content: ""; + position: fixed; + inset: 0; + background: rgba(0,0,20,0.45); + pointer-events: none; + z-index: -1; +} +EOF diff --git a/apps/homepage-archived-2026-06-07/config-test/custom.css.pre-cyberpunk-backup b/apps/homepage-archived-2026-06-07/config-test/custom.css.pre-cyberpunk-backup new file mode 100644 index 0000000..e83b228 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/custom.css.pre-cyberpunk-backup @@ -0,0 +1,31 @@ +body::before { + content: ""; + display: block; + height: 155px; + margin: 24px 38px 10px 38px; + background-image: url("/images/kitestacks-logo.png"); + background-repeat: no-repeat; + background-position: left center; + background-size: 360px auto; + border-bottom: 2px solid rgba(56, 189, 248, 0.75); +} + +body::after { + content: "Personal Infrastructure Platform"; + position: absolute; + top: 128px; + left: 185px; + font-size: 15px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: rgba(203, 213, 225, 0.75); +} + +.service-card, +div[class*="service"] { + border-radius: 14px !important; +} + +h2 { + letter-spacing: -0.02em; +} diff --git a/apps/homepage-archived-2026-06-07/config-test/custom.js b/apps/homepage-archived-2026-06-07/config-test/custom.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/config-test/docker.yaml b/apps/homepage-archived-2026-06-07/config-test/docker.yaml new file mode 100644 index 0000000..2f4c4e3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/docker.yaml @@ -0,0 +1,10 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/docker/ + +# my-docker: +# host: 127.0.0.1 +# port: 2375 + +# my-docker: +# socket: /var/run/docker.sock diff --git a/apps/homepage-archived-2026-06-07/config-test/images/cyberpunk-bg.png b/apps/homepage-archived-2026-06-07/config-test/images/cyberpunk-bg.png new file mode 100644 index 0000000..9ce3953 Binary files /dev/null and b/apps/homepage-archived-2026-06-07/config-test/images/cyberpunk-bg.png differ diff --git a/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-icon.png b/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-icon.png new file mode 100644 index 0000000..1bd0078 Binary files /dev/null and b/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-icon.png differ diff --git a/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-logo.png b/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-logo.png new file mode 100644 index 0000000..9e86c0f Binary files /dev/null and b/apps/homepage-archived-2026-06-07/config-test/images/kitestacks-logo.png differ diff --git a/apps/homepage-archived-2026-06-07/config-test/kubernetes.yaml b/apps/homepage-archived-2026-06-07/config-test/kubernetes.yaml new file mode 100644 index 0000000..aca6e82 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/kubernetes.yaml @@ -0,0 +1,2 @@ +--- +# sample kubernetes config diff --git a/apps/homepage-archived-2026-06-07/config-test/proxmox.yaml b/apps/homepage-archived-2026-06-07/config-test/proxmox.yaml new file mode 100644 index 0000000..90aacd7 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/proxmox.yaml @@ -0,0 +1,5 @@ +--- +# pve: +# url: https://proxmox.host.or.ip:8006 +# token: username@pam!Token ID +# secret: secret diff --git a/apps/homepage-archived-2026-06-07/config-test/services-live.yaml b/apps/homepage-archived-2026-06-07/config-test/services-live.yaml new file mode 100644 index 0000000..20b02ee --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/services-live.yaml @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Linkding: + icon: linkding.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/config-test/services.yaml b/apps/homepage-archived-2026-06-07/config-test/services.yaml new file mode 100644 index 0000000..f3550a7 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/services.yaml @@ -0,0 +1,89 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity & Access + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Control + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Service Health + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Dashboards & Metrics + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Telemetry + +- AI & Automation: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Knowledge Base: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Shaarli: + icon: shaarli.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Development: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Community: + - Discord: + icon: discord.png + href: https://discord.gg + description: Community Hub + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Coming Soon + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: Coming Soon + - Cilium: + icon: cilium.png + href: https://cilium.io + description: Coming Soon + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: Coming Soon diff --git a/apps/homepage-archived-2026-06-07/config-test/services.yaml.bak b/apps/homepage-archived-2026-06-07/config-test/services.yaml.bak new file mode 100644 index 0000000..f40945d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/services.yaml.bak @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Shaarli: + icon: shaarli.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/config-test/settings-live.yaml b/apps/homepage-archived-2026-06-07/config-test/settings-live.yaml new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/settings-live.yaml @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/config-test/settings.yaml b/apps/homepage-archived-2026-06-07/config-test/settings.yaml new file mode 100644 index 0000000..c1184a2 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/settings.yaml @@ -0,0 +1,37 @@ +--- +title: KiteStacks +theme: dark +color: slate + +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 4 + + Monitoring: + style: row + columns: 3 + + AI & Automation: + style: row + columns: 3 + + Knowledge Base: + style: row + columns: 2 + + Development: + style: row + columns: 2 + + Community: + style: row + columns: 1 + + Future Projects: + style: row + columns: 4 diff --git a/apps/homepage-archived-2026-06-07/config-test/settings.yaml.bak b/apps/homepage-archived-2026-06-07/config-test/settings.yaml.bak new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/settings.yaml.bak @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/config-test/widgets-live.yaml b/apps/homepage-archived-2026-06-07/config-test/widgets-live.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/widgets-live.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/config-test/widgets.yaml b/apps/homepage-archived-2026-06-07/config-test/widgets.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/widgets.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/config-test/widgets.yaml.bak b/apps/homepage-archived-2026-06-07/config-test/widgets.yaml.bak new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config-test/widgets.yaml.bak @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/config/bookmarks.yaml b/apps/homepage-archived-2026-06-07/config/bookmarks.yaml new file mode 100644 index 0000000..5404130 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/bookmarks.yaml @@ -0,0 +1,6 @@ +- Social: + - Discord: + - abbr: + href: https://discord.gg/QbdveTb6Kw + + diff --git a/apps/homepage-archived-2026-06-07/config/custom.css b/apps/homepage-archived-2026-06-07/config/custom.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/config/custom.js b/apps/homepage-archived-2026-06-07/config/custom.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-archived-2026-06-07/config/docker.yaml b/apps/homepage-archived-2026-06-07/config/docker.yaml new file mode 100644 index 0000000..2f4c4e3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/docker.yaml @@ -0,0 +1,10 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/docker/ + +# my-docker: +# host: 127.0.0.1 +# port: 2375 + +# my-docker: +# socket: /var/run/docker.sock diff --git a/apps/homepage-archived-2026-06-07/config/kubernetes.yaml b/apps/homepage-archived-2026-06-07/config/kubernetes.yaml new file mode 100644 index 0000000..aca6e82 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/kubernetes.yaml @@ -0,0 +1,2 @@ +--- +# sample kubernetes config diff --git a/apps/homepage-archived-2026-06-07/config/proxmox.yaml b/apps/homepage-archived-2026-06-07/config/proxmox.yaml new file mode 100644 index 0000000..90aacd7 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/proxmox.yaml @@ -0,0 +1,5 @@ +--- +# pve: +# url: https://proxmox.host.or.ip:8006 +# token: username@pam!Token ID +# secret: secret diff --git a/apps/homepage-archived-2026-06-07/config/services-live.yaml b/apps/homepage-archived-2026-06-07/config/services-live.yaml new file mode 100644 index 0000000..20b02ee --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/services-live.yaml @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Linkding: + icon: linkding.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/config/services.yaml b/apps/homepage-archived-2026-06-07/config/services.yaml new file mode 100644 index 0000000..f40945d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/services.yaml @@ -0,0 +1,90 @@ +- Infrastructure: + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Docker Management + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + - Cloudflare: + icon: cloudflare.png + href: https://dash.cloudflare.com + description: DNS & Tunnel Management + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Uptime Monitoring + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: Private AI Workspace + - LiteLLM: + icon: si-openai + href: https://llm.kitestacks.com + description: AI Model Gateway + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books, Comics & PDFs + - Shaarli: + icon: shaarli.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Code: + - Forgejo: + icon: forgejo.png + href: https://gitforge.kitestacks.com + description: Self-Hosted Git +- TicketSystem: + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- Monitoring: + - Grafana: + icon: grafana.png + href: http://192.168.1.205:3150 + description: Monitoring Dashboards + - Prometheus: + icon: prometheus.png + href: http://192.168.1.205:9090 + description: Metrics Database + - Node Exporter: + icon: prometheus.png + href: http://192.168.1.205:9100 + description: Host Metrics + +- Future Projects: + - Kubernetes: + icon: kubernetes.png + href: https://kubernetes.io + description: Cluster Platform + - FluxCD: + icon: flux-cd.png + href: https://fluxcd.io + description: GitOps Automation + - Cilium: + icon: cilium.png + href: https://cilium.io + description: eBPF Networking + - CloudNativePG: + icon: postgresql.png + href: https://cloudnative-pg.io + description: PostgreSQL Operator + +- Social: + - Discord: + icon: discord.png + href: https://discord.gg + description: Discord diff --git a/apps/homepage-archived-2026-06-07/config/settings-live.yaml b/apps/homepage-archived-2026-06-07/config/settings-live.yaml new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/settings-live.yaml @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/config/settings.yaml b/apps/homepage-archived-2026-06-07/config/settings.yaml new file mode 100644 index 0000000..61f21b3 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/settings.yaml @@ -0,0 +1,32 @@ +--- +title: KiteStacks.AO +theme: dark +color: slate +headerStyle: boxed +hideVersion: true +useEqualHeights: true + +layout: + Infrastructure: + style: row + columns: 1 + + Library: + style: row + columns: 1 + + Code: + style: row + columns: 1 + + Projects: + style: row + columns: 1 + + Monitoring: + style: row + columns: 2 + + Social: + style: row + columns: 1 diff --git a/apps/homepage-archived-2026-06-07/config/widgets-live.yaml b/apps/homepage-archived-2026-06-07/config/widgets-live.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/widgets-live.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/config/widgets.yaml b/apps/homepage-archived-2026-06-07/config/widgets.yaml new file mode 100644 index 0000000..06d034d --- /dev/null +++ b/apps/homepage-archived-2026-06-07/config/widgets.yaml @@ -0,0 +1,24 @@ +--- +- resources: + label: System + cpu: true + memory: true + disk: / + +- datetime: + text_size: xl + format: + dateStyle: full + timeStyle: short + +- openmeteo: + label: Wheaton + latitude: 41.8661 + longitude: -88.1065 + timezone: America/Chicago + units: imperial + cache: 5 + +- search: + provider: google + target: _blank diff --git a/apps/homepage-archived-2026-06-07/docker-compose.test.yml b/apps/homepage-archived-2026-06-07/docker-compose.test.yml new file mode 100644 index 0000000..3ea5611 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/docker-compose.test.yml @@ -0,0 +1,12 @@ +services: + homepage-test: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage-test + restart: unless-stopped + ports: + - "3007:3000" + environment: + - HOMEPAGE_ALLOWED_HOSTS=localhost:3007,192.168.1.205:3007,www.kitestacks.test.com,kitestacks.test.com + volumes: + - ./config-test:/app/config + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/apps/homepage-archived-2026-06-07/docker-compose.yml b/apps/homepage-archived-2026-06-07/docker-compose.yml new file mode 100644 index 0000000..7a463d5 --- /dev/null +++ b/apps/homepage-archived-2026-06-07/docker-compose.yml @@ -0,0 +1,13 @@ +services: + homepage: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage + restart: unless-stopped + ports: + - "3005:3000" + environment: + - HOMEPAGE_ALLOWED_HOSTS=localhost:3005,192.168.1.205:3005,www.kitestacks.com,kitestacks.com,home.kitestacks.com + + volumes: + - ./config:/app/config + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/apps/homepage-archived-2026-06-07/services.yaml b/apps/homepage-archived-2026-06-07/services.yaml new file mode 100644 index 0000000..057e0bd --- /dev/null +++ b/apps/homepage-archived-2026-06-07/services.yaml @@ -0,0 +1,69 @@ +- Infrastructure: + - Homepage: + icon: homepage.png + href: https://www.kitestacks.com + description: Main Dashboard + + - Authentik: + icon: authentik.png + href: https://auth.kitestacks.com + description: Identity Provider + + - Portainer: + icon: portainer.png + href: https://portainer.kitestacks.com + description: Container Management + +- Development: + - Forgejo: + icon: forgejo.png + href: https://git.kitestacks.com + description: Git Repositories + + - OpenProject: + icon: openproject.png + href: https://tasks.kitestacks.com + description: Project Management + +- AI: + - Kite AI: + icon: open-webui.png + href: https://ai.kitestacks.com + description: AI Workspace + + - LiteLLM: + icon: litellm.png + href: https://llm.kitestacks.com + description: Model Gateway + + - OpenRouter: + icon: si-openai + href: https://openrouter.ai + description: Hosted AI Models + +- Library: + - Kavita: + icon: kavita.png + href: https://kavita.kitestacks.com + description: Books & Documents + + - Shaarli: + icon: shaarli.png + href: https://links.kitestacks.com + description: Bookmark Library + +- Monitoring: + - Grafana: + icon: grafana.png + href: https://grafana.kitestacks.com + description: Dashboards + + - Prometheus: + icon: prometheus.png + href: https://prometheus.kitestacks.com + description: Metrics Collection + + - Uptime Kuma: + icon: uptime-kuma.png + href: https://status.kitestacks.com + description: Service Monitoring diff --git a/apps/homepage-archived-2026-06-07/test-autosync.txt b/apps/homepage-archived-2026-06-07/test-autosync.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/homepage-backup-pre-cyberpunk-2026-06-07-0152.tar.gz b/apps/homepage-backup-pre-cyberpunk-2026-06-07-0152.tar.gz new file mode 100644 index 0000000..6eb89ba Binary files /dev/null and b/apps/homepage-backup-pre-cyberpunk-2026-06-07-0152.tar.gz differ diff --git a/apps/homepage/deployment.yaml b/apps/homepage/deployment.yaml deleted file mode 100644 index e75871f..0000000 --- a/apps/homepage/deployment.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: homepage - namespace: homepage -spec: - replicas: 1 - selector: - matchLabels: - app: homepage - template: - metadata: - labels: - app: homepage - spec: - containers: - - name: homepage - image: ghcr.io/yourusername/homepage:latest - ports: - - containerPort: 80 diff --git a/apps/homepage/kustomization.yaml b/apps/homepage/kustomization.yaml deleted file mode 100644 index e3ef270..0000000 --- a/apps/homepage/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: homepage - namespace: flux-system -spec: - interval: 1m - path: ./apps/homepage - prune: true - sourceRef: - kind: GitRepository - name: kitestacks-homelab - targetNamespace: homepage diff --git a/apps/homepage/namespace.yaml b/apps/homepage/namespace.yaml deleted file mode 100644 index 19d1b55..0000000 --- a/apps/homepage/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: homepage diff --git a/apps/homepage/service.yaml b/apps/homepage/service.yaml deleted file mode 100644 index 2b6051f..0000000 --- a/apps/homepage/service.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: homepage - namespace: homepage -spec: - selector: - app: homepage - ports: - - protocol: TCP - port: 80 - targetPort: 80 - type: ClusterIP diff --git a/apps/kavita-docker-automation/kavita-docker-restart-cronjob.yaml b/apps/kavita-docker-automation/kavita-docker-restart-cronjob.yaml deleted file mode 100644 index 05b211f..0000000 --- a/apps/kavita-docker-automation/kavita-docker-restart-cronjob.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: batch/v1 -kind: CronJob -metadata: - name: kavita-docker-auto-restart - namespace: automation -spec: - schedule: "*/15 * * * *" - successfulJobsHistoryLimit: 2 - failedJobsHistoryLimit: 3 - jobTemplate: - spec: - template: - spec: - restartPolicy: OnFailure - containers: - - name: restart-kavita - image: docker:27-cli - command: - - /bin/sh - - -c - - | - echo "Restarting Kavita Docker container..." - docker restart kavita - echo "Kavita restart completed." - volumeMounts: - - name: docker-sock - mountPath: /var/run/docker.sock - volumes: - - name: docker-sock - hostPath: - path: /var/run/docker.sock - type: Socket diff --git a/apps/kavita-docker-automation/kustomization.yaml b/apps/kavita-docker-automation/kustomization.yaml deleted file mode 100644 index e7e1940..0000000 --- a/apps/kavita-docker-automation/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - namespace.yaml - - kavita-docker-restart-cronjob.yaml diff --git a/apps/kavita-docker-automation/namespace.yaml b/apps/kavita-docker-automation/namespace.yaml deleted file mode 100644 index 4f34a0a..0000000 --- a/apps/kavita-docker-automation/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: automation diff --git a/apps/kavita/config/appsettings.json b/apps/kavita/config/appsettings.json new file mode 100644 index 0000000..e0215b0 --- /dev/null +++ b/apps/kavita/config/appsettings.json @@ -0,0 +1,15 @@ +{ + "TokenKey": "0dAB10RcaX3mUwxMGE5pVcauZhDybFyoaPM6bGup5GLTFGq3yO6GFKpsnWCJ2TMS8GT2BpB4cXXc8wqB7mOV14\u002BS3ys5fgb2eWjX31DrmDgfJJYapAFr2Unx\u002BTv5fpeS9TyH\u002BnzAEhISPxXRApn4n6zJ7RUbJ79QEGyX2eKCjxJqsV6xBrHta4weL7zGQmPcWoMswezglOnFMoEYhzURpyVkwl1KeXFnfbdrPuGzcUCtsbdjBoRYXqIn5gcdjDOyrdwAxNT8Of3CGMnYFGLzg0kMIwPzBPqD5nsGXBisHQYEPUSwYsIIGwAfYLV3HtS\u002B\u002BXagEW3pgnbHYljsxSTQ==", + "Port": 5000, + "IpAddresses": "", + "BaseUrl": "/", + "Cache": 75, + "AllowIFraming": false, + "OpenIdConnectSettings": { + "Authority": "", + "ClientId": "kavita", + "Secret": "", + "CustomScopes": [], + "Enabled": false + } +} \ No newline at end of file diff --git a/apps/kavita/config/backups/kavita_backup_v0.9.0.2_06_05_2026_2026-06-05T02_00_06Z.zip b/apps/kavita/config/backups/kavita_backup_v0.9.0.2_06_05_2026_2026-06-05T02_00_06Z.zip new file mode 100644 index 0000000..1245375 Binary files /dev/null and b/apps/kavita/config/backups/kavita_backup_v0.9.0.2_06_05_2026_2026-06-05T02_00_06Z.zip differ diff --git a/apps/kavita/config/cache-long/pr_cache/pr_4691.json b/apps/kavita/config/cache-long/pr_cache/pr_4691.json new file mode 100644 index 0000000..cf666d8 --- /dev/null +++ b/apps/kavita/config/cache-long/pr_cache/pr_4691.json @@ -0,0 +1,7 @@ +{ + "Title": "Cover Chooser Overhaul", + "Body": "\r\n\u003Cimg width=\u00221127\u0022 height=\u0022773\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/ba3fe03a-880c-4c48-a348-25169f0afdc8\u0022 /\u003E\r\n\u003Cimg width=\u0022965\u0022 height=\u0022658\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/55b8c9ac-3781-4725-ac7a-2176214b3489\u0022 /\u003E\r\n\r\n\r\n# Added\r\n- Added: (Kavita\u002B) Cover chooser will now present cover image choices from Kavita\u002B (powered by Hardcover, MangaBaka, and ComicBookRoundup). \r\n\r\n# Changed\r\n- Changed: Redesigned Kavita\u0027s Cover Image chooser to use tabs for individual types of media (Current, Uploaded, Volume, Chapter, Kavita\u002B). (Closes #3891) (Thanks @therobbiedavis for the great design)\r\n- Changed: Moved the Reset cover image from a weird button into a dedicated button with clear labeling if the underlying cover was locked or not. \r\n\r\n# Fixed\r\n- Fixed: Fixed up a case where entity title service would avoid having Volume X in some cases\r\n- Fixed: Fixed Chrome PWA not showing the install button (Thanks @Ansh2209 )\r\n", + "Html_Url": "https://github.com/Kareadita/Kavita/pull/4691", + "Merged_At": "2026-05-19T18:27:50Z", + "Number": 4691 +} \ No newline at end of file diff --git a/apps/kavita/config/cache-long/pr_cache/pr_4711.json b/apps/kavita/config/cache-long/pr_cache/pr_4711.json new file mode 100644 index 0000000..9315053 --- /dev/null +++ b/apps/kavita/config/cache-long/pr_cache/pr_4711.json @@ -0,0 +1,7 @@ +{ + "Title": "Kavita\u002B Audit Log", + "Body": "\r\n\u003Cimg width=\u00221860\u0022 height=\u0022743\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/563b5045-c954-4b9a-9206-1ceb81fa773d\u0022 /\u003E\r\n\r\n\u003Cimg width=\u00221656\u0022 height=\u0022755\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/fb372b91-0ef1-4472-8215-633c776b3ebb\u0022 /\u003E\r\n\r\n\u003Cimg width=\u00221113\u0022 height=\u0022751\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/ae3496e3-31eb-4854-8c2f-d84307f75881\u0022 /\u003E\r\n\r\n\r\nThis is the first piece of a lot of architectural changes to make the Kavita\u002B integration feel understandable. Since Kavita\u002B launched, the systems have been evolving, but understanding what is happening under the hood has been difficult. The pressure was added with both AniList and MAL having many downtime events and users wondering when metadata is matching, needs rematching, or why a scrobble event didn\u0027t post. \r\n\r\nI hope this Audit system helps shed light and I am open to feedback to ensure it addresses the needs. \r\n\r\n\r\n# Added\r\n- Added: Added 3 new screens that help users understand what Kavita\u002B is doing around their series, their scrobbling, and for admins, the whole behind the scenes logic. (Closes #4705)\r\n\r\n# Changed\r\n- Changed: Scrobbling screen is likely going to be replaced by the new screens\r\n\r\n# Fixed\r\n- Fixed: Fixed a bug where marking a chapter as read wasn\u0027t triggering scrobbling.\r\n- Fixed: Fixed a bug where MAL was never getting a proper response for token expiration in the Account Screen\r\n- Fixed: Fixed a bug where series detail page could refresh the cover when a chapter cover update event triggered. \r\n\r\nNote: Relies on #4691", + "Html_Url": "https://github.com/Kareadita/Kavita/pull/4711", + "Merged_At": "2026-05-21T13:09:15Z", + "Number": 4711 +} \ No newline at end of file diff --git a/apps/kavita/config/cache-long/pr_cache/pr_4727.json b/apps/kavita/config/cache-long/pr_cache/pr_4727.json new file mode 100644 index 0000000..e6e934d --- /dev/null +++ b/apps/kavita/config/cache-long/pr_cache/pr_4727.json @@ -0,0 +1,7 @@ +{ + "Title": "Kavita\u002B Match UX Refresh", + "Body": "\r\n\u003Cimg width=\u00221150\u0022 height=\u0022551\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/dfb1fcb3-9502-4b3c-89d5-1ac823a7f0c3\u0022 /\u003E\r\n\r\nNote: Although you see Hardcover, it is not enabled yet in Kavita. This will come later. \r\n\r\n# Added\r\n- Added: Kavita\u002B Match can now accept direct ids via anilist:1234 in addition to existing urls. \r\n\r\n# Changed\r\n- Changed: Massive UX refresh to the Match modal for Kavita\u002B to surface tips on how to search. (Closes #4725)\r\n\r\n", + "Html_Url": "https://github.com/Kareadita/Kavita/pull/4727", + "Merged_At": "2026-05-27T16:42:40Z", + "Number": 4727 +} \ No newline at end of file diff --git a/apps/kavita/config/cache-long/pr_cache/pr_4731.json b/apps/kavita/config/cache-long/pr_cache/pr_4731.json new file mode 100644 index 0000000..f007b82 --- /dev/null +++ b/apps/kavita/config/cache-long/pr_cache/pr_4731.json @@ -0,0 +1,7 @@ +{ + "Title": "Feature/kavita\u002B license", + "Body": "\r\n\u003Cimg width=\u00221880\u0022 height=\u0022653\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/203bc8bf-a4c2-49c0-ae74-03d86de5d4fb\u0022 /\u003E\r\n\r\n\u003Cimg width=\u00221876\u0022 height=\u0022817\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/f62d4b8e-6660-4aca-8b5a-fb98b38b45e9\u0022 /\u003E\r\n\r\n\u003Cimg width=\u00221852\u0022 height=\u0022794\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/d13674e9-28e3-430c-8322-3b131d572018\u0022 /\u003E\r\n\r\nThis is another massive rework to Kavita/Kavita\u002B around the first setup to understanding the impact and status of Kavita\u002B. The main focus is on expanded availablility of information for licensed servers, helping users understand what Kavita\u002B is, and streamlining and polishing the registration/management points. \r\n\r\n**Note: The discord button is not implemented and some providers are not fully realized. This will come later.**\r\n\r\n\r\n# Added\r\n- Added: Added an upsell page that explains what Kavita\u002B is, so users are more informed, rather than linking to the wiki\r\n- Added: Added a status page to services that Kavita\u002B relies on, to surface incidents (like AL going down and scrobbling not working)\r\n- Added: Added stats about what Kavita\u002B is doing and how many calls your license has made (note: Not all data will be present, existing data is mixy)\r\n\r\n# Changed\r\n- Changed: Complete overhaul to the Kavita\u002B license page. New design have an upsell feel (from the main site) that explains what Kavita\u002B is. \r\n- Changed: When editing the license, the email is auto-filled for you\r\n- Changed: Expanded how much information we log out in the Kavita\u002B Audit pages (develop)\r\n- Changed: Kavita\u002B Audit will now track Metadata Sync trigger (Manual, on file Add, Background Sync)\r\n- Changed: Lots of polish added to the Match screen and made all the screens much nicer on mobile (develop)\r\n\r\n# Fixed\r\n- Fixed: Fixed incorrect native/docker wiki links (Fixes #4704)\r\n- Fixed: Fixed incorrect setup link (Fixes #4702)\r\n- Fixed: Fixed my activity throwing an exception when no K\u002B license. (develop)\r\n- Fixed: Fixed CBL Upload restriction and reworked the hardening of how file upload validation checks are done. Ensure we log out when we reject and why.\r\n- Fixed: Fixed up/down not responding to keypresses (Fixes #4697)\r\n\r\n# Developer\r\n- Reworked the cover chooser logic so that everything is streamlined via a file upload rather than base64 nonsense (bloated images). Base64 still exists for a non-breaking API, but Kavita will upload a file via upload/upload-by-file which scopes to a temp directory and returns a filename to pass going forward.\r\n\r\n#4709 ", + "Html_Url": "https://github.com/Kareadita/Kavita/pull/4731", + "Merged_At": "2026-05-31T14:58:33Z", + "Number": 4731 +} \ No newline at end of file diff --git a/apps/kavita/config/cache-long/pr_cache/pr_4733.json b/apps/kavita/config/cache-long/pr_cache/pr_4733.json new file mode 100644 index 0000000..e78de13 --- /dev/null +++ b/apps/kavita/config/cache-long/pr_cache/pr_4733.json @@ -0,0 +1,7 @@ +{ + "Title": "Scrobble Provider Rework", + "Body": "This is a massive update to our Scrobble system and a major expansion into providers, by adding 2 new ones: Hardcover (Traditional Books) and MangaBaka (Manga, Light Novels, etc). We\u0027ve reworked the code from scratch to bring a much better experience, from rate limit tweaks, to allowing backfilling per-provider unlimited times, to the ability to build rules to trigger states for scrobble. \r\n\r\nWe will be taking a break from delivering overhauls to realign the UX and polish these recent additions. Please help by testing and providing feedback, both positive and constructive. \r\n\r\n\r\n\u003Cimg width=\u00221624\u0022 height=\u0022814\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/8d302034-7cc0-4aa6-80a7-d5ab3e87ae8b\u0022 /\u003E\r\n\r\n\u003Cimg width=\u00221612\u0022 height=\u0022846\u0022 alt=\u0022image\u0022 src=\u0022https://github.com/user-attachments/assets/a394f31b-2701-44d4-866c-db13950cd271\u0022 /\u003E\r\n\r\n\r\n# Added \r\n- Added: Added Mangabaka as scrobble provider\r\n- Added: Added Hardcover as scrobble provider\r\n- Added: Added per provider settings (library, age ratings, scrobble progress/ratings/reviews, etc))\r\n- Added: Added inactive \u0026 on hold rules (Auto transition series/books from reading to on hold after x days) (Closes #2447)\r\n- Added: Scrobble Providers can now show information, like username. \r\n\r\n# Changed\r\n- Changed: Improved AniList scrobble speed drastically \r\n- Changed: Scrobble keybind now opens my activity instead\r\n- Changed: Users can now run backfilling of history per provider as many times as they want. Kavita will slowly churn thru it all (note: Re-running multiple times will drastically inflate queue for no reason). \r\n- Changed: (UX) Complete UX overhaul of Scrobble providers (now found under Kavita\u002B \u003E Connections )\r\n\r\n# Fixed\r\n- Fixed: Fixed up token expired warning showing too often (Fixes #4728, Fixes #4720)\r\n- Fixed: Fixed being unable to reset external ids (Fixes #4719)\r\n- Fixed: Fixed getting stuck in a loop if OIDC config is removed while previously being logged in with OIDC\r\n- Fixed: Fixed scrobble events getting marked as processed when hitting the rate limit under some circumstances\r\n- Fixed: Fixed a bug where Rereading a chapter then moving to the next wouldn\u0027t reset the page to 0\r\n\r\nCloses #4710, Closes #4733, Closes #3685", + "Html_Url": "https://github.com/Kareadita/Kavita/pull/4733", + "Merged_At": "2026-06-05T21:45:36Z", + "Number": 4733 +} \ No newline at end of file diff --git a/apps/kavita/config/covers/v1_c1.png b/apps/kavita/config/covers/v1_c1.png new file mode 100644 index 0000000..514bb49 Binary files /dev/null and b/apps/kavita/config/covers/v1_c1.png differ diff --git a/apps/kavita/config/covers/v2_c2.png b/apps/kavita/config/covers/v2_c2.png new file mode 100644 index 0000000..1e94318 Binary files /dev/null and b/apps/kavita/config/covers/v2_c2.png differ diff --git a/apps/kavita/config/progress_export-v0.8.5.csv b/apps/kavita/config/progress_export-v0.8.5.csv new file mode 100644 index 0000000..6dcd722 --- /dev/null +++ b/apps/kavita/config/progress_export-v0.8.5.csv @@ -0,0 +1 @@ +LibraryId,LibraryName,SeriesName,VolumeRange,VolumeLookupName,ChapterRange,MangaFileName,MangaFilePath,AppUserName,AppUserId,PagesRead,BookScrollId,ProgressCreated,ProgressLastModified diff --git a/apps/kavita/config/progress_export.csv b/apps/kavita/config/progress_export.csv new file mode 100644 index 0000000..6dcd722 --- /dev/null +++ b/apps/kavita/config/progress_export.csv @@ -0,0 +1 @@ +LibraryId,LibraryName,SeriesName,VolumeRange,VolumeLookupName,ChapterRange,MangaFileName,MangaFilePath,AppUserName,AppUserId,PagesRead,BookScrollId,ProgressCreated,ProgressLastModified diff --git a/apps/kavita/config/temp/README.md b/apps/kavita/config/temp/README.md new file mode 100644 index 0000000..cb27db3 --- /dev/null +++ b/apps/kavita/config/temp/README.md @@ -0,0 +1,74 @@ +# Kavita Themes +This will serve as a central hub to store all community made themes + +**Themes are provided as-is. Any issues with the themes need to be directed to the theme's author.** + +--- +## Native Themes +| Theme Name | Author | Description | Kavita Compatibility | Preview | Total Installs | +|------------|--------|-----------------------------------------------------|----------------------|---------|----------------| +| Light | [gmahomarf](https://github.com/gmahomarf) | A light theme for those that don't like—or can't use—dark themes | 0.8.8 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Light/preview.png) | | +| Dark-Pre-Overhaul | [majora2007](https://github.com/majora2007) | The dark theme from v0.8.2 for those that don't like the newer dark theme | 0.8.2 | [Pre1](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Dark-Pre-Overhaul/preview1.png) [Pre2](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Dark-Pre-Overhaul/preview2.png) | | +| DarkNight | [Elry](https://github.com/ElryWeeb) | A very Dark Theme, for the Darkmode lovers. | 0.7.14 | [Pre1](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/DarkNight/preview1.png) [Pre2](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/DarkNight/preview2.png) | | +| CutePink | [Elry](https://github.com/ElryWeeb) | A cute and soft Pink Theme. | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/CutePink/preview.png) | | +| Brilliance | [Elry](https://github.com/ElryWeeb) | A modern theme for e-ink devices. | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Brilliance/preview.png) | | +| E-Ink | [majora2007](https://github.com/majora2007) | A simplified theme for e-ink devices | 0.8.2 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/E-Ink/preview.jpg) | | +| Nord | [ice45core0](https://github.com/ice45core0) | A dark pastel theme using the Nord color palette | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Nord/preview.png) | | +| Dracula | [Matguitarist](https://github.com/matguitarist) | A Dracula inspired theme | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Dracula/preview.jpg) | | +| Overseerr | [Matguitarist](https://github.com/matguitarist) | A Overseer inspired theme | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Overseerr/preview.jpg) | | +| Aquamarine | [Matguitarist](https://github.com/matguitarist) | An Aquamarine theme | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Aquamarine/preview.jpg) | | +| Solarized | [Matguitarist](https://github.com/matguitarist) | A Solarized theme | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Solarized/preview.jpg) | | +| DarkPink | [Matguitarist](https://github.com/matguitarist) | A Darkpink theme | 0.7.14 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/DarkPink/preview.jpg) | | +| Battery-Saver-Dark-Mode | [RPLgrime](https://github.com/RPLgrime) | A dark mode with gray text and reduced brightness on images. | 0.8.2 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Battery-Saver-Dark-Mode/preview.jpg) | | +| No-Colorscape | [RPLgrime](https://github.com/majora2007) | Everything Kavita but with the Colorscape system turned off | 0.8.3 | [Preview 1](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/No-Colorscape/preview-1.png) [Preview 2](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/No-Colorscape/preview-2.png) | | +| Chapter-List-View | [Zeoic](https://github.com/Zeoic) | Changes the chapter card view into a list without thumbnails | 0.8.3 | [Preview](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/Chapter-List-View/preview.png) | | +| ElegantFin | [enieuwy](https://github.com/enieuwy/) | Soft "glass" header, decluttered covers, long multi-line titles, rounded corners, cover mouse-over animations, & space-saving for first-class library view on mobile | 0.8.9.0 | [Library, mobile](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/ElegantFin/Library,%20mobile.png) [Library, large](https://github.com/Kareadita/Themes/blob/main/Native%20Themes/ElegantFin/Library,%20large.png) | | + + +### Native Theme Installation (pre-v0.8.2.0) +1. Download the `theme-name.css` file. +2. Place it in the `/config/themes` folder of your [Kavita](https://github.com/Kareadita/Kavita) installation. +3. Within Kavita, navigate to your user settings and click the Theme tab. +4. Click the Scan button. +5. Apply and/or Set Default your newly added theme. + +### Native Theme Installation (v0.8.2.0+) +1. In the upper right corner of Kavita, drop down your Username > Settings > Theme tab, then download and apply the desired theme from this repo. + +--- + +## Style Injected Themes +| Theme Name | Author | Description | Kavita Compatibility | Preview | +|------------|--------|-----------------------------------------------------|----------------------|---------| +| Plex-like | [MrRobotjs](https://github.com/MrRobotjs) | A Plex-inspired theme | ? | [Preview](https://github.com/Kareadita/Themes/blob/main/Style%20Injected%20Themes/Plex-like/preview.png) +| | | | | + +### Style Injected Theme Installation +1. Install a style injector extension for your browser, like [xStyle](https://chrome.google.com/webstore/detail/xstyle/hncgkmhphmncjohllpoleelnibpmccpj/related?hl=en) +2. Download the theme scss/css. +3. If necessary edit the theme with the domain where your Kavita install is running. +4. If scss, compile to css using your preferred method. [You can do it online.](https://www.cssportal.com/scss-to-css/) +5. Install the theme in the style injector extension. +6. Load/Refresh your Kavita site. + +--- +## Contributing +1. Clone the repo. +2. Create a folder with your theme name in the appropriate top level folder (native or style injected). +3. Add your `theme-name.css` file inside the created folder with the appropriate filename. + - You can optionally add a preview image with the following format: `preview.ext` (feel free to add multiple showcasing your theme) +4. Add your theme to the `README.md` table with all fields filled out. Ensure the Theme Name matches the Folder name exactly. +5. Submit a PR. + +! Note: Your folder name and theme filename should match for the theme to work correctly in Kavita. + +--- +## Issues +If you have an issue, raise an Issue with the theme name in the title i.e. `[Light] Side Nav not readable on mobile` and tag the person from the Readme table. +Kavita team does not provide support on any themes, even those created by majora2007. + +If you identify bugs in the themeing, please raise an Issue on the Kavita Github for support or development work. Theme feature requests belong on the Kavita Discussions. + +--- +## Creating a Theme +Please follow the documentation on our Wiki: https://wiki.kavitareader.com/guides/themes diff --git a/apps/kavita/config/templates/EmailChange.html b/apps/kavita/config/templates/EmailChange.html new file mode 100644 index 0000000..7eb3461 --- /dev/null +++ b/apps/kavita/config/templates/EmailChange.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + Event - [Plain HTML] + + + + + + + + + + + + + + + + + + + + + + +
+ +
Your account's email has been updated on {{InvitingUser}}'s Kavita instance. Click the button to validate your email.
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/apps/kavita/config/templates/EmailConfirm.html b/apps/kavita/config/templates/EmailConfirm.html new file mode 100644 index 0000000..dff300d --- /dev/null +++ b/apps/kavita/config/templates/EmailConfirm.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + Event - [Plain HTML] + + + + + + + + + + + + + + + + + + + + + + +
+ +
You have been invited to {{InvitingUser}}'s Kavita instance. Click the button to accept the invite.
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/apps/kavita/config/templates/EmailPasswordReset.html b/apps/kavita/config/templates/EmailPasswordReset.html new file mode 100644 index 0000000..8c7c0a9 --- /dev/null +++ b/apps/kavita/config/templates/EmailPasswordReset.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + Kavita - [Plain HTML] + + + + + + + + + + + + + + + + + + + + + + +
+ +
Email confirmation is required for continued access. Click the button to confirm your email.
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/apps/kavita/config/templates/EmailTest.html b/apps/kavita/config/templates/EmailTest.html new file mode 100644 index 0000000..1851765 --- /dev/null +++ b/apps/kavita/config/templates/EmailTest.html @@ -0,0 +1,325 @@ + + + + + + + + + + + + + Event - [Plain HTML] + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ This is a Test Email +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/apps/kavita/config/templates/SendToDevice.html b/apps/kavita/config/templates/SendToDevice.html new file mode 100644 index 0000000..4f82e19 --- /dev/null +++ b/apps/kavita/config/templates/SendToDevice.html @@ -0,0 +1,323 @@ + + + + + + + + + + + + + Event - [Plain HTML] + + + + + + + + + + + + + + + + + + + + + + +
+ +
You've been sent a file from Kavita!
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/apps/kite-ai/docker-compose.yml b/apps/kite-ai/docker-compose.yml new file mode 100644 index 0000000..9dfe0e1 --- /dev/null +++ b/apps/kite-ai/docker-compose.yml @@ -0,0 +1,34 @@ +services: + litellm: + image: ghcr.io/berriai/litellm:main-latest + container_name: kite-litellm + restart: unless-stopped + env_file: + - .env + command: ["--config", "/app/config.yaml", "--port", "4000"] + volumes: + - ./litellm_config.yaml:/app/config.yaml + ports: + - "4000:4000" + + openwebui: + image: ghcr.io/open-webui/open-webui:main + container_name: kite-openwebui + restart: unless-stopped + env_file: + - .env + environment: + - WEBUI_NAME=Kite AI + - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY} + - ENABLE_SIGNUP=false + - OPENAI_API_BASE_URL=http://litellm:4000/v1 + - OPENAI_API_KEY=${LITELLM_MASTER_KEY} + volumes: + - open-webui:/app/backend/data + ports: + - "3100:8080" + depends_on: + - litellm + +volumes: + open-webui: diff --git a/apps/kite-ai/litellm_config.yaml b/apps/kite-ai/litellm_config.yaml new file mode 100644 index 0000000..502c08b --- /dev/null +++ b/apps/kite-ai/litellm_config.yaml @@ -0,0 +1,18 @@ +model_list: + - model_name: kite-free + litellm_params: + model: openrouter/openrouter/free + api_key: os.environ/OPENROUTER_API_KEY + + - model_name: kite-llama + litellm_params: + model: openrouter/meta-llama/llama-3.1-8b-instruct:free + api_key: os.environ/OPENROUTER_API_KEY + + - model_name: kite-deepseek + litellm_params: + model: openrouter/deepseek/deepseek-chat-v3-0324:free + api_key: os.environ/OPENROUTER_API_KEY + +general_settings: + master_key: os.environ/LITELLM_MASTER_KEY diff --git a/apps/kitestacks-portal-test/api/Dockerfile b/apps/kitestacks-portal-test/api/Dockerfile new file mode 100644 index 0000000..b35acc5 --- /dev/null +++ b/apps/kitestacks-portal-test/api/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim + +# docker CLI so we can report the host's docker version via the mounted socket +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl gnupg && \ + install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \ + chmod a+r /etc/apt/keyrings/docker.asc && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \ + > /etc/apt/sources.list.d/docker.list && \ + apt-get update && apt-get install -y --no-install-recommends docker-ce-cli && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY main.py . + +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/apps/kitestacks-portal-test/api/main.py b/apps/kitestacks-portal-test/api/main.py new file mode 100644 index 0000000..637d5fb --- /dev/null +++ b/apps/kitestacks-portal-test/api/main.py @@ -0,0 +1,352 @@ +""" +KiteStacks metrics API. + +Exposes real host metrics via psutil. Container must be launched with +`pid: host` and the relevant host paths mounted so psutil reads the +laptop's namespaces rather than the container's. +""" +import os +os.environ.setdefault("PROCFS_PATH", os.environ.get("HOST_PROC", "/proc")) + +import socket +import platform +import subprocess +import time +from functools import lru_cache + +import httpx +import psutil +psutil.PROCFS_PATH = os.environ.get("HOST_PROC", "/proc") + +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI(title="KiteStacks Metrics API") + +# Cache last network sample so we can compute deltas (bytes/sec) +_last_net = {"ts": time.time(), "sent": 0, "recv": 0, "iface": None} + +# Skip virtual / container interfaces when auto-detecting the host's nic +SKIP_IFACE_PREFIXES = ( + "docker", "br-", "veth", "cni", "flannel", "cali", + "lo", "tun", "tap", "virbr", "kube", +) + + +def active_interface() -> str | None: + """Auto-detect the interface used for the default route on the HOST.""" + try: + route_path = "/host/proc/net/route" + if os.path.exists(route_path): + with open(route_path) as f: + for line in f.readlines()[1:]: + fields = line.strip().split() + if len(fields) >= 2 and fields[1] == "00000000": + name = fields[0] + if not name.startswith(SKIP_IFACE_PREFIXES): + return name + except Exception: + pass + # Fallback: first non-virtual interface that is up + try: + for name, stats in psutil.net_if_stats().items(): + if stats.isup and not name.startswith(SKIP_IFACE_PREFIXES): + return name + except Exception: + pass + return None + + +@app.get("/api/metrics") +def metrics(): + global _last_net + + # CPU — short blocking sample so we don't return 0 on the first call + cpu_pct = psutil.cpu_percent(interval=0.3) + cpu_per_core = psutil.cpu_percent(interval=None, percpu=True) + + # RAM + vm = psutil.virtual_memory() + ram_used_gb = vm.used / (1024 ** 3) + ram_total_gb = vm.total / (1024 ** 3) + ram_pct = vm.percent + + # Storage — root filesystem (host root is mounted at /host) + root_path = "/host" if os.path.ismount("/host") else "/" + du = psutil.disk_usage(root_path) + storage_used_gb = du.used / (1024 ** 3) + storage_total_gb = du.total / (1024 ** 3) + storage_pct = du.percent + + # Uptime — from host /proc/uptime if available, otherwise psutil + uptime_s = None + try: + host_uptime = "/host/proc/uptime" + if os.path.exists(host_uptime): + with open(host_uptime) as f: + uptime_s = int(float(f.read().split()[0])) + except Exception: + pass + if uptime_s is None: + uptime_s = int(time.time() - psutil.boot_time()) + + days = uptime_s // 86400 + hours = (uptime_s % 86400) // 3600 + minutes = (uptime_s % 3600) // 60 + + # Network — active host interface, bytes/sec since last call + iface = active_interface() + net_sent_kbs = 0.0 + net_recv_kbs = 0.0 + if iface: + counters = psutil.net_io_counters(pernic=True).get(iface) + if counters: + now = time.time() + dt = max(now - _last_net["ts"], 0.001) + if _last_net["iface"] == iface and _last_net["sent"] > 0: + net_sent_kbs = max(0.0, (counters.bytes_sent - _last_net["sent"]) / dt / 1024) + net_recv_kbs = max(0.0, (counters.bytes_recv - _last_net["recv"]) / dt / 1024) + _last_net = { + "ts": now, + "sent": counters.bytes_sent, + "recv": counters.bytes_recv, + "iface": iface, + } + + overview = _system_overview() + + return { + "cpu": {"pct": round(cpu_pct, 1), "cores": [round(c, 1) for c in cpu_per_core]}, + "ram": { + "pct": round(ram_pct, 1), + "used_gb": round(ram_used_gb, 1), + "total_gb": round(ram_total_gb, 1), + }, + "storage": { + "pct": round(storage_pct, 1), + "used_gb": round(storage_used_gb, 0), + "total_gb": round(storage_total_gb, 0), + }, + "uptime": { + "days": days, + "hours": hours, + "minutes": minutes, + "seconds": uptime_s, + }, + "network": { + "iface": iface, + "tx_kbs": round(net_sent_kbs, 1), + "rx_kbs": round(net_recv_kbs, 1), + }, + "overview": overview, + "ts": int(time.time()), + } + + +@lru_cache(maxsize=1) +def _system_overview(): + """Cached because none of this changes during a session.""" + # Hostname — read from host /etc/hostname if mounted; fall back to socket + hostname = socket.gethostname() + try: + host_hostname = "/host/etc/hostname" + if os.path.exists(host_hostname): + with open(host_hostname) as f: + hostname = f.read().strip() or hostname + except Exception: + pass + + # OS pretty name from host /etc/os-release if mounted + os_pretty = platform.platform() + try: + release_path = ( + "/host/etc/os-release" + if os.path.exists("/host/etc/os-release") + else "/etc/os-release" + ) + with open(release_path) as f: + for line in f: + if line.startswith("PRETTY_NAME="): + os_pretty = line.split("=", 1)[1].strip().strip('"') + break + except Exception: + pass + + # Kernel from host /proc/sys/kernel/osrelease if mounted + kernel = platform.release() + try: + version_path = "/host/proc/sys/kernel/osrelease" + if os.path.exists(version_path): + with open(version_path) as f: + kernel = f.read().strip() + except Exception: + pass + + # Docker version via mounted socket + docker_ver = "unknown" + try: + out = subprocess.check_output( + ["docker", "--version"], stderr=subprocess.DEVNULL, timeout=2 + ).decode() + # "Docker version 25.0.5, build ..." + docker_ver = out.split()[2].rstrip(",") + except Exception: + pass + + # Timezone + tz = time.tzname[0] if time.tzname else "UTC" + try: + tz_link = ( + "/host/etc/localtime" + if os.path.exists("/host/etc/localtime") + else "/etc/localtime" + ) + if os.path.islink(tz_link): + tz = os.readlink(tz_link).split("zoneinfo/")[-1] + except Exception: + pass + + return { + "hostname": hostname, + "os": os_pretty, + "kernel": kernel, + "docker": docker_ver, + "timezone": tz, + } + + +# Weather — cached for 10 minutes +_weather_cache = {"ts": 0, "data": None} + + +@app.get("/api/weather") +async def weather(): + global _weather_cache + now = time.time() + if _weather_cache["data"] and (now - _weather_cache["ts"]) < 600: + return _weather_cache["data"] + + try: + async with httpx.AsyncClient(timeout=5.0) as client: + # 1. Geolocate via public IP (no key needed) + geo = await client.get("https://ipapi.co/json/") + geo.raise_for_status() + g = geo.json() + lat, lon = g.get("latitude"), g.get("longitude") + city = g.get("city", "Unknown") + + # 2. Weather from open-meteo (no key) + wx = await client.get( + "https://api.open-meteo.com/v1/forecast", + params={ + "latitude": lat, + "longitude": lon, + "current": "temperature_2m,weather_code,is_day", + "temperature_unit": "fahrenheit", + }, + ) + wx.raise_for_status() + w = wx.json()["current"] + + data = { + "temp_f": round(w["temperature_2m"]), + "code": w["weather_code"], + "is_day": bool(w["is_day"]), + "city": city, + "description": _wcode(w["weather_code"]), + } + _weather_cache = {"ts": now, "data": data} + return data + except Exception as e: + return JSONResponse( + {"error": str(e), "temp_f": None, "description": "Offline"}, + status_code=200, + ) + + +def _wcode(code: int) -> str: + """Open-Meteo WMO weather code → short description.""" + m = { + 0: "Clear Sky", 1: "Mainly Clear", 2: "Partly Cloudy", 3: "Overcast", + 45: "Fog", 48: "Rime Fog", + 51: "Light Drizzle", 53: "Drizzle", 55: "Heavy Drizzle", + 61: "Light Rain", 63: "Rain", 65: "Heavy Rain", + 71: "Light Snow", 73: "Snow", 75: "Heavy Snow", + 77: "Snow Grains", + 80: "Rain Showers", 81: "Heavy Showers", 82: "Violent Showers", + 85: "Snow Showers", 86: "Heavy Snow Showers", + 95: "Thunderstorm", 96: "Thunderstorm + Hail", 99: "Severe Thunderstorm", + } + return m.get(code, "Unknown") + + + +# Forgejo activity — public events only, cached 60s +_forge_cache = {"ts": 0, "data": None} +FORGEJO_BASE = os.environ.get("FORGEJO_BASE", "https://gitforge.kitestacks.com") + + +@app.get("/api/activity") +async def activity(): + """Fetch recent public Forgejo events across all public repos.""" + global _forge_cache + now = time.time() + if _forge_cache["data"] is not None and (now - _forge_cache["ts"]) < 60: + return _forge_cache["data"] + + items = [] + try: + async with httpx.AsyncClient(timeout=6.0, follow_redirects=True) as client: + # /repos/search?limit=20&sort=updated → most recently active public repos + r = await client.get( + f"{FORGEJO_BASE}/api/v1/repos/search", + params={"limit": 20, "sort": "updated", "order": "desc", "private": "false"}, + ) + r.raise_for_status() + repos = r.json().get("data", []) + + # For each repo, fetch its activity feed (small, fast); take a few + for repo in repos[:8]: + owner = repo.get("owner", {}).get("login") + name = repo.get("name") + if not owner or not name: + continue + if repo.get("private"): + continue + try: + fr = await client.get( + f"{FORGEJO_BASE}/api/v1/repos/{owner}/{name}/activities/feeds", + params={"limit": 5}, + ) + if fr.status_code != 200: + continue + for ev in fr.json(): + items.append({ + "repo": f"{owner}/{name}", + "repo_url": f"{FORGEJO_BASE}/{owner}/{name}", + "op_type": ev.get("op_type", "unknown"), + "ref_name": ev.get("ref_name", ""), + "comment": (ev.get("content") or "")[:120], + "actor": (ev.get("act_user") or {}).get("login", "unknown"), + "created": ev.get("created"), + }) + except Exception: + continue + + # Sort by created desc, take top 8 + items.sort(key=lambda x: x.get("created", ""), reverse=True) + items = items[:8] + + data = {"items": items, "source": "forgejo", "ts": int(now)} + _forge_cache = {"ts": now, "data": data} + return data + except Exception as e: + return JSONResponse( + {"error": str(e), "items": [], "source": "forgejo"}, + status_code=200, + ) + + +@app.get("/api/health") +def health(): + return {"ok": True} diff --git a/apps/kitestacks-portal-test/api/requirements.txt b/apps/kitestacks-portal-test/api/requirements.txt new file mode 100644 index 0000000..4e9e0bd --- /dev/null +++ b/apps/kitestacks-portal-test/api/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 +psutil==6.1.0 +httpx==0.27.2 diff --git a/apps/kitestacks-portal-test/docker-compose.yml b/apps/kitestacks-portal-test/docker-compose.yml new file mode 100644 index 0000000..8f365e8 --- /dev/null +++ b/apps/kitestacks-portal-test/docker-compose.yml @@ -0,0 +1,33 @@ +services: + kitestacks-portal-test: + image: nginx:alpine + container_name: kitestacks-portal-test + restart: unless-stopped + ports: + - "3008:80" + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./public:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - metrics-api + + metrics-api: + build: ./api + container_name: kitestacks-metrics-api + restart: unless-stopped + pid: host + network_mode: host # see host interfaces & routes for real network metrics + environment: + - HOST_PROC=/host/proc + - HOST_SYS=/host/sys + - HOST_ETC=/host/etc + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + - /etc/hostname:/host/etc/hostname:ro + - /etc/localtime:/host/etc/localtime:ro + - /:/host:ro + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/apps/kitestacks-portal-test/docker-compose.yml.backup-pre-upgrade-2026-06-07-0017 b/apps/kitestacks-portal-test/docker-compose.yml.backup-pre-upgrade-2026-06-07-0017 new file mode 100644 index 0000000..73150aa --- /dev/null +++ b/apps/kitestacks-portal-test/docker-compose.yml.backup-pre-upgrade-2026-06-07-0017 @@ -0,0 +1,9 @@ +services: + kitestacks-portal-test: + image: nginx:alpine + container_name: kitestacks-portal-test + restart: unless-stopped + ports: + - "3008:80" + volumes: + - ./public:/usr/share/nginx/html:ro diff --git a/apps/kitestacks-portal-test/nginx.conf b/apps/kitestacks-portal-test/nginx.conf new file mode 100644 index 0000000..03a7700 --- /dev/null +++ b/apps/kitestacks-portal-test/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://host.docker.internal:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 10s; + } + + location /images/ { + expires 7d; + add_header Cache-Control "public, max-age=604800"; + } +} diff --git a/apps/kitestacks-portal-test/public/app.js b/apps/kitestacks-portal-test/public/app.js new file mode 100644 index 0000000..8adf430 --- /dev/null +++ b/apps/kitestacks-portal-test/public/app.js @@ -0,0 +1,304 @@ +/* KiteStacks portal — live metrics client */ + +const GAUGE_CIRCUMFERENCE = 2 * Math.PI * 48; // r=48 in viewBox + +const els = { + cpuHud: document.getElementById('hud-cpu'), + ramHud: document.getElementById('hud-ram'), + storageHud: document.getElementById('hud-storage'), + uptimeHud: document.getElementById('hud-uptime'), + temp: document.getElementById('hud-temp'), + weatherDesc: document.getElementById('hud-weather-desc'), + + gCpu: document.getElementById('g-cpu'), + gCpuVal: document.getElementById('g-cpu-val'), + gRam: document.getElementById('g-ram'), + gRamVal: document.getElementById('g-ram-val'), + gRamSub: document.getElementById('g-ram-sub'), + gStorage: document.getElementById('g-storage'), + gStorageVal: document.getElementById('g-storage-val'), + gStorageSub: document.getElementById('g-storage-sub'), + + netTx: document.getElementById('net-tx'), + netRx: document.getElementById('net-rx'), + + waveCpu: document.getElementById('wave-cpu'), + waveNet: document.getElementById('wave-net'), +}; + +// History buffers for waveforms (last 30 samples) +const histCpu = []; +const histNet = []; +const HIST_MAX = 30; + +function setGauge(el, pct) { + if (!el) return; + const v = Math.max(0, Math.min(100, pct || 0)); + const offset = GAUGE_CIRCUMFERENCE * (1 - v / 100); + el.style.strokeDasharray = GAUGE_CIRCUMFERENCE.toFixed(2); + el.style.strokeDashoffset = offset.toFixed(2); +} + +function fmtKbs(v) { + if (v < 1) return v.toFixed(2) + ' KB/s'; + if (v < 1024) return v.toFixed(1) + ' KB/s'; + return (v / 1024).toFixed(2) + ' MB/s'; +} + +function updateWave(polyline, history) { + if (!polyline) return; + const max = Math.max(1, ...history); + const w = 120, h = 20; + const step = w / Math.max(1, HIST_MAX - 1); + const pts = history.map((v, i) => { + const x = i * step; + const y = h - (v / max) * (h - 2) - 1; + return `${x.toFixed(1)},${y.toFixed(1)}`; + }); + polyline.setAttribute('points', pts.join(' ')); +} + +async function fetchMetrics() { + try { + const r = await fetch('/api/metrics', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + + // HUD + els.cpuHud.textContent = d.cpu.pct.toFixed(0) + '%'; + els.ramHud.textContent = `${d.ram.used_gb} GB / ${d.ram.total_gb} GB`; + els.storageHud.textContent = `${d.storage.used_gb} GB / ${d.storage.total_gb} GB`; + const u = d.uptime; + els.uptimeHud.textContent = `${u.days}d ${u.hours}h ${u.minutes}m`; + + // Gauges + setGauge(els.gCpu, d.cpu.pct); + els.gCpuVal.textContent = d.cpu.pct.toFixed(0) + '%'; + + setGauge(els.gRam, d.ram.pct); + els.gRamVal.textContent = d.ram.pct.toFixed(0) + '%'; + els.gRamSub.textContent = `${d.ram.used_gb} GB / ${d.ram.total_gb} GB`; + + setGauge(els.gStorage, d.storage.pct); + els.gStorageVal.textContent = d.storage.pct.toFixed(0) + '%'; + els.gStorageSub.textContent = `${d.storage.used_gb} GB / ${d.storage.total_gb} GB`; + + // Network + els.netTx.textContent = fmtKbs(d.network.tx_kbs); + els.netRx.textContent = fmtKbs(d.network.rx_kbs); + + // Per-core CPU + renderCores(d.cpu.cores || []); + + // Waveform histories + histCpu.push(d.cpu.pct); + if (histCpu.length > HIST_MAX) histCpu.shift(); + updateWave(els.waveCpu, histCpu); + + histNet.push(d.network.tx_kbs + d.network.rx_kbs); + if (histNet.length > HIST_MAX) histNet.shift(); + updateWave(els.waveNet, histNet); + + } catch (err) { + console.warn('metrics fetch failed', err); + } +} + +async function fetchWeather() { + try { + const r = await fetch('/api/weather', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + if (d.temp_f != null) { + els.temp.textContent = d.temp_f + '°F'; + els.weatherDesc.textContent = d.description || ''; + } else { + els.temp.textContent = '--°F'; + els.weatherDesc.textContent = d.description || 'Offline'; + } + } catch (err) { + console.warn('weather fetch failed', err); + els.weatherDesc.textContent = 'Offline'; + } +} + +// Search — checks card data-name first, else fall through to Google +window.kiteSearch = function (e) { + e.preventDefault(); + const q = document.getElementById('search-input').value.trim(); + if (!q) return; + const cards = Array.from(document.querySelectorAll('.card')); + const match = cards.find(c => + (c.dataset.name || '').toLowerCase().includes(q.toLowerCase()) + ); + // a valid match is a card with a real href (not a placeholder) + if (match && match.getAttribute('href') && match.getAttribute('href') !== '#') { + window.open(match.href, '_blank', 'noopener'); + } else { + window.open(`https://www.google.com/search?q=${encodeURIComponent(q)}`, '_blank', 'noopener'); + } +}; + +// Coming-soon toast for cards without a real URL +function showToast(msg) { + let t = document.getElementById('ks-toast'); + if (!t) { + t = document.createElement('div'); + t.id = 'ks-toast'; + document.body.appendChild(t); + } + t.textContent = msg; + t.classList.add('show'); + clearTimeout(t._hide); + t._hide = setTimeout(() => t.classList.remove('show'), 2200); +} + +document.addEventListener('click', (e) => { + const card = e.target.closest('.card[data-coming-soon]'); + if (card) { + e.preventDefault(); + showToast(`${card.dataset.name} — coming soon`); + } +}); + +// Kick off +fetchMetrics(); +fetchWeather(); +setInterval(fetchMetrics, 2000); +setInterval(fetchWeather, 10 * 60 * 1000); // every 10 min + +// ─── Recent Activity (Forgejo public events) ─────────────────── +const ACT_ICONS = { + commit_repo: { glyph: '◆', color: '#54e8ff', label: 'Commit' }, + push_tag: { glyph: '⛓', color: '#bd6cff', label: 'Tag' }, + create_branch: { glyph: '⎇', color: '#39ff7a', label: 'Branch' }, + create_repo: { glyph: '✦', color: '#ffd84a', label: 'Repo' }, + fork_repo: { glyph: '⑂', color: '#ff5fb1', label: 'Fork' }, + pull_request: { glyph: '⇄', color: '#54e8ff', label: 'PR' }, + comment_pull: { glyph: '💬', color: '#bd6cff', label: 'Comment' }, + comment_issue: { glyph: '💬', color: '#bd6cff', label: 'Comment' }, + create_issue: { glyph: '◉', color: '#ff45c8', label: 'Issue' }, + close_issue: { glyph: '⊘', color: '#39ff7a', label: 'Closed' }, + merge_pull_request:{ glyph: '⮕', color: '#39ff7a', label: 'Merged' }, + delete_branch: { glyph: '✕', color: '#ff5fb1', label: 'Del' }, + star_repo: { glyph: '★', color: '#ffd84a', label: 'Star' }, +}; + +function relTime(iso) { + if (!iso) return ''; + const d = new Date(iso); + const diff = (Date.now() - d.getTime()) / 1000; + if (diff < 60) return Math.max(1, Math.floor(diff)) + 's ago'; + if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; + if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; + if (diff < 86400*7) return Math.floor(diff / 86400) + 'd ago'; + return d.toISOString().slice(0, 10); +} + +function describeEvent(ev) { + const meta = ACT_ICONS[ev.op_type] || { glyph: '●', color: '#54e8ff', label: ev.op_type }; + let text; + switch (ev.op_type) { + case 'commit_repo': + text = `${ev.actor} pushed to ${ev.repo}`; + break; + case 'create_branch': + text = `${ev.actor} created branch ${ev.ref_name || ''} in ${ev.repo}`; + break; + case 'push_tag': + text = `${ev.actor} tagged ${ev.ref_name || ''} in ${ev.repo}`; + break; + case 'create_repo': + text = `${ev.actor} created ${ev.repo}`; + break; + case 'fork_repo': + text = `${ev.actor} forked ${ev.repo}`; + break; + case 'pull_request': + case 'merge_pull_request': + text = `${ev.actor} ${ev.op_type === 'merge_pull_request' ? 'merged PR in' : 'opened PR in'} ${ev.repo}`; + break; + case 'create_issue': + text = `${ev.actor} opened issue in ${ev.repo}`; + break; + case 'close_issue': + text = `${ev.actor} closed issue in ${ev.repo}`; + break; + case 'comment_issue': + case 'comment_pull': + text = `${ev.actor} commented in ${ev.repo}`; + break; + case 'star_repo': + text = `${ev.actor} starred ${ev.repo}`; + break; + case 'delete_branch': + text = `${ev.actor} deleted ${ev.ref_name || 'branch'} in ${ev.repo}`; + break; + default: + text = `${ev.actor} · ${ev.op_type} · ${ev.repo}`; + } + return { meta, text }; +} + +async function fetchActivity() { + const ul = document.getElementById('activity-list'); + if (!ul) return; + try { + const r = await fetch('/api/activity', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + const items = d.items || []; + if (items.length === 0) { + ul.innerHTML = '
  • No recent public activity
  • '; + return; + } + ul.innerHTML = items.slice(0, 5).map(ev => { + const { meta, text } = describeEvent(ev); + const safeText = text.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + return `
  • + ${meta.glyph} + ${safeText} + ${relTime(ev.created)} +
  • `; + }).join(''); + } catch (err) { + console.warn('activity fetch failed', err); + ul.innerHTML = '
  • Forgejo unreachable
  • '; + } +} + +fetchActivity(); +setInterval(fetchActivity, 60 * 1000); + +// ─── Per-core CPU renderer ────────────────────────────────────── +function renderCores(cores) { + const grid = document.getElementById('cores-grid'); + const meta = document.getElementById('cores-meta'); + if (!grid) return; + if (!cores.length) { + meta.textContent = '--'; + return; + } + meta.textContent = `${cores.length} cores · avg ${(cores.reduce((a,b)=>a+b,0)/cores.length).toFixed(0)}%`; + + // Build once, then update in place to avoid layout thrash + if (grid.children.length !== cores.length) { + grid.innerHTML = cores.map((_, i) => ` +
    +
    + C${i} + 0% +
    +
    +
    + `).join(''); + } + cores.forEach((pct, i) => { + const el = grid.children[i]; + if (!el) return; + el.querySelector('.core-pct').textContent = pct.toFixed(0) + '%'; + el.querySelector('.core-bar').style.width = Math.max(0, Math.min(100, pct)) + '%'; + el.dataset.load = pct > 75 ? 'high' : pct > 40 ? 'med' : 'low'; + }); +} + diff --git a/apps/kitestacks-portal-test/public/images/cyberpunk-bg.png b/apps/kitestacks-portal-test/public/images/cyberpunk-bg.png new file mode 100644 index 0000000..9ce3953 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/cyberpunk-bg.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/artificial-intelligence.png b/apps/kitestacks-portal-test/public/images/icons/artificial-intelligence.png new file mode 100644 index 0000000..19f877c Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/artificial-intelligence.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/authentik.png b/apps/kitestacks-portal-test/public/images/icons/authentik.png new file mode 100644 index 0000000..791d370 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/authentik.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/cilium.png b/apps/kitestacks-portal-test/public/images/icons/cilium.png new file mode 100644 index 0000000..4550926 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/cilium.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/cloudflare.png b/apps/kitestacks-portal-test/public/images/icons/cloudflare.png new file mode 100644 index 0000000..b17dc59 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/cloudflare.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/cloudnativepg.png b/apps/kitestacks-portal-test/public/images/icons/cloudnativepg.png new file mode 100644 index 0000000..2c4e4df Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/cloudnativepg.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/fluxcd.png b/apps/kitestacks-portal-test/public/images/icons/fluxcd.png new file mode 100644 index 0000000..9009b64 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/fluxcd.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/forgejo.png b/apps/kitestacks-portal-test/public/images/icons/forgejo.png new file mode 100644 index 0000000..d2c4ee5 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/forgejo.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/grafana.png b/apps/kitestacks-portal-test/public/images/icons/grafana.png new file mode 100644 index 0000000..6f7d9a0 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/grafana.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/kavita.png b/apps/kitestacks-portal-test/public/images/icons/kavita.png new file mode 100644 index 0000000..ef8a34c Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/kavita.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/kubernetes.png b/apps/kitestacks-portal-test/public/images/icons/kubernetes.png new file mode 100644 index 0000000..7e0985c Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/kubernetes.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/node-exporter.png b/apps/kitestacks-portal-test/public/images/icons/node-exporter.png new file mode 100644 index 0000000..d9790fe Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/node-exporter.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/open-webui.png b/apps/kitestacks-portal-test/public/images/icons/open-webui.png new file mode 100644 index 0000000..df1da98 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/open-webui.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/openproject.png b/apps/kitestacks-portal-test/public/images/icons/openproject.png new file mode 100644 index 0000000..3fe146a Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/openproject.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/openrouter.png b/apps/kitestacks-portal-test/public/images/icons/openrouter.png new file mode 100644 index 0000000..8118d79 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/openrouter.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/portainer.png b/apps/kitestacks-portal-test/public/images/icons/portainer.png new file mode 100644 index 0000000..5a8a585 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/portainer.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/prometheus.png b/apps/kitestacks-portal-test/public/images/icons/prometheus.png new file mode 100644 index 0000000..d9790fe Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/prometheus.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/shaarli.png b/apps/kitestacks-portal-test/public/images/icons/shaarli.png new file mode 100644 index 0000000..3439af9 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/shaarli.png differ diff --git a/apps/kitestacks-portal-test/public/images/icons/uptime-kuma.png b/apps/kitestacks-portal-test/public/images/icons/uptime-kuma.png new file mode 100644 index 0000000..713ca37 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/icons/uptime-kuma.png differ diff --git a/apps/kitestacks-portal-test/public/images/kitestacks-icon.png b/apps/kitestacks-portal-test/public/images/kitestacks-icon.png new file mode 100644 index 0000000..1bd0078 Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/kitestacks-icon.png differ diff --git a/apps/kitestacks-portal-test/public/images/kitestacks-logo.png b/apps/kitestacks-portal-test/public/images/kitestacks-logo.png new file mode 100644 index 0000000..9e86c0f Binary files /dev/null and b/apps/kitestacks-portal-test/public/images/kitestacks-logo.png differ diff --git a/apps/kitestacks-portal-test/public/index.html b/apps/kitestacks-portal-test/public/index.html new file mode 100644 index 0000000..549d8ec --- /dev/null +++ b/apps/kitestacks-portal-test/public/index.html @@ -0,0 +1,253 @@ + + + + + +KiteStacks.ao + + + + + + + + +
    + +
    + + +
    +
    + +
    + CPU + --% +
    +
    + +
    + +
    + RAM + -- GB / -- GB +
    +
    + +
    + +
    + STORAGE + -- GB / -- GB +
    +
    + +
    + +
    + UPTIME + --d --h --m +
    +
    + +
    + +
    + --°F + Loading… +
    +
    + + +
    + + +
    +
    + KiteStacks +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    + + +
    +
    » INFRASTRUCTURE
    + +
    + + +
    +
    +
    » MONITORING
    + +
    + +
    +
    » AI & AUTOMATION
    + +
    +
    + + +
    +
    +
    » KNOWLEDGE BASE
    + +
    + +
    +
    » DEVELOPMENT
    + +
    + +
    + + +
    + +
    +
    » SYSTEM STATUS
    +
    +
    + + + + +
    CPU
    +
    --%
    + + + +
    + +
    + + + + +
    RAM
    +
    --%
    +
    -- GB / -- GB
    +
    + +
    + + + + +
    STORAGE
    +
    --%
    +
    -- GB / -- GB
    +
    + +
    +
    NETWORK
    +
    +
    -- KB/s
    +
    -- KB/s
    +
    + + + +
    +
    + +
    +
    + CPU CORES + -- +
    +
    +
    +
    + +
    +
    » RECENT ACTIVITY
    +
      +
    • Loading recent activity…
    • +
    +
    + +
    + + + +
    + + + + diff --git a/apps/kitestacks-portal-test/public/index.html.backup-before-clean-layout b/apps/kitestacks-portal-test/public/index.html.backup-before-clean-layout new file mode 100644 index 0000000..6f36a5e --- /dev/null +++ b/apps/kitestacks-portal-test/public/index.html.backup-before-clean-layout @@ -0,0 +1,89 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    ☾ 72°F
    Clear Sky
    + +
    + +
    +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + + + diff --git a/apps/kitestacks-portal-test/public/index.html.backup-before-phase2 b/apps/kitestacks-portal-test/public/index.html.backup-before-phase2 new file mode 100644 index 0000000..322db0d --- /dev/null +++ b/apps/kitestacks-portal-test/public/index.html.backup-before-phase2 @@ -0,0 +1,84 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    72°FClear Sky
    + +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + diff --git a/apps/kitestacks-portal-test/public/index.html.backup-pre-upgrade-2026-06-07-0017 b/apps/kitestacks-portal-test/public/index.html.backup-pre-upgrade-2026-06-07-0017 new file mode 100644 index 0000000..322db0d --- /dev/null +++ b/apps/kitestacks-portal-test/public/index.html.backup-pre-upgrade-2026-06-07-0017 @@ -0,0 +1,84 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    72°FClear Sky
    + +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + diff --git a/apps/kitestacks-portal-test/public/style.css b/apps/kitestacks-portal-test/public/style.css new file mode 100644 index 0000000..8859799 --- /dev/null +++ b/apps/kitestacks-portal-test/public/style.css @@ -0,0 +1,678 @@ +/* =================================================================== + KiteStacks.ao — cyberpunk portal stylesheet + =================================================================== */ + +:root { + --bg: #05030f; + --panel-bg: rgba(10, 6, 28, 0.72); + --panel-bg-solid: #0a061c; + + --cyan: #54e8ff; + --cyan-bright: #7df3ff; + --magenta: #ff45c8; + --magenta-bright: #ff7adb; + --purple: #bd6cff; + --purple-bright: #d99dff; + --pink: #ff5fb1; + --green: #39ff7a; + --yellow: #ffd84a; + --orange: #ff8b3a; + + --text: #e6f3ff; + --text-dim: #8aa3c0; + + --font-display: 'Orbitron', sans-serif; + --font-ui: 'Rajdhani', sans-serif; + --font-mono: 'Share Tech Mono', monospace; +} + +* { box-sizing: border-box; } + +html, body { margin: 0; padding: 0; min-height: 100%; } + +body { + font-family: var(--font-ui); + color: var(--text); + background: var(--bg) url('/images/cyberpunk-bg.png') center top / cover no-repeat fixed; + background-attachment: fixed; + min-height: 100vh; + overflow-x: hidden; +} + +/* darkening overlay so neon pops */ +.bg-overlay { + position: fixed; inset: 0; + background: + radial-gradient(ellipse at top, rgba(120, 60, 200, 0.18), transparent 60%), + linear-gradient(180deg, rgba(5, 3, 15, 0.55), rgba(5, 3, 15, 0.85)); + pointer-events: none; + z-index: 0; +} + +.portal { + position: relative; + z-index: 1; + max-width: 1700px; + margin: 0 auto; + padding: 18px 26px 32px; + display: flex; + flex-direction: column; + gap: 18px; +} + +/* ================================================================= + TOP HUD + ================================================================= */ +.hud { + display: grid; + grid-template-columns: repeat(5, minmax(160px, 1fr)) 2fr; + gap: 14px; + align-items: stretch; +} + +.hud-cell { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + border-radius: 10px; + background: linear-gradient(180deg, rgba(15, 10, 40, 0.7), rgba(8, 5, 22, 0.85)); + border: 1px solid rgba(84, 232, 255, 0.35); + box-shadow: + 0 0 12px rgba(84, 232, 255, 0.15), + inset 0 0 18px rgba(84, 232, 255, 0.05); + color: var(--cyan); + min-height: 64px; +} + +.hud-icon { + width: 30px; height: 30px; + flex: 0 0 auto; + filter: drop-shadow(0 0 6px currentColor); +} + +.hud-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; } + +.hud-label { + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.16em; + color: var(--text-dim); + text-transform: uppercase; +} + +.hud-value { + font-family: var(--font-display); + font-weight: 700; + font-size: 17px; + color: var(--cyan-bright); + text-shadow: 0 0 8px rgba(84, 232, 255, 0.6); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* per-cell accents */ +.hud-cpu { border-color: rgba(84, 232, 255, 0.45); color: var(--cyan); } +.hud-ram { border-color: rgba(189, 108, 255, 0.45); color: var(--purple); } +.hud-storage { border-color: rgba(255, 69, 200, 0.45); color: var(--magenta); } +.hud-uptime { border-color: rgba(57, 255, 122, 0.45); color: var(--green); } +.hud-weather { border-color: rgba(255, 216, 74, 0.45); color: var(--yellow); } + +.hud-cpu .hud-value { color: #9af0ff; text-shadow: 0 0 8px var(--cyan); } +.hud-ram .hud-value { color: #d9b5ff; text-shadow: 0 0 8px var(--purple); } +.hud-storage .hud-value { color: #ff9ddc; text-shadow: 0 0 8px var(--magenta); } +.hud-uptime .hud-value { color: #9affc1; text-shadow: 0 0 8px var(--green); } +.hud-weather .hud-value { color: #ffec9a; text-shadow: 0 0 8px var(--yellow); } + +.hud-temp { font-size: 19px; } + +.hud-search { + display: flex; align-items: center; + background: linear-gradient(180deg, rgba(15, 10, 40, 0.7), rgba(8, 5, 22, 0.85)); + border: 1px solid rgba(84, 232, 255, 0.4); + border-radius: 10px; + padding: 0 14px; + box-shadow: + 0 0 12px rgba(84, 232, 255, 0.18), + inset 0 0 18px rgba(84, 232, 255, 0.05); +} +.hud-search input { + flex: 1; + background: transparent; + border: 0; + outline: none; + color: var(--text); + font-family: var(--font-ui); + font-size: 17px; + padding: 14px 0; +} +.hud-search input::placeholder { color: var(--text-dim); } +.hud-search button { + background: transparent; + border: 0; + color: var(--cyan); + cursor: pointer; + padding: 6px; + display: flex; + filter: drop-shadow(0 0 6px var(--cyan)); +} +.hud-search button svg { width: 22px; height: 22px; } + +/* ================================================================= + HERO + ================================================================= */ +.hero { + display: flex; + align-items: center; + justify-content: center; + gap: 32px; + padding: 8px 12px 4px; + min-height: 220px; +} + +.hero-center { + display: flex; + align-items: center; + gap: 24px; +} + +.hero-kite { + width: 140px; height: 140px; + object-fit: contain; + filter: + drop-shadow(0 0 18px var(--cyan)) + drop-shadow(0 0 32px rgba(84, 232, 255, 0.6)); + animation: kite-pulse 4s ease-in-out infinite; +} +@keyframes kite-pulse { + 0%, 100% { filter: drop-shadow(0 0 14px var(--cyan)) drop-shadow(0 0 28px rgba(84, 232, 255, 0.5)); } + 50% { filter: drop-shadow(0 0 22px var(--cyan)) drop-shadow(0 0 44px rgba(84, 232, 255, 0.8)); } +} + +.hero-titles { display: flex; flex-direction: column; } +.hero-title { + font-family: var(--font-display); + font-weight: 900; + font-size: clamp(48px, 6vw, 92px); + margin: 0; + line-height: 1; + color: #ffffff; + text-shadow: + 0 0 12px rgba(255, 255, 255, 0.6), + 0 0 24px var(--cyan), + 0 0 48px rgba(84, 232, 255, 0.6); + letter-spacing: 0.01em; +} +.dot-ao { + color: var(--cyan-bright); + text-shadow: + 0 0 12px var(--cyan), + 0 0 30px rgba(84, 232, 255, 0.8); +} +.hero-tagline { + font-family: var(--font-display); + font-weight: 500; + font-size: clamp(13px, 1.2vw, 17px); + letter-spacing: 0.42em; + color: var(--text-dim); + margin: 8px 0 0; + text-shadow: 0 0 6px rgba(84, 232, 255, 0.4); +} + +/* hex badge */ +.hex-badge { position: relative; width: 180px; height: 200px; } +.hex-svg { width: 100%; height: 100%; filter: drop-shadow(0 0 14px var(--magenta)); } +.hex-outer { fill: none; stroke: var(--magenta); stroke-width: 3; } +.hex-inner { fill: rgba(20, 8, 32, 0.7); stroke: rgba(255, 69, 200, 0.6); stroke-width: 1.5; } +.hex-content { + position: absolute; inset: 0; + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 2px; + font-family: var(--font-display); + text-shadow: 0 0 8px var(--magenta); +} +.hex-l1 { font-size: 13px; letter-spacing: 0.3em; color: var(--magenta-bright); } +.hex-l2 { font-size: 22px; font-weight: 700; letter-spacing: 0.15em; color: #ff9ddc; } +.hex-l3 { + font-size: 38px; font-weight: 900; + color: var(--cyan-bright); + text-shadow: 0 0 12px var(--cyan); + line-height: 1; +} +.hex-l3 span { font-size: 22px; color: var(--magenta-bright); text-shadow: 0 0 8px var(--magenta); } +.hex-pulse { + width: 80px; height: 14px; margin-top: 4px; +} +.hex-pulse polyline { + fill: none; stroke: var(--green); stroke-width: 1.5; + filter: drop-shadow(0 0 4px var(--green)); +} + +/* ================================================================= + PANELS + ================================================================= */ +.row { display: grid; gap: 18px; } +.row-2 { grid-template-columns: 1fr 1fr; } +.row-3 { grid-template-columns: 1fr 1fr; } +.row-bottom { grid-template-columns: 1.6fr 1fr; align-items: start; } + +.panel { + position: relative; + background: var(--panel-bg); + border: 1.5px solid var(--panel-border, var(--cyan)); + border-radius: 10px; + padding: 14px 18px 14px; + box-shadow: + 0 0 22px var(--panel-glow, rgba(84, 232, 255, 0.22)), + inset 0 0 30px rgba(0, 0, 0, 0.4); + backdrop-filter: blur(6px); +} + +.panel-cyan { --panel-border: var(--cyan); --panel-glow: rgba(84, 232, 255, 0.28); --panel-head-color: var(--cyan); } +.panel-magenta { --panel-border: var(--magenta); --panel-glow: rgba(255, 69, 200, 0.28); --panel-head-color: var(--magenta); } +.panel-purple { --panel-border: var(--purple); --panel-glow: rgba(189, 108, 255, 0.28); --panel-head-color: var(--purple); } +.panel-pink { --panel-border: var(--pink); --panel-glow: rgba(255, 95, 177, 0.28); --panel-head-color: var(--pink); } + +.panel-head { + font-family: var(--font-display); + font-weight: 700; + font-size: 14px; + letter-spacing: 0.22em; + color: var(--panel-head-color); + text-shadow: 0 0 8px var(--panel-head-color); + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} +.chevrons { color: var(--panel-head-color); font-weight: 700; opacity: 0.85; } + +/* ================================================================= + CARDS + ================================================================= */ +.cards { display: grid; gap: 14px; } +.cards-2 { grid-template-columns: 1fr 1fr; } +.cards-3 { grid-template-columns: repeat(3, 1fr); } +.cards-4 { grid-template-columns: repeat(4, 1fr); } + +.card { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + border-radius: 8px; + border: 1px solid rgba(84, 232, 255, 0.22); + background: linear-gradient(180deg, rgba(20, 10, 40, 0.55), rgba(8, 4, 22, 0.75)); + text-decoration: none; + color: var(--text); + transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease; + min-height: 76px; +} +.card:hover { + transform: translateY(-2px); + border-color: var(--panel-border, var(--cyan)); + box-shadow: 0 0 18px var(--panel-glow, rgba(84, 232, 255, 0.4)); + background: linear-gradient(180deg, rgba(30, 15, 55, 0.65), rgba(12, 6, 28, 0.8)); +} + +.card img { + width: 44px; height: 44px; + object-fit: contain; + flex: 0 0 auto; + filter: drop-shadow(0 0 6px rgba(84, 232, 255, 0.4)); +} + +.card-title { + font-family: var(--font-display); + font-weight: 700; + font-size: 17px; + color: #ffffff; + line-height: 1.1; + text-shadow: 0 0 4px rgba(255, 255, 255, 0.25); +} +.card-sub { + font-family: var(--font-ui); + font-size: 13px; + color: var(--text-dim); + margin-top: 3px; + letter-spacing: 0.04em; +} + +.sc-icon { + width: 44px; height: 44px; + display: inline-flex; align-items: center; justify-content: center; + font-family: var(--font-mono); + font-size: 22px; + color: var(--purple-bright); + text-shadow: 0 0 8px var(--purple); + background: rgba(189, 108, 255, 0.08); + border: 1px solid rgba(189, 108, 255, 0.35); + border-radius: 6px; + flex: 0 0 auto; +} + +/* ================================================================= + OVERVIEW + ================================================================= */ +.overview { + list-style: none; + margin: 0; padding: 4px 0; + font-family: var(--font-mono); + font-size: 15px; + display: flex; + flex-direction: column; + gap: 10px; +} +.overview li { + display: grid; + grid-template-columns: 110px 14px 1fr; + align-items: center; + gap: 4px; +} +.ov-key { + color: var(--text-dim); + letter-spacing: 0.08em; + text-transform: uppercase; + font-family: var(--font-display); + font-size: 12px; + font-weight: 600; +} +.ov-sep { color: var(--cyan); text-align: center; } +.ov-val { color: var(--cyan-bright); text-shadow: 0 0 6px rgba(84, 232, 255, 0.5); } + +/* ================================================================= + GAUGES + ================================================================= */ +.gauges { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 14px; + padding: 4px 0 0; +} +.gauge { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 4px 2px 8px; +} +.gauge svg:first-child { width: 110px; height: 110px; transform: rotate(-90deg); } +.g-track { fill: none; stroke: rgba(255, 255, 255, 0.08); stroke-width: 9; } +.g-fill { + fill: none; + stroke-width: 9; + stroke-linecap: round; + stroke-dasharray: 301.6; /* 2π·48 */ + stroke-dashoffset: 301.6; + transition: stroke-dashoffset 0.8s cubic-bezier(.2,.8,.2,1); +} +.gauge[data-color="cyan"] .g-fill { stroke: var(--cyan); filter: drop-shadow(0 0 6px var(--cyan)); } +.gauge[data-color="green"] .g-fill { stroke: var(--green); filter: drop-shadow(0 0 6px var(--green)); } +.gauge[data-color="yellow"] .g-fill { stroke: var(--yellow); filter: drop-shadow(0 0 6px var(--yellow)); } +.gauge[data-color="pink"] .g-fill { stroke: var(--pink); filter: drop-shadow(0 0 6px var(--pink)); } + +.gauge .g-label { + position: absolute; + top: 38px; + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.18em; + color: var(--text-dim); +} +.gauge .g-value { + position: absolute; + top: 54px; + font-family: var(--font-display); + font-weight: 700; + font-size: 22px; + color: var(--cyan-bright); + text-shadow: 0 0 8px currentColor; +} +.gauge[data-color="green"] .g-value { color: var(--green); } +.gauge[data-color="yellow"] .g-value { color: var(--yellow); } + +.gauge .g-sub { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-dim); + margin-top: 6px; +} +.g-wave { + width: 100%; height: 16px; margin-top: 4px; +} +.g-wave polyline { + fill: none; + stroke: var(--cyan); + stroke-width: 1.2; + filter: drop-shadow(0 0 3px var(--cyan)); +} + +.gauge-net { + justify-content: flex-start; + padding-top: 38px; +} +.gauge-net .g-label { position: static; margin-bottom: 6px; } +.g-net { display: flex; flex-direction: column; gap: 4px; align-items: center; } +.net-line { + font-family: var(--font-display); + font-weight: 700; + font-size: 14px; +} +.net-line.up { color: var(--green); text-shadow: 0 0 6px var(--green); } +.net-line.dn { color: var(--magenta); text-shadow: 0 0 6px var(--magenta); } + +/* ================================================================= + RECENT ACTIVITY + ================================================================= */ +.activity { + list-style: none; + margin: 0; padding: 4px 0; + display: flex; + flex-direction: column; + gap: 12px; +} +.activity li { + display: grid; + grid-template-columns: 18px 1fr auto; + align-items: center; + gap: 12px; + padding: 8px 6px; + border-radius: 6px; + background: rgba(20, 10, 40, 0.35); + border: 1px solid rgba(84, 232, 255, 0.12); +} +.act-ico { font-size: 14px; filter: drop-shadow(0 0 4px currentColor); } +.act-txt { font-family: var(--font-ui); font-size: 15px; color: var(--text); } +.act-time { font-family: var(--font-mono); font-size: 13px; color: var(--text-dim); } + +/* ================================================================= + FOOTER + ================================================================= */ +.portal-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 18px; + padding: 18px 0 6px; + font-family: var(--font-display); + font-size: 13px; + letter-spacing: 0.16em; + color: var(--text-dim); +} +.foot-text { text-shadow: 0 0 4px rgba(189, 108, 255, 0.4); } +.foot-bracket { + flex: 1; + height: 8px; + position: relative; +} +.foot-bracket::before { + content: ""; + position: absolute; + top: 50%; + left: 0; right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--purple) 30%, var(--cyan) 70%, transparent); + opacity: 0.7; +} +.foot-bracket.left::after, .foot-bracket.right::after { + content: ""; + position: absolute; top: 50%; + width: 26px; height: 8px; + transform: translateY(-50%); + background: + repeating-linear-gradient(90deg, var(--cyan) 0 3px, transparent 3px 6px); + opacity: 0.7; +} +.foot-bracket.left::after { right: 8px; } +.foot-bracket.right::after { left: 8px; } + +/* ================================================================= + RESPONSIVE + ================================================================= */ +@media (max-width: 1400px) { + .hud { grid-template-columns: repeat(3, 1fr); } + .hud-search { grid-column: 1 / -1; } + .hero { grid-template-columns: 1fr; } + .hero-right { justify-content: center; } + .row-3, .row-bottom { grid-template-columns: 1fr; } + .row-2 { grid-template-columns: 1fr; } +} +@media (max-width: 900px) { + .hud { grid-template-columns: 1fr 1fr; } + .cards-4, .cards-3 { grid-template-columns: 1fr 1fr; } + .hero-title { font-size: 44px; } + .hero-kite { width: 96px; height: 96px; } + .gauges { grid-template-columns: 1fr 1fr; } +} +@media (max-width: 560px) { + .hud { grid-template-columns: 1fr; } + .cards-2, .cards-3, .cards-4 { grid-template-columns: 1fr; } + .gauges { grid-template-columns: 1fr; } +} + +/* ================================================================= + TOAST (coming soon) + ================================================================= */ +#ks-toast { + position: fixed; + left: 50%; + bottom: 40px; + transform: translate(-50%, 30px); + background: linear-gradient(180deg, rgba(20, 10, 40, 0.95), rgba(8, 4, 22, 0.95)); + border: 1px solid var(--magenta); + border-radius: 8px; + padding: 12px 22px; + font-family: var(--font-display); + font-weight: 600; + font-size: 14px; + letter-spacing: 0.14em; + color: var(--magenta-bright); + text-shadow: 0 0 6px var(--magenta); + box-shadow: 0 0 24px rgba(255, 69, 200, 0.5); + opacity: 0; + pointer-events: none; + transition: opacity 0.25s ease, transform 0.25s ease; + z-index: 1000; +} +#ks-toast.show { + opacity: 1; + transform: translate(-50%, 0); +} + + +/* ─── Activity panel polish ─── */ +.activity li.act-empty { + opacity: 0.7; + font-style: italic; +} +.activity li .act-txt { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + +/* ─── Per-core CPU bars ─── */ +.cores { + margin-top: 14px; + padding: 12px 4px 2px; + border-top: 1px dashed rgba(255, 69, 200, 0.25); +} +.cores-head { + display: flex; + align-items: baseline; + justify-content: space-between; + margin-bottom: 10px; + padding: 0 6px; +} +.cores-title { + font-family: var(--font-display); + font-size: 12px; + letter-spacing: 0.22em; + color: var(--magenta); + text-shadow: 0 0 6px var(--magenta); +} +.cores-meta { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-dim); +} +.cores-grid { + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 10px; + padding: 0 4px; + justify-content: center; +} +@media (max-width: 1100px) { + .cores-grid { grid-template-columns: repeat(4, 1fr); } +} +@media (max-width: 560px) { + .cores-grid { grid-template-columns: repeat(2, 1fr); } +} +.core { + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px 8px; + border-radius: 6px; + background: rgba(20, 10, 40, 0.45); + border: 1px solid rgba(84, 232, 255, 0.15); +} +.core-head { + display: flex; + align-items: baseline; + justify-content: space-between; + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.1em; +} +.core-label { color: var(--text-dim); } +.core-pct { + color: var(--cyan-bright); + text-shadow: 0 0 4px var(--cyan); + font-weight: 700; +} +.core-track { + position: relative; + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} +.core-bar { + position: absolute; + top: 0; left: 0; bottom: 0; + width: 0%; + background: linear-gradient(90deg, var(--cyan), var(--purple)); + box-shadow: 0 0 6px var(--cyan); + transition: width 0.6s cubic-bezier(.2,.8,.2,1); + border-radius: 3px; +} +/* color shifts as load increases */ +.core[data-load="med"] .core-bar { background: linear-gradient(90deg, var(--green), var(--yellow)); box-shadow: 0 0 6px var(--yellow); } +.core[data-load="med"] .core-pct { color: var(--yellow); text-shadow: 0 0 4px var(--yellow); } +.core[data-load="high"] .core-bar { background: linear-gradient(90deg, var(--yellow), var(--magenta)); box-shadow: 0 0 6px var(--magenta); } +.core[data-load="high"] .core-pct { color: var(--magenta-bright); text-shadow: 0 0 4px var(--magenta); } diff --git a/apps/kitestacks-portal-test/public/style.css.backup-before-clean-layout b/apps/kitestacks-portal-test/public/style.css.backup-before-clean-layout new file mode 100644 index 0000000..a6c05c0 --- /dev/null +++ b/apps/kitestacks-portal-test/public/style.css.backup-before-clean-layout @@ -0,0 +1,314 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --blue: #2f7dff; + --pink: #ff3df2; + --purple: #9b5cff; + --glass: rgba(7, 12, 32, 0.62); + --glass-strong: rgba(10, 18, 44, 0.78); + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(4, 6, 18, 0.2), rgba(4, 6, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 18%, rgba(56, 248, 255, 0.22), transparent 25%), + radial-gradient(circle at 25% 35%, rgba(255, 61, 242, 0.18), transparent 26%), + radial-gradient(circle at 75% 42%, rgba(155, 92, 255, 0.18), transparent 26%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +body::after { + content: ""; + position: fixed; + inset: 0; + background-image: + linear-gradient(rgba(56, 248, 255, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(56, 248, 255, 0.045) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(to bottom, transparent, black 18%, black 85%, transparent); + pointer-events: none; + z-index: 0; +} + +.topbar, +.hero, +.grid, +footer { + position: relative; + z-index: 1; +} + +.topbar { + width: min(1500px, calc(100% - 48px)); + margin: 22px auto 0; + display: grid; + grid-template-columns: repeat(4, minmax(150px, 1fr)) 130px minmax(220px, 1.2fr); + gap: 12px; +} + +.stat, +.weather, +.search { + min-height: 62px; + padding: 12px 14px; + border: 1px solid rgba(56, 248, 255, 0.32); + border-radius: 18px; + background: linear-gradient(145deg, rgba(6, 12, 35, 0.78), rgba(12, 20, 55, 0.5)); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.12), + inset 0 0 20px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.12em; +} + +.stat b { + display: block; + margin-top: 6px; + color: var(--cyan); + font-size: 18px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.85); +} + +.weather { + color: var(--cyan); + font-weight: 800; + text-align: center; +} + +.weather span { + color: var(--muted); + font-size: 11px; + letter-spacing: 0.08em; +} + +.search { + color: white; + outline: none; + font-size: 15px; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.6); +} + +.hero { + width: min(1500px, calc(100% - 48px)); + margin: 32px auto 24px; + min-height: 210px; + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + text-align: left; +} + +.hero-logo { + width: 128px; + height: 128px; + object-fit: contain; + filter: + drop-shadow(0 0 16px rgba(56, 248, 255, 0.9)) + drop-shadow(0 0 32px rgba(255, 61, 242, 0.45)); +} + +.hero h1 { + margin: 0; + font-size: clamp(58px, 8vw, 132px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #f7fbff; + text-transform: none; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 26px rgba(47, 125, 255, 0.65), + 0 0 44px rgba(255, 61, 242, 0.45); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.78); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 700; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.65); +} + +.grid { + width: min(1500px, calc(100% - 48px)); + margin: 0 auto 24px; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-auto-flow: dense; + gap: 18px; +} + +.panel { + min-height: 250px; + padding: 18px; + border-radius: 24px; + border: 1px solid rgba(56, 248, 255, 0.28); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.82), rgba(19, 8, 48, 0.56)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.13), transparent 36%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.13), + 0 0 42px rgba(155, 92, 255, 0.08), + inset 0 0 38px rgba(56, 248, 255, 0.035); + backdrop-filter: blur(18px); + overflow: hidden; + position: relative; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 24px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.7), rgba(255, 61, 242, 0.45), rgba(155, 92, 255, 0.45)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.panel.wide { + grid-column: span 2; +} + +.panel h2 { + margin: 0 0 15px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.18em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 11px; +} + +.wide .cards { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 68px; + padding: 13px 15px; + text-decoration: none; + color: white; + border-radius: 17px; + border: 1px solid rgba(255, 255, 255, 0.11); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.13), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.44); + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.03); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 800; + letter-spacing: 0.02em; + transition: 0.18s ease; +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.75); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.28), + 0 0 32px rgba(255, 61, 242, 0.16); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; +} + +footer { + width: min(1500px, calc(100% - 48px)); + margin: 12px auto 30px; + text-align: center; + color: rgba(226, 241, 255, 0.68); + font-size: 12px; + letter-spacing: 0.2em; + text-transform: uppercase; +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: repeat(2, 1fr); + } + + .grid { + grid-template-columns: repeat(2, 1fr); + } + + .panel.wide { + grid-column: span 2; + } +} + +@media (max-width: 700px) { + .topbar, + .grid, + .hero, + footer { + width: calc(100% - 24px); + } + + .topbar, + .grid, + .wide .cards { + grid-template-columns: 1fr; + } + + .panel.wide { + grid-column: span 1; + } + + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } +} diff --git a/apps/kitestacks-portal-test/public/style.css.backup-before-phase2 b/apps/kitestacks-portal-test/public/style.css.backup-before-phase2 new file mode 100644 index 0000000..56e0a71 --- /dev/null +++ b/apps/kitestacks-portal-test/public/style.css.backup-before-phase2 @@ -0,0 +1,285 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --pink: #ff3df2; + --purple: #9b5cff; + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(2, 5, 18, 0.28), rgba(2, 5, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 12%, rgba(56, 248, 255, 0.26), transparent 22%), + radial-gradient(circle at 25% 45%, rgba(255, 61, 242, 0.2), transparent 28%), + radial-gradient(circle at 80% 48%, rgba(155, 92, 255, 0.18), transparent 28%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +.hero, +.topbar, +.grid { + position: relative; + z-index: 1; +} + +.hero { + width: min(1380px, calc(100% - 48px)); + margin: 28px auto 18px; + min-height: 185px; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; +} + +.hero-logo { + width: 122px; + height: 122px; + object-fit: contain; + filter: + drop-shadow(0 0 14px rgba(56, 248, 255, 0.95)) + drop-shadow(0 0 34px rgba(255, 61, 242, 0.55)); +} + +.hero h1 { + margin: 0; + font-size: clamp(62px, 8vw, 126px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #ffffff; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 26px rgba(47, 125, 255, 0.7), + 0 0 48px rgba(255, 61, 242, 0.52); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.8); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 800; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.75); +} + +.topbar { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 28px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.stat, +.weather, +.searchbox { + min-height: 76px; + padding: 15px 18px; + border-radius: 22px; + border: 1px solid rgba(56, 248, 255, 0.34); + background: + linear-gradient(135deg, rgba(7, 13, 39, 0.84), rgba(16, 10, 45, 0.62)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + inset 0 0 28px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.16em; +} + +.stat b { + display: block; + margin-top: 7px; + color: var(--cyan); + font-size: 20px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather { + display: flex; + flex-direction: column; + justify-content: center; + color: var(--cyan); + font-weight: 900; + font-size: 17px; +} + +.weather b { + font-size: 24px; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather span { + margin-top: 4px; + color: var(--muted); + font-size: 12px; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.searchbox { + display: flex; + align-items: center; +} + +.search { + width: 100%; + border: 0; + outline: 0; + color: white; + font-size: 18px; + background: transparent; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.65); +} + +.grid { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 42px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.panel { + min-height: 235px; + padding: 20px; + border-radius: 26px; + border: 1px solid rgba(56, 248, 255, 0.3); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.84), rgba(20, 8, 50, 0.6)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 38%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 42%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + 0 0 42px rgba(155, 92, 255, 0.1), + inset 0 0 38px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(18px); + position: relative; + overflow: hidden; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 26px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.78), rgba(255, 61, 242, 0.48), rgba(155, 92, 255, 0.42)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.infrastructure, +.future { + grid-column: 1 / -1; +} + +.panel h2 { + margin: 0 0 16px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.2em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 12px; +} + +.cards.two { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 72px; + padding: 14px 16px; + text-decoration: none; + color: white; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.14), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.46); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 900; + letter-spacing: 0.03em; + transition: 0.18s ease; + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.035); +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.78); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.3), + 0 0 34px rgba(255, 61, 242, 0.18); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.06em; +} + +@media (max-width: 760px) { + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } + + .topbar, + .grid, + .cards.two { + grid-template-columns: 1fr; + } + + .infrastructure, + .future { + grid-column: auto; + } +} diff --git a/apps/kitestacks-portal-test/public/style.css.backup-layout-v2 b/apps/kitestacks-portal-test/public/style.css.backup-layout-v2 new file mode 100644 index 0000000..a6c05c0 --- /dev/null +++ b/apps/kitestacks-portal-test/public/style.css.backup-layout-v2 @@ -0,0 +1,314 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --blue: #2f7dff; + --pink: #ff3df2; + --purple: #9b5cff; + --glass: rgba(7, 12, 32, 0.62); + --glass-strong: rgba(10, 18, 44, 0.78); + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(4, 6, 18, 0.2), rgba(4, 6, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 18%, rgba(56, 248, 255, 0.22), transparent 25%), + radial-gradient(circle at 25% 35%, rgba(255, 61, 242, 0.18), transparent 26%), + radial-gradient(circle at 75% 42%, rgba(155, 92, 255, 0.18), transparent 26%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +body::after { + content: ""; + position: fixed; + inset: 0; + background-image: + linear-gradient(rgba(56, 248, 255, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(56, 248, 255, 0.045) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(to bottom, transparent, black 18%, black 85%, transparent); + pointer-events: none; + z-index: 0; +} + +.topbar, +.hero, +.grid, +footer { + position: relative; + z-index: 1; +} + +.topbar { + width: min(1500px, calc(100% - 48px)); + margin: 22px auto 0; + display: grid; + grid-template-columns: repeat(4, minmax(150px, 1fr)) 130px minmax(220px, 1.2fr); + gap: 12px; +} + +.stat, +.weather, +.search { + min-height: 62px; + padding: 12px 14px; + border: 1px solid rgba(56, 248, 255, 0.32); + border-radius: 18px; + background: linear-gradient(145deg, rgba(6, 12, 35, 0.78), rgba(12, 20, 55, 0.5)); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.12), + inset 0 0 20px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.12em; +} + +.stat b { + display: block; + margin-top: 6px; + color: var(--cyan); + font-size: 18px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.85); +} + +.weather { + color: var(--cyan); + font-weight: 800; + text-align: center; +} + +.weather span { + color: var(--muted); + font-size: 11px; + letter-spacing: 0.08em; +} + +.search { + color: white; + outline: none; + font-size: 15px; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.6); +} + +.hero { + width: min(1500px, calc(100% - 48px)); + margin: 32px auto 24px; + min-height: 210px; + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + text-align: left; +} + +.hero-logo { + width: 128px; + height: 128px; + object-fit: contain; + filter: + drop-shadow(0 0 16px rgba(56, 248, 255, 0.9)) + drop-shadow(0 0 32px rgba(255, 61, 242, 0.45)); +} + +.hero h1 { + margin: 0; + font-size: clamp(58px, 8vw, 132px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #f7fbff; + text-transform: none; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 26px rgba(47, 125, 255, 0.65), + 0 0 44px rgba(255, 61, 242, 0.45); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.78); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 700; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.65); +} + +.grid { + width: min(1500px, calc(100% - 48px)); + margin: 0 auto 24px; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-auto-flow: dense; + gap: 18px; +} + +.panel { + min-height: 250px; + padding: 18px; + border-radius: 24px; + border: 1px solid rgba(56, 248, 255, 0.28); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.82), rgba(19, 8, 48, 0.56)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.13), transparent 36%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.13), + 0 0 42px rgba(155, 92, 255, 0.08), + inset 0 0 38px rgba(56, 248, 255, 0.035); + backdrop-filter: blur(18px); + overflow: hidden; + position: relative; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 24px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.7), rgba(255, 61, 242, 0.45), rgba(155, 92, 255, 0.45)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.panel.wide { + grid-column: span 2; +} + +.panel h2 { + margin: 0 0 15px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.18em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 11px; +} + +.wide .cards { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 68px; + padding: 13px 15px; + text-decoration: none; + color: white; + border-radius: 17px; + border: 1px solid rgba(255, 255, 255, 0.11); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.13), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.44); + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.03); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 800; + letter-spacing: 0.02em; + transition: 0.18s ease; +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.75); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.28), + 0 0 32px rgba(255, 61, 242, 0.16); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; +} + +footer { + width: min(1500px, calc(100% - 48px)); + margin: 12px auto 30px; + text-align: center; + color: rgba(226, 241, 255, 0.68); + font-size: 12px; + letter-spacing: 0.2em; + text-transform: uppercase; +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: repeat(2, 1fr); + } + + .grid { + grid-template-columns: repeat(2, 1fr); + } + + .panel.wide { + grid-column: span 2; + } +} + +@media (max-width: 700px) { + .topbar, + .grid, + .hero, + footer { + width: calc(100% - 24px); + } + + .topbar, + .grid, + .wide .cards { + grid-template-columns: 1fr; + } + + .panel.wide { + grid-column: span 1; + } + + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } +} diff --git a/apps/kitestacks-portal-test/public/style.css.backup-pre-upgrade-2026-06-07-0017 b/apps/kitestacks-portal-test/public/style.css.backup-pre-upgrade-2026-06-07-0017 new file mode 100644 index 0000000..56e0a71 --- /dev/null +++ b/apps/kitestacks-portal-test/public/style.css.backup-pre-upgrade-2026-06-07-0017 @@ -0,0 +1,285 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --pink: #ff3df2; + --purple: #9b5cff; + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(2, 5, 18, 0.28), rgba(2, 5, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 12%, rgba(56, 248, 255, 0.26), transparent 22%), + radial-gradient(circle at 25% 45%, rgba(255, 61, 242, 0.2), transparent 28%), + radial-gradient(circle at 80% 48%, rgba(155, 92, 255, 0.18), transparent 28%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +.hero, +.topbar, +.grid { + position: relative; + z-index: 1; +} + +.hero { + width: min(1380px, calc(100% - 48px)); + margin: 28px auto 18px; + min-height: 185px; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; +} + +.hero-logo { + width: 122px; + height: 122px; + object-fit: contain; + filter: + drop-shadow(0 0 14px rgba(56, 248, 255, 0.95)) + drop-shadow(0 0 34px rgba(255, 61, 242, 0.55)); +} + +.hero h1 { + margin: 0; + font-size: clamp(62px, 8vw, 126px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #ffffff; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 26px rgba(47, 125, 255, 0.7), + 0 0 48px rgba(255, 61, 242, 0.52); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.8); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 800; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.75); +} + +.topbar { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 28px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.stat, +.weather, +.searchbox { + min-height: 76px; + padding: 15px 18px; + border-radius: 22px; + border: 1px solid rgba(56, 248, 255, 0.34); + background: + linear-gradient(135deg, rgba(7, 13, 39, 0.84), rgba(16, 10, 45, 0.62)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + inset 0 0 28px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.16em; +} + +.stat b { + display: block; + margin-top: 7px; + color: var(--cyan); + font-size: 20px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather { + display: flex; + flex-direction: column; + justify-content: center; + color: var(--cyan); + font-weight: 900; + font-size: 17px; +} + +.weather b { + font-size: 24px; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather span { + margin-top: 4px; + color: var(--muted); + font-size: 12px; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.searchbox { + display: flex; + align-items: center; +} + +.search { + width: 100%; + border: 0; + outline: 0; + color: white; + font-size: 18px; + background: transparent; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.65); +} + +.grid { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 42px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.panel { + min-height: 235px; + padding: 20px; + border-radius: 26px; + border: 1px solid rgba(56, 248, 255, 0.3); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.84), rgba(20, 8, 50, 0.6)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 38%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 42%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + 0 0 42px rgba(155, 92, 255, 0.1), + inset 0 0 38px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(18px); + position: relative; + overflow: hidden; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 26px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.78), rgba(255, 61, 242, 0.48), rgba(155, 92, 255, 0.42)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.infrastructure, +.future { + grid-column: 1 / -1; +} + +.panel h2 { + margin: 0 0 16px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.2em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 12px; +} + +.cards.two { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 72px; + padding: 14px 16px; + text-decoration: none; + color: white; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.14), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.46); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 900; + letter-spacing: 0.03em; + transition: 0.18s ease; + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.035); +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.78); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.3), + 0 0 34px rgba(255, 61, 242, 0.18); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.06em; +} + +@media (max-width: 760px) { + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } + + .topbar, + .grid, + .cards.two { + grid-template-columns: 1fr; + } + + .infrastructure, + .future { + grid-column: auto; + } +} diff --git a/apps/kitestacks-portal/api/Dockerfile b/apps/kitestacks-portal/api/Dockerfile new file mode 100644 index 0000000..b35acc5 --- /dev/null +++ b/apps/kitestacks-portal/api/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim + +# docker CLI so we can report the host's docker version via the mounted socket +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl gnupg && \ + install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \ + chmod a+r /etc/apt/keyrings/docker.asc && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \ + > /etc/apt/sources.list.d/docker.list && \ + apt-get update && apt-get install -y --no-install-recommends docker-ce-cli && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY main.py . + +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/apps/kitestacks-portal/api/main.py b/apps/kitestacks-portal/api/main.py new file mode 100644 index 0000000..4c31257 --- /dev/null +++ b/apps/kitestacks-portal/api/main.py @@ -0,0 +1,353 @@ +""" +KiteStacks metrics API. + +Exposes real host metrics via psutil. Container must be launched with +`pid: host` and the relevant host paths mounted so psutil reads the +laptop's namespaces rather than the container's. +""" +import os +os.environ.setdefault("PROCFS_PATH", os.environ.get("HOST_PROC", "/proc")) + +import socket +import platform +import subprocess +import time +from functools import lru_cache + +import httpx +import psutil +psutil.PROCFS_PATH = os.environ.get("HOST_PROC", "/proc") + +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI(title="KiteStacks Metrics API") + +# Cache last network sample so we can compute deltas (bytes/sec) +_last_net = {"ts": time.time(), "sent": 0, "recv": 0, "iface": None} + +# Skip virtual / container interfaces when auto-detecting the host's nic +SKIP_IFACE_PREFIXES = ( + "docker", "br-", "veth", "cni", "flannel", "cali", + "lo", "tun", "tap", "virbr", "kube", +) + + +def active_interface() -> str | None: + """Auto-detect the interface used for the default route on the HOST.""" + try: + route_path = "/host/proc/net/route" + if os.path.exists(route_path): + with open(route_path) as f: + for line in f.readlines()[1:]: + fields = line.strip().split() + if len(fields) >= 2 and fields[1] == "00000000": + name = fields[0] + if not name.startswith(SKIP_IFACE_PREFIXES): + return name + except Exception: + pass + # Fallback: first non-virtual interface that is up + try: + for name, stats in psutil.net_if_stats().items(): + if stats.isup and not name.startswith(SKIP_IFACE_PREFIXES): + return name + except Exception: + pass + return None + + +@app.get("/api/metrics") +def metrics(): + global _last_net + + # CPU — short blocking sample so we don't return 0 on the first call + cpu_pct = psutil.cpu_percent(interval=0.3) + cpu_per_core = psutil.cpu_percent(interval=None, percpu=True) + + # RAM + vm = psutil.virtual_memory() + ram_used_gb = vm.used / (1024 ** 3) + ram_total_gb = vm.total / (1024 ** 3) + ram_pct = vm.percent + + # Storage — root filesystem (host root is mounted at /host) + root_path = "/host" if os.path.ismount("/host") else "/" + du = psutil.disk_usage(root_path) + storage_used_gb = du.used / (1024 ** 3) + storage_total_gb = du.total / (1024 ** 3) + storage_pct = du.percent + + # Uptime — from host /proc/uptime if available, otherwise psutil + uptime_s = None + try: + host_uptime = "/host/proc/uptime" + if os.path.exists(host_uptime): + with open(host_uptime) as f: + uptime_s = int(float(f.read().split()[0])) + except Exception: + pass + if uptime_s is None: + uptime_s = int(time.time() - psutil.boot_time()) + + days = uptime_s // 86400 + hours = (uptime_s % 86400) // 3600 + minutes = (uptime_s % 3600) // 60 + + # Network — active host interface, bytes/sec since last call + iface = active_interface() + net_sent_kbs = 0.0 + net_recv_kbs = 0.0 + if iface: + counters = psutil.net_io_counters(pernic=True).get(iface) + if counters: + now = time.time() + dt = max(now - _last_net["ts"], 0.001) + if _last_net["iface"] == iface and _last_net["sent"] > 0: + net_sent_kbs = max(0.0, (counters.bytes_sent - _last_net["sent"]) / dt / 1024) + net_recv_kbs = max(0.0, (counters.bytes_recv - _last_net["recv"]) / dt / 1024) + _last_net = { + "ts": now, + "sent": counters.bytes_sent, + "recv": counters.bytes_recv, + "iface": iface, + } + + overview = _system_overview() + + return { + "cpu": {"pct": round(cpu_pct, 1), "cores": [round(c, 1) for c in cpu_per_core]}, + "ram": { + "pct": round(ram_pct, 1), + "used_gb": round(ram_used_gb, 1), + "total_gb": round(ram_total_gb, 1), + }, + "storage": { + "pct": round(storage_pct, 1), + "used_gb": round(storage_used_gb, 0), + "total_gb": round(storage_total_gb, 0), + }, + "uptime": { + "days": days, + "hours": hours, + "minutes": minutes, + "seconds": uptime_s, + }, + "network": { + "iface": iface, + "tx_kbs": round(net_sent_kbs, 1), + "rx_kbs": round(net_recv_kbs, 1), + }, + "overview": overview, + "ts": int(time.time()), + } + + +@lru_cache(maxsize=1) +def _system_overview(): + """Cached because none of this changes during a session.""" + # Hostname — read from host /etc/hostname if mounted; fall back to socket + hostname = socket.gethostname() + try: + host_hostname = "/host/etc/hostname" + if os.path.exists(host_hostname): + with open(host_hostname) as f: + hostname = f.read().strip() or hostname + except Exception: + pass + + # OS pretty name from host /etc/os-release if mounted + os_pretty = platform.platform() + try: + release_path = ( + "/host/etc/os-release" + if os.path.exists("/host/etc/os-release") + else "/etc/os-release" + ) + with open(release_path) as f: + for line in f: + if line.startswith("PRETTY_NAME="): + os_pretty = line.split("=", 1)[1].strip().strip('"') + break + except Exception: + pass + + # Kernel from host /proc/sys/kernel/osrelease if mounted + kernel = platform.release() + try: + version_path = "/host/proc/sys/kernel/osrelease" + if os.path.exists(version_path): + with open(version_path) as f: + kernel = f.read().strip() + except Exception: + pass + + # Docker version via mounted socket + docker_ver = "unknown" + try: + out = subprocess.check_output( + ["docker", "--version"], stderr=subprocess.DEVNULL, timeout=2 + ).decode() + # "Docker version 25.0.5, build ..." + docker_ver = out.split()[2].rstrip(",") + except Exception: + pass + + # Timezone + tz = time.tzname[0] if time.tzname else "UTC" + try: + tz_link = ( + "/host/etc/localtime" + if os.path.exists("/host/etc/localtime") + else "/etc/localtime" + ) + if os.path.islink(tz_link): + tz = os.readlink(tz_link).split("zoneinfo/")[-1] + except Exception: + pass + + return { + "hostname": hostname, + "os": os_pretty, + "kernel": kernel, + "docker": docker_ver, + "timezone": tz, + } + + +# Weather — cached for 10 minutes +_weather_cache = {"ts": 0, "data": None} + + +@app.get("/api/weather") +async def weather(): + global _weather_cache + now = time.time() + if _weather_cache["data"] and (now - _weather_cache["ts"]) < 600: + return _weather_cache["data"] + + try: + async with httpx.AsyncClient(timeout=5.0) as client: + # 1. Geolocate via public IP (no key needed) + geo = await client.get("https://ipapi.co/json/") + geo.raise_for_status() + g = geo.json() + lat, lon = g.get("latitude"), g.get("longitude") + city = g.get("city", "Unknown") + + # 2. Weather from open-meteo (no key) + wx = await client.get( + "https://api.open-meteo.com/v1/forecast", + params={ + "latitude": lat, + "longitude": lon, + "current": "temperature_2m,weather_code,is_day", + "temperature_unit": "fahrenheit", + }, + ) + wx.raise_for_status() + w = wx.json()["current"] + + data = { + "temp_f": round(w["temperature_2m"]), + "code": w["weather_code"], + "is_day": bool(w["is_day"]), + "city": city, + "description": _wcode(w["weather_code"]), + } + _weather_cache = {"ts": now, "data": data} + return data + except Exception as e: + return JSONResponse( + {"error": str(e), "temp_f": None, "description": "Offline"}, + status_code=200, + ) + + +def _wcode(code: int) -> str: + """Open-Meteo WMO weather code → short description.""" + m = { + 0: "Clear Sky", 1: "Mainly Clear", 2: "Partly Cloudy", 3: "Overcast", + 45: "Fog", 48: "Rime Fog", + 51: "Light Drizzle", 53: "Drizzle", 55: "Heavy Drizzle", + 61: "Light Rain", 63: "Rain", 65: "Heavy Rain", + 71: "Light Snow", 73: "Snow", 75: "Heavy Snow", + 77: "Snow Grains", + 80: "Rain Showers", 81: "Heavy Showers", 82: "Violent Showers", + 85: "Snow Showers", 86: "Heavy Snow Showers", + 95: "Thunderstorm", 96: "Thunderstorm + Hail", 99: "Severe Thunderstorm", + } + return m.get(code, "Unknown") + + + +# Forgejo activity — public events only, cached 60s +_forge_cache = {"ts": 0, "data": None} +FORGEJO_BASE = os.environ.get("FORGEJO_BASE", "https://gitforge.kitestacks.com").rstrip("/") +FORGEJO_API_BASE = os.environ.get("FORGEJO_API_BASE", FORGEJO_BASE).rstrip("/") + + +@app.get("/api/activity") +async def activity(): + """Fetch recent public Forgejo events across all public repos.""" + global _forge_cache + now = time.time() + if _forge_cache["data"] is not None and (now - _forge_cache["ts"]) < 60: + return _forge_cache["data"] + + items = [] + try: + async with httpx.AsyncClient(timeout=6.0, follow_redirects=True) as client: + # /repos/search?limit=20&sort=updated → most recently active public repos + r = await client.get( + f"{FORGEJO_API_BASE}/api/v1/repos/search", + params={"limit": 20, "sort": "updated", "order": "desc", "private": "false"}, + ) + r.raise_for_status() + repos = r.json().get("data", []) + + # For each repo, fetch its activity feed (small, fast); take a few + for repo in repos[:8]: + owner = repo.get("owner", {}).get("login") + name = repo.get("name") + if not owner or not name: + continue + if repo.get("private"): + continue + try: + fr = await client.get( + f"{FORGEJO_API_BASE}/api/v1/repos/{owner}/{name}/activities/feeds", + params={"limit": 5}, + ) + if fr.status_code != 200: + continue + for ev in fr.json(): + items.append({ + "repo": f"{owner}/{name}", + "repo_url": f"{FORGEJO_BASE}/{owner}/{name}", + "op_type": ev.get("op_type", "unknown"), + "ref_name": ev.get("ref_name", ""), + "comment": (ev.get("content") or "")[:120], + "actor": (ev.get("act_user") or {}).get("login", "unknown"), + "created": ev.get("created"), + }) + except Exception: + continue + + # Sort by created desc, take top 8 + items.sort(key=lambda x: x.get("created", ""), reverse=True) + items = items[:8] + + data = {"items": items, "source": "forgejo", "ts": int(now)} + _forge_cache = {"ts": now, "data": data} + return data + except Exception as e: + return JSONResponse( + {"error": str(e), "items": [], "source": "forgejo"}, + status_code=200, + ) + + +@app.get("/api/health") +def health(): + return {"ok": True} diff --git a/apps/kitestacks-portal/api/requirements.txt b/apps/kitestacks-portal/api/requirements.txt new file mode 100644 index 0000000..4e9e0bd --- /dev/null +++ b/apps/kitestacks-portal/api/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 +psutil==6.1.0 +httpx==0.27.2 diff --git a/apps/kitestacks-portal/docker-compose.yml b/apps/kitestacks-portal/docker-compose.yml new file mode 100644 index 0000000..1d2df0c --- /dev/null +++ b/apps/kitestacks-portal/docker-compose.yml @@ -0,0 +1,31 @@ +# Production cyberpunk portal — replaces gethomepage/homepage +# +# Notes: +# - Container name MUST be "homepage" so the existing Cloudflare tunnel route +# (www.kitestacks.com -> http://homepage:3000) continues to work unchanged. +# - Attached to the "kitestacks" external network so the cloudflared container +# can resolve "homepage" by DNS. +# - Listens internally on port 3000 (matching the tunnel route) AND externally +# on 3005 to preserve http://192.168.1.205:3005 access. +# - Metrics are served by the existing kitestacks-metrics-api container +# (shared with the test portal). nginx reaches it via host.docker.internal:8000. + +services: + homepage: + image: nginx:alpine + container_name: homepage + restart: unless-stopped + ports: + - "3005:3000" + networks: + - default + - kitestacks + volumes: + - ./public:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + extra_hosts: + - "host.docker.internal:host-gateway" + +networks: + kitestacks: + external: true diff --git a/apps/kitestacks-portal/nginx.conf b/apps/kitestacks-portal/nginx.conf new file mode 100644 index 0000000..a3d6fb7 --- /dev/null +++ b/apps/kitestacks-portal/nginx.conf @@ -0,0 +1,26 @@ +# Production nginx config for KiteStacks portal +# Listens on port 3000 to match the Cloudflare tunnel route. + +server { + listen 3000; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://host.docker.internal:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 10s; + } + + location /images/ { + expires 7d; + add_header Cache-Control "public, max-age=604800"; + } +} diff --git a/apps/kitestacks-portal/public/app.js b/apps/kitestacks-portal/public/app.js new file mode 100644 index 0000000..8adf430 --- /dev/null +++ b/apps/kitestacks-portal/public/app.js @@ -0,0 +1,304 @@ +/* KiteStacks portal — live metrics client */ + +const GAUGE_CIRCUMFERENCE = 2 * Math.PI * 48; // r=48 in viewBox + +const els = { + cpuHud: document.getElementById('hud-cpu'), + ramHud: document.getElementById('hud-ram'), + storageHud: document.getElementById('hud-storage'), + uptimeHud: document.getElementById('hud-uptime'), + temp: document.getElementById('hud-temp'), + weatherDesc: document.getElementById('hud-weather-desc'), + + gCpu: document.getElementById('g-cpu'), + gCpuVal: document.getElementById('g-cpu-val'), + gRam: document.getElementById('g-ram'), + gRamVal: document.getElementById('g-ram-val'), + gRamSub: document.getElementById('g-ram-sub'), + gStorage: document.getElementById('g-storage'), + gStorageVal: document.getElementById('g-storage-val'), + gStorageSub: document.getElementById('g-storage-sub'), + + netTx: document.getElementById('net-tx'), + netRx: document.getElementById('net-rx'), + + waveCpu: document.getElementById('wave-cpu'), + waveNet: document.getElementById('wave-net'), +}; + +// History buffers for waveforms (last 30 samples) +const histCpu = []; +const histNet = []; +const HIST_MAX = 30; + +function setGauge(el, pct) { + if (!el) return; + const v = Math.max(0, Math.min(100, pct || 0)); + const offset = GAUGE_CIRCUMFERENCE * (1 - v / 100); + el.style.strokeDasharray = GAUGE_CIRCUMFERENCE.toFixed(2); + el.style.strokeDashoffset = offset.toFixed(2); +} + +function fmtKbs(v) { + if (v < 1) return v.toFixed(2) + ' KB/s'; + if (v < 1024) return v.toFixed(1) + ' KB/s'; + return (v / 1024).toFixed(2) + ' MB/s'; +} + +function updateWave(polyline, history) { + if (!polyline) return; + const max = Math.max(1, ...history); + const w = 120, h = 20; + const step = w / Math.max(1, HIST_MAX - 1); + const pts = history.map((v, i) => { + const x = i * step; + const y = h - (v / max) * (h - 2) - 1; + return `${x.toFixed(1)},${y.toFixed(1)}`; + }); + polyline.setAttribute('points', pts.join(' ')); +} + +async function fetchMetrics() { + try { + const r = await fetch('/api/metrics', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + + // HUD + els.cpuHud.textContent = d.cpu.pct.toFixed(0) + '%'; + els.ramHud.textContent = `${d.ram.used_gb} GB / ${d.ram.total_gb} GB`; + els.storageHud.textContent = `${d.storage.used_gb} GB / ${d.storage.total_gb} GB`; + const u = d.uptime; + els.uptimeHud.textContent = `${u.days}d ${u.hours}h ${u.minutes}m`; + + // Gauges + setGauge(els.gCpu, d.cpu.pct); + els.gCpuVal.textContent = d.cpu.pct.toFixed(0) + '%'; + + setGauge(els.gRam, d.ram.pct); + els.gRamVal.textContent = d.ram.pct.toFixed(0) + '%'; + els.gRamSub.textContent = `${d.ram.used_gb} GB / ${d.ram.total_gb} GB`; + + setGauge(els.gStorage, d.storage.pct); + els.gStorageVal.textContent = d.storage.pct.toFixed(0) + '%'; + els.gStorageSub.textContent = `${d.storage.used_gb} GB / ${d.storage.total_gb} GB`; + + // Network + els.netTx.textContent = fmtKbs(d.network.tx_kbs); + els.netRx.textContent = fmtKbs(d.network.rx_kbs); + + // Per-core CPU + renderCores(d.cpu.cores || []); + + // Waveform histories + histCpu.push(d.cpu.pct); + if (histCpu.length > HIST_MAX) histCpu.shift(); + updateWave(els.waveCpu, histCpu); + + histNet.push(d.network.tx_kbs + d.network.rx_kbs); + if (histNet.length > HIST_MAX) histNet.shift(); + updateWave(els.waveNet, histNet); + + } catch (err) { + console.warn('metrics fetch failed', err); + } +} + +async function fetchWeather() { + try { + const r = await fetch('/api/weather', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + if (d.temp_f != null) { + els.temp.textContent = d.temp_f + '°F'; + els.weatherDesc.textContent = d.description || ''; + } else { + els.temp.textContent = '--°F'; + els.weatherDesc.textContent = d.description || 'Offline'; + } + } catch (err) { + console.warn('weather fetch failed', err); + els.weatherDesc.textContent = 'Offline'; + } +} + +// Search — checks card data-name first, else fall through to Google +window.kiteSearch = function (e) { + e.preventDefault(); + const q = document.getElementById('search-input').value.trim(); + if (!q) return; + const cards = Array.from(document.querySelectorAll('.card')); + const match = cards.find(c => + (c.dataset.name || '').toLowerCase().includes(q.toLowerCase()) + ); + // a valid match is a card with a real href (not a placeholder) + if (match && match.getAttribute('href') && match.getAttribute('href') !== '#') { + window.open(match.href, '_blank', 'noopener'); + } else { + window.open(`https://www.google.com/search?q=${encodeURIComponent(q)}`, '_blank', 'noopener'); + } +}; + +// Coming-soon toast for cards without a real URL +function showToast(msg) { + let t = document.getElementById('ks-toast'); + if (!t) { + t = document.createElement('div'); + t.id = 'ks-toast'; + document.body.appendChild(t); + } + t.textContent = msg; + t.classList.add('show'); + clearTimeout(t._hide); + t._hide = setTimeout(() => t.classList.remove('show'), 2200); +} + +document.addEventListener('click', (e) => { + const card = e.target.closest('.card[data-coming-soon]'); + if (card) { + e.preventDefault(); + showToast(`${card.dataset.name} — coming soon`); + } +}); + +// Kick off +fetchMetrics(); +fetchWeather(); +setInterval(fetchMetrics, 2000); +setInterval(fetchWeather, 10 * 60 * 1000); // every 10 min + +// ─── Recent Activity (Forgejo public events) ─────────────────── +const ACT_ICONS = { + commit_repo: { glyph: '◆', color: '#54e8ff', label: 'Commit' }, + push_tag: { glyph: '⛓', color: '#bd6cff', label: 'Tag' }, + create_branch: { glyph: '⎇', color: '#39ff7a', label: 'Branch' }, + create_repo: { glyph: '✦', color: '#ffd84a', label: 'Repo' }, + fork_repo: { glyph: '⑂', color: '#ff5fb1', label: 'Fork' }, + pull_request: { glyph: '⇄', color: '#54e8ff', label: 'PR' }, + comment_pull: { glyph: '💬', color: '#bd6cff', label: 'Comment' }, + comment_issue: { glyph: '💬', color: '#bd6cff', label: 'Comment' }, + create_issue: { glyph: '◉', color: '#ff45c8', label: 'Issue' }, + close_issue: { glyph: '⊘', color: '#39ff7a', label: 'Closed' }, + merge_pull_request:{ glyph: '⮕', color: '#39ff7a', label: 'Merged' }, + delete_branch: { glyph: '✕', color: '#ff5fb1', label: 'Del' }, + star_repo: { glyph: '★', color: '#ffd84a', label: 'Star' }, +}; + +function relTime(iso) { + if (!iso) return ''; + const d = new Date(iso); + const diff = (Date.now() - d.getTime()) / 1000; + if (diff < 60) return Math.max(1, Math.floor(diff)) + 's ago'; + if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; + if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; + if (diff < 86400*7) return Math.floor(diff / 86400) + 'd ago'; + return d.toISOString().slice(0, 10); +} + +function describeEvent(ev) { + const meta = ACT_ICONS[ev.op_type] || { glyph: '●', color: '#54e8ff', label: ev.op_type }; + let text; + switch (ev.op_type) { + case 'commit_repo': + text = `${ev.actor} pushed to ${ev.repo}`; + break; + case 'create_branch': + text = `${ev.actor} created branch ${ev.ref_name || ''} in ${ev.repo}`; + break; + case 'push_tag': + text = `${ev.actor} tagged ${ev.ref_name || ''} in ${ev.repo}`; + break; + case 'create_repo': + text = `${ev.actor} created ${ev.repo}`; + break; + case 'fork_repo': + text = `${ev.actor} forked ${ev.repo}`; + break; + case 'pull_request': + case 'merge_pull_request': + text = `${ev.actor} ${ev.op_type === 'merge_pull_request' ? 'merged PR in' : 'opened PR in'} ${ev.repo}`; + break; + case 'create_issue': + text = `${ev.actor} opened issue in ${ev.repo}`; + break; + case 'close_issue': + text = `${ev.actor} closed issue in ${ev.repo}`; + break; + case 'comment_issue': + case 'comment_pull': + text = `${ev.actor} commented in ${ev.repo}`; + break; + case 'star_repo': + text = `${ev.actor} starred ${ev.repo}`; + break; + case 'delete_branch': + text = `${ev.actor} deleted ${ev.ref_name || 'branch'} in ${ev.repo}`; + break; + default: + text = `${ev.actor} · ${ev.op_type} · ${ev.repo}`; + } + return { meta, text }; +} + +async function fetchActivity() { + const ul = document.getElementById('activity-list'); + if (!ul) return; + try { + const r = await fetch('/api/activity', { cache: 'no-store' }); + if (!r.ok) throw new Error('HTTP ' + r.status); + const d = await r.json(); + const items = d.items || []; + if (items.length === 0) { + ul.innerHTML = '
  • No recent public activity
  • '; + return; + } + ul.innerHTML = items.slice(0, 5).map(ev => { + const { meta, text } = describeEvent(ev); + const safeText = text.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + return `
  • + ${meta.glyph} + ${safeText} + ${relTime(ev.created)} +
  • `; + }).join(''); + } catch (err) { + console.warn('activity fetch failed', err); + ul.innerHTML = '
  • Forgejo unreachable
  • '; + } +} + +fetchActivity(); +setInterval(fetchActivity, 60 * 1000); + +// ─── Per-core CPU renderer ────────────────────────────────────── +function renderCores(cores) { + const grid = document.getElementById('cores-grid'); + const meta = document.getElementById('cores-meta'); + if (!grid) return; + if (!cores.length) { + meta.textContent = '--'; + return; + } + meta.textContent = `${cores.length} cores · avg ${(cores.reduce((a,b)=>a+b,0)/cores.length).toFixed(0)}%`; + + // Build once, then update in place to avoid layout thrash + if (grid.children.length !== cores.length) { + grid.innerHTML = cores.map((_, i) => ` +
    +
    + C${i} + 0% +
    +
    +
    + `).join(''); + } + cores.forEach((pct, i) => { + const el = grid.children[i]; + if (!el) return; + el.querySelector('.core-pct').textContent = pct.toFixed(0) + '%'; + el.querySelector('.core-bar').style.width = Math.max(0, Math.min(100, pct)) + '%'; + el.dataset.load = pct > 75 ? 'high' : pct > 40 ? 'med' : 'low'; + }); +} + diff --git a/apps/kitestacks-portal/public/images/cyberpunk-bg.png b/apps/kitestacks-portal/public/images/cyberpunk-bg.png new file mode 100644 index 0000000..9ce3953 Binary files /dev/null and b/apps/kitestacks-portal/public/images/cyberpunk-bg.png differ diff --git a/apps/kitestacks-portal/public/images/icons/artificial-intelligence.png b/apps/kitestacks-portal/public/images/icons/artificial-intelligence.png new file mode 100644 index 0000000..19f877c Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/artificial-intelligence.png differ diff --git a/apps/kitestacks-portal/public/images/icons/authentik.png b/apps/kitestacks-portal/public/images/icons/authentik.png new file mode 100644 index 0000000..791d370 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/authentik.png differ diff --git a/apps/kitestacks-portal/public/images/icons/cilium.png b/apps/kitestacks-portal/public/images/icons/cilium.png new file mode 100644 index 0000000..4550926 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/cilium.png differ diff --git a/apps/kitestacks-portal/public/images/icons/cloudflare.png b/apps/kitestacks-portal/public/images/icons/cloudflare.png new file mode 100644 index 0000000..b17dc59 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/cloudflare.png differ diff --git a/apps/kitestacks-portal/public/images/icons/cloudnativepg.png b/apps/kitestacks-portal/public/images/icons/cloudnativepg.png new file mode 100644 index 0000000..2c4e4df Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/cloudnativepg.png differ diff --git a/apps/kitestacks-portal/public/images/icons/fluxcd.png b/apps/kitestacks-portal/public/images/icons/fluxcd.png new file mode 100644 index 0000000..9009b64 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/fluxcd.png differ diff --git a/apps/kitestacks-portal/public/images/icons/forgejo.png b/apps/kitestacks-portal/public/images/icons/forgejo.png new file mode 100644 index 0000000..d2c4ee5 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/forgejo.png differ diff --git a/apps/kitestacks-portal/public/images/icons/grafana.png b/apps/kitestacks-portal/public/images/icons/grafana.png new file mode 100644 index 0000000..6f7d9a0 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/grafana.png differ diff --git a/apps/kitestacks-portal/public/images/icons/kavita.png b/apps/kitestacks-portal/public/images/icons/kavita.png new file mode 100644 index 0000000..ef8a34c Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/kavita.png differ diff --git a/apps/kitestacks-portal/public/images/icons/kubernetes.png b/apps/kitestacks-portal/public/images/icons/kubernetes.png new file mode 100644 index 0000000..7e0985c Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/kubernetes.png differ diff --git a/apps/kitestacks-portal/public/images/icons/node-exporter.png b/apps/kitestacks-portal/public/images/icons/node-exporter.png new file mode 100644 index 0000000..d9790fe Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/node-exporter.png differ diff --git a/apps/kitestacks-portal/public/images/icons/open-webui.png b/apps/kitestacks-portal/public/images/icons/open-webui.png new file mode 100644 index 0000000..df1da98 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/open-webui.png differ diff --git a/apps/kitestacks-portal/public/images/icons/openproject.png b/apps/kitestacks-portal/public/images/icons/openproject.png new file mode 100644 index 0000000..3fe146a Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/openproject.png differ diff --git a/apps/kitestacks-portal/public/images/icons/openrouter.png b/apps/kitestacks-portal/public/images/icons/openrouter.png new file mode 100644 index 0000000..8118d79 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/openrouter.png differ diff --git a/apps/kitestacks-portal/public/images/icons/portainer.png b/apps/kitestacks-portal/public/images/icons/portainer.png new file mode 100644 index 0000000..5a8a585 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/portainer.png differ diff --git a/apps/kitestacks-portal/public/images/icons/prometheus.png b/apps/kitestacks-portal/public/images/icons/prometheus.png new file mode 100644 index 0000000..d9790fe Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/prometheus.png differ diff --git a/apps/kitestacks-portal/public/images/icons/shaarli.png b/apps/kitestacks-portal/public/images/icons/shaarli.png new file mode 100644 index 0000000..3439af9 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/shaarli.png differ diff --git a/apps/kitestacks-portal/public/images/icons/uptime-kuma.png b/apps/kitestacks-portal/public/images/icons/uptime-kuma.png new file mode 100644 index 0000000..713ca37 Binary files /dev/null and b/apps/kitestacks-portal/public/images/icons/uptime-kuma.png differ diff --git a/apps/kitestacks-portal/public/images/kitestacks-icon.png b/apps/kitestacks-portal/public/images/kitestacks-icon.png new file mode 100644 index 0000000..1bd0078 Binary files /dev/null and b/apps/kitestacks-portal/public/images/kitestacks-icon.png differ diff --git a/apps/kitestacks-portal/public/images/kitestacks-logo.png b/apps/kitestacks-portal/public/images/kitestacks-logo.png new file mode 100644 index 0000000..9e86c0f Binary files /dev/null and b/apps/kitestacks-portal/public/images/kitestacks-logo.png differ diff --git a/apps/kitestacks-portal/public/index.html b/apps/kitestacks-portal/public/index.html new file mode 100644 index 0000000..549d8ec --- /dev/null +++ b/apps/kitestacks-portal/public/index.html @@ -0,0 +1,253 @@ + + + + + +KiteStacks.ao + + + + + + + + +
    + +
    + + +
    +
    + +
    + CPU + --% +
    +
    + +
    + +
    + RAM + -- GB / -- GB +
    +
    + +
    + +
    + STORAGE + -- GB / -- GB +
    +
    + +
    + +
    + UPTIME + --d --h --m +
    +
    + +
    + +
    + --°F + Loading… +
    +
    + + +
    + + +
    +
    + KiteStacks +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    + + +
    +
    » INFRASTRUCTURE
    + +
    + + +
    +
    +
    » MONITORING
    + +
    + +
    +
    » AI & AUTOMATION
    + +
    +
    + + +
    +
    +
    » KNOWLEDGE BASE
    + +
    + +
    +
    » DEVELOPMENT
    + +
    + +
    + + +
    + +
    +
    » SYSTEM STATUS
    +
    +
    + + + + +
    CPU
    +
    --%
    + + + +
    + +
    + + + + +
    RAM
    +
    --%
    +
    -- GB / -- GB
    +
    + +
    + + + + +
    STORAGE
    +
    --%
    +
    -- GB / -- GB
    +
    + +
    +
    NETWORK
    +
    +
    -- KB/s
    +
    -- KB/s
    +
    + + + +
    +
    + +
    +
    + CPU CORES + -- +
    +
    +
    +
    + +
    +
    » RECENT ACTIVITY
    +
      +
    • Loading recent activity…
    • +
    +
    + +
    + + + +
    + + + + diff --git a/apps/kitestacks-portal/public/index.html.backup-before-clean-layout b/apps/kitestacks-portal/public/index.html.backup-before-clean-layout new file mode 100644 index 0000000..6f36a5e --- /dev/null +++ b/apps/kitestacks-portal/public/index.html.backup-before-clean-layout @@ -0,0 +1,89 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    ☾ 72°F
    Clear Sky
    + +
    + +
    +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + + + diff --git a/apps/kitestacks-portal/public/index.html.backup-before-phase2 b/apps/kitestacks-portal/public/index.html.backup-before-phase2 new file mode 100644 index 0000000..322db0d --- /dev/null +++ b/apps/kitestacks-portal/public/index.html.backup-before-phase2 @@ -0,0 +1,84 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    72°FClear Sky
    + +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + diff --git a/apps/kitestacks-portal/public/index.html.backup-pre-upgrade-2026-06-07-0017 b/apps/kitestacks-portal/public/index.html.backup-pre-upgrade-2026-06-07-0017 new file mode 100644 index 0000000..322db0d --- /dev/null +++ b/apps/kitestacks-portal/public/index.html.backup-pre-upgrade-2026-06-07-0017 @@ -0,0 +1,84 @@ + + + + + + KiteStacks.ao + + + + + +
    + +
    +

    KiteStacks.ao

    +

    STACK HIGHER. FLY FURTHER.

    +
    +
    + +
    +
    ▣ CPU 12%
    +
    ▤ RAM 2.1 GB / 16 GB
    +
    ▰ STORAGE 166 GB / 500 GB
    +
    ⌁ UPTIME 3d 14h 27m
    +
    72°FClear Sky
    + +
    + +
    +
    +

    » INFRASTRUCTURE

    + +
    + +
    +

    » CODE / DEPLOY / AI

    + +
    + +
    +

    » TICKET SYSTEM

    + +
    + +
    +

    » MONITORING

    + +
    + +
    +

    » KNOWLEDGE BASE

    + +
    + +
    +

    » FUTURE PROJECTS

    + +
    +
    + + diff --git a/apps/kitestacks-portal/public/style.css b/apps/kitestacks-portal/public/style.css new file mode 100644 index 0000000..8859799 --- /dev/null +++ b/apps/kitestacks-portal/public/style.css @@ -0,0 +1,678 @@ +/* =================================================================== + KiteStacks.ao — cyberpunk portal stylesheet + =================================================================== */ + +:root { + --bg: #05030f; + --panel-bg: rgba(10, 6, 28, 0.72); + --panel-bg-solid: #0a061c; + + --cyan: #54e8ff; + --cyan-bright: #7df3ff; + --magenta: #ff45c8; + --magenta-bright: #ff7adb; + --purple: #bd6cff; + --purple-bright: #d99dff; + --pink: #ff5fb1; + --green: #39ff7a; + --yellow: #ffd84a; + --orange: #ff8b3a; + + --text: #e6f3ff; + --text-dim: #8aa3c0; + + --font-display: 'Orbitron', sans-serif; + --font-ui: 'Rajdhani', sans-serif; + --font-mono: 'Share Tech Mono', monospace; +} + +* { box-sizing: border-box; } + +html, body { margin: 0; padding: 0; min-height: 100%; } + +body { + font-family: var(--font-ui); + color: var(--text); + background: var(--bg) url('/images/cyberpunk-bg.png') center top / cover no-repeat fixed; + background-attachment: fixed; + min-height: 100vh; + overflow-x: hidden; +} + +/* darkening overlay so neon pops */ +.bg-overlay { + position: fixed; inset: 0; + background: + radial-gradient(ellipse at top, rgba(120, 60, 200, 0.18), transparent 60%), + linear-gradient(180deg, rgba(5, 3, 15, 0.55), rgba(5, 3, 15, 0.85)); + pointer-events: none; + z-index: 0; +} + +.portal { + position: relative; + z-index: 1; + max-width: 1700px; + margin: 0 auto; + padding: 18px 26px 32px; + display: flex; + flex-direction: column; + gap: 18px; +} + +/* ================================================================= + TOP HUD + ================================================================= */ +.hud { + display: grid; + grid-template-columns: repeat(5, minmax(160px, 1fr)) 2fr; + gap: 14px; + align-items: stretch; +} + +.hud-cell { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + border-radius: 10px; + background: linear-gradient(180deg, rgba(15, 10, 40, 0.7), rgba(8, 5, 22, 0.85)); + border: 1px solid rgba(84, 232, 255, 0.35); + box-shadow: + 0 0 12px rgba(84, 232, 255, 0.15), + inset 0 0 18px rgba(84, 232, 255, 0.05); + color: var(--cyan); + min-height: 64px; +} + +.hud-icon { + width: 30px; height: 30px; + flex: 0 0 auto; + filter: drop-shadow(0 0 6px currentColor); +} + +.hud-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; } + +.hud-label { + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.16em; + color: var(--text-dim); + text-transform: uppercase; +} + +.hud-value { + font-family: var(--font-display); + font-weight: 700; + font-size: 17px; + color: var(--cyan-bright); + text-shadow: 0 0 8px rgba(84, 232, 255, 0.6); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* per-cell accents */ +.hud-cpu { border-color: rgba(84, 232, 255, 0.45); color: var(--cyan); } +.hud-ram { border-color: rgba(189, 108, 255, 0.45); color: var(--purple); } +.hud-storage { border-color: rgba(255, 69, 200, 0.45); color: var(--magenta); } +.hud-uptime { border-color: rgba(57, 255, 122, 0.45); color: var(--green); } +.hud-weather { border-color: rgba(255, 216, 74, 0.45); color: var(--yellow); } + +.hud-cpu .hud-value { color: #9af0ff; text-shadow: 0 0 8px var(--cyan); } +.hud-ram .hud-value { color: #d9b5ff; text-shadow: 0 0 8px var(--purple); } +.hud-storage .hud-value { color: #ff9ddc; text-shadow: 0 0 8px var(--magenta); } +.hud-uptime .hud-value { color: #9affc1; text-shadow: 0 0 8px var(--green); } +.hud-weather .hud-value { color: #ffec9a; text-shadow: 0 0 8px var(--yellow); } + +.hud-temp { font-size: 19px; } + +.hud-search { + display: flex; align-items: center; + background: linear-gradient(180deg, rgba(15, 10, 40, 0.7), rgba(8, 5, 22, 0.85)); + border: 1px solid rgba(84, 232, 255, 0.4); + border-radius: 10px; + padding: 0 14px; + box-shadow: + 0 0 12px rgba(84, 232, 255, 0.18), + inset 0 0 18px rgba(84, 232, 255, 0.05); +} +.hud-search input { + flex: 1; + background: transparent; + border: 0; + outline: none; + color: var(--text); + font-family: var(--font-ui); + font-size: 17px; + padding: 14px 0; +} +.hud-search input::placeholder { color: var(--text-dim); } +.hud-search button { + background: transparent; + border: 0; + color: var(--cyan); + cursor: pointer; + padding: 6px; + display: flex; + filter: drop-shadow(0 0 6px var(--cyan)); +} +.hud-search button svg { width: 22px; height: 22px; } + +/* ================================================================= + HERO + ================================================================= */ +.hero { + display: flex; + align-items: center; + justify-content: center; + gap: 32px; + padding: 8px 12px 4px; + min-height: 220px; +} + +.hero-center { + display: flex; + align-items: center; + gap: 24px; +} + +.hero-kite { + width: 140px; height: 140px; + object-fit: contain; + filter: + drop-shadow(0 0 18px var(--cyan)) + drop-shadow(0 0 32px rgba(84, 232, 255, 0.6)); + animation: kite-pulse 4s ease-in-out infinite; +} +@keyframes kite-pulse { + 0%, 100% { filter: drop-shadow(0 0 14px var(--cyan)) drop-shadow(0 0 28px rgba(84, 232, 255, 0.5)); } + 50% { filter: drop-shadow(0 0 22px var(--cyan)) drop-shadow(0 0 44px rgba(84, 232, 255, 0.8)); } +} + +.hero-titles { display: flex; flex-direction: column; } +.hero-title { + font-family: var(--font-display); + font-weight: 900; + font-size: clamp(48px, 6vw, 92px); + margin: 0; + line-height: 1; + color: #ffffff; + text-shadow: + 0 0 12px rgba(255, 255, 255, 0.6), + 0 0 24px var(--cyan), + 0 0 48px rgba(84, 232, 255, 0.6); + letter-spacing: 0.01em; +} +.dot-ao { + color: var(--cyan-bright); + text-shadow: + 0 0 12px var(--cyan), + 0 0 30px rgba(84, 232, 255, 0.8); +} +.hero-tagline { + font-family: var(--font-display); + font-weight: 500; + font-size: clamp(13px, 1.2vw, 17px); + letter-spacing: 0.42em; + color: var(--text-dim); + margin: 8px 0 0; + text-shadow: 0 0 6px rgba(84, 232, 255, 0.4); +} + +/* hex badge */ +.hex-badge { position: relative; width: 180px; height: 200px; } +.hex-svg { width: 100%; height: 100%; filter: drop-shadow(0 0 14px var(--magenta)); } +.hex-outer { fill: none; stroke: var(--magenta); stroke-width: 3; } +.hex-inner { fill: rgba(20, 8, 32, 0.7); stroke: rgba(255, 69, 200, 0.6); stroke-width: 1.5; } +.hex-content { + position: absolute; inset: 0; + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 2px; + font-family: var(--font-display); + text-shadow: 0 0 8px var(--magenta); +} +.hex-l1 { font-size: 13px; letter-spacing: 0.3em; color: var(--magenta-bright); } +.hex-l2 { font-size: 22px; font-weight: 700; letter-spacing: 0.15em; color: #ff9ddc; } +.hex-l3 { + font-size: 38px; font-weight: 900; + color: var(--cyan-bright); + text-shadow: 0 0 12px var(--cyan); + line-height: 1; +} +.hex-l3 span { font-size: 22px; color: var(--magenta-bright); text-shadow: 0 0 8px var(--magenta); } +.hex-pulse { + width: 80px; height: 14px; margin-top: 4px; +} +.hex-pulse polyline { + fill: none; stroke: var(--green); stroke-width: 1.5; + filter: drop-shadow(0 0 4px var(--green)); +} + +/* ================================================================= + PANELS + ================================================================= */ +.row { display: grid; gap: 18px; } +.row-2 { grid-template-columns: 1fr 1fr; } +.row-3 { grid-template-columns: 1fr 1fr; } +.row-bottom { grid-template-columns: 1.6fr 1fr; align-items: start; } + +.panel { + position: relative; + background: var(--panel-bg); + border: 1.5px solid var(--panel-border, var(--cyan)); + border-radius: 10px; + padding: 14px 18px 14px; + box-shadow: + 0 0 22px var(--panel-glow, rgba(84, 232, 255, 0.22)), + inset 0 0 30px rgba(0, 0, 0, 0.4); + backdrop-filter: blur(6px); +} + +.panel-cyan { --panel-border: var(--cyan); --panel-glow: rgba(84, 232, 255, 0.28); --panel-head-color: var(--cyan); } +.panel-magenta { --panel-border: var(--magenta); --panel-glow: rgba(255, 69, 200, 0.28); --panel-head-color: var(--magenta); } +.panel-purple { --panel-border: var(--purple); --panel-glow: rgba(189, 108, 255, 0.28); --panel-head-color: var(--purple); } +.panel-pink { --panel-border: var(--pink); --panel-glow: rgba(255, 95, 177, 0.28); --panel-head-color: var(--pink); } + +.panel-head { + font-family: var(--font-display); + font-weight: 700; + font-size: 14px; + letter-spacing: 0.22em; + color: var(--panel-head-color); + text-shadow: 0 0 8px var(--panel-head-color); + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} +.chevrons { color: var(--panel-head-color); font-weight: 700; opacity: 0.85; } + +/* ================================================================= + CARDS + ================================================================= */ +.cards { display: grid; gap: 14px; } +.cards-2 { grid-template-columns: 1fr 1fr; } +.cards-3 { grid-template-columns: repeat(3, 1fr); } +.cards-4 { grid-template-columns: repeat(4, 1fr); } + +.card { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + border-radius: 8px; + border: 1px solid rgba(84, 232, 255, 0.22); + background: linear-gradient(180deg, rgba(20, 10, 40, 0.55), rgba(8, 4, 22, 0.75)); + text-decoration: none; + color: var(--text); + transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease; + min-height: 76px; +} +.card:hover { + transform: translateY(-2px); + border-color: var(--panel-border, var(--cyan)); + box-shadow: 0 0 18px var(--panel-glow, rgba(84, 232, 255, 0.4)); + background: linear-gradient(180deg, rgba(30, 15, 55, 0.65), rgba(12, 6, 28, 0.8)); +} + +.card img { + width: 44px; height: 44px; + object-fit: contain; + flex: 0 0 auto; + filter: drop-shadow(0 0 6px rgba(84, 232, 255, 0.4)); +} + +.card-title { + font-family: var(--font-display); + font-weight: 700; + font-size: 17px; + color: #ffffff; + line-height: 1.1; + text-shadow: 0 0 4px rgba(255, 255, 255, 0.25); +} +.card-sub { + font-family: var(--font-ui); + font-size: 13px; + color: var(--text-dim); + margin-top: 3px; + letter-spacing: 0.04em; +} + +.sc-icon { + width: 44px; height: 44px; + display: inline-flex; align-items: center; justify-content: center; + font-family: var(--font-mono); + font-size: 22px; + color: var(--purple-bright); + text-shadow: 0 0 8px var(--purple); + background: rgba(189, 108, 255, 0.08); + border: 1px solid rgba(189, 108, 255, 0.35); + border-radius: 6px; + flex: 0 0 auto; +} + +/* ================================================================= + OVERVIEW + ================================================================= */ +.overview { + list-style: none; + margin: 0; padding: 4px 0; + font-family: var(--font-mono); + font-size: 15px; + display: flex; + flex-direction: column; + gap: 10px; +} +.overview li { + display: grid; + grid-template-columns: 110px 14px 1fr; + align-items: center; + gap: 4px; +} +.ov-key { + color: var(--text-dim); + letter-spacing: 0.08em; + text-transform: uppercase; + font-family: var(--font-display); + font-size: 12px; + font-weight: 600; +} +.ov-sep { color: var(--cyan); text-align: center; } +.ov-val { color: var(--cyan-bright); text-shadow: 0 0 6px rgba(84, 232, 255, 0.5); } + +/* ================================================================= + GAUGES + ================================================================= */ +.gauges { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 14px; + padding: 4px 0 0; +} +.gauge { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 4px 2px 8px; +} +.gauge svg:first-child { width: 110px; height: 110px; transform: rotate(-90deg); } +.g-track { fill: none; stroke: rgba(255, 255, 255, 0.08); stroke-width: 9; } +.g-fill { + fill: none; + stroke-width: 9; + stroke-linecap: round; + stroke-dasharray: 301.6; /* 2π·48 */ + stroke-dashoffset: 301.6; + transition: stroke-dashoffset 0.8s cubic-bezier(.2,.8,.2,1); +} +.gauge[data-color="cyan"] .g-fill { stroke: var(--cyan); filter: drop-shadow(0 0 6px var(--cyan)); } +.gauge[data-color="green"] .g-fill { stroke: var(--green); filter: drop-shadow(0 0 6px var(--green)); } +.gauge[data-color="yellow"] .g-fill { stroke: var(--yellow); filter: drop-shadow(0 0 6px var(--yellow)); } +.gauge[data-color="pink"] .g-fill { stroke: var(--pink); filter: drop-shadow(0 0 6px var(--pink)); } + +.gauge .g-label { + position: absolute; + top: 38px; + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.18em; + color: var(--text-dim); +} +.gauge .g-value { + position: absolute; + top: 54px; + font-family: var(--font-display); + font-weight: 700; + font-size: 22px; + color: var(--cyan-bright); + text-shadow: 0 0 8px currentColor; +} +.gauge[data-color="green"] .g-value { color: var(--green); } +.gauge[data-color="yellow"] .g-value { color: var(--yellow); } + +.gauge .g-sub { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-dim); + margin-top: 6px; +} +.g-wave { + width: 100%; height: 16px; margin-top: 4px; +} +.g-wave polyline { + fill: none; + stroke: var(--cyan); + stroke-width: 1.2; + filter: drop-shadow(0 0 3px var(--cyan)); +} + +.gauge-net { + justify-content: flex-start; + padding-top: 38px; +} +.gauge-net .g-label { position: static; margin-bottom: 6px; } +.g-net { display: flex; flex-direction: column; gap: 4px; align-items: center; } +.net-line { + font-family: var(--font-display); + font-weight: 700; + font-size: 14px; +} +.net-line.up { color: var(--green); text-shadow: 0 0 6px var(--green); } +.net-line.dn { color: var(--magenta); text-shadow: 0 0 6px var(--magenta); } + +/* ================================================================= + RECENT ACTIVITY + ================================================================= */ +.activity { + list-style: none; + margin: 0; padding: 4px 0; + display: flex; + flex-direction: column; + gap: 12px; +} +.activity li { + display: grid; + grid-template-columns: 18px 1fr auto; + align-items: center; + gap: 12px; + padding: 8px 6px; + border-radius: 6px; + background: rgba(20, 10, 40, 0.35); + border: 1px solid rgba(84, 232, 255, 0.12); +} +.act-ico { font-size: 14px; filter: drop-shadow(0 0 4px currentColor); } +.act-txt { font-family: var(--font-ui); font-size: 15px; color: var(--text); } +.act-time { font-family: var(--font-mono); font-size: 13px; color: var(--text-dim); } + +/* ================================================================= + FOOTER + ================================================================= */ +.portal-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 18px; + padding: 18px 0 6px; + font-family: var(--font-display); + font-size: 13px; + letter-spacing: 0.16em; + color: var(--text-dim); +} +.foot-text { text-shadow: 0 0 4px rgba(189, 108, 255, 0.4); } +.foot-bracket { + flex: 1; + height: 8px; + position: relative; +} +.foot-bracket::before { + content: ""; + position: absolute; + top: 50%; + left: 0; right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--purple) 30%, var(--cyan) 70%, transparent); + opacity: 0.7; +} +.foot-bracket.left::after, .foot-bracket.right::after { + content: ""; + position: absolute; top: 50%; + width: 26px; height: 8px; + transform: translateY(-50%); + background: + repeating-linear-gradient(90deg, var(--cyan) 0 3px, transparent 3px 6px); + opacity: 0.7; +} +.foot-bracket.left::after { right: 8px; } +.foot-bracket.right::after { left: 8px; } + +/* ================================================================= + RESPONSIVE + ================================================================= */ +@media (max-width: 1400px) { + .hud { grid-template-columns: repeat(3, 1fr); } + .hud-search { grid-column: 1 / -1; } + .hero { grid-template-columns: 1fr; } + .hero-right { justify-content: center; } + .row-3, .row-bottom { grid-template-columns: 1fr; } + .row-2 { grid-template-columns: 1fr; } +} +@media (max-width: 900px) { + .hud { grid-template-columns: 1fr 1fr; } + .cards-4, .cards-3 { grid-template-columns: 1fr 1fr; } + .hero-title { font-size: 44px; } + .hero-kite { width: 96px; height: 96px; } + .gauges { grid-template-columns: 1fr 1fr; } +} +@media (max-width: 560px) { + .hud { grid-template-columns: 1fr; } + .cards-2, .cards-3, .cards-4 { grid-template-columns: 1fr; } + .gauges { grid-template-columns: 1fr; } +} + +/* ================================================================= + TOAST (coming soon) + ================================================================= */ +#ks-toast { + position: fixed; + left: 50%; + bottom: 40px; + transform: translate(-50%, 30px); + background: linear-gradient(180deg, rgba(20, 10, 40, 0.95), rgba(8, 4, 22, 0.95)); + border: 1px solid var(--magenta); + border-radius: 8px; + padding: 12px 22px; + font-family: var(--font-display); + font-weight: 600; + font-size: 14px; + letter-spacing: 0.14em; + color: var(--magenta-bright); + text-shadow: 0 0 6px var(--magenta); + box-shadow: 0 0 24px rgba(255, 69, 200, 0.5); + opacity: 0; + pointer-events: none; + transition: opacity 0.25s ease, transform 0.25s ease; + z-index: 1000; +} +#ks-toast.show { + opacity: 1; + transform: translate(-50%, 0); +} + + +/* ─── Activity panel polish ─── */ +.activity li.act-empty { + opacity: 0.7; + font-style: italic; +} +.activity li .act-txt { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + +/* ─── Per-core CPU bars ─── */ +.cores { + margin-top: 14px; + padding: 12px 4px 2px; + border-top: 1px dashed rgba(255, 69, 200, 0.25); +} +.cores-head { + display: flex; + align-items: baseline; + justify-content: space-between; + margin-bottom: 10px; + padding: 0 6px; +} +.cores-title { + font-family: var(--font-display); + font-size: 12px; + letter-spacing: 0.22em; + color: var(--magenta); + text-shadow: 0 0 6px var(--magenta); +} +.cores-meta { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-dim); +} +.cores-grid { + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 10px; + padding: 0 4px; + justify-content: center; +} +@media (max-width: 1100px) { + .cores-grid { grid-template-columns: repeat(4, 1fr); } +} +@media (max-width: 560px) { + .cores-grid { grid-template-columns: repeat(2, 1fr); } +} +.core { + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px 8px; + border-radius: 6px; + background: rgba(20, 10, 40, 0.45); + border: 1px solid rgba(84, 232, 255, 0.15); +} +.core-head { + display: flex; + align-items: baseline; + justify-content: space-between; + font-family: var(--font-display); + font-size: 11px; + letter-spacing: 0.1em; +} +.core-label { color: var(--text-dim); } +.core-pct { + color: var(--cyan-bright); + text-shadow: 0 0 4px var(--cyan); + font-weight: 700; +} +.core-track { + position: relative; + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} +.core-bar { + position: absolute; + top: 0; left: 0; bottom: 0; + width: 0%; + background: linear-gradient(90deg, var(--cyan), var(--purple)); + box-shadow: 0 0 6px var(--cyan); + transition: width 0.6s cubic-bezier(.2,.8,.2,1); + border-radius: 3px; +} +/* color shifts as load increases */ +.core[data-load="med"] .core-bar { background: linear-gradient(90deg, var(--green), var(--yellow)); box-shadow: 0 0 6px var(--yellow); } +.core[data-load="med"] .core-pct { color: var(--yellow); text-shadow: 0 0 4px var(--yellow); } +.core[data-load="high"] .core-bar { background: linear-gradient(90deg, var(--yellow), var(--magenta)); box-shadow: 0 0 6px var(--magenta); } +.core[data-load="high"] .core-pct { color: var(--magenta-bright); text-shadow: 0 0 4px var(--magenta); } diff --git a/apps/kitestacks-portal/public/style.css.backup-before-clean-layout b/apps/kitestacks-portal/public/style.css.backup-before-clean-layout new file mode 100644 index 0000000..a6c05c0 --- /dev/null +++ b/apps/kitestacks-portal/public/style.css.backup-before-clean-layout @@ -0,0 +1,314 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --blue: #2f7dff; + --pink: #ff3df2; + --purple: #9b5cff; + --glass: rgba(7, 12, 32, 0.62); + --glass-strong: rgba(10, 18, 44, 0.78); + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(4, 6, 18, 0.2), rgba(4, 6, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 18%, rgba(56, 248, 255, 0.22), transparent 25%), + radial-gradient(circle at 25% 35%, rgba(255, 61, 242, 0.18), transparent 26%), + radial-gradient(circle at 75% 42%, rgba(155, 92, 255, 0.18), transparent 26%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +body::after { + content: ""; + position: fixed; + inset: 0; + background-image: + linear-gradient(rgba(56, 248, 255, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(56, 248, 255, 0.045) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(to bottom, transparent, black 18%, black 85%, transparent); + pointer-events: none; + z-index: 0; +} + +.topbar, +.hero, +.grid, +footer { + position: relative; + z-index: 1; +} + +.topbar { + width: min(1500px, calc(100% - 48px)); + margin: 22px auto 0; + display: grid; + grid-template-columns: repeat(4, minmax(150px, 1fr)) 130px minmax(220px, 1.2fr); + gap: 12px; +} + +.stat, +.weather, +.search { + min-height: 62px; + padding: 12px 14px; + border: 1px solid rgba(56, 248, 255, 0.32); + border-radius: 18px; + background: linear-gradient(145deg, rgba(6, 12, 35, 0.78), rgba(12, 20, 55, 0.5)); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.12), + inset 0 0 20px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.12em; +} + +.stat b { + display: block; + margin-top: 6px; + color: var(--cyan); + font-size: 18px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.85); +} + +.weather { + color: var(--cyan); + font-weight: 800; + text-align: center; +} + +.weather span { + color: var(--muted); + font-size: 11px; + letter-spacing: 0.08em; +} + +.search { + color: white; + outline: none; + font-size: 15px; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.6); +} + +.hero { + width: min(1500px, calc(100% - 48px)); + margin: 32px auto 24px; + min-height: 210px; + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + text-align: left; +} + +.hero-logo { + width: 128px; + height: 128px; + object-fit: contain; + filter: + drop-shadow(0 0 16px rgba(56, 248, 255, 0.9)) + drop-shadow(0 0 32px rgba(255, 61, 242, 0.45)); +} + +.hero h1 { + margin: 0; + font-size: clamp(58px, 8vw, 132px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #f7fbff; + text-transform: none; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 26px rgba(47, 125, 255, 0.65), + 0 0 44px rgba(255, 61, 242, 0.45); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.78); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 700; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.65); +} + +.grid { + width: min(1500px, calc(100% - 48px)); + margin: 0 auto 24px; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-auto-flow: dense; + gap: 18px; +} + +.panel { + min-height: 250px; + padding: 18px; + border-radius: 24px; + border: 1px solid rgba(56, 248, 255, 0.28); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.82), rgba(19, 8, 48, 0.56)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.13), transparent 36%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.13), + 0 0 42px rgba(155, 92, 255, 0.08), + inset 0 0 38px rgba(56, 248, 255, 0.035); + backdrop-filter: blur(18px); + overflow: hidden; + position: relative; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 24px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.7), rgba(255, 61, 242, 0.45), rgba(155, 92, 255, 0.45)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.panel.wide { + grid-column: span 2; +} + +.panel h2 { + margin: 0 0 15px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.18em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 11px; +} + +.wide .cards { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 68px; + padding: 13px 15px; + text-decoration: none; + color: white; + border-radius: 17px; + border: 1px solid rgba(255, 255, 255, 0.11); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.13), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.44); + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.03); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 800; + letter-spacing: 0.02em; + transition: 0.18s ease; +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.75); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.28), + 0 0 32px rgba(255, 61, 242, 0.16); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; +} + +footer { + width: min(1500px, calc(100% - 48px)); + margin: 12px auto 30px; + text-align: center; + color: rgba(226, 241, 255, 0.68); + font-size: 12px; + letter-spacing: 0.2em; + text-transform: uppercase; +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: repeat(2, 1fr); + } + + .grid { + grid-template-columns: repeat(2, 1fr); + } + + .panel.wide { + grid-column: span 2; + } +} + +@media (max-width: 700px) { + .topbar, + .grid, + .hero, + footer { + width: calc(100% - 24px); + } + + .topbar, + .grid, + .wide .cards { + grid-template-columns: 1fr; + } + + .panel.wide { + grid-column: span 1; + } + + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } +} diff --git a/apps/kitestacks-portal/public/style.css.backup-before-phase2 b/apps/kitestacks-portal/public/style.css.backup-before-phase2 new file mode 100644 index 0000000..56e0a71 --- /dev/null +++ b/apps/kitestacks-portal/public/style.css.backup-before-phase2 @@ -0,0 +1,285 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --pink: #ff3df2; + --purple: #9b5cff; + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(2, 5, 18, 0.28), rgba(2, 5, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 12%, rgba(56, 248, 255, 0.26), transparent 22%), + radial-gradient(circle at 25% 45%, rgba(255, 61, 242, 0.2), transparent 28%), + radial-gradient(circle at 80% 48%, rgba(155, 92, 255, 0.18), transparent 28%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +.hero, +.topbar, +.grid { + position: relative; + z-index: 1; +} + +.hero { + width: min(1380px, calc(100% - 48px)); + margin: 28px auto 18px; + min-height: 185px; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; +} + +.hero-logo { + width: 122px; + height: 122px; + object-fit: contain; + filter: + drop-shadow(0 0 14px rgba(56, 248, 255, 0.95)) + drop-shadow(0 0 34px rgba(255, 61, 242, 0.55)); +} + +.hero h1 { + margin: 0; + font-size: clamp(62px, 8vw, 126px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #ffffff; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 26px rgba(47, 125, 255, 0.7), + 0 0 48px rgba(255, 61, 242, 0.52); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.8); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 800; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.75); +} + +.topbar { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 28px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.stat, +.weather, +.searchbox { + min-height: 76px; + padding: 15px 18px; + border-radius: 22px; + border: 1px solid rgba(56, 248, 255, 0.34); + background: + linear-gradient(135deg, rgba(7, 13, 39, 0.84), rgba(16, 10, 45, 0.62)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + inset 0 0 28px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.16em; +} + +.stat b { + display: block; + margin-top: 7px; + color: var(--cyan); + font-size: 20px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather { + display: flex; + flex-direction: column; + justify-content: center; + color: var(--cyan); + font-weight: 900; + font-size: 17px; +} + +.weather b { + font-size: 24px; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather span { + margin-top: 4px; + color: var(--muted); + font-size: 12px; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.searchbox { + display: flex; + align-items: center; +} + +.search { + width: 100%; + border: 0; + outline: 0; + color: white; + font-size: 18px; + background: transparent; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.65); +} + +.grid { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 42px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.panel { + min-height: 235px; + padding: 20px; + border-radius: 26px; + border: 1px solid rgba(56, 248, 255, 0.3); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.84), rgba(20, 8, 50, 0.6)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 38%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 42%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + 0 0 42px rgba(155, 92, 255, 0.1), + inset 0 0 38px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(18px); + position: relative; + overflow: hidden; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 26px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.78), rgba(255, 61, 242, 0.48), rgba(155, 92, 255, 0.42)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.infrastructure, +.future { + grid-column: 1 / -1; +} + +.panel h2 { + margin: 0 0 16px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.2em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 12px; +} + +.cards.two { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 72px; + padding: 14px 16px; + text-decoration: none; + color: white; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.14), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.46); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 900; + letter-spacing: 0.03em; + transition: 0.18s ease; + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.035); +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.78); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.3), + 0 0 34px rgba(255, 61, 242, 0.18); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.06em; +} + +@media (max-width: 760px) { + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } + + .topbar, + .grid, + .cards.two { + grid-template-columns: 1fr; + } + + .infrastructure, + .future { + grid-column: auto; + } +} diff --git a/apps/kitestacks-portal/public/style.css.backup-layout-v2 b/apps/kitestacks-portal/public/style.css.backup-layout-v2 new file mode 100644 index 0000000..a6c05c0 --- /dev/null +++ b/apps/kitestacks-portal/public/style.css.backup-layout-v2 @@ -0,0 +1,314 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --blue: #2f7dff; + --pink: #ff3df2; + --purple: #9b5cff; + --glass: rgba(7, 12, 32, 0.62); + --glass-strong: rgba(10, 18, 44, 0.78); + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(4, 6, 18, 0.2), rgba(4, 6, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 18%, rgba(56, 248, 255, 0.22), transparent 25%), + radial-gradient(circle at 25% 35%, rgba(255, 61, 242, 0.18), transparent 26%), + radial-gradient(circle at 75% 42%, rgba(155, 92, 255, 0.18), transparent 26%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +body::after { + content: ""; + position: fixed; + inset: 0; + background-image: + linear-gradient(rgba(56, 248, 255, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(56, 248, 255, 0.045) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(to bottom, transparent, black 18%, black 85%, transparent); + pointer-events: none; + z-index: 0; +} + +.topbar, +.hero, +.grid, +footer { + position: relative; + z-index: 1; +} + +.topbar { + width: min(1500px, calc(100% - 48px)); + margin: 22px auto 0; + display: grid; + grid-template-columns: repeat(4, minmax(150px, 1fr)) 130px minmax(220px, 1.2fr); + gap: 12px; +} + +.stat, +.weather, +.search { + min-height: 62px; + padding: 12px 14px; + border: 1px solid rgba(56, 248, 255, 0.32); + border-radius: 18px; + background: linear-gradient(145deg, rgba(6, 12, 35, 0.78), rgba(12, 20, 55, 0.5)); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.12), + inset 0 0 20px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.12em; +} + +.stat b { + display: block; + margin-top: 6px; + color: var(--cyan); + font-size: 18px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.85); +} + +.weather { + color: var(--cyan); + font-weight: 800; + text-align: center; +} + +.weather span { + color: var(--muted); + font-size: 11px; + letter-spacing: 0.08em; +} + +.search { + color: white; + outline: none; + font-size: 15px; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.6); +} + +.hero { + width: min(1500px, calc(100% - 48px)); + margin: 32px auto 24px; + min-height: 210px; + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + text-align: left; +} + +.hero-logo { + width: 128px; + height: 128px; + object-fit: contain; + filter: + drop-shadow(0 0 16px rgba(56, 248, 255, 0.9)) + drop-shadow(0 0 32px rgba(255, 61, 242, 0.45)); +} + +.hero h1 { + margin: 0; + font-size: clamp(58px, 8vw, 132px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #f7fbff; + text-transform: none; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 26px rgba(47, 125, 255, 0.65), + 0 0 44px rgba(255, 61, 242, 0.45); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.78); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 700; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.65); +} + +.grid { + width: min(1500px, calc(100% - 48px)); + margin: 0 auto 24px; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-auto-flow: dense; + gap: 18px; +} + +.panel { + min-height: 250px; + padding: 18px; + border-radius: 24px; + border: 1px solid rgba(56, 248, 255, 0.28); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.82), rgba(19, 8, 48, 0.56)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.13), transparent 36%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.13), + 0 0 42px rgba(155, 92, 255, 0.08), + inset 0 0 38px rgba(56, 248, 255, 0.035); + backdrop-filter: blur(18px); + overflow: hidden; + position: relative; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 24px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.7), rgba(255, 61, 242, 0.45), rgba(155, 92, 255, 0.45)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.panel.wide { + grid-column: span 2; +} + +.panel h2 { + margin: 0 0 15px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.18em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.9), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 11px; +} + +.wide .cards { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 68px; + padding: 13px 15px; + text-decoration: none; + color: white; + border-radius: 17px; + border: 1px solid rgba(255, 255, 255, 0.11); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.13), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.44); + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.03); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 800; + letter-spacing: 0.02em; + transition: 0.18s ease; +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.75); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.28), + 0 0 32px rgba(255, 61, 242, 0.16); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; +} + +footer { + width: min(1500px, calc(100% - 48px)); + margin: 12px auto 30px; + text-align: center; + color: rgba(226, 241, 255, 0.68); + font-size: 12px; + letter-spacing: 0.2em; + text-transform: uppercase; +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: repeat(2, 1fr); + } + + .grid { + grid-template-columns: repeat(2, 1fr); + } + + .panel.wide { + grid-column: span 2; + } +} + +@media (max-width: 700px) { + .topbar, + .grid, + .hero, + footer { + width: calc(100% - 24px); + } + + .topbar, + .grid, + .wide .cards { + grid-template-columns: 1fr; + } + + .panel.wide { + grid-column: span 1; + } + + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } +} diff --git a/apps/kitestacks-portal/public/style.css.backup-pre-upgrade-2026-06-07-0017 b/apps/kitestacks-portal/public/style.css.backup-pre-upgrade-2026-06-07-0017 new file mode 100644 index 0000000..56e0a71 --- /dev/null +++ b/apps/kitestacks-portal/public/style.css.backup-pre-upgrade-2026-06-07-0017 @@ -0,0 +1,285 @@ +* { + box-sizing: border-box; +} + +:root { + --cyan: #38f8ff; + --pink: #ff3df2; + --purple: #9b5cff; + --text: #e9f7ff; + --muted: rgba(226, 241, 255, 0.72); +} + +body { + margin: 0; + min-height: 100vh; + font-family: Arial, Helvetica, sans-serif; + color: var(--text); + background: + linear-gradient(180deg, rgba(2, 5, 18, 0.28), rgba(2, 5, 18, 0.92)), + url("/images/cyberpunk-bg.png") center center / cover fixed; + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + background: + radial-gradient(circle at 50% 12%, rgba(56, 248, 255, 0.26), transparent 22%), + radial-gradient(circle at 25% 45%, rgba(255, 61, 242, 0.2), transparent 28%), + radial-gradient(circle at 80% 48%, rgba(155, 92, 255, 0.18), transparent 28%), + rgba(0, 0, 20, 0.34); + pointer-events: none; + z-index: 0; +} + +.hero, +.topbar, +.grid { + position: relative; + z-index: 1; +} + +.hero { + width: min(1380px, calc(100% - 48px)); + margin: 28px auto 18px; + min-height: 185px; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; +} + +.hero-logo { + width: 122px; + height: 122px; + object-fit: contain; + filter: + drop-shadow(0 0 14px rgba(56, 248, 255, 0.95)) + drop-shadow(0 0 34px rgba(255, 61, 242, 0.55)); +} + +.hero h1 { + margin: 0; + font-size: clamp(62px, 8vw, 126px); + line-height: 0.9; + letter-spacing: -0.08em; + color: #ffffff; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 26px rgba(47, 125, 255, 0.7), + 0 0 48px rgba(255, 61, 242, 0.52); +} + +.hero h1 span { + color: var(--cyan); +} + +.hero p { + margin: 18px 0 0 8px; + color: rgba(230, 247, 255, 0.8); + letter-spacing: 0.34em; + font-size: 14px; + font-weight: 800; + text-shadow: 0 0 10px rgba(56, 248, 255, 0.75); +} + +.topbar { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 28px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.stat, +.weather, +.searchbox { + min-height: 76px; + padding: 15px 18px; + border-radius: 22px; + border: 1px solid rgba(56, 248, 255, 0.34); + background: + linear-gradient(135deg, rgba(7, 13, 39, 0.84), rgba(16, 10, 45, 0.62)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 40%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + inset 0 0 28px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(14px); +} + +.stat { + color: var(--muted); + font-size: 12px; + letter-spacing: 0.16em; +} + +.stat b { + display: block; + margin-top: 7px; + color: var(--cyan); + font-size: 20px; + letter-spacing: normal; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather { + display: flex; + flex-direction: column; + justify-content: center; + color: var(--cyan); + font-weight: 900; + font-size: 17px; +} + +.weather b { + font-size: 24px; + text-shadow: 0 0 14px rgba(56, 248, 255, 0.9); +} + +.weather span { + margin-top: 4px; + color: var(--muted); + font-size: 12px; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.searchbox { + display: flex; + align-items: center; +} + +.search { + width: 100%; + border: 0; + outline: 0; + color: white; + font-size: 18px; + background: transparent; +} + +.search::placeholder { + color: rgba(226, 241, 255, 0.65); +} + +.grid { + width: min(1180px, calc(100% - 48px)); + margin: 0 auto 42px; + display: grid; + grid-template-columns: repeat(2, minmax(260px, 1fr)); + gap: 18px; +} + +.panel { + min-height: 235px; + padding: 20px; + border-radius: 26px; + border: 1px solid rgba(56, 248, 255, 0.3); + background: + linear-gradient(135deg, rgba(8, 14, 39, 0.84), rgba(20, 8, 50, 0.6)), + radial-gradient(circle at top right, rgba(255, 61, 242, 0.14), transparent 38%), + radial-gradient(circle at bottom left, rgba(56, 248, 255, 0.12), transparent 42%); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.14), + 0 0 42px rgba(155, 92, 255, 0.1), + inset 0 0 38px rgba(56, 248, 255, 0.04); + backdrop-filter: blur(18px); + position: relative; + overflow: hidden; +} + +.panel::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 26px; + padding: 1px; + background: linear-gradient(135deg, rgba(56, 248, 255, 0.78), rgba(255, 61, 242, 0.48), rgba(155, 92, 255, 0.42)); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.infrastructure, +.future { + grid-column: 1 / -1; +} + +.panel h2 { + margin: 0 0 16px; + color: var(--cyan); + font-size: 15px; + letter-spacing: 0.2em; + text-shadow: + 0 0 10px rgba(56, 248, 255, 0.95), + 0 0 22px rgba(255, 61, 242, 0.35); +} + +.cards { + display: grid; + gap: 12px; +} + +.cards.two { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.cards a { + min-height: 72px; + padding: 14px 16px; + text-decoration: none; + color: white; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: + linear-gradient(90deg, rgba(56, 248, 255, 0.14), rgba(255, 61, 242, 0.08)), + rgba(2, 8, 24, 0.46); + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 900; + letter-spacing: 0.03em; + transition: 0.18s ease; + box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.035); +} + +.cards a:hover { + transform: translateY(-3px) scale(1.01); + border-color: rgba(56, 248, 255, 0.78); + box-shadow: + 0 0 18px rgba(56, 248, 255, 0.3), + 0 0 34px rgba(255, 61, 242, 0.18); +} + +.cards span { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.06em; +} + +@media (max-width: 760px) { + .hero { + flex-direction: column; + text-align: center; + } + + .hero p { + margin-left: 0; + letter-spacing: 0.18em; + } + + .topbar, + .grid, + .cards.two { + grid-template-columns: 1fr; + } + + .infrastructure, + .future { + grid-column: auto; + } +} diff --git a/apps/linkding/data_backup/db.sqlite3 b/apps/linkding/data_backup/db.sqlite3 new file mode 100644 index 0000000..45f9dc5 Binary files /dev/null and b/apps/linkding/data_backup/db.sqlite3 differ diff --git a/apps/linkding/data_backup/db.sqlite3-shm b/apps/linkding/data_backup/db.sqlite3-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/apps/linkding/data_backup/db.sqlite3-shm differ diff --git a/apps/linkding/data_backup/db.sqlite3-wal b/apps/linkding/data_backup/db.sqlite3-wal new file mode 100644 index 0000000..e69de29 diff --git a/apps/linkding/data_backup/secretkey.txt b/apps/linkding/data_backup/secretkey.txt new file mode 100644 index 0000000..46f8509 --- /dev/null +++ b/apps/linkding/data_backup/secretkey.txt @@ -0,0 +1 @@ +)61j!39csxl!i$)q3r^gco$+9@v2cn+1xv#(+58x^nyfeayu41 \ No newline at end of file diff --git a/apps/linkding/data_backup/tasks.sqlite3 b/apps/linkding/data_backup/tasks.sqlite3 new file mode 100644 index 0000000..63ab9fe Binary files /dev/null and b/apps/linkding/data_backup/tasks.sqlite3 differ diff --git a/apps/linkding/data_backup/tasks.sqlite3-shm b/apps/linkding/data_backup/tasks.sqlite3-shm new file mode 100644 index 0000000..0bfc598 Binary files /dev/null and b/apps/linkding/data_backup/tasks.sqlite3-shm differ diff --git a/apps/linkding/data_backup/tasks.sqlite3-wal b/apps/linkding/data_backup/tasks.sqlite3-wal new file mode 100644 index 0000000..6b98404 Binary files /dev/null and b/apps/linkding/data_backup/tasks.sqlite3-wal differ diff --git a/apps/linkding/docker-compose.yml b/apps/linkding/docker-compose.yml new file mode 100644 index 0000000..c953c5e --- /dev/null +++ b/apps/linkding/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.8" + +services: + linkding: + image: sissbruecker/linkding:latest + container_name: linkding + restart: unless-stopped + ports: + - "192.168.1.205:9095:9090" # Bind to your LAN IP + volumes: + - ./data:/etc/linkding/data:rw + environment: + - LINKDING_ADMIN_USER=admin + - LINKDING_ADMIN_PASSWORD=kite.link.p12217177 diff --git a/apps/linkding/docker-compose.yml.bak b/apps/linkding/docker-compose.yml.bak new file mode 100644 index 0000000..c953c5e --- /dev/null +++ b/apps/linkding/docker-compose.yml.bak @@ -0,0 +1,14 @@ +version: "3.8" + +services: + linkding: + image: sissbruecker/linkding:latest + container_name: linkding + restart: unless-stopped + ports: + - "192.168.1.205:9095:9090" # Bind to your LAN IP + volumes: + - ./data:/etc/linkding/data:rw + environment: + - LINKDING_ADMIN_USER=admin + - LINKDING_ADMIN_PASSWORD=kite.link.p12217177 diff --git a/apps/openproject/docker-compose.yml b/apps/openproject/docker-compose.yml new file mode 100644 index 0000000..830b93a --- /dev/null +++ b/apps/openproject/docker-compose.yml @@ -0,0 +1,21 @@ +services: + openproject: + image: openproject/openproject:15 + container_name: openproject + restart: unless-stopped + + ports: + - "80:80" + + environment: + OPENPROJECT_SECRET_KEY_BASE: I-want-to-be-a-millionare-919!<3 + OPENPROJECT_HOST__NAME: tasks.kitestacks.com + OPENPROJECT_HTTPS: false + + volumes: + - openproject_pgdata:/var/openproject/pgdata + - openproject_assets:/var/openproject/assets + +volumes: + openproject_pgdata: + openproject_assets: diff --git a/apps/prometheus/docker-compose.yml b/apps/prometheus/docker-compose.yml new file mode 100644 index 0000000..2dc103e --- /dev/null +++ b/apps/prometheus/docker-compose.yml @@ -0,0 +1,16 @@ +services: + prometheus: + image: prom/prometheus + container_name: prometheus + restart: unless-stopped + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + + node-exporter: + image: prom/node-exporter + container_name: node-exporter + restart: unless-stopped + ports: + - "9100:9100" diff --git a/apps/prometheus/docker-compose.yml~ b/apps/prometheus/docker-compose.yml~ new file mode 100644 index 0000000..2dc103e --- /dev/null +++ b/apps/prometheus/docker-compose.yml~ @@ -0,0 +1,16 @@ +services: + prometheus: + image: prom/prometheus + container_name: prometheus + restart: unless-stopped + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + + node-exporter: + image: prom/node-exporter + container_name: node-exporter + restart: unless-stopped + ports: + - "9100:9100" diff --git a/apps/prometheus/prometheus.yml b/apps/prometheus/prometheus.yml new file mode 100644 index 0000000..74bc850 --- /dev/null +++ b/apps/prometheus/prometheus.yml @@ -0,0 +1,7 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: "t14-node" + static_configs: + - targets: ["node-exporter:9100"] diff --git a/apps/shaarli/.htpasswd b/apps/shaarli/.htpasswd new file mode 100644 index 0000000..26b396d --- /dev/null +++ b/apps/shaarli/.htpasswd @@ -0,0 +1,2 @@ +admin:$2y$05$4PNrkVS8Ofom4Udew3K0OuPJ4i8fVloYHfDBuPAhHALAHLCCc4ik. + diff --git a/apps/shaarli/docker-compose.yml b/apps/shaarli/docker-compose.yml new file mode 100644 index 0000000..1ca6867 --- /dev/null +++ b/apps/shaarli/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.8" + +services: + shaarli: + image: ghcr.io/shaarli/shaarli:latest + container_name: shaarli + restart: unless-stopped + ports: + - "8085:80" # change 8085 to whatever port you want + volumes: + - ./data:/var/www/html/data + - ./htpasswd:/var/www/html/.htpasswd:ro + diff --git a/apps/zammad/docker-compose.old.yml b/apps/zammad/docker-compose.old.yml new file mode 100644 index 0000000..88e2487 --- /dev/null +++ b/apps/zammad/docker-compose.old.yml @@ -0,0 +1,38 @@ +services: + zammad-postgresql: + image: postgres:15-alpine + container_name: zammad-postgresql + restart: unless-stopped + environment: + POSTGRES_USER: zammad + POSTGRES_PASSWORD: zammadpass + POSTGRES_DB: zammad + volumes: + - zammad-postgresql:/var/lib/postgresql/data + + zammad-redis: + image: redis:7-alpine + container_name: zammad-redis + restart: unless-stopped + + zammad: + image: zammad/zammad:latest + container_name: zammad + restart: unless-stopped + depends_on: + - zammad-postgresql + - zammad-redis + environment: + POSTGRESQL_HOST: zammad-postgresql + POSTGRESQL_USER: zammad + POSTGRESQL_PASS: zammadpass + POSTGRESQL_DB: zammad + REDIS_URL: redis://zammad-redis:6379 + ports: + - "8080:3000" + volumes: + - zammad-data:/opt/zammad + +volumes: + zammad-postgresql: + zammad-data: