Skip to content

Commit 7b6a70c

Browse files
committed
fix: post-merge cleanup (TOTP + Hysteria 2 UI)
- deduplicate parseDurationSeconds/normalizeHopInterval into shared helpers - replace hardcoded Russian error strings with i18n - remove dead acme schema fields - fix parseInt without radix - extend REST API, MCP and OpenAPI with Hysteria 2 node fields - fix MCP handlers to actually pass new fields on create/update - fix MCP/OpenAPI schemas to match Mongoose model structure - save useTlsFiles on node creation via form parser - restore C³ branding across docs and views
1 parent f852d0c commit 7b6a70c

File tree

16 files changed

+283
-67
lines changed

16 files changed

+283
-67
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CELERITY
1+
# CELERITY
22

33
**Fast. Simple. Long-lasting.**
44

@@ -11,12 +11,12 @@
1111
[![Hysteria](https://img.shields.io/badge/Hysteria-2.x-9B59B6)](https://v2.hysteria.network/)
1212
[![Xray](https://img.shields.io/badge/Xray-VLESS-00ADD8)](https://xtls.github.io/)
1313

14-
**CELERITY** by Click Connect — modern web panel for managing [Hysteria 2](https://v2.hysteria.network/) and [Xray VLESS](https://xtls.github.io/) proxy servers with centralized authentication, one-click node setup, and flexible user-to-server group mapping.
14+
**CELERITY** by Click Connect — modern web panel for managing [Hysteria 2](https://v2.hysteria.network/) and [Xray VLESS](https://xtls.github.io/) proxy servers with centralized authentication, one-click node setup, and flexible user-to-server group mapping.
1515

1616
**Built for performance:** Lightweight architecture designed for speed at any scale.
1717

1818
<p align="center">
19-
<img src="docs/dashboard.png" alt="CELERITY Dashboard" width="800">
19+
<img src="docs/dashboard.png" alt="CELERITY Dashboard" width="800">
2020
<br>
2121
<em>Dashboard — real-time server monitoring and statistics</em>
2222
</p>

README.ru.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CELERITY
1+
# CELERITY
22

33
**Быстро, просто и надолго**
44

@@ -11,7 +11,7 @@
1111
[Hysteria](https://v2.hysteria.network/)
1212
[Xray](https://xtls.github.io/)
1313

14-
**CELERITY** by Click Connect — современная веб-панель для управления серверами [Hysteria 2](https://v2.hysteria.network/) и [Xray VLESS](https://xtls.github.io/) с централизованной авторизацией, автоматической настройкой нод и гибким распределением пользователей по группам.
14+
**CELERITY** by Click Connect — современная веб-панель для управления серверами [Hysteria 2](https://v2.hysteria.network/) и [Xray VLESS](https://xtls.github.io/) с централизованной авторизацией, автоматической настройкой нод и гибким распределением пользователей по группам.
1515

1616
**Создана для скорости:** Лёгкая архитектура, оптимизированная для быстрой работы на любом масштабе.
1717

docker-compose.hub.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CELERITY - Quick Start (Docker Hub)
1+
# CELERITY - Quick Start (Docker Hub)
22
# Use this file for quick deployment without building from source
33
# Run: docker compose -f docker-compose.hub.yml up -d
44

docs/mcp-user-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# 🤖 MCP Integration User Guide
22

3-
> Connect AI assistants directly to your CELERITY panel for automated management.
3+
> Connect AI assistants directly to your CELERITY panel for automated management.
44
55
---
66

77
## 📖 What is MCP?
88

9-
**Model Context Protocol (MCP)** is a protocol that allows AI assistants (Claude, Cursor, etc.) to directly interact with the CELERITY panel.
9+
**Model Context Protocol (MCP)** is a protocol that allows AI assistants (Claude, Cursor, etc.) to directly interact with the CELERITY panel.
1010

1111
### ✨ Capabilities
1212

docs/mcp-user-guide.ru.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# 🤖 Руководство по использованию MCP-интеграции
22

3-
> Подключите AI-ассистентов напрямую к панели CELERITY для автоматизированного управления.
3+
> Подключите AI-ассистентов напрямую к панели CELERITY для автоматизированного управления.
44
55
---
66

77
## 📖 Что такое MCP?
88

9-
**Model Context Protocol (MCP)** — это протокол, который позволяет AI-ассистентам (Claude, Cursor и др.) напрямую взаимодействовать с панелью CELERITY.
9+
**Model Context Protocol (MCP)** — это протокол, который позволяет AI-ассистентам (Claude, Cursor и др.) напрямую взаимодействовать с панелью CELERITY.
1010

1111
### ✨ Возможности
1212

docs/safe-update.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Safe Production Updates
22

3-
Step-by-step guide for updating CELERITY on production servers with minimal downtime.
3+
Step-by-step guide for updating CELERITY on production servers with minimal downtime.
44

55
---
66

docs/safe-update.ru.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Безопасное обновление на проде
22

3-
Пошаговое руководство по обновлению CELERITY на production-сервере с минимальным временем простоя.
3+
Пошаговое руководство по обновлению CELERITY на production-сервере с минимальным временем простоя.
44

55
---
66

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "c3-celerity",
33
"version": "1.0.0",
4-
"description": "CELERITY - Management panel for Hysteria 2 nodes by Click Connect",
4+
"description": "CELERITY - Management panel for Hysteria 2 nodes by Click Connect",
55
"keywords": [
66
"nodejs",
77
"docker",

src/docs/openapi.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,113 @@ Admin sessions (cookie) bypass scope checks entirely.
164164
rx: { type: 'integer' },
165165
},
166166
},
167+
// Hysteria 2 advanced configuration
168+
hopInterval: { type: 'string', example: '30s', description: 'Port-hopping interval' },
169+
ignoreClientBandwidth: { type: 'boolean', example: false },
170+
speedTest: { type: 'boolean', example: false },
171+
disableUDP: { type: 'boolean', example: false },
172+
udpIdleTimeout: { type: 'string', example: '60s' },
173+
acme: {
174+
type: 'object',
175+
properties: {
176+
email: { type: 'string' },
177+
ca: { type: 'string', example: 'letsencrypt' },
178+
listenHost: { type: 'string', example: '0.0.0.0' },
179+
type: { type: 'string', enum: ['', 'http', 'tls', 'dns'] },
180+
httpAltPort: { type: 'integer', example: 0 },
181+
tlsAltPort: { type: 'integer', example: 0 },
182+
dnsName: { type: 'string' },
183+
dnsConfig: { type: 'object' },
184+
},
185+
},
186+
masquerade: {
187+
type: 'object',
188+
properties: {
189+
type: { type: 'string', enum: ['proxy', 'string'], example: 'proxy' },
190+
proxy: {
191+
type: 'object',
192+
properties: {
193+
url: { type: 'string', example: 'https://www.google.com' },
194+
rewriteHost: { type: 'boolean' },
195+
insecure: { type: 'boolean' },
196+
},
197+
},
198+
string: {
199+
type: 'object',
200+
properties: {
201+
content: { type: 'string' },
202+
headers: { type: 'object' },
203+
statusCode: { type: 'integer', example: 503 },
204+
},
205+
},
206+
listenHTTP: { type: 'string', description: 'HTTP listen address for masquerade' },
207+
listenHTTPS: { type: 'string', description: 'HTTPS listen address for masquerade' },
208+
forceHTTPS: { type: 'boolean' },
209+
},
210+
},
211+
bandwidth: {
212+
type: 'object',
213+
properties: {
214+
up: { type: 'string', example: '1 gbps' },
215+
down: { type: 'string', example: '1 gbps' },
216+
},
217+
},
218+
sniff: {
219+
type: 'object',
220+
properties: {
221+
enabled: { type: 'boolean' },
222+
enable: { type: 'boolean', description: 'Enable sniffing within the protocol' },
223+
timeout: { type: 'string', example: '2s' },
224+
rewriteDomain: { type: 'boolean' },
225+
tcpPorts: { type: 'string', example: '80,443,8000-9000' },
226+
udpPorts: { type: 'string', example: '443,80,53' },
227+
},
228+
},
229+
quic: {
230+
type: 'object',
231+
properties: {
232+
enabled: { type: 'boolean' },
233+
initStreamReceiveWindow: { type: 'integer' },
234+
maxStreamReceiveWindow: { type: 'integer' },
235+
initConnReceiveWindow: { type: 'integer' },
236+
maxConnReceiveWindow: { type: 'integer' },
237+
maxIdleTimeout: { type: 'string', example: '60s' },
238+
maxIncomingStreams: { type: 'integer' },
239+
disablePathMTUDiscovery: { type: 'boolean' },
240+
},
241+
},
242+
resolver: {
243+
type: 'object',
244+
properties: {
245+
enabled: { type: 'boolean' },
246+
type: { type: 'string', enum: ['udp', 'tcp', 'tls', 'https'] },
247+
udpAddr: { type: 'string', example: '8.8.4.4:53' },
248+
udpTimeout: { type: 'string', example: '4s' },
249+
tcpAddr: { type: 'string', example: '8.8.8.8:53' },
250+
tcpTimeout: { type: 'string', example: '4s' },
251+
tlsAddr: { type: 'string', example: '1.1.1.1:853' },
252+
tlsTimeout: { type: 'string', example: '10s' },
253+
tlsSni: { type: 'string', example: 'cloudflare-dns.com' },
254+
tlsInsecure: { type: 'boolean' },
255+
httpsAddr: { type: 'string', example: '1.1.1.1:443' },
256+
httpsTimeout: { type: 'string', example: '10s' },
257+
httpsSni: { type: 'string', example: 'cloudflare-dns.com' },
258+
httpsInsecure: { type: 'boolean' },
259+
},
260+
},
261+
acl: {
262+
type: 'object',
263+
properties: {
264+
enabled: { type: 'boolean' },
265+
type: { type: 'string', enum: ['inline', 'file'] },
266+
file: { type: 'string' },
267+
geoip: { type: 'string' },
268+
geosite: { type: 'string' },
269+
geoUpdateInterval: { type: 'string' },
270+
},
271+
},
272+
aclRules: { type: 'array', items: { type: 'string' }, description: 'Inline ACL rules' },
273+
useTlsFiles: { type: 'boolean', description: 'Whether to use TLS cert/key files instead of ACME' },
167274
},
168275
},
169276
GroupRef: {

src/mcp/tools/nodes.js

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,86 @@ const manageNodeSchema = z.object({
6161
username: z.string().optional(),
6262
password: z.string().optional(),
6363
}).optional(),
64+
// Hysteria 2 advanced configuration
65+
hopInterval: z.string().optional().describe('Port-hopping interval, e.g. "30s"'),
66+
acme: z.object({
67+
email: z.string().optional(),
68+
ca: z.string().optional(),
69+
listenHost: z.string().optional(),
70+
type: z.enum(['', 'http', 'tls', 'dns']).optional(),
71+
httpAltPort: z.number().optional(),
72+
tlsAltPort: z.number().optional(),
73+
dnsName: z.string().optional(),
74+
dnsConfig: z.record(z.unknown()).optional(),
75+
}).optional(),
76+
masquerade: z.object({
77+
type: z.enum(['proxy', 'string']).optional(),
78+
proxy: z.object({
79+
url: z.string().optional(),
80+
rewriteHost: z.boolean().optional(),
81+
insecure: z.boolean().optional(),
82+
}).optional(),
83+
string: z.object({
84+
content: z.string().optional(),
85+
headers: z.record(z.string()).optional(),
86+
statusCode: z.number().optional(),
87+
}).optional(),
88+
listenHTTP: z.string().optional(),
89+
listenHTTPS: z.string().optional(),
90+
forceHTTPS: z.boolean().optional(),
91+
}).optional(),
92+
bandwidth: z.object({
93+
up: z.string().optional(),
94+
down: z.string().optional(),
95+
}).optional(),
96+
ignoreClientBandwidth: z.boolean().optional(),
97+
speedTest: z.boolean().optional(),
98+
disableUDP: z.boolean().optional(),
99+
udpIdleTimeout: z.string().optional().describe('UDP idle timeout, e.g. "60s"'),
100+
sniff: z.object({
101+
enabled: z.boolean().optional(),
102+
enable: z.boolean().optional().describe('Enable sniffing within the protocol'),
103+
timeout: z.string().optional().describe('Sniff timeout, e.g. "2s"'),
104+
rewriteDomain: z.boolean().optional(),
105+
tcpPorts: z.string().optional().describe('TCP ports to sniff, e.g. "80,443,8000-9000"'),
106+
udpPorts: z.string().optional().describe('UDP ports to sniff, e.g. "443,80,53"'),
107+
}).optional(),
108+
quic: z.object({
109+
enabled: z.boolean().optional(),
110+
initStreamReceiveWindow: z.number().optional(),
111+
maxStreamReceiveWindow: z.number().optional(),
112+
initConnReceiveWindow: z.number().optional(),
113+
maxConnReceiveWindow: z.number().optional(),
114+
maxIdleTimeout: z.string().optional(),
115+
maxIncomingStreams: z.number().optional(),
116+
disablePathMTUDiscovery: z.boolean().optional(),
117+
}).optional(),
118+
resolver: z.object({
119+
enabled: z.boolean().optional(),
120+
type: z.enum(['udp', 'tcp', 'tls', 'https']).optional(),
121+
udpAddr: z.string().optional(),
122+
udpTimeout: z.string().optional(),
123+
tcpAddr: z.string().optional(),
124+
tcpTimeout: z.string().optional(),
125+
tlsAddr: z.string().optional(),
126+
tlsTimeout: z.string().optional(),
127+
tlsSni: z.string().optional(),
128+
tlsInsecure: z.boolean().optional(),
129+
httpsAddr: z.string().optional(),
130+
httpsTimeout: z.string().optional(),
131+
httpsSni: z.string().optional(),
132+
httpsInsecure: z.boolean().optional(),
133+
}).optional(),
134+
acl: z.object({
135+
enabled: z.boolean().optional(),
136+
type: z.enum(['inline', 'file']).optional(),
137+
file: z.string().optional(),
138+
geoip: z.string().optional(),
139+
geosite: z.string().optional(),
140+
geoUpdateInterval: z.string().optional(),
141+
}).optional(),
142+
aclRules: z.array(z.string()).optional().describe('Inline ACL rules (stored on node root, not inside acl)'),
143+
useTlsFiles: z.boolean().optional().describe('Whether to use TLS cert/key files instead of ACME'),
64144
}).optional(),
65145
setupOptions: z.object({
66146
installHysteria: z.boolean().default(true),
@@ -156,7 +236,7 @@ async function manageNode(args, emit) {
156236
if (existing) return { error: 'Node with this IP already exists', code: 409 };
157237

158238
const statsSecret = cryptoService.generateNodeSecret();
159-
const node = new HyNode({
239+
const nodeData = {
160240
name: data.name,
161241
ip: data.ip,
162242
type: data.type || 'hysteria',
@@ -172,7 +252,16 @@ async function manageNode(args, emit) {
172252
status: 'offline',
173253
cascadeRole: data.cascadeRole || 'standalone',
174254
country: data.country || '',
175-
});
255+
};
256+
const hy2Keys = [
257+
'hopInterval', 'acme', 'masquerade', 'bandwidth',
258+
'ignoreClientBandwidth', 'speedTest', 'disableUDP',
259+
'udpIdleTimeout', 'sniff', 'quic', 'resolver', 'acl', 'aclRules', 'useTlsFiles',
260+
];
261+
for (const k of hy2Keys) {
262+
if (data[k] !== undefined) nodeData[k] = data[k];
263+
}
264+
const node = new HyNode(nodeData);
176265
await node.save();
177266
await invalidateNodesCache();
178267
logger.info(`[MCP] Created node ${data.name} (${data.ip})`);
@@ -181,7 +270,12 @@ async function manageNode(args, emit) {
181270

182271
case 'update': {
183272
if (!id) throw new Error('id is required for update');
184-
const allowed = ['name', 'domain', 'sni', 'port', 'portRange', 'groups', 'active', 'country', 'cascadeRole', 'type'];
273+
const allowed = [
274+
'name', 'domain', 'sni', 'port', 'portRange', 'groups', 'active', 'country', 'cascadeRole', 'type',
275+
'hopInterval', 'acme', 'masquerade', 'bandwidth',
276+
'ignoreClientBandwidth', 'speedTest', 'disableUDP',
277+
'udpIdleTimeout', 'sniff', 'quic', 'resolver', 'acl', 'aclRules', 'useTlsFiles',
278+
];
185279
const updates = {};
186280
for (const k of allowed) {
187281
if (data[k] !== undefined) updates[k] = data[k];

0 commit comments

Comments
 (0)