hiddifypanel 10.85.0b21__py3-none-any.whl → 10.86.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/hutils/network/auto_ip_selector.py +19 -10
- hiddifypanel/hutils/network/net.py +25 -5
- hiddifypanel/hutils/proxy/clash.py +1 -1
- hiddifypanel/hutils/proxy/shared.py +118 -56
- hiddifypanel/hutils/proxy/xray.py +57 -40
- hiddifypanel/hutils/proxy/xrayjson.py +48 -28
- hiddifypanel/models/config.py +1 -1
- hiddifypanel/models/config_enum.py +4 -0
- hiddifypanel/models/domain.py +33 -8
- hiddifypanel/models/proxy.py +6 -2
- hiddifypanel/panel/admin/DomainAdmin.py +22 -25
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +13 -4
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +1 -1
- hiddifypanel/panel/custom_widgets.py +45 -0
- hiddifypanel/panel/init_db.py +102 -184
- hiddifypanel/panel/user/user.py +12 -12
- hiddifypanel/static/js/custom-rtl.js +1 -1
- hiddifypanel/templates/fake.html +11 -2
- hiddifypanel/templates/flaskadmin-layout.html +11 -9
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +33 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +33 -0
- hiddifypanel/translations/my/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +33 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +33 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +33 -0
- hiddifypanel/translations.i18n/en.json +19 -0
- hiddifypanel/translations.i18n/fa.json +19 -0
- hiddifypanel/translations.i18n/pt.json +19 -0
- hiddifypanel/translations.i18n/ru.json +19 -0
- hiddifypanel/translations.i18n/zh.json +19 -0
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/METADATA +1 -1
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/RECORD +43 -43
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/licenses/LICENSE.md +0 -0
- {hiddifypanel-10.85.0b21.dist-info → hiddifypanel-10.86.0b2.dist-info}/top_level.txt +0 -0
@@ -87,8 +87,7 @@ def configs_as_json(domains: list[Domain], user: User, expire_days: int, remarks
|
|
87
87
|
if not all_configs:
|
88
88
|
return ''
|
89
89
|
|
90
|
-
json_configs = json.dumps(all_configs, indent=2,
|
91
|
-
cls=hutils.proxy.ProxyJsonEncoder)
|
90
|
+
json_configs = json.dumps(all_configs, indent=2, cls=hutils.proxy.ProxyJsonEncoder)
|
92
91
|
return json_configs
|
93
92
|
|
94
93
|
|
@@ -162,7 +161,7 @@ def add_vless_settings(base: dict, proxy: dict):
|
|
162
161
|
'id': proxy['uuid'],
|
163
162
|
'encryption': 'none',
|
164
163
|
# 'security': 'auto',
|
165
|
-
'flow':
|
164
|
+
'flow': proxy.get('flow',''),
|
166
165
|
'level': OUTBOUND_LEVEL
|
167
166
|
}
|
168
167
|
]
|
@@ -216,14 +215,17 @@ def add_shadowsocks_settings(base: dict, proxy: dict):
|
|
216
215
|
|
217
216
|
# region stream settings
|
218
217
|
|
219
|
-
def
|
220
|
-
|
218
|
+
def _add_security(base_dict, proxy, tls_info=None):
|
219
|
+
if not tls_info:
|
220
|
+
tls_info = proxy
|
221
|
+
|
222
|
+
ss = base_dict
|
221
223
|
ss['security'] = 'none' # default
|
222
224
|
|
223
225
|
# security
|
224
|
-
if
|
226
|
+
if 'reality' in tls_info['mode']:
|
225
227
|
ss['security'] = 'reality'
|
226
|
-
elif proxy['l3'] in [ProxyL3.tls, ProxyL3.tls_h2, ProxyL3.tls_h2_h1, ProxyL3.h3_quic]:
|
228
|
+
elif proxy['l3'] in [ProxyL3.tls, ProxyL3.tls_h2, ProxyL3.tls_h2_h1, ProxyL3.h3_quic, ProxyL3.reality]:
|
227
229
|
ss['security'] = 'tls'
|
228
230
|
|
229
231
|
# network and transport settings
|
@@ -231,15 +233,14 @@ def add_stream_settings(base: dict, proxy: dict):
|
|
231
233
|
# ss['security'] == 'tls' or 'xtls' -----> ss['security'] in ['tls','xtls']
|
232
234
|
# TODO: FIX THE CONDITION AND TEST CONFIGS ON THE CLIENT SIDE
|
233
235
|
if ss['security'] == 'reality':
|
234
|
-
ss['network'] = proxy['transport']
|
235
|
-
add_reality_stream(ss, proxy)
|
236
|
+
# ss['network'] = proxy['transport']
|
237
|
+
add_reality_stream(ss, proxy, tls_info)
|
236
238
|
elif ss['security'] in ['tls', "xtls"] and proxy['proto'] != ProxyProto.ss:
|
237
|
-
|
238
239
|
ss['tlsSettings'] = {
|
239
|
-
'serverName':
|
240
|
-
'allowInsecure':
|
240
|
+
'serverName': tls_info['sni'],
|
241
|
+
'allowInsecure': tls_info['allow_insecure'],
|
241
242
|
'fingerprint': proxy['fingerprint'],
|
242
|
-
'alpn': [
|
243
|
+
'alpn': [tls_info['alpn']],
|
243
244
|
# 'minVersion': '1.2',
|
244
245
|
# 'disableSystemRoot': '',
|
245
246
|
# 'enableSessionResumption': '',
|
@@ -250,6 +251,11 @@ def add_stream_settings(base: dict, proxy: dict):
|
|
250
251
|
# 'rejectUnknownSni': '', # default is false
|
251
252
|
}
|
252
253
|
|
254
|
+
|
255
|
+
def add_stream_settings(base: dict, proxy: dict):
|
256
|
+
ss = base['streamSettings']
|
257
|
+
|
258
|
+
_add_security(ss, proxy, proxy)
|
253
259
|
if proxy['l3'] == ProxyL3.kcp:
|
254
260
|
ss['network'] = 'kcp'
|
255
261
|
add_kcp_stream(ss, proxy)
|
@@ -299,7 +305,7 @@ def add_tcp_stream(ss: dict, proxy: dict):
|
|
299
305
|
"Connection": "keep-alive",
|
300
306
|
"Pragma": "no-cache"
|
301
307
|
},
|
302
|
-
|
308
|
+
|
303
309
|
}
|
304
310
|
}
|
305
311
|
}
|
@@ -358,23 +364,37 @@ def add_httpupgrade_stream(ss: dict, proxy: dict):
|
|
358
364
|
|
359
365
|
|
360
366
|
def add_xhttp_stream(ss: dict, proxy: dict):
|
361
|
-
if ss['transport'] == "xhttp" and not hutils.flask.is_client_version(hutils.flask.ClientVersion.hiddify_next, 3, 0, 0):
|
367
|
+
if ss['transport'] == "xhttp" and g.user_agent.get(hutils.flask.ClientVersion.hiddify_next) and not hutils.flask.is_client_version(hutils.flask.ClientVersion.hiddify_next, 3, 0, 0):
|
362
368
|
ss['transport'] = "splithttp"
|
363
369
|
ss['splithttpSettings'] = {
|
364
370
|
'path': proxy['path'],
|
365
371
|
'host': proxy['host'],
|
366
|
-
"headers": {
|
367
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
|
368
|
-
}
|
372
|
+
"headers": proxy['params'].get('headers', {})
|
369
373
|
}
|
370
374
|
else:
|
371
|
-
ss
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
375
|
+
_add_xhttp_details(ss, proxy)
|
376
|
+
|
377
|
+
|
378
|
+
|
379
|
+
|
380
|
+
def _add_xhttp_details(ss: dict, proxy: dict):
|
381
|
+
ss['network'] = "xhttp"
|
382
|
+
ss['xhttpSettings'] = {
|
383
|
+
'path': proxy['path'],
|
384
|
+
'host': proxy['host'],
|
385
|
+
'mode':proxy['xhttp_mode'],
|
386
|
+
"extra": {
|
387
|
+
"headers": proxy['params'].get('headers', {})
|
377
388
|
}
|
389
|
+
}
|
390
|
+
if proxy.get("download"):
|
391
|
+
ss['xhttpSettings']['extra']['downloadSettings'] = {
|
392
|
+
"address": proxy['download'].get("server"),
|
393
|
+
"port": proxy['port'],
|
394
|
+
}
|
395
|
+
|
396
|
+
_add_xhttp_details(ss['xhttpSettings']['extra']['downloadSettings'], proxy['download'])
|
397
|
+
_add_security(ss['xhttpSettings']['extra']['downloadSettings'], proxy, proxy['download'])
|
378
398
|
|
379
399
|
|
380
400
|
def add_kcp_stream(ss: dict, proxy: dict):
|
@@ -409,12 +429,12 @@ def add_quic_stream(ss: dict, proxy: dict):
|
|
409
429
|
}
|
410
430
|
|
411
431
|
|
412
|
-
def add_reality_stream(ss: dict, proxy: dict):
|
432
|
+
def add_reality_stream(ss: dict, proxy: dict, domain_info: dict):
|
413
433
|
ss['realitySettings'] = {
|
414
|
-
'serverName':
|
434
|
+
'serverName': domain_info['sni'],
|
415
435
|
'fingerprint': proxy['fingerprint'],
|
416
|
-
'shortId':
|
417
|
-
'publicKey':
|
436
|
+
'shortId': domain_info['reality_short_id'],
|
437
|
+
'publicKey': domain_info['reality_pbk'],
|
418
438
|
'show': False,
|
419
439
|
}
|
420
440
|
|
hiddifypanel/models/config.py
CHANGED
@@ -80,7 +80,7 @@ def hconfig(key: ConfigEnum, child_id: Optional[int] = None): # -> str | int |
|
|
80
80
|
except BaseException:
|
81
81
|
logger.exception(f'{key} error!')
|
82
82
|
raise
|
83
|
-
if value
|
83
|
+
if value is not None:
|
84
84
|
if key.type == int:
|
85
85
|
return int(value)
|
86
86
|
elif hasattr(key.type, 'from_str'):
|
@@ -128,6 +128,7 @@ class ConfigEnum(metaclass=FastEnum):
|
|
128
128
|
reality_private_key = _StrConfigDscr(ConfigCategory.reality, ApplyMode.apply_config, hide_in_virtual_child=True)
|
129
129
|
reality_public_key = _StrConfigDscr(ConfigCategory.reality, ApplyMode.apply_config, hide_in_virtual_child=True)
|
130
130
|
reality_port = _StrConfigDscr(ConfigCategory.reality, ApplyMode.apply_config, hide_in_virtual_child=True)
|
131
|
+
special_port = _StrConfigDscr(ConfigCategory.reality, ApplyMode.apply_config, hide_in_virtual_child=True)
|
131
132
|
|
132
133
|
restls1_2_domain = _StrConfigDscr(ConfigCategory.hidden)
|
133
134
|
restls1_3_domain = _StrConfigDscr(ConfigCategory.hidden)
|
@@ -161,6 +162,8 @@ class ConfigEnum(metaclass=FastEnum):
|
|
161
162
|
admin_lang = _TypedConfigDscr(Lang, ConfigCategory.admin)
|
162
163
|
admin_secret = _StrConfigDscr(ConfigCategory.hidden) # removed
|
163
164
|
|
165
|
+
default_useragent_string = _StrConfigDscr(ConfigCategory.general)
|
166
|
+
use_ip_in_config=_BoolConfigDscr(ConfigCategory.general)
|
164
167
|
# tls
|
165
168
|
tls_ports = _StrConfigDscr(ConfigCategory.tls, ApplyMode.apply_config)
|
166
169
|
|
@@ -291,6 +294,7 @@ class ConfigEnum(metaclass=FastEnum):
|
|
291
294
|
ssh_host_dsa_pub = _StrConfigDscr(ConfigCategory.hidden)
|
292
295
|
|
293
296
|
|
297
|
+
|
294
298
|
|
295
299
|
hiddifycli_enable = _BoolConfigDscr(ConfigCategory.hidden, ApplyMode.reinstall)
|
296
300
|
|
hiddifypanel/models/domain.py
CHANGED
@@ -8,7 +8,6 @@ from sqlalchemy.orm import backref
|
|
8
8
|
from strenum import StrEnum
|
9
9
|
|
10
10
|
|
11
|
-
|
12
11
|
from hiddifypanel.database import db
|
13
12
|
from hiddifypanel.models.config import hconfig
|
14
13
|
from .child import Child
|
@@ -21,11 +20,16 @@ class DomainType(StrEnum):
|
|
21
20
|
cdn = auto()
|
22
21
|
auto_cdn_ip = auto()
|
23
22
|
relay = auto()
|
24
|
-
reality = auto()
|
25
|
-
old_xtls_direct = auto()
|
26
23
|
worker = auto()
|
27
24
|
fake = auto()
|
28
25
|
|
26
|
+
reality = auto() #deprecated
|
27
|
+
special_reality_tcp = auto()
|
28
|
+
special_reality_xhttp = auto()
|
29
|
+
special_reality_grpc = auto()
|
30
|
+
old_xtls_direct = auto() #deprecated
|
31
|
+
# special_shadowtls = auto()
|
32
|
+
|
29
33
|
# fake_cdn = "fake_cdn"
|
30
34
|
# telegram_faketls = "telegram_faketls"
|
31
35
|
# ss_faketls = "ss_faketls"
|
@@ -37,6 +41,7 @@ ShowDomain = db.Table('show_domain',
|
|
37
41
|
)
|
38
42
|
|
39
43
|
|
44
|
+
|
40
45
|
class Domain(db.Model):
|
41
46
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
42
47
|
child_id = db.Column(db.Integer, db.ForeignKey('child.id'), default=0)
|
@@ -54,6 +59,8 @@ class Domain(db.Model):
|
|
54
59
|
secondaryjoin=id == ShowDomain.c.related_id,
|
55
60
|
backref=backref('showed_by_domains', lazy='dynamic')
|
56
61
|
)
|
62
|
+
download_domain_id= db.Column(db.Integer, db.ForeignKey('domain.id', ondelete='SET NULL'), default=None,nullable=True)
|
63
|
+
download_domain = db.relationship('Domain',remote_side=[id], foreign_keys=[download_domain_id])
|
57
64
|
extra_params = db.Column(db.String(200), nullable=True, default='')
|
58
65
|
|
59
66
|
def __repr__(self):
|
@@ -79,6 +86,7 @@ class Domain(db.Model):
|
|
79
86
|
'cdn_ip': self.cdn_ip,
|
80
87
|
'servernames': self.servernames,
|
81
88
|
'grpc': self.grpc,
|
89
|
+
'download_domain':self.download_domain.domain if self.download_domain else "",
|
82
90
|
'show_domains': [dd.domain for dd in self.show_domains], # type: ignore
|
83
91
|
}
|
84
92
|
if dump_child_id:
|
@@ -86,7 +94,7 @@ class Domain(db.Model):
|
|
86
94
|
if dump_ports:
|
87
95
|
data["internal_port_hysteria2"] = self.internal_port_hysteria2
|
88
96
|
data["internal_port_tuic"] = self.internal_port_tuic
|
89
|
-
data["
|
97
|
+
data["internal_port_special"] = self.internal_port_special
|
90
98
|
data["need_valid_ssl"] = self.need_valid_ssl
|
91
99
|
|
92
100
|
return data
|
@@ -100,6 +108,13 @@ class Domain(db.Model):
|
|
100
108
|
from hiddifypanel.panel.commercial.restapi.v2.parent.schema import DomainSchema
|
101
109
|
return DomainSchema().load(domain_dict)
|
102
110
|
|
111
|
+
|
112
|
+
def auto_cdn_ip(self):
|
113
|
+
from hiddifypanel import hutils
|
114
|
+
if self.cdn_ip:
|
115
|
+
return hutils.network.auto_ip_selector.get_clean_ip(self.cdn_ip)
|
116
|
+
return None
|
117
|
+
|
103
118
|
@property
|
104
119
|
def need_valid_ssl(self):
|
105
120
|
return self.mode in [DomainType.direct, DomainType.cdn, DomainType.worker, DomainType.relay, DomainType.auto_cdn_ip, DomainType.old_xtls_direct, DomainType.sub_link_only]
|
@@ -124,11 +139,11 @@ class Domain(db.Model):
|
|
124
139
|
return int(hconfig(ConfigEnum.tuic_port, self.child_id)) + self.port_index
|
125
140
|
|
126
141
|
@property
|
127
|
-
def
|
128
|
-
if self.mode != DomainType.reality:
|
142
|
+
def internal_port_special(self):
|
143
|
+
if self.mode != DomainType.reality and "special" not in self.mode.value:
|
129
144
|
return 0
|
130
145
|
# TODO: check validity of the range of the port
|
131
|
-
return int(hconfig(ConfigEnum.
|
146
|
+
return int(hconfig(ConfigEnum.special_port, self.child_id)) + self.port_index
|
132
147
|
|
133
148
|
@classmethod
|
134
149
|
def by_mode(cls, mode: DomainType) -> List['Domain']:
|
@@ -163,7 +178,7 @@ class Domain(db.Model):
|
|
163
178
|
domains = []
|
164
179
|
domains = db.session.query(Domain).filter(Domain.mode == DomainType.sub_link_only, Domain.child_id == Child.current().id).all()
|
165
180
|
if not len(domains) or always_add_all_domains:
|
166
|
-
domains = db.session.query(Domain).filter(Domain.mode.notin_([DomainType.fake, DomainType.reality])).all()
|
181
|
+
domains = db.session.query(Domain).filter(Domain.mode.notin_([DomainType.fake, DomainType.reality,DomainType.special_reality_tcp,DomainType.special_reality_xhttp,DomainType.special_reality_grpc])).all()
|
167
182
|
|
168
183
|
if len(domains) == 0 and request:
|
169
184
|
domains = [Domain(domain=request.host)] # type: ignore
|
@@ -188,6 +203,16 @@ class Domain(db.Model):
|
|
188
203
|
dbdomain.servernames = domain.get('servernames', '')
|
189
204
|
show_domains = domain.get('show_domains', [])
|
190
205
|
dbdomain.show_domains = Domain.query.filter(Domain.domain.in_(show_domains)).all()
|
206
|
+
dl_domain=domain.get("download_domain")
|
207
|
+
if dl_domain:
|
208
|
+
dbdldomain = Domain.query.filter(Domain.domain == dl_domain).first()
|
209
|
+
if not dbdldomain:
|
210
|
+
dbdldomain = Domain(domain=dl_domain) # type: ignore
|
211
|
+
db.session.add(dbdldomain)
|
212
|
+
db.session.commit()
|
213
|
+
dbdldomain=Domain.query.filter(Domain.domain == dl_domain).first()
|
214
|
+
assert dbdldomain
|
215
|
+
dbdomain.download_domain_id=dbdldomain.id
|
191
216
|
if commit:
|
192
217
|
db.session.commit()
|
193
218
|
|
hiddifypanel/models/proxy.py
CHANGED
@@ -5,12 +5,13 @@ from sqlalchemy import Column, String, Integer, Boolean, Enum, ForeignKey
|
|
5
5
|
|
6
6
|
from hiddifypanel.database import db
|
7
7
|
|
8
|
+
from sqlalchemy.types import JSON
|
8
9
|
|
9
10
|
|
10
11
|
class ProxyTransport(StrEnum):
|
11
12
|
h2 = auto()
|
12
13
|
grpc = auto()
|
13
|
-
XTLS = auto()
|
14
|
+
# XTLS = auto()
|
14
15
|
faketls = auto()
|
15
16
|
shadowtls = auto()
|
16
17
|
restls1_2 = auto()
|
@@ -68,6 +69,7 @@ class Proxy(db.Model): # type: ignore
|
|
68
69
|
l3 = Column(Enum(ProxyL3), nullable=False)
|
69
70
|
transport = Column(Enum(ProxyTransport), nullable=False)
|
70
71
|
cdn = Column(Enum(ProxyCDN), nullable=False)
|
72
|
+
params = Column(JSON,default={})
|
71
73
|
|
72
74
|
@property
|
73
75
|
def enabled(self):
|
@@ -81,7 +83,8 @@ class Proxy(db.Model): # type: ignore
|
|
81
83
|
'l3': self.l3,
|
82
84
|
'transport': self.transport,
|
83
85
|
'cdn': self.cdn,
|
84
|
-
'child_unique_id': self.child.unique_id if self.child else ''
|
86
|
+
'child_unique_id': self.child.unique_id if self.child else '',
|
87
|
+
'params': self.params
|
85
88
|
}
|
86
89
|
|
87
90
|
def __str__(self):
|
@@ -101,6 +104,7 @@ class Proxy(db.Model): # type: ignore
|
|
101
104
|
dbproxy.transport = proxy['transport']
|
102
105
|
dbproxy.cdn = proxy['cdn']
|
103
106
|
dbproxy.l3 = proxy['l3']
|
107
|
+
dbproxy.params=proxy['params']
|
104
108
|
dbproxy.child_id = child_id
|
105
109
|
if commit:
|
106
110
|
db.session.commit() # type: ignore
|
@@ -46,39 +46,31 @@ class DomainAdmin(AdminLTEModelView):
|
|
46
46
|
domain=_("domain.description"),
|
47
47
|
mode=_("Direct mode means you want to use your server directly (for usual use), CDN means that you use your server on behind of a CDN provider."),
|
48
48
|
cdn_ip=_("config.cdn_forced_host.description"),
|
49
|
-
show_domains=_(
|
50
|
-
'domain.show_domains_description'),
|
49
|
+
show_domains=_('domain.show_domains_description'),
|
51
50
|
alias=_('The name shown in the configs for this domain.'),
|
52
51
|
servernames=_('config.reality_server_names.description'),
|
53
52
|
sub_link_only=_('This can be used for giving your users a permanent non blockable links.'),
|
54
|
-
grpc=_('grpc-proxy.description')
|
55
|
-
|
53
|
+
grpc=_('grpc-proxy.description'),
|
54
|
+
download_domain=_('download_domain.description')
|
55
|
+
)
|
56
56
|
# create_modal = True
|
57
57
|
can_export = False
|
58
|
-
form_widget_args = {'show_domains': {'class': 'form-control ltr'}}
|
58
|
+
form_widget_args = {'show_domains': {'class': 'form-control ltr'},'download_domain': {'class': 'form-control ltr'}}
|
59
|
+
|
59
60
|
form_args = {
|
60
|
-
'mode': {
|
61
|
-
'enum': DomainType},
|
61
|
+
'mode': {'enum': DomainType},
|
62
62
|
'show_domains': {
|
63
|
-
'query_factory': lambda: Domain.query.filter(
|
64
|
-
Domain.sub_link_only == False),
|
63
|
+
'query_factory': lambda: Domain.query.filter( Domain.sub_link_only == False),
|
65
64
|
},
|
66
65
|
'domain': {
|
67
66
|
'validators': [
|
68
|
-
Regexp(
|
69
|
-
r'^(\*\.)?([A-Za-z0-9\-\.]+\.[a-zA-Z]{2,})$|^$',
|
70
|
-
message=__("Should be a valid domain"))]},
|
67
|
+
Regexp(r'^(\*\.)?([A-Za-z0-9\-\.]+\.[a-zA-Z]{2,})$|^$',message=__("Should be a valid domain"))]},
|
71
68
|
"cdn_ip": {
|
72
69
|
'validators': [
|
73
|
-
Regexp(
|
74
|
-
r"(((((25[0-5]|(2[0-4]|1\d|[1-9]|)\d).){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d))|^([A-Za-z0-9\-\.]+\.[a-zA-Z]{2,}))[ \t\n,;]*\w{3}[ \t\n,;]*)*",
|
75
|
-
message=__("Invalid IP or domain"))]},
|
70
|
+
Regexp(r"(((((25[0-5]|(2[0-4]|1\d|[1-9]|)\d).){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d))|^([A-Za-z0-9\-\.]+\.[a-zA-Z]{2,}))[ \t\n,;]*\w{3}[ \t\n,;]*)*",message=__("Invalid IP or domain"))]},
|
76
71
|
"servernames": {
|
77
72
|
'validators': [
|
78
|
-
Regexp(
|
79
|
-
r"^([\w-]+\.)+[\w-]+(,\s*([\w-]+\.)+[\w-]+)*$",
|
80
|
-
re.IGNORECASE,
|
81
|
-
_("Invalid REALITY hostnames"))]}}
|
73
|
+
Regexp(r"^([\w-]+\.)+[\w-]+(,\s*([\w-]+\.)+[\w-]+)*$",re.IGNORECASE,_("Invalid REALITY hostnames"))]}}
|
82
74
|
column_list = ["domain", "alias", "mode", "domain_ip", "show_domains"]
|
83
75
|
column_editable_list = ["alias"]
|
84
76
|
# column_filters=["domain","mode"]
|
@@ -93,10 +85,11 @@ class DomainAdmin(AdminLTEModelView):
|
|
93
85
|
'servernames': _('config.reality_server_names.label'),
|
94
86
|
'show_domains': _('Show Domains'),
|
95
87
|
'alias': _('Alias'),
|
96
|
-
'grpc': _('gRPC')
|
88
|
+
'grpc': _('gRPC'),
|
89
|
+
"download_domain":_('Download Domain')
|
97
90
|
}
|
98
91
|
|
99
|
-
form_columns = ['mode', 'domain',
|
92
|
+
form_columns = ['mode', 'domain','download_domain', 'alias', 'servernames', 'cdn_ip', 'show_domains']
|
100
93
|
|
101
94
|
def _domain_admin_link(view, context, model, name):
|
102
95
|
if model.mode == DomainType.fake:
|
@@ -155,7 +148,9 @@ class DomainAdmin(AdminLTEModelView):
|
|
155
148
|
def on_model_change(self, form, model, is_created):
|
156
149
|
# Sanitize domain input
|
157
150
|
model.domain = (model.domain or '').lower().strip()
|
158
|
-
|
151
|
+
if model.domain==model.download_domain.domain:
|
152
|
+
model.download_domain_id=None
|
153
|
+
model.download_domain=None
|
159
154
|
# Basic validation
|
160
155
|
if model.domain == '' and model.mode != DomainType.fake:
|
161
156
|
raise ValidationError(_("domain.empty.allowed_for_fake_only"))
|
@@ -199,7 +194,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
199
194
|
if model.mode == DomainType.old_xtls_direct and not hconfig(ConfigEnum.xtls_enable):
|
200
195
|
set_hconfig(ConfigEnum.xtls_enable, True)
|
201
196
|
hutils.proxy.get_proxies().invalidate_all()
|
202
|
-
elif model.mode
|
197
|
+
elif "reality" in model.mode:
|
203
198
|
self._validate_reality_settings(model, server_ips)
|
204
199
|
|
205
200
|
# Signal config update if needed
|
@@ -267,7 +262,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
267
262
|
if model.domain == configs[c]:
|
268
263
|
raise ValidationError(_("You have used this domain in: ") + _(f"config.{c}.label"))
|
269
264
|
|
270
|
-
for td in Domain.query.filter(Domain.mode == DomainType.reality, Domain.domain != model.domain).all():
|
265
|
+
for td in Domain.query.filter(Domain.mode._in([DomainType.reality,DomainType.special_reality_xhttp,DomainType.special_reality_grpc,DomainType.special_reality_tcp]) == DomainType.reality, Domain.domain != model.domain).all():
|
271
266
|
# print(td)
|
272
267
|
if td.servernames and (model.domain in td.servernames.split(",")):
|
273
268
|
raise ValidationError(_("You have used this domain in: ") + _(f"config.reality_server_names.label") + td.domain)
|
@@ -283,6 +278,8 @@ class DomainAdmin(AdminLTEModelView):
|
|
283
278
|
return True
|
284
279
|
if model.mode in [DomainType.fake, DomainType.reality, DomainType.relay]:
|
285
280
|
return True
|
281
|
+
if "special" in model.mode:
|
282
|
+
return True
|
286
283
|
# Resolve domain IPs with timeout
|
287
284
|
try:
|
288
285
|
dips = hutils.network.get_domain_ips(model.domain)
|
@@ -319,7 +316,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
319
316
|
def on_model_delete(self, model):
|
320
317
|
if len(Domain.query.all()) <= 1:
|
321
318
|
raise ValidationError(f"at least one domain should exist")
|
322
|
-
if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.reality, DomainType.relay]:
|
319
|
+
if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.reality, DomainType.relay] and "special" not in model.mode:
|
323
320
|
if not hutils.network.cf_api.delete_dns_record(model.domain):
|
324
321
|
hutils.flask.flash(_('cf-delete.failed'), 'warning') # type: ignore
|
325
322
|
model.showed_by_domains = []
|
@@ -8,11 +8,13 @@ from hiddifypanel.auth import login_required
|
|
8
8
|
from flask_admin.actions import action
|
9
9
|
from flask_admin.contrib.sqla import form, filters as sqla_filters, tools
|
10
10
|
from flask_admin import expose
|
11
|
-
|
11
|
+
from hiddifypanel.panel import custom_widgets
|
12
12
|
|
13
13
|
# Define a custom field type for the related domains
|
14
14
|
from hiddifypanel import hutils
|
15
15
|
|
16
|
+
from wtforms.widgets import TextArea
|
17
|
+
import json
|
16
18
|
|
17
19
|
class ProxyDetailsAdmin(AdminLTEModelView):
|
18
20
|
list_template = 'model/proxydetail_list.html'
|
@@ -22,7 +24,12 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
22
24
|
column_exclude_list = ['child']
|
23
25
|
column_searchable_list = ['name', 'proto', 'transport', 'l3', 'cdn']
|
24
26
|
column_editable_list = ['name']
|
25
|
-
|
27
|
+
form_extra_fields = {
|
28
|
+
'params': custom_widgets.JSONField(label='Proxy Params')
|
29
|
+
}
|
30
|
+
form_overrides={
|
31
|
+
'params': custom_widgets.JSONField
|
32
|
+
}
|
26
33
|
@expose('reset_proxies')
|
27
34
|
def reset_proxies(self):
|
28
35
|
from hiddifypanel.panel.init_db import get_proxy_rows_v1
|
@@ -70,7 +77,8 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
70
77
|
if login_required(roles={Role.super_admin, Role.admin})(lambda: True)() != True:
|
71
78
|
return False
|
72
79
|
return True
|
73
|
-
|
80
|
+
def _params_formatter(view, context, model, name):
|
81
|
+
return str(model.params)
|
74
82
|
def _enable_formatter(view, context, model, name):
|
75
83
|
if model.enable:
|
76
84
|
link = '<i class="fa-solid fa-circle-check text-success"></i> '
|
@@ -79,5 +87,6 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
79
87
|
return Markup(link)
|
80
88
|
column_formatters = {
|
81
89
|
|
82
|
-
"enable": _enable_formatter
|
90
|
+
"enable": _enable_formatter,
|
91
|
+
"params": _params_formatter
|
83
92
|
}
|
@@ -130,7 +130,7 @@ class AllConfigsAPI(MethodView):
|
|
130
130
|
items.append(
|
131
131
|
create_item(
|
132
132
|
pinfo["name"].replace("_", " "),
|
133
|
-
f"{
|
133
|
+
f"{pinfo['mode']}",
|
134
134
|
pinfo['server'],
|
135
135
|
pinfo['proto'],
|
136
136
|
pinfo['transport'],
|
@@ -6,6 +6,11 @@ from wtforms import TextAreaField
|
|
6
6
|
from wtforms.fields import IntegerField, SelectField, DecimalField
|
7
7
|
from wtforms.widgets import TextArea
|
8
8
|
|
9
|
+
from wtforms import Field
|
10
|
+
from wtforms.validators import ValidationError
|
11
|
+
from wtforms.widgets import TextArea
|
12
|
+
import json
|
13
|
+
|
9
14
|
from hiddifypanel.models import *
|
10
15
|
|
11
16
|
|
@@ -89,3 +94,43 @@ class UsageField(DecimalField):
|
|
89
94
|
self.data = int(float(valuelist[0]) * ONE_GIG)
|
90
95
|
else:
|
91
96
|
self.data = None
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
class JSONWidget(TextArea):
|
105
|
+
def __call__(self, field, **kwargs):
|
106
|
+
if isinstance(field.data, dict):
|
107
|
+
try:
|
108
|
+
field.data = json.dumps(field.data, indent=2)
|
109
|
+
except Exception:
|
110
|
+
pass
|
111
|
+
if kwargs.get('class'):
|
112
|
+
kwargs['class'] += ' ltr json-editor'
|
113
|
+
else:
|
114
|
+
kwargs.setdefault('class', 'ltr json-editor')
|
115
|
+
|
116
|
+
return super().__call__(field, **kwargs)
|
117
|
+
|
118
|
+
class JSONField(Field):
|
119
|
+
widget = JSONWidget()
|
120
|
+
|
121
|
+
def _value(self):
|
122
|
+
if self.data is None:
|
123
|
+
return ''
|
124
|
+
if isinstance(self.data, str):
|
125
|
+
return self.data
|
126
|
+
try:
|
127
|
+
return json.dumps(self.data, indent=2)
|
128
|
+
except Exception:
|
129
|
+
return str(self.data)
|
130
|
+
|
131
|
+
def process_formdata(self, valuelist):
|
132
|
+
if valuelist:
|
133
|
+
try:
|
134
|
+
self.data = json.loads(valuelist[0])
|
135
|
+
except Exception as e:
|
136
|
+
raise ValidationError(f'Invalid JSON: {e}')
|