From 21ae964012a5db5615792a082232bb18b31d1c0a Mon Sep 17 00:00:00 2001 From: Den Piligrim <89912505+vasiljevdenis@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:55:34 +0300 Subject: [PATCH] hysteria --- README.md | 2 +- README_CN.md | 2 +- README_EN.md | 2 +- README_IR.md | 2 +- README_TK.md | 2 +- client/src/components/Header.tsx | 2 +- client/src/pages/DomainsPage.tsx | 27 +- client/src/pages/LoginPage.tsx | 2 +- client/src/pages/SubscriptionsPage.tsx | 405 +++++++++++++++--- client/src/pages/TunnelsPage.tsx | 42 +- package.json | 2 +- server/package-lock.json | 40 ++ server/package.json | 2 + server/src/domains/domains.controller.ts | 5 + server/src/domains/domains.service.ts | 4 + .../src/inbounds/inbound-builder.service.ts | 2 +- server/src/inbounds/inbounds.constants.ts | 12 + .../dto/create-subscription.dto.ts | 27 ++ 18 files changed, 482 insertions(+), 100 deletions(-) create mode 100644 server/src/inbounds/inbounds.constants.ts create mode 100644 server/src/subscriptions/dto/create-subscription.dto.ts diff --git a/README.md b/README.md index 876927b..96b79ff 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

3dp-manager preview

-![Version](https://img.shields.io/badge/version-2.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) +![Version](https://img.shields.io/badge/version-2.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) # 3DP-MANAGER diff --git a/README_CN.md b/README_CN.md index c30994e..8f708cb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,7 +2,7 @@

3dp-manager preview

-![Version](https://img.shields.io/badge/version-2.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) +![Version](https://img.shields.io/badge/version-2.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) # 3DP-MANAGER diff --git a/README_EN.md b/README_EN.md index c51f854..7694c6a 100644 --- a/README_EN.md +++ b/README_EN.md @@ -2,7 +2,7 @@

3dp-manager preview

-![Version](https://img.shields.io/badge/version-2.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) +![Version](https://img.shields.io/badge/version-2.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) # 3DP-MANAGER diff --git a/README_IR.md b/README_IR.md index b11341b..0eb951a 100644 --- a/README_IR.md +++ b/README_IR.md @@ -2,7 +2,7 @@

3dp-manager preview

-![Version](https://img.shields.io/badge/version-2.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) +![Version](https://img.shields.io/badge/version-2.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) # 3DP-MANAGER diff --git a/README_TK.md b/README_TK.md index e4d2dce..0685712 100644 --- a/README_TK.md +++ b/README_TK.md @@ -2,7 +2,7 @@

3dp-manager preview

-![Version](https://img.shields.io/badge/version-2.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) +![Version](https://img.shields.io/badge/version-2.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim) # 3DP-MANAGER diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index eb6e5d0..fe8ba97 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -132,7 +132,7 @@ export default function Header({ onMenuClick, isMobile }: HeaderProps) { - Версия: 2.0.1
+ Версия: 2.0.2
Разработчик: DenPiligrim
diff --git a/client/src/pages/DomainsPage.tsx b/client/src/pages/DomainsPage.tsx index 225ab40..30cd5bf 100644 --- a/client/src/pages/DomainsPage.tsx +++ b/client/src/pages/DomainsPage.tsx @@ -94,14 +94,24 @@ export default function DomainsPage() { label="Доменное имя" size="small" fullWidth value={newDomain} onChange={(e) => setNewDomain(e.target.value)} /> - + {isMobile ? ( + <> + fileInputRef.current?.click()}> + + + ) : ( + <> + + + + )} - {domains.length > 0 && ( diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 5e69424..a3109d0 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -35,7 +35,7 @@ export default function LoginPage() { animation: 'fadeIn 1.5s ease-out', boxShadow: '0 15px 25px rgba(0,0,0,0.5)' }}> - Вход в 3DP-MANAGER + Вход в 3DP-MANAGER {error && {error}} diff --git a/client/src/pages/SubscriptionsPage.tsx b/client/src/pages/SubscriptionsPage.tsx index 8b34698..2235891 100644 --- a/client/src/pages/SubscriptionsPage.tsx +++ b/client/src/pages/SubscriptionsPage.tsx @@ -2,16 +2,15 @@ import { useEffect, useState } from 'react'; import { Box, Button, Typography, Paper, Table, TableBody, TableCell, TableHead, TableRow, IconButton, Dialog, DialogTitle, - DialogContent, TextField, DialogActions, - FormControl, - Select, - InputAdornment, - MenuItem, - type SelectChangeEvent, + DialogContent, TextField, DialogActions, FormControl, Select, + InputAdornment, InputLabel, MenuItem, + useTheme, useMediaQuery, - useTheme + Menu, + ListItemIcon, + ListItemText } from '@mui/material'; -import { Delete, Add, Link as LinkIcon, OpenInNew, ContentCopy, Dns, Router } from '@mui/icons-material'; +import { Delete, Add, Link as LinkIcon, OpenInNew, ContentCopy, Dns, Router, Edit, MoreVert, Remove } from '@mui/icons-material'; import api from '../api'; interface Subscription { @@ -29,15 +28,33 @@ interface Tunnel { isInstalled: boolean; } +interface InboundConfigUI { + id: string; + type: string; + port: string; + sni: string; +} + +interface Domain { id: number; name: string; } + +const CONNECTION_OPTIONS = [ + 'vless-tcp-reality', + 'vless-xhttp-reality', + 'vless-grpc-reality', + 'vless-ws', + 'hysteria2-udp', + 'vmess-tcp', + 'shadowsocks-tcp', + 'trojan-tcp-reality', +]; + const patchLink = function (link: string, newHost: string): string { if (link.startsWith('vmess://')) { try { const base64Part = link.substring(8); const jsonStr = Buffer.from(base64Part, 'base64').toString('utf-8'); const config = JSON.parse(jsonStr); - config.add = newHost; - const newJsonStr = JSON.stringify(config); const newBase64 = Buffer.from(newJsonStr).toString('base64'); return `vmess://${newBase64}`; @@ -52,37 +69,184 @@ const patchLink = function (link: string, newHost: string): string { } return link; } - return link; -} +}; + +const generateId = () => Math.random().toString(36).substring(7); export default function SubscriptionsPage() { const [subs, setSubs] = useState([]); - const [open, setOpen] = useState(false); - const [name, setName] = useState(''); const [tunnels, setTunnels] = useState([]); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const [selectedServer, setSelectedServer] = useState('main'); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [activeSub, setActiveSub] = useState(null); + const [domains, setDomains] = useState([]); + const openActionMenu = Boolean(menuAnchorEl); + // Состояния модального окна конструктора + const [open, setOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [name, setName] = useState(''); + const [inbounds, setInbounds] = useState([]); + const [portErrors, setPortErrors] = useState>({}); + + // Состояния ссылок const [linksOpen, setLinksOpen] = useState(false); const [currentLinks, setCurrentLinks] = useState([]); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + useEffect(() => { loadSubs(); }, []); const loadSubs = async () => { const { data } = await api.get('/subscriptions'); setSubs(data); + const tunnelsRes = await api.get('/tunnels'); setTunnels(tunnelsRes.data.filter((el: Tunnel) => el.isInstalled)); + + const allDomains = await api.get('/domains/all'); + setDomains(allDomains.data); }; - const handleCreate = async () => { - await api.post('/subscriptions', { name }); - setOpen(false); + const handleActionMenuClick = (event: React.MouseEvent, sub: Subscription) => { + setMenuAnchorEl(event.currentTarget); + setActiveSub(sub); + }; + + const handleActionMenuClose = () => { + setMenuAnchorEl(null); + setActiveSub(null); + }; + + const handleOpenCreate = () => { + setEditingId(null); setName(''); - loadSubs(); + setInbounds([ + { id: generateId(), type: 'hysteria2-udp', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-xhttp-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-grpc-reality', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vless-ws', port: 'random', sni: 'random' }, + { id: generateId(), type: 'vmess-tcp', port: 'random', sni: 'random' }, + { id: generateId(), type: 'shadowsocks-tcp', port: 'random', sni: 'random' }, + ]); + setPortErrors({}); + setOpen(true); + }; + + const handleOpenEdit = (sub: Subscription) => { + setEditingId(sub.id); + setName(sub.name); + + // Пытаемся маппить существующие инбаунды, или даем дефолтные + if (sub.inbounds && sub.inbounds.length > 0) { + setInbounds(sub.inbounds.map(i => ({ + id: generateId(), + type: i.type || 'vless-tcp-reality', + port: i.port ? i.port.toString() : 'random', + sni: i.sni || 'random' + }))); + } else { + setInbounds([{ id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }]); + } + setPortErrors({}); + setOpen(true); + }; + + // Проверка порта + const handlePortCheck = async (portValue: string, id: string) => { + if (portValue === 'random' || portValue.trim() === '') { + setPortErrors(prev => { const n = { ...prev }; delete n[id]; return n; }); + return; + } + const portNum = parseInt(portValue); + if (isNaN(portNum) || portNum < 1 || portNum > 65535) { + setPortErrors(prev => ({ ...prev, [id]: 'Некорректный порт' })); + return; + } + try { + const { data } = await api.get(`/tunnels/check-port/${portNum}`); + if (!data.isFree) { + setPortErrors(prev => ({ ...prev, [id]: 'Порт занят' })); + } else { + setPortErrors(prev => { const n = { ...prev }; delete n[id]; return n; }); + } + } catch (e) { + console.error('Ошибка проверки порта', e); + } + }; + + const handleInboundChange = (id: string, field: keyof InboundConfigUI, value: string) => { + setInbounds(prev => prev.map(inb => inb.id === id ? { ...inb, [field]: value } : inb)); + if (field === 'port') { + setPortErrors(prev => { const n = { ...prev }; delete n[id]; return n; }); + } + }; + + const addInbound = () => { + if (inbounds.length < 20) { + setInbounds([...inbounds, { id: generateId(), type: 'vless-tcp-reality', port: 'random', sni: 'random' }]); + } + }; + +const removeInbound = (id?: string) => { + if (id) { + if (inbounds.length > 1) { + setInbounds(inbounds.filter(inb => inb.id !== id)); + setPortErrors(prev => { + const n = { ...prev }; + delete n[id]; + return n; + }); + } + } else { + setInbounds([ + { + id: crypto.randomUUID(), + type: 'vless-tcp-reality', + port: 'random', + sni: 'random' + } + ]); + setPortErrors({}); + } +}; + + const handleSave = async () => { + if (Object.keys(portErrors).length > 0) { + alert('Пожалуйста, исправьте ошибки с портами'); + return; + } + if (!name.trim()) { + alert('Введите имя подписки'); + return; + } + + const payload = { + name, + inboundsConfig: inbounds.map(i => ({ + type: i.type, + port: i.port === 'random' ? 'random' : parseInt(i.port), + sni: i.sni + })) + }; + + try { + if (editingId) { + await api.put(`/subscriptions/${editingId}`, payload); + } else { + await api.post('/subscriptions', payload); + } + setOpen(false); + loadSubs(); + } catch (error: any) { + alert(error.response?.data?.message || 'Произошла ошибка при сохранении'); + } }; const handleDelete = async (id: string) => { @@ -108,10 +272,6 @@ export default function SubscriptionsPage() { setLinksOpen(true); }; - const handleServerChange = (event: SelectChangeEvent) => { - setSelectedServer(event.target.value as string); - }; - return ( @@ -119,9 +279,8 @@ export default function SubscriptionsPage() { {tunnels.length > 0 && ( )} - + - - + @@ -167,25 +320,28 @@ export default function SubscriptionsPage() { {sub.uuid} {sub.inbounds?.length || 0} - navigator.clipboard.writeText(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}/${selectedServer}`)} - title="Копировать ссылку" - > - - - window.open(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}/${selectedServer}`, '_blank')} - title="Открыть подписку" - > - - - showLinks(sub)} title="Показать конфиги"> - - - handleDelete(sub.id)} title="Удалить"> - + {!isMobile && ( + <> + navigator.clipboard.writeText(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}/${selectedServer}`)} + title="Копировать ссылку" + > + + + window.open(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${sub.uuid}/${selectedServer}`, '_blank')} + title="Открыть подписку" + > + + + + )} + + {/* Кнопка "Три точки" для вызова меню действий */} + handleActionMenuClick(e, sub)}> + @@ -195,26 +351,141 @@ export default function SubscriptionsPage() { {subs.length === 0 && Нет подписок} - setOpen(false)} disableRestoreFocus> - Новая подписка - + + {isMobile && activeSub && ( + navigator.clipboard.writeText(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${activeSub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${activeSub.uuid}/${selectedServer}`)}> + + Копировать ссылку + + )} + {isMobile && activeSub && ( + window.open(selectedServer === 'main' ? `${location.protocol}//${location.hostname}:3000/bus/${activeSub.uuid}` : `${location.protocol}//${location.hostname}:3000/bus/${activeSub.uuid}/${selectedServer}`, '_blank')}> + + Открыть подписку + + )} + + {activeSub && ( + showLinks(activeSub)}> + + Показать конфиги + + )} + {activeSub && ( + handleOpenEdit(activeSub)}> + + Редактировать + + )} + {activeSub && ( + handleDelete(activeSub.id)}> + + Удалить + + )} + + + {/* Модальное окно создания / редактирования */} + setOpen(false)} maxWidth="md" fullWidth disableRestoreFocus> + {editingId ? 'Редактировать подписку' : 'Новая подписка'} + setName(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - handleCreate(); - } - }} + sx={{ mb: 4 }} /> + + + Инбаунды ({inbounds.length}/20) + + + {inbounds.map((inb, index) => ( + + #{index + 1} + + + Тип + + + + + handleInboundChange(inb.id, 'port', e.target.value)} + onBlur={(e) => handlePortCheck(e.target.value, inb.id)} + error={!!portErrors[inb.id]} + helperText={portErrors[inb.id] || ""} + sx={{ width: 140 }} + /> + + + + SNI + + + + removeInbound(inb.id)} + disabled={inbounds.length <= 1} + sx={{ mt: 0.5 }} + > + + + + ))} + + + + - + + {/* Модальное окно ссылок */} setLinksOpen(false)} maxWidth="md" fullWidth> Активные ссылки diff --git a/client/src/pages/TunnelsPage.tsx b/client/src/pages/TunnelsPage.tsx index 48c5fa4..9dadfc5 100644 --- a/client/src/pages/TunnelsPage.tsx +++ b/client/src/pages/TunnelsPage.tsx @@ -78,7 +78,7 @@ export default function TunnelsPage() { - +
@@ -99,23 +99,35 @@ export default function TunnelsPage() { - {t.isInstalled ? - } label="Активен" color="success" size="small" variant="outlined" /> : - } label="Не настроен" color="warning" size="small" variant="outlined" /> - } + {!isMobile && (t.isInstalled ? + } label={"Активен"} color="success" size="small" variant="outlined" /> : + } label={"Не настроен"} color="warning" size="small" variant="outlined" /> + )} + {isMobile && (t.isInstalled ? + : + + )} {!t.isInstalled && ( - + <> + {isMobile ? ( + handleInstall(t.id)}> + {loadingId === t.id ? : } + + ) : ( + + )} + )} handleDelete(t.id)}> diff --git a/package.json b/package.json index 11a120e..b894992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "3dp-manager", - "version": "2.0.1", + "version": "2.0.2", "description": "Inbound generator for 3x-ui", "private": false, "repository": { diff --git a/server/package-lock.json b/server/package-lock.json index f0d6bca..0df26fc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,6 +21,8 @@ "axios": "^1.13.2", "bcrypt": "^6.0.0", "cache-manager": "^7.2.8", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "pg": "^8.17.1", @@ -2975,6 +2977,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -4521,6 +4529,23 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz", + "integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -7540,6 +7565,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.40", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.40.tgz", + "integrity": "sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==", + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10716,6 +10747,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/server/package.json b/server/package.json index 4ac6430..2f09617 100644 --- a/server/package.json +++ b/server/package.json @@ -32,6 +32,8 @@ "axios": "^1.13.2", "bcrypt": "^6.0.0", "cache-manager": "^7.2.8", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "pg": "^8.17.1", diff --git a/server/src/domains/domains.controller.ts b/server/src/domains/domains.controller.ts index b7bd758..b5ce985 100644 --- a/server/src/domains/domains.controller.ts +++ b/server/src/domains/domains.controller.ts @@ -15,6 +15,11 @@ export class DomainsController { return this.domainsService.createMany(body.domains); } + @Get('all') + findAllWithoutPagination() { + return this.domainsService.findAllUnpaginated(); + } + @Get() findAll( @Query('page') page: number, diff --git a/server/src/domains/domains.service.ts b/server/src/domains/domains.service.ts index c714907..a8cfc88 100644 --- a/server/src/domains/domains.service.ts +++ b/server/src/domains/domains.service.ts @@ -60,6 +60,10 @@ export class DomainsService implements OnModuleInit { }; } + async findAllUnpaginated(): Promise { + return this.repo.find({ order: { name: 'ASC' } }); + } + findOne(id: number) { return this.repo.findOneBy({ id }); } diff --git a/server/src/inbounds/inbound-builder.service.ts b/server/src/inbounds/inbound-builder.service.ts index 7883583..482ebfc 100644 --- a/server/src/inbounds/inbound-builder.service.ts +++ b/server/src/inbounds/inbound-builder.service.ts @@ -92,7 +92,7 @@ export class InboundBuilderService { enable: true, port, protocol: "vless", - remark: "vless-reality-grpc", + remark: "vless-grpc-reality", settings: JSON.stringify({ clients: [{ id: uuid, diff --git a/server/src/inbounds/inbounds.constants.ts b/server/src/inbounds/inbounds.constants.ts new file mode 100644 index 0000000..df79e10 --- /dev/null +++ b/server/src/inbounds/inbounds.constants.ts @@ -0,0 +1,12 @@ +export const CONNECTION_TYPES = [ + 'vless-tcp-reality', + 'vless-xhttp-reality', + 'vless-grpc-reality', + 'vless-ws', + 'vmess-tcp', + 'shadowsocks-tcp', + 'trojan-tcp-reality', + 'hysteria2-udp', +] as const; + +export type ConnectionType = typeof CONNECTION_TYPES[number]; \ No newline at end of file diff --git a/server/src/subscriptions/dto/create-subscription.dto.ts b/server/src/subscriptions/dto/create-subscription.dto.ts new file mode 100644 index 0000000..ccb0f5a --- /dev/null +++ b/server/src/subscriptions/dto/create-subscription.dto.ts @@ -0,0 +1,27 @@ +import { IsString, IsArray, ValidateNested, IsOptional, Min, Max, ArrayMinSize, ArrayMaxSize } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class InboundConfigDto { + @IsString() + type: string; + + @IsOptional() + port?: number | 'random'; + + @IsString() + @IsOptional() + sni?: string | 'random'; +} + +export class CreateSubscriptionDto { + @IsString() + name: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => InboundConfigDto) + @ArrayMinSize(1) + @ArrayMaxSize(20) + @IsOptional() + inboundsConfig?: InboundConfigDto[]; +} \ No newline at end of file