From cafa978d3ad27ed6f4086ceefbd579f3b84b6e88 Mon Sep 17 00:00:00 2001 From: DyAxy Date: Wed, 17 Jul 2024 03:32:38 +0100 Subject: [PATCH] add: docker intro & improve --- README.md | 28 ++++++-- app/page.tsx | 169 ++++++++++++++++++++------------------------- docker-compose.yml | 2 + 3 files changed, 99 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 6689f70..6a9abb1 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,31 @@ ## 快速部署 -使用 Vercel 服务 +### 使用 Vercel 服务 [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDyAxy%2Fyet-another-sub-web&env=NEXT_PUBLIC_SHORTURL,NEXT_PUBLIC_BACKENDS&envDescription=%E5%A6%82%E6%9E%9C%E4%B8%8D%E4%BC%9A%E5%A1%AB%E7%82%B9%E5%8F%B3%E8%BE%B9%20%20Learn%20More&envLink=https%3A%2F%2Fgithub.com%2FDyAxy%2Fyet-another-sub-web%2Fblob%2Fmaster%2F.env&project-name=yet-another-sub-web&repository-name=yet-another-sub-web) -本地使用 Docker +### 本地使用 Docker - Docker run方式部署 ``` -docker run -d -p 127.0.0.1:3000:3000 --name yet-another-sub-web --restart=always moefaq/yet-another-sub-web:latest +docker run -d -p 127.0.0.1:3000:3000 --name yet-another-sub-web --restart=always moefaq/yet-another-sub-web:latest -e NEXT_PUBLIC_SHORTURL=https://suo.yt/ -e NEXT_PUBLIC_BACKENDS=http://127.0.0.1:25500/sub? +# 替换后端或短链接服务 ``` - Docker compose方式部署 ``` -# curl -LO https://raw.githubusercontent.com/DyAxy/yet-another-sub-web/master/docker-compose.yml -# docker compose up -d +curl -LO https://raw.githubusercontent.com/DyAxy/yet-another-sub-web/master/docker-compose.yml +# 你可以修改 docker-compose.yml 中的 env +docker compose up -d ``` +### 环境变量 +| NEXT_PUBLIC_SHORTURL | NEXT_PUBLIC_BACKENDS | +| -------------------- | --------------------------- | +| 短链接服务,记得带 / | 后端完整地址 | +| https://suo.yt/ | http://127.0.0.1:25500/sub? | + + ## 常规部署 首先你需要 [Node.js](https://nodejs.org/en/download/package-manager/all) 环境 @@ -54,11 +63,16 @@ npm run start ``` 你的域名.com { -encode gzip -reverse_proxy 127.0.0.1:3000 + encode gzip + reverse_proxy 127.0.0.1:3000 } ``` +## 静态导出 + +在常规部署 `npm run dev` 之后,测试没问题的情况下,修改文件:`next.config.mjs`,添加一行:`nextConfig.output = 'export'` +则会将html及其js文件静态导出至out文件夹,使用 `Caddy`、`nginx` 之类即可使用。 + ## 感谢 diff --git a/app/page.tsx b/app/page.tsx index 52d956a..e510969 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from "react"; +import { useCallback, useState } from "react"; import { Autocomplete, AutocompleteItem, @@ -20,7 +20,7 @@ import { toast } from "sonner"; import { encode } from 'js-base64'; import copy from 'copy-to-clipboard'; -import { config } from '../config' +import { config as cfg } from '../config' import { SwitchCell } from "@/components/SwitchCell"; import { InputCell } from "@/components/InputCell"; @@ -29,8 +29,26 @@ import { SwitchTheme } from "@/components/SwitchTheme"; const backends = process.env.NEXT_PUBLIC_BACKENDS?.split('|') ?? ["http://127.0.0.1:25500/sub?"] -export default function Home() { +const initialParams = { + mode: 'easy', + subLink: '', + shortSubLink: '', + shortSubLoading: false, + backend: backends[0], + url: '', + target: '', + config: '', + include: '', + exclude: '', + tfo: false, + udp: false, + scv: false, + append_type: false, + emoji: false, + list: false, +}; +export default function Home() { const tabs = [ { key: 'easy', @@ -40,63 +58,38 @@ export default function Home() { { key: 'hard', label: '进阶模式', - icon: 'solar:winrar-linear' + icon: 'solar:winrar-linear', }, - ] + ]; - const [params, setParams] = useState({ - mode: "easy", - subLink: '', - shortSubLink: '', - shortSubLoading: false, + const [params, setParams] = useState(initialParams) - backend: backends[0], - url: '', - target: '', - config: '', - include: '', - exclude: '', - tfo: false, - udp: false, - scv: false, - append_type: false, - emoji: false, - list: false - }) - const createSub = () => { - if (!params.url) { - return toast.error('请在输入订阅链接后再试') - } - if (!params.target) { - return toast.error('请在选择客户端后再试') - } - if (!params.backend) { - setParams(prevParams => ({ ...prevParams, backend: backends[0] })) - } - const flow = [] - const backend = params.backend || backends[0] - const targetClient = config.clients[params.target as keyof typeof config.clients] + const createSub = useCallback(() => { + const { url, target, backend, mode, config, include, exclude, tfo, udp, scv, append_type, emoji, list } = params; + + if (!url) return toast.error('请在输入订阅链接后再试'); + if (!target) return toast.error('请在选择客户端后再试'); - flow.push(backend) - flow.push(`target=${targetClient}`) - flow.push(`&url=${encodeURIComponent(params.url.replace(/\n/g, '|'))}`) - flow.push('&insert=false') + const flow = [ + backend || backends[0], + `target=${cfg.clients[target as keyof typeof cfg.clients]}`, + `&url=${encodeURIComponent(url.replace(/\n/g, '|'))}`, + '&insert=false', + ]; if (params.mode === 'hard') { - const configItem = config.remoteConfig - .flatMap(category => category.items) - .find(item => item.label === params.config) - const configValue = configItem ? configItem.value : params.config + const configItem = cfg.remoteConfig.flatMap(category => category.items).find(item => item.label === config); + const configValue = configItem ? configItem.value : config; - if (params.config) flow.push(`&config=${encodeURIComponent(configValue)}`) - if (params.include) flow.push(`&include=${encodeURIComponent(params.include)}`) - if (params.exclude) flow.push(`&exclude=${encodeURIComponent(params.exclude)}`) - if (params.tfo) flow.push(`&tfo=true`) - if (params.udp) flow.push(`&udp=true`) - if (params.scv) flow.push(`&scv=true`) - if (params.append_type) flow.push(`&append_type=true`) - if (params.emoji) flow.push(`&emoji=true`) - if (params.list) flow.push(`&list=true`) + if (config) flow.push(`&config=${encodeURIComponent(configValue)}`); + if (include) flow.push(`&include=${encodeURIComponent(include)}`); + if (exclude) flow.push(`&exclude=${encodeURIComponent(exclude)}`); + if (tfo) flow.push('&tfo=true'); + if (udp) flow.push('&udp=true'); + if (scv) flow.push('&scv=true'); + if (append_type) flow.push('&append_type=true'); + if (emoji) flow.push('&emoji=true'); + if (list) flow.push('&list=true'); } const subLink = flow.join('') @@ -104,14 +97,17 @@ export default function Home() { toast.success('定制订阅已复制到剪贴板') setParams(prevParams => ({ ...prevParams, subLink })) - } - const createShortSub = async () => { - if (!params.subLink) return toast.error('请在生成订阅链接后再试') + }, [params]) + + const createShortSub = useCallback(async () => { + const { subLink } = params; + + if (!subLink) return toast.error('请在生成订阅链接后再试'); setParams(prevParams => ({ ...prevParams, shortSubLoading: true })); try { const formData = new FormData(); - formData.append('longUrl', encode(params.subLink)); + formData.append('longUrl', encode(subLink)); const res = await fetch(`${process.env.NEXT_PUBLIC_SHORTURL}short`, { method: 'POST', @@ -121,8 +117,9 @@ export default function Home() { if (res.status === 200) { const data = await res.json(); if (data.Code !== 1) throw new Error(data.Message); - copy(data.ShortUrl) - toast.success('短链接已复制到剪贴板') + + copy(data.ShortUrl); + toast.success('短链接已复制到剪贴板'); setParams(prevParams => ({ ...prevParams, shortSubLink: data.ShortUrl })); } } catch (e) { @@ -130,14 +127,16 @@ export default function Home() { } finally { setParams(prevParams => ({ ...prevParams, shortSubLoading: false })); } - }; - const importClash = () => { - if (!params.subLink) return toast.error('请在生成订阅链接后再试') + }, [params.subLink]); - let url = params.subLink - if (params.shortSubLink) url = params.shortSubLink - window.location.href = `clash://install-config?url=${encodeURIComponent(url)}` - } + const importClash = useCallback(() => { + const { subLink, shortSubLink } = params; + + if (!subLink) return toast.error('请在生成订阅链接后再试'); + + const url = shortSubLink || subLink; + window.location.href = `clash://install-config?url=${encodeURIComponent(url)}`; + }, [params]); return (
@@ -149,9 +148,7 @@ export default function Home() { aria-label="Mode" items={tabs} selectedKey={params.mode} - onSelectionChange={(key) => { - setParams({ ...params, mode: key.toString() }) - }} + onSelectionChange={(key) => setParams({ ...params, mode: key.toString() })} > {(item) => ( { - setParams({ ...params, url: value }); - }} + onValueChange={(value) => setParams({ ...params, url: value })} /> {/* 客户端 */} { - setParams({ ...params, target: (key ?? '').toString() }); - }} - defaultItems={Object.entries(config.clients)} + onSelectionChange={(key) => setParams({ ...params, target: (key ?? '').toString() })} + defaultItems={Object.entries(cfg.clients)} > {(item => ( @@ -199,9 +192,7 @@ export default function Home() { className="w-full" allowsCustomValue inputValue={params.backend} - onInputChange={(value) => { - setParams({ ...params, backend: value }); - }} + onInputChange={(value) => setParams({ ...params, backend: value })} defaultItems={backends.map(value => ({ value: value }))} @@ -219,10 +210,8 @@ export default function Home() { className="w-full" allowsCustomValue inputValue={params.config} - onInputChange={(value) => { - setParams({ ...params, config: value }); - }} - defaultItems={config.remoteConfig} + onInputChange={(value) => setParams({ ...params, config: value })} + defaultItems={cfg.remoteConfig} > {(item => ( { - setParams({ ...params, include: value }); - }} + onValueChange={(value) => setParams({ ...params, include: value })} placeholder="节点名包含的关键字,支持正则" /> { - setParams({ ...params, exclude: value }); - }} + onValueChange={(value) => setParams({ ...params, exclude: value })} placeholder="节点名排除的关键字,支持正则" />
- {config.switchCells.map((cell) => ( + {cfg.switchCells.map((cell) => ( { - setParams({ ...params, [cell.key]: value }); - }} + onValueChange={(value) => setParams({ ...params, [cell.key]: value })} /> ))}
diff --git a/docker-compose.yml b/docker-compose.yml index 29a2085..ce44dc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,8 @@ services: restart: always environment: TZ: Asia/Shanghai + NEXT_PUBLIC_SHORTURL: https://suo.yt/ + NEXT_PUBLIC_BACKENDS: http://127.0.0.1:25500/sub? ports: - "127.0.0.1:3000:3000" networks: