This commit is contained in:
kastov
2025-11-07 21:54:26 +03:00
parent e41ab2429b
commit 06222cba38
30 changed files with 139 additions and 1346 deletions

96
.github/workflows/build-docs.yml vendored Normal file
View File

@@ -0,0 +1,96 @@
name: Build docs and landing
on:
push:
branches:
- main
jobs:
build-landing:
name: Build landing
runs-on: ubuntu-22.04
steps:
- name: Checkout landing repository
uses: actions/checkout@v4
with:
repository: remnawave/landing
token: ${{ secrets.LANDING_REPO_TOKEN }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.18.0'
- name: Install dependencies
run: npm ci
- name: Build landing
run: npm run start:build
- name: Upload landing artifact
uses: actions/upload-artifact@v4
with:
name: landing-build
path: landing/
build-docs:
name: Build documentation
runs-on: ubuntu-22.04
steps:
- name: Checkout docs repository
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.18.0'
- name: Install dependencies
run: npm ci
- name: Build documentation
run: npm run build
- name: Upload docs artifact
uses: actions/upload-artifact@v4
with:
name: docs-build
path: build/
build-docker:
name: Build and push Docker image
runs-on: ubuntu-22.04
needs: [build-landing, build-docs]
steps:
- name: Checkout docs repository
uses: actions/checkout@v4
- name: Download landing artifact
uses: actions/download-artifact@v4
with:
name: landing-build
path: landing/
- name: Download docs artifact
uses: actions/download-artifact@v4
with:
name: docs-build
path: build/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
remnawave/internal-docs:latest
platforms: linux/amd64

3
.gitignore vendored
View File

@@ -160,4 +160,5 @@ yarn-error.log*
.wip/
samples/
samples/
docker-compose.local.yml

View File

@@ -29,22 +29,47 @@
redir /blog/learn /docs/learn/quick-start permanent
redir /blog/learn/quick-start /docs/learn/quick-start permanent
redir /docs/install/reverse-proxies/ /docs/install/reverse-proxies/ permanent
redir /docs/ /docs/overview/introduction permanent
redir /apps /docs/clients permanent
redir /client /docs/clients permanent
redir /donate /docs/donate permanent
redir /blog/learn /docs/learn/quick-start permanent
redir /blog/learn/quick-start /docs/learn/quick-start permanent
root * /app/build
encode gzip
file_server
try_files {path} /index.html
handle / {
root * /app/landing
encode gzip
file_server
try_files {path} /index.html
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
}
handle_path /landing-assets/* {
root * /app/landing/landing-assets
encode gzip
file_server
try_files {path} /index.html
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
}
handle {
root * /app/docs
encode gzip
file_server
try_files {path} /index.html
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
}
}
}

View File

@@ -1,67 +1,14 @@
# # docker build -t docusaurus-starter .
# # docker run -d -p 80:80 --name docusaurus-starter-app docusaurus-starter
# ARG NODE_VERSION=22.17.0
# ARG NGINX_VERSION=1.27.3
# ARG APP_PORT=80
# ARG IMAGE_NAME=remnawave-docs
# FROM node:${NODE_VERSION}-alpine as builder
# WORKDIR /usr/src/app
# COPY .npmrc ./
# COPY package*.json ./
# RUN npm install
# COPY . .
# RUN npm run build
# # ---
# FROM nginx:${NGINX_VERSION}-alpine
# LABEL name=${IMAGE_NAME}
# WORKDIR /usr/src/app
# #COPY --from=builder /usr/src/app/nginx.conf /usr/share/nginx/
# COPY --from=builder /usr/src/app/build /usr/share/nginx/html/
# EXPOSE ${APP_PORT}
# CMD ["nginx", "-g", "daemon off;"]
ARG NODE_VERSION=22.17.0
ARG CADDY_VERSION=2.10
ARG IMAGE_NAME=remnawave-docs
FROM node:${NODE_VERSION}-alpine as builder
WORKDIR /usr/src/app
COPY .npmrc ./
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---
FROM caddy:${CADDY_VERSION}-alpine
LABEL name=${IMAGE_NAME}
WORKDIR /app
COPY --from=builder /usr/src/app/build /app/build
COPY build /app/docs
COPY landing /app/landing
COPY Caddyfile /etc/caddy/Caddyfile

View File

@@ -176,7 +176,8 @@ const config: Config = {
title: 'Remnawave',
logo: {
alt: 'Remnawave Logo',
src: 'img/logo.svg'
src: 'img/logo.svg',
href: 'https://docs.rw'
},
items: [
{

343
package-lock.json generated
View File

@@ -12,13 +12,10 @@
"@docusaurus/plugin-client-redirects": "^3.9.2",
"@docusaurus/preset-classic": "^3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
"@mantine/core": "^8.3.6",
"@mantine/hooks": "^8.3.6",
"@mdx-js/react": "^3.1.1",
"@scalar/docusaurus": "^0.7.21",
"@tabler/icons-react": "^3.35.0",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"prism-react-renderer": "^2.4.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -4383,59 +4380,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/react": {
"version": "0.27.16",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz",
"integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.6",
"@floating-ui/utils": "^0.2.10",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
"integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.7.4"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -4776,34 +4720,6 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
"license": "MIT"
},
"node_modules/@mantine/core": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz",
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.27.16",
"clsx": "^2.1.1",
"react-number-format": "^5.4.4",
"react-remove-scroll": "^2.7.1",
"react-textarea-autosize": "8.5.9",
"type-fest": "^4.41.0"
},
"peerDependencies": {
"@mantine/hooks": "8.3.6",
"react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x"
}
},
"node_modules/@mantine/hooks": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz",
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==",
"license": "MIT",
"peerDependencies": {
"react": "^18.x || ^19.x"
}
},
"node_modules/@mdx-js/mdx": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
@@ -9713,12 +9629,6 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT"
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/detect-port": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz",
@@ -11423,33 +11333,6 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -11570,15 +11453,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-own-enumerable-property-symbols": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
@@ -16249,21 +16123,6 @@
"pathe": "^2.0.1"
}
},
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -19190,63 +19049,6 @@
"webpack": ">=4.41.1 || 5.x"
}
},
"node_modules/react-number-format": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz",
"integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==",
"license": "MIT",
"peerDependencies": {
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-remove-scroll": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
"integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
"license": "MIT",
"dependencies": {
"react-remove-scroll-bar": "^2.3.7",
"react-style-singleton": "^2.2.3",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.3",
"use-sidecar": "^1.1.3"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-remove-scroll-bar": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
"license": "MIT",
"dependencies": {
"react-style-singleton": "^2.2.2",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-router": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
@@ -19298,45 +19100,6 @@
"react": ">=15"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
"license": "MIT",
"dependencies": {
"get-nonce": "^1.0.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-textarea-autosize": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
"integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
"use-composed-ref": "^1.3.0",
"use-latest": "^1.2.1"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -21140,12 +20903,6 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tabbable": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz",
"integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==",
"license": "MIT"
},
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
@@ -21485,18 +21242,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -22102,94 +21847,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-composed-ref": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
"integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
"integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-latest": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz",
"integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
"license": "MIT",
"dependencies": {
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sidecar": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
"license": "MIT",
"dependencies": {
"detect-node-es": "^1.1.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",

View File

@@ -19,13 +19,10 @@
"@docusaurus/plugin-client-redirects": "^3.9.2",
"@docusaurus/preset-classic": "^3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
"@mantine/core": "^8.3.6",
"@mantine/hooks": "^8.3.6",
"@mdx-js/react": "^3.1.1",
"@scalar/docusaurus": "^0.7.21",
"@tabler/icons-react": "^3.35.0",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"prism-react-renderer": "^2.4.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",

View File

@@ -1,8 +0,0 @@
export const AnimatedBackground = () => {
return (
<div className="animated-background">
<div className="blob-1" />
<div className="blob-2" />
</div>
)
}

View File

@@ -1,2 +0,0 @@
export { AnimatedBackground } from './AnimatedBackground'

View File

@@ -1,74 +0,0 @@
import { Card, Group, Stack, Text, ThemeIcon, Title } from '@mantine/core'
import { motion } from 'framer-motion'
import React from 'react'
interface FeatureCardProps {
color?: string
description: string
icon: React.ReactNode
title: string
}
const cardVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
type: 'spring' as const,
stiffness: 80,
damping: 15
}
}
}
export const FeatureCard = ({ title, description, icon, color = 'cyan' }: FeatureCardProps) => {
return (
<motion.div
initial="hidden"
style={{ height: '100%' }}
variants={cardVariants}
viewport={{ once: true }}
whileHover={{
scale: 1.03,
y: -4,
transition: { duration: 0.2 }
}}
whileInView="visible"
>
<Card
p={{ base: 'md', sm: 'lg', md: 'xl' }}
radius="lg"
style={{
background: 'rgba(255, 255, 255, 0.02)',
border: '1px solid rgba(255, 255, 255, 0.08)',
backdropFilter: 'blur(10px)',
height: '100%',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden'
}}
>
<Stack gap="md">
<Group gap="md" wrap="nowrap">
<ThemeIcon color={color} radius="md" size="xl" variant="outline">
{icon}
</ThemeIcon>
</Group>
<Title
c="white"
order={3}
style={{
fontSize: 'clamp(1.1rem, 3vw, 1.5rem)'
}}
>
{title}
</Title>
<Text c="dimmed" size="sm" style={{ lineHeight: 1.6 }}>
{description}
</Text>
</Stack>
</Card>
</motion.div>
)
}

View File

@@ -1,2 +0,0 @@
export { FeatureCard } from './FeatureCard'

View File

@@ -1,110 +0,0 @@
import { Box, Card, Stack, Text, ThemeIcon } from '@mantine/core'
import { motion } from 'framer-motion'
import React from 'react'
interface StatCardProps {
color: 'blue' | 'cyan' | 'green' | 'violet'
icon: React.ReactNode
label: string
value: number | string
}
const gradients = {
violet: {
background:
'linear-gradient(135deg, rgba(151, 117, 250, 0.1) 0%, rgba(132, 94, 247, 0.05) 100%)',
border: 'rgba(151, 117, 250, 0.2)',
radial: 'radial-gradient(circle, rgba(151, 117, 250, 0.15) 0%, transparent 70%)',
text: 'linear-gradient(135deg, #9775fa 0%, #845ef7 100%)'
},
cyan: {
background:
'linear-gradient(135deg, rgba(34, 211, 238, 0.1) 0%, rgba(6, 182, 212, 0.05) 100%)',
border: 'rgba(34, 211, 238, 0.2)',
radial: 'radial-gradient(circle, rgba(34, 211, 238, 0.15) 0%, transparent 70%)',
text: 'linear-gradient(135deg, #22d3ee 0%, #06b6d4 100%)'
},
blue: {
background:
'linear-gradient(135deg, rgba(34, 139, 230, 0.1) 0%, rgba(28, 126, 214, 0.05) 100%)',
border: 'rgba(34, 139, 230, 0.2)',
radial: 'radial-gradient(circle, rgba(34, 139, 230, 0.15) 0%, transparent 70%)',
text: 'linear-gradient(135deg, #228be6 0%, #1c7ed6 100%)'
},
green: {
background:
'linear-gradient(135deg, rgba(64, 192, 87, 0.1) 0%, rgba(55, 178, 77, 0.05) 100%)',
border: 'rgba(64, 192, 87, 0.2)',
radial: 'radial-gradient(circle, rgba(64, 192, 87, 0.15) 0%, transparent 70%)',
text: 'linear-gradient(135deg, #40c057 0%, #37b24d 100%)'
}
}
export const StatCard = ({ label, value, icon, color }: StatCardProps) => {
const gradient = gradients[color]
return (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
transition={{
type: 'spring' as const,
stiffness: 300,
damping: 25
}}
viewport={{ once: true }}
whileHover={{
scale: 1.03,
transition: { duration: 0.2 }
}}
whileInView={{ opacity: 1, scale: 1 }}
>
<Card
p={{ base: 'lg', sm: 'xl' }}
radius="xl"
style={{
background: gradient.background,
border: '1px solid',
borderColor: gradient.border,
backdropFilter: 'blur(10px)',
position: 'relative',
overflow: 'hidden'
}}
>
<Box
style={{
position: 'absolute',
top: '-50%',
right: '-50%',
width: '200%',
height: '200%',
background: gradient.radial,
pointerEvents: 'none'
}}
/>
<Stack align="center" gap="sm" style={{ position: 'relative', zIndex: 1 }}>
<ThemeIcon color={color} radius="xl" size={72} variant="outline">
{icon}
</ThemeIcon>
<Text
fw={700}
style={{
fontSize: 'clamp(2rem, 6vw, 3.5rem)',
background: gradient.text,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}
ta="center"
>
{value}
</Text>
<Text c="dimmed" fw={600} size="md" ta="center">
{label}
</Text>
</Stack>
</Card>
</motion.div>
)
}

View File

@@ -1,2 +0,0 @@
export { StatCard } from './StatCard'

View File

@@ -4,7 +4,7 @@
* work well for content-centric websites.
*/
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Unbounded:wght@200..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
body {
font-family: 'Montserrat', sans-serif !important;

View File

@@ -1,485 +0,0 @@
import {
IconBox,
IconBrandGithub,
IconBrandTelegram,
IconCloudDataConnection,
IconDevices,
IconHeart,
IconRocket,
IconServer,
IconShieldCheck,
IconUsers
} from '@tabler/icons-react'
import '@mantine/core/styles.css'
import {
Anchor,
Badge,
Box,
Button,
Container,
Divider,
Grid,
Group,
Image,
MantineProvider,
Stack,
Text,
ThemeIcon,
Title
} from '@mantine/core'
import { TbArrowRightDashed, TbCloud, TbHeartFilled } from 'react-icons/tb'
import { BiLogoTelegram } from 'react-icons/bi'
import { SiNestjs } from 'react-icons/si'
import { motion } from 'framer-motion'
import Link from '@docusaurus/Link'
import React, { JSX } from 'react'
import Layout from '@theme/Layout'
import { AnimatedBackground } from '../components/AnimatedBackground'
import { FeatureCard } from '../components/FeatureCard'
import { theme } from '../theme/mantine-theme/theme'
import { StatCard } from '../components/StatCard'
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
}
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
type: 'spring' as const,
stiffness: 80,
damping: 15
}
}
}
export function HomePage() {
return (
<div className="landing-page-wrapper">
<AnimatedBackground />
<Container
maw={1400}
px={{ base: 'md', sm: 'lg', md: 'xl' }}
py={{ base: 'xl', sm: '3rem', md: '4rem' }}
style={{ position: 'relative', zIndex: 1 }}
>
<motion.div
animate="visible"
initial="hidden"
style={{
display: 'flex',
flexDirection: 'column',
gap: 'var(--section-gap, 4rem)'
}}
variants={containerVariants}
>
<motion.div variants={itemVariants}>
<Stack align="center" gap="xl">
<Box ta="center">
<Title
c="white"
ff="Unbounded"
mb="md"
order={1}
style={{
fontSize: 'clamp(2rem, 8vw, 3.5rem)',
lineHeight: 1.2
}}
ta="center"
>
<Text c="cyan" component="span" fw="inherit" fz="inherit">
Remna
</Text>
<Text c="white" component="span" fw="inherit" fz="inherit">
wave
</Text>
</Title>
<Title
c="white"
ff="Unbounded"
mb="md"
order={2}
style={{
fontSize: 'clamp(1.5rem, 5vw, 2.5rem)',
lineHeight: 1.3
}}
ta="center"
>
<Text
component="span"
fw="inherit"
fz="inherit"
style={{
background:
'linear-gradient(135deg, #22d3ee 0%, #06b6d4 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}
>
Proxy and user management
</Text>{' '}
<Text component="span" fw="inherit" fz="inherit">
solution
</Text>
</Title>
<Text
c="dimmed"
ff="Unbounded"
maw={800}
mx="auto"
px={{ base: 'sm', sm: 'sm' }}
style={{
lineHeight: 1.7,
fontSize: 'clamp(0.95rem, 2.5vw, 1.25rem)'
}}
ta="center"
>
Built on top of{' '}
<Text
c="cyan"
component="a"
fw={600}
fz="inherit"
href="https://github.com/XTLS/Xray-core"
rel="noopener noreferrer"
target="_blank"
>
Xray Core
</Text>
, Remnawave provides rich functionality for user and proxy
management.
<br />
Easily add users, nodes, configure Xray and much more with a
feature-rich{' '}
<Text c="cyan" component="span" fw={600} fz="inherit">
REST API
</Text>{' '}
powered by{' '}
<Text
c="cyan"
component="a"
fw={600}
fz="inherit"
href="https://nestjs.com"
rel="noopener noreferrer"
target="_blank"
>
<SiNestjs size={20} style={{ verticalAlign: 'middle' }} />{' '}
NestJS
</Text>
.
</Text>
</Box>
</Stack>
</motion.div>
<Stack justify="center">
<motion.div variants={itemVariants}>
<Group justify="center" wrap="wrap">
<Button
component={Link}
href="/docs/overview/quick-start"
leftSection={<TbArrowRightDashed size={20} />}
radius="md"
size="md"
td="none"
>
Get started
</Button>
<Button
component={Link}
href="https://try.tg"
leftSection={<TbCloud size={20} />}
radius="md"
size="md"
td="none"
variant="light"
>
Try risk-free
</Button>
</Group>
</motion.div>
<motion.div variants={itemVariants}>
<Group justify="center" wrap="wrap">
<Button
color="#0088cc"
component="a"
href="https://t.me/remnawave"
leftSection={<BiLogoTelegram color="white" size={20} />}
radius="md"
size="md"
td="none"
variant="filled"
>
Join Community
</Button>
<Button
color="red"
component="a"
href="/docs/donate"
leftSection={<TbHeartFilled size={20} />}
radius="md"
size="md"
td="none"
variant="filled"
>
Sponsor
</Button>
</Group>
</motion.div>
</Stack>
<motion.div variants={itemVariants}>
<Box
px={{ base: 'xs', sm: 'md' }}
style={{
maxWidth: '1500px',
margin: '0 auto'
}}
>
<Image
alt="Remnawave Panel"
radius="md"
src="/pages/landing_page.webp"
style={{
display: 'block',
width: '100%',
position: 'relative',
zIndex: 0
}}
/>
</Box>
</motion.div>
<motion.div variants={itemVariants}>
<Grid gutter="xl" justify="center">
<Grid.Col span={{ base: 12, sm: 6, lg: 3 }}>
<StatCard
color="violet"
icon={<IconUsers size={32} />}
label="Active community members"
value="3K+"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, lg: 3 }}>
<StatCard
color="cyan"
icon={<IconRocket size={32} />}
label="DockerHub pulls"
value="40K+"
/>
</Grid.Col>
</Grid>
</motion.div>
<motion.div variants={itemVariants}>
<Stack gap="xl">
<Box ta="center">
<Badge
mb="md"
size="lg"
style={{
background: 'rgba(34, 211, 238, 0.1)',
border: '1px solid rgba(34, 211, 238, 0.3)',
color: '#22d3ee'
}}
variant="dot"
>
KEY FEATURES
</Badge>
<Title
c="white"
order={2}
px={{ base: 'sm', sm: 'md' }}
style={{
fontSize: 'clamp(1.5rem, 5vw, 2.5rem)'
}}
>
Everything You Need
</Title>
</Box>
<Grid gutter="lg">
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="cyan"
description="Connect as many nodes as you want, and manage them all in one place."
icon={<IconServer size={24} />}
title="Multiple nodes support"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="violet"
description="Create and manage users with flexible settings."
icon={<IconUsers size={24} />}
title="User Management"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="blue"
description="Full-featured REST API with comprehensive documentation for easy integration."
icon={<IconCloudDataConnection size={24} />}
title="REST API"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="green"
description="Support for all major protocols: VLESS, Trojan, Shadowsocks."
icon={<IconBox size={24} />}
title="Protocols support"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="cyan"
description="Remnawave providers for your users to connect to. Supports all major client applications."
icon={<IconDevices size={24} />}
title="Subscription Links"
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>
<FeatureCard
color="violet"
description="Auth with Passkeys, GitHub, and more. Connections to nodes are secured with mTLS."
icon={<IconShieldCheck size={24} />}
title="Security First"
/>
</Grid.Col>
</Grid>
</Stack>
</motion.div>
<Container maw={800}>
<motion.div animate="visible" initial="hidden" variants={itemVariants}>
<Stack gap="lg" pb={0} py="xl">
<Divider opacity="0.5" />
<Group gap="xl" justify="center" wrap="wrap">
<Anchor
href="https://github.com/remnawave"
rel="noopener noreferrer"
style={{
textDecoration: 'none'
}}
target="_blank"
>
<Group gap="xs">
<ThemeIcon
color="gray"
radius="md"
size="lg"
variant="light"
>
<IconBrandGithub size={18} />
</ThemeIcon>
<Text c="dimmed" fw={500} size="sm">
GitHub
</Text>
</Group>
</Anchor>
<Anchor
href="https://t.me/remnawave"
rel="noopener noreferrer"
style={{
textDecoration: 'none'
}}
target="_blank"
>
<Group gap="xs">
<ThemeIcon
color="blue"
radius="md"
size="lg"
variant="light"
>
<IconBrandTelegram size={18} />
</ThemeIcon>
<Text c="dimmed" fw={500} size="sm">
Telegram
</Text>
</Group>
</Anchor>
</Group>
<Divider />
<Stack align="center" gap="xs" px="sm">
<Group
align="center"
gap={6}
justify="center"
style={{ flexWrap: 'wrap' }}
>
<Text c="dimmed" ff="Unbounded" size="sm" ta="center">
Created by{' '}
<Anchor
c="cyan"
fw={600}
href="https://github.com/kastov"
rel="noopener noreferrer"
target="_blank"
td="none"
>
kastov
</Anchor>{' '}
and{' '}
<Anchor
c="cyan"
fw={600}
href="https://t.me/remnawave"
rel="noopener noreferrer"
target="_blank"
td="none"
>
<IconHeart
color="#ef4444"
fill="#ef4444"
size={20}
style={{ verticalAlign: 'middle' }}
/>{' '}
community
</Anchor>
</Text>
</Group>
</Stack>
</Stack>
</motion.div>
</Container>
</motion.div>
</Container>
</div>
)
}
export default function Home(): JSX.Element {
return (
<MantineProvider defaultColorScheme="dark" theme={theme}>
<Layout
description="Remnawave user and proxy management solution"
noFooter={true}
wrapperClassName="landing-page-layout"
>
<HomePage />
</Layout>
</MantineProvider>
)
}

View File

@@ -1 +0,0 @@
export * from './theme'

View File

@@ -1,10 +0,0 @@
import { Badge } from '@mantine/core'
export default {
Badge: Badge.extend({
defaultProps: {
radius: 'md',
variant: 'outline'
}
})
}

View File

@@ -1,16 +0,0 @@
import { ActionIcon, Button } from '@mantine/core'
export default {
ActionIcon: ActionIcon.extend({
defaultProps: {
radius: 'lg',
variant: 'outline'
}
}),
Button: Button.extend({
defaultProps: {
radius: 'lg',
variant: 'outline'
}
})
}

View File

@@ -1,3 +0,0 @@
.root {
background-color: var(--mantine-color-body);
}

View File

@@ -1,13 +0,0 @@
import { Card } from '@mantine/core'
import classes from './card.module.css'
export default {
Card: Card.extend({
classNames: classes,
defaultProps: {
radius: 'md',
withBorder: true
}
})
}

View File

@@ -1,10 +0,0 @@
import { DrawerOverlay } from '@mantine/core'
export default {
DrawerOverlay: DrawerOverlay.extend({
defaultProps: {
backgroundOpacity: 0.5,
blur: 2
}
})
}

View File

@@ -1,25 +0,0 @@
import loadingOverlay from './loading-overlay'
import ringProgress from './ring-progress'
import notification from './notification'
import buttons from './buttons'
import layouts from './layouts'
import tooltip from './tooltip'
import drawer from './drawer'
import badge from './badge'
import table from './table'
import card from './card'
import menu from './menu'
export default {
...card,
...badge,
...buttons,
...drawer,
...loadingOverlay,
...menu,
...notification,
...ringProgress,
...table,
...tooltip,
...layouts
}

View File

@@ -1,9 +0,0 @@
import { Paper } from '@mantine/core'
export default {
Paper: Paper.extend({
defaultProps: {
radius: 'lg'
}
})
}

View File

@@ -1,13 +0,0 @@
import { LoadingOverlay } from '@mantine/core'
export default {
LoadingOverlay: LoadingOverlay.extend({
defaultProps: {
zIndex: 1000,
overlayProps: {
radius: 'sm',
blur: 4
}
}
})
}

View File

@@ -1,11 +0,0 @@
import { Menu } from '@mantine/core'
export default {
Menu: Menu.extend({
defaultProps: {
shadow: 'md',
withArrow: true,
transitionProps: { transition: 'scale', duration: 200 }
}
})
}

View File

@@ -1,9 +0,0 @@
import { Notification } from '@mantine/core'
export default {
Notification: Notification.extend({
defaultProps: {
radius: 'md'
}
})
}

View File

@@ -1,10 +0,0 @@
import { RingProgress } from '@mantine/core'
export default {
RingProgress: RingProgress.extend({
defaultProps: {
thickness: 6,
roundCaps: true
}
})
}

View File

@@ -1,9 +0,0 @@
import { Table } from '@mantine/core'
export default {
Table: Table.extend({
defaultProps: {
highlightOnHover: true
}
})
}

View File

@@ -1,13 +0,0 @@
import { Tooltip } from '@mantine/core'
export default {
Tooltip: Tooltip.extend({
defaultProps: {
radius: 'md',
withArrow: true,
transitionProps: { transition: 'scale-x', duration: 300 },
arrowSize: 2,
color: 'gray'
}
})
}

View File

@@ -1,96 +0,0 @@
import { createTheme } from '@mantine/core'
import components from './overrides'
export const theme = createTheme({
components,
cursorType: 'pointer',
fontFamily: 'Montserrat, sans-serif',
fontFamilyMonospace: 'Fira Mono, monospace',
breakpoints: {
xs: '30em',
sm: '40em',
md: '48em',
lg: '64em',
xl: '80em',
'2xl': '96em',
'3xl': '120em',
'4xl': '160em'
},
scale: 1,
fontSmoothing: true,
focusRing: 'never',
white: '#ffffff',
black: '#24292f',
colors: {
dark: [
'#c9d1d9',
'#b1bac4',
'#8b949e',
'#6e7681',
'#484f58',
'#30363d',
'#21262d',
'#161b22',
'#0d1117',
'#010409'
],
blue: [
'#ddf4ff',
'#b6e3ff',
'#80ccff',
'#54aeff',
'#218bff',
'#0969da',
'#0550ae',
'#033d8b',
'#0a3069',
'#002155'
],
green: [
'#dafbe1',
'#aceebb',
'#6fdd8b',
'#4ac26b',
'#2da44e',
'#1a7f37',
'#116329',
'#044f1e',
'#003d16',
'#002d11'
],
yellow: [
'#fff8c5',
'#fae17d',
'#eac54f',
'#d4a72c',
'#bf8700',
'#9a6700',
'#7d4e00',
'#633c01',
'#4d2d00',
'#3b2300'
],
orange: [
'#fff1e5',
'#ffd8b5',
'#ffb77c',
'#fb8f44',
'#e16f24',
'#bc4c00',
'#953800',
'#762c00',
'#5c2200',
'#471700'
]
},
primaryShade: 8,
primaryColor: 'cyan',
autoContrast: true,
luminanceThreshold: 0.3,
headings: {
fontWeight: '600'
},
defaultRadius: 'md'
})