Skip to content

Commit

Permalink
add: docker intro & improve
Browse files Browse the repository at this point in the history
  • Loading branch information
DyAxy committed Jul 17, 2024
1 parent ea252d3 commit cafa978
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 100 deletions.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) 环境
Expand Down Expand Up @@ -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` 之类即可使用。


## 感谢

Expand Down
169 changes: 76 additions & 93 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useState } from "react";
import { useCallback, useState } from "react";
import {
Autocomplete,
AutocompleteItem,
Expand All @@ -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";
Expand All @@ -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',
Expand All @@ -40,78 +58,56 @@ 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('')
copy(subLink)
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',
Expand All @@ -121,23 +117,26 @@ 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) {
toast.error((e as Error).message)
} 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 (
<div className="w-full p-4 flex flex-col justify-center items-center gap-3">
Expand All @@ -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) => (
<Tab key={item.key} title={
Expand All @@ -169,9 +166,7 @@ export default function Home() {
className="w-full"
minRows={1}
value={params.url}
onValueChange={(value) => {
setParams({ ...params, url: value });
}}
onValueChange={(value) => setParams({ ...params, url: value })}
/>
{/* 客户端 */}
<Autocomplete
Expand All @@ -180,10 +175,8 @@ export default function Home() {
placeholder="请选择你使用的客户端类型"
className="w-full"
selectedKey={params.target}
onSelectionChange={(key) => {
setParams({ ...params, target: (key ?? '').toString() });
}}
defaultItems={Object.entries(config.clients)}
onSelectionChange={(key) => setParams({ ...params, target: (key ?? '').toString() })}
defaultItems={Object.entries(cfg.clients)}
>
{(item => (
<AutocompleteItem key={item[0]}>
Expand All @@ -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
}))}
Expand All @@ -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 => (
<AutocompleteSection
Expand All @@ -244,29 +233,23 @@ export default function Home() {
<InputCell
label="包含节点"
value={params.include}
onValueChange={(value) => {
setParams({ ...params, include: value });
}}
onValueChange={(value) => setParams({ ...params, include: value })}
placeholder="节点名包含的关键字,支持正则"
/>
<InputCell
label="排除节点"
value={params.exclude}
onValueChange={(value) => {
setParams({ ...params, exclude: value });
}}
onValueChange={(value) => setParams({ ...params, exclude: value })}
placeholder="节点名排除的关键字,支持正则"
/>
</div>
<div className='flex flex-col sm:grid sm:grid-cols-2 md:grid-cols-3 gap-3'>
{config.switchCells.map((cell) => (
{cfg.switchCells.map((cell) => (
<SwitchCell
key={cell.key}
title={cell.title}
isSelected={params[cell.key as keyof typeof params] as boolean}
onValueChange={(value) => {
setParams({ ...params, [cell.key]: value });
}}
onValueChange={(value) => setParams({ ...params, [cell.key]: value })}
/>
))}
</div>
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit cafa978

Please sign in to comment.