Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b5ad11185 | |||
| 2e1cd86e99 | |||
| 97a8c41fc0 | |||
| b2cc357dca | |||
| fc8fbc976f |
@@ -83,6 +83,7 @@ interface InboundConfigUI {
|
|||||||
name?: string;
|
name?: string;
|
||||||
certificateFile?: string;
|
certificateFile?: string;
|
||||||
keyFile?: string;
|
keyFile?: string;
|
||||||
|
routingProfile?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Domain {
|
interface Domain {
|
||||||
@@ -106,6 +107,7 @@ const CONNECTION_OPTIONS = [
|
|||||||
'shadowsocks-tcp',
|
'shadowsocks-tcp',
|
||||||
'trojan-tcp-reality',
|
'trojan-tcp-reality',
|
||||||
'custom',
|
'custom',
|
||||||
|
'happ-routing',
|
||||||
];
|
];
|
||||||
|
|
||||||
const generateId = () => Math.random().toString(36).substring(7);
|
const generateId = () => Math.random().toString(36).substring(7);
|
||||||
@@ -221,17 +223,30 @@ export default function SubscriptionsPage() {
|
|||||||
|
|
||||||
const createInbound = (type = 'vless-tcp-reality'): InboundConfigUI => {
|
const createInbound = (type = 'vless-tcp-reality'): InboundConfigUI => {
|
||||||
const nodeId = getDefaultNodeId();
|
const nodeId = getDefaultNodeId();
|
||||||
|
|
||||||
|
// HAPP ROUTING - специальная обработка
|
||||||
|
if (type === 'happ-routing') {
|
||||||
|
return {
|
||||||
|
id: generateId(),
|
||||||
|
type,
|
||||||
|
port: '0',
|
||||||
|
sni: '',
|
||||||
|
link: '',
|
||||||
|
routingProfile: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const certDefaults = type === 'hysteria2-udp' ? getHysteriaCertDefaults(nodeId) : {};
|
const certDefaults = type === 'hysteria2-udp' ? getHysteriaCertDefaults(nodeId) : {};
|
||||||
return {
|
return {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
type,
|
type,
|
||||||
port: 'random',
|
port: 'random',
|
||||||
sni: type === 'hysteria2-udp' ? '' : 'random',
|
sni: type === 'hysteria2-udp' ? '' : 'random',
|
||||||
link: '',
|
link: '',
|
||||||
nodeId,
|
nodeId,
|
||||||
flag: getNodeFlag(nodeId),
|
flag: getNodeFlag(nodeId),
|
||||||
name: '',
|
name: '',
|
||||||
...certDefaults,
|
...certDefaults,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -280,6 +295,7 @@ export default function SubscriptionsPage() {
|
|||||||
relayServerId: item.relayServerId ? item.relayServerId.toString() : '',
|
relayServerId: item.relayServerId ? item.relayServerId.toString() : '',
|
||||||
flag: item.flag || getNodeFlag(nodeId),
|
flag: item.flag || getNodeFlag(nodeId),
|
||||||
name: item.name || '',
|
name: item.name || '',
|
||||||
|
routingProfile: item.routingProfile || '',
|
||||||
certificateFile:
|
certificateFile:
|
||||||
item.type === 'hysteria2-udp'
|
item.type === 'hysteria2-udp'
|
||||||
? item.certificateFile || certDefaults.certificateFile
|
? item.certificateFile || certDefaults.certificateFile
|
||||||
@@ -323,7 +339,7 @@ export default function SubscriptionsPage() {
|
|||||||
next.keyFile = next.keyFile || defaults.keyFile;
|
next.keyFile = next.keyFile || defaults.keyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'type' && value !== 'custom' && !next.nodeId) {
|
if (field === 'type' && value !== 'custom' && value !== 'happ-routing' && !next.nodeId) {
|
||||||
next.nodeId = getDefaultNodeId();
|
next.nodeId = getDefaultNodeId();
|
||||||
next.flag = getNodeFlag(next.nodeId);
|
next.flag = getNodeFlag(next.nodeId);
|
||||||
}
|
}
|
||||||
@@ -363,7 +379,7 @@ export default function SubscriptionsPage() {
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const nextPortErrors = inbounds.reduce<Record<string, string>>((acc, inbound) => {
|
const nextPortErrors = inbounds.reduce<Record<string, string>>((acc, inbound) => {
|
||||||
if (inbound.type !== 'custom' && !isValidPort(inbound.port)) {
|
if (inbound.type !== 'custom' && inbound.type !== 'happ-routing' && !isValidPort(inbound.port)) {
|
||||||
acc[inbound.id] = 'Порт: число 1-65535 или random';
|
acc[inbound.id] = 'Порт: число 1-65535 или random';
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@@ -385,29 +401,36 @@ export default function SubscriptionsPage() {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name,
|
name,
|
||||||
inboundsConfig: inbounds.map((inbound) =>
|
inboundsConfig: inbounds.map((inbound) => {
|
||||||
inbound.type === 'custom'
|
if (inbound.type === 'custom') {
|
||||||
? { type: inbound.type, link: inbound.link }
|
return { type: inbound.type, link: inbound.link };
|
||||||
: {
|
}
|
||||||
type: inbound.type,
|
if (inbound.type === 'happ-routing') {
|
||||||
port: inbound.port === 'random' ? 'random' : parseInt(inbound.port, 10),
|
return {
|
||||||
sni: inbound.type === 'hysteria2-udp' ? undefined : inbound.sni,
|
type: inbound.type,
|
||||||
nodeId: inbound.nodeId || undefined,
|
routingProfile: inbound.routingProfile,
|
||||||
relayServerId: inbound.relayServerId
|
};
|
||||||
? parseInt(inbound.relayServerId, 10)
|
}
|
||||||
: undefined,
|
return {
|
||||||
flag: inbound.flag || getNodeFlag(inbound.nodeId) || undefined,
|
type: inbound.type,
|
||||||
name: inbound.name?.trim() || undefined,
|
port: inbound.port === 'random' ? 'random' : parseInt(inbound.port, 10),
|
||||||
certificateFile:
|
sni: inbound.type === 'hysteria2-udp' ? undefined : inbound.sni,
|
||||||
inbound.type === 'hysteria2-udp'
|
nodeId: inbound.nodeId || undefined,
|
||||||
? inbound.certificateFile?.trim() || undefined
|
relayServerId: inbound.relayServerId
|
||||||
: undefined,
|
? parseInt(inbound.relayServerId, 10)
|
||||||
keyFile:
|
: undefined,
|
||||||
inbound.type === 'hysteria2-udp'
|
flag: inbound.flag || getNodeFlag(inbound.nodeId) || undefined,
|
||||||
? inbound.keyFile?.trim() || undefined
|
name: inbound.name?.trim() || undefined,
|
||||||
: undefined,
|
certificateFile:
|
||||||
},
|
inbound.type === 'hysteria2-udp'
|
||||||
),
|
? inbound.certificateFile?.trim() || undefined
|
||||||
|
: undefined,
|
||||||
|
keyFile:
|
||||||
|
inbound.type === 'hysteria2-udp'
|
||||||
|
? inbound.keyFile?.trim() || undefined
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -735,6 +758,18 @@ export default function SubscriptionsPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
{inbound.type === 'custom' ? (
|
{inbound.type === 'custom' ? (
|
||||||
<TextField size="small" label="Ссылка на подключение" placeholder="vless://..." value={inbound.link || ''} onChange={(e) => handleInboundChange(inbound.id, 'link', e.target.value)} sx={{ width: 460, flexShrink: 0 }} />
|
<TextField size="small" label="Ссылка на подключение" placeholder="vless://..." value={inbound.link || ''} onChange={(e) => handleInboundChange(inbound.id, 'link', e.target.value)} sx={{ width: 460, flexShrink: 0 }} />
|
||||||
|
) : inbound.type === 'happ-routing' ? (
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label="Routing профиль (JSON или base64)"
|
||||||
|
placeholder='{"Name":"MyProfile","GlobalProxy":"true","DirectSites":["geosite:cn"]}'
|
||||||
|
value={inbound.routingProfile || ''}
|
||||||
|
onChange={(e) => handleInboundChange(inbound.id, 'routingProfile', e.target.value)}
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
sx={{ width: 460, flexShrink: 0 }}
|
||||||
|
helperText="Вставьте JSON профиль или готовую ссылку happ://routing/..."
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<FormControl size="small" sx={{ width: 170, flexShrink: 0 }}>
|
<FormControl size="small" sx={{ width: 170, flexShrink: 0 }}>
|
||||||
@@ -860,4 +895,4 @@ export default function SubscriptionsPage() {
|
|||||||
</Snackbar>
|
</Snackbar>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+2
-2
@@ -5,8 +5,8 @@ set -euo pipefail
|
|||||||
# КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ
|
# КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ
|
||||||
#################################
|
#################################
|
||||||
PROJECT_DIR="/opt/3dp-manager"
|
PROJECT_DIR="/opt/3dp-manager"
|
||||||
DOCKER_USER="denpiligrim"
|
DOCKER_USER="zangetsuka"
|
||||||
DOCKER_TAG="main"
|
DOCKER_TAG="feature-happ-routing"
|
||||||
IMAGE_SERVER="ghcr.io/${DOCKER_USER}/3dp-manager-server:${DOCKER_TAG}"
|
IMAGE_SERVER="ghcr.io/${DOCKER_USER}/3dp-manager-server:${DOCKER_TAG}"
|
||||||
IMAGE_CLIENT="ghcr.io/${DOCKER_USER}/3dp-manager-client:${DOCKER_TAG}"
|
IMAGE_CLIENT="ghcr.io/${DOCKER_USER}/3dp-manager-client:${DOCKER_TAG}"
|
||||||
|
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ export class RotationService implements OnModuleInit {
|
|||||||
sni = config.sni === 'random' ? this.pickDomain(domains) : config.sni;
|
sni = config.sni === 'random' ? this.pickDomain(domains) : config.sni;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// === 2. Обработка Hysteria2 ===
|
// === 2. Обработка Hysteria2 ===
|
||||||
if (type === 'hysteria2-udp') {
|
if (type === 'hysteria2-udp') {
|
||||||
let port = 0;
|
let port = 0;
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ export class InboundConfigDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
keyFile?: string;
|
keyFile?: string;
|
||||||
|
|
||||||
|
// ⬇️ НОВОЕ ПОЛЕ ДЛЯ HAPP ROUTING ⬇️
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
routingProfile?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateSubscriptionDto {
|
export class CreateSubscriptionDto {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export class Subscription {
|
|||||||
name?: string;
|
name?: string;
|
||||||
certificateFile?: string;
|
certificateFile?: string;
|
||||||
keyFile?: string;
|
keyFile?: string;
|
||||||
|
routingProfile?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user