hiddifypanel 10.10.20__py3-none-any.whl → 10.11.1__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/__init__.py +3 -0
- hiddifypanel/hutils/crypto.py +29 -0
- hiddifypanel/hutils/encode.py +4 -0
- hiddifypanel/hutils/flask.py +4 -0
- hiddifypanel/hutils/github_issue.py +1 -1
- hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
- hiddifypanel/hutils/network/net.py +46 -2
- hiddifypanel/hutils/proxy/__init__.py +4 -0
- hiddifypanel/hutils/proxy/clash.py +161 -0
- hiddifypanel/hutils/proxy/shared.py +414 -0
- hiddifypanel/hutils/proxy/singbox.py +338 -0
- hiddifypanel/hutils/proxy/xray.py +561 -0
- hiddifypanel/models/admin.py +16 -14
- hiddifypanel/models/base_account.py +4 -4
- hiddifypanel/models/config.py +1 -1
- hiddifypanel/models/proxy.py +17 -17
- hiddifypanel/models/user.py +15 -13
- hiddifypanel/panel/admin/AdminstratorAdmin.py +1 -1
- hiddifypanel/panel/admin/DomainAdmin.py +13 -27
- hiddifypanel/panel/admin/ProxyAdmin.py +3 -3
- hiddifypanel/panel/admin/SettingAdmin.py +22 -11
- hiddifypanel/panel/admin/UserAdmin.py +9 -7
- hiddifypanel/panel/cf_api.py +1 -2
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +5 -6
- hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +4 -7
- hiddifypanel/panel/common.py +5 -3
- hiddifypanel/panel/hiddify.py +0 -90
- hiddifypanel/panel/init_db.py +14 -9
- hiddifypanel/panel/user/__init__.py +0 -1
- hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
- hiddifypanel/panel/user/templates/all_configs.txt +2 -2
- hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
- hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
- hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
- hiddifypanel/panel/user/templates/clash_config.yml +4 -4
- hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
- hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
- hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
- hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
- hiddifypanel/panel/user/user.py +50 -44
- hiddifypanel/templates/admin-layout.html +16 -24
- hiddifypanel/templates/fake.html +0 -276
- hiddifypanel/templates/flaskadmin-layout.html +2 -1
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +16 -9
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +12 -6
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +9 -4
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +12 -7
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +9 -4
- hiddifypanel/translations.i18n/en.json +8 -7
- hiddifypanel/translations.i18n/fa.json +5 -4
- hiddifypanel/translations.i18n/pt.json +3 -2
- hiddifypanel/translations.i18n/ru.json +6 -5
- hiddifypanel/translations.i18n/zh.json +3 -2
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/METADATA +1 -1
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/RECORD +67 -61
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/WHEEL +1 -1
- hiddifypanel/panel/user/link_maker.py +0 -1089
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/top_level.txt +0 -0
hiddifypanel/models/proxy.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from enum import auto
|
2
|
-
|
3
1
|
from sqlalchemy_serializer import SerializerMixin
|
4
2
|
from strenum import StrEnum
|
3
|
+
from enum import auto
|
4
|
+
from sqlalchemy import Column, String, Integer, Boolean, Enum, ForeignKey
|
5
5
|
|
6
6
|
from hiddifypanel.database import db
|
7
7
|
|
@@ -57,15 +57,15 @@ class ProxyL3(StrEnum):
|
|
57
57
|
custom = auto()
|
58
58
|
|
59
59
|
|
60
|
-
class Proxy(db.Model, SerializerMixin):
|
61
|
-
id =
|
62
|
-
child_id =
|
63
|
-
name =
|
64
|
-
enable =
|
65
|
-
proto =
|
66
|
-
l3 =
|
67
|
-
transport =
|
68
|
-
cdn =
|
60
|
+
class Proxy(db.Model, SerializerMixin): # type: ignore
|
61
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
62
|
+
child_id = Column(Integer, ForeignKey('child.id'), default=0)
|
63
|
+
name = Column(String(200), nullable=False, unique=False)
|
64
|
+
enable = Column(Boolean, nullable=False)
|
65
|
+
proto = Column(Enum(ProxyProto), nullable=False)
|
66
|
+
l3 = Column(Enum(ProxyL3), nullable=False)
|
67
|
+
transport = Column(Enum(ProxyTransport), nullable=False)
|
68
|
+
cdn = Column(Enum(ProxyCDN), nullable=False)
|
69
69
|
|
70
70
|
@property
|
71
71
|
def enabled(self):
|
@@ -82,12 +82,15 @@ class Proxy(db.Model, SerializerMixin):
|
|
82
82
|
'child_unique_id': self.child.unique_id if self.child else ''
|
83
83
|
}
|
84
84
|
|
85
|
+
def __str__(self):
|
86
|
+
return str(self.to_dict())
|
87
|
+
|
85
88
|
@staticmethod
|
86
89
|
def add_or_update(commit=True, child_id=0, **proxy):
|
87
90
|
dbproxy = Proxy.query.filter(Proxy.name == proxy['name']).first()
|
88
91
|
if not dbproxy:
|
89
92
|
dbproxy = Proxy()
|
90
|
-
db.session.add(dbproxy)
|
93
|
+
db.session.add(dbproxy) # type: ignore
|
91
94
|
dbproxy.enable = proxy['enable']
|
92
95
|
dbproxy.name = proxy['name']
|
93
96
|
dbproxy.proto = proxy['proto']
|
@@ -96,7 +99,7 @@ class Proxy(db.Model, SerializerMixin):
|
|
96
99
|
dbproxy.l3 = proxy['l3']
|
97
100
|
dbproxy.child_id = child_id
|
98
101
|
if commit:
|
99
|
-
db.session.commit()
|
102
|
+
db.session.commit() # type: ignore
|
100
103
|
|
101
104
|
@staticmethod
|
102
105
|
def bulk_register(proxies, commit=True, override_child_unique_id=None):
|
@@ -105,7 +108,4 @@ class Proxy(db.Model, SerializerMixin):
|
|
105
108
|
child_id = hiddify.get_child(unique_id=None)
|
106
109
|
Proxy.add_or_update(commit=False, child_id=child_id, **proxy)
|
107
110
|
if commit:
|
108
|
-
db.session.commit()
|
109
|
-
|
110
|
-
def __str__(self):
|
111
|
-
return str(self.to_dict())
|
111
|
+
db.session.commit() # type: ignore
|
hiddifypanel/models/user.py
CHANGED
@@ -8,7 +8,6 @@ from sqlalchemy import event
|
|
8
8
|
|
9
9
|
from hiddifypanel.database import db
|
10
10
|
from hiddifypanel.models import Lang
|
11
|
-
from hiddifypanel.models.utils import fill_password, fill_username
|
12
11
|
from hiddifypanel.models.base_account import BaseAccount
|
13
12
|
from hiddifypanel.models.admin import AdminUser
|
14
13
|
|
@@ -208,7 +207,7 @@ class User(BaseAccount):
|
|
208
207
|
from hiddifypanel import hutils
|
209
208
|
dbuser = super().add_or_update(commit=commit, **data)
|
210
209
|
if data.get('added_by_uuid'):
|
211
|
-
admin = AdminUser.by_uuid(data.get('added_by_uuid'), create=True) or AdminUser.current_admin_or_owner()
|
210
|
+
admin = AdminUser.by_uuid(data.get('added_by_uuid'), create=True) or AdminUser.current_admin_or_owner() # type: ignore
|
212
211
|
dbuser.added_by = admin.id
|
213
212
|
else:
|
214
213
|
dbuser.added_by = 1
|
@@ -233,17 +232,17 @@ class User(BaseAccount):
|
|
233
232
|
if data.get('ed25519_private_key', ''):
|
234
233
|
dbuser.ed25519_private_key = data.get('ed25519_private_key', '')
|
235
234
|
dbuser.ed25519_public_key = data.get('ed25519_public_key', '')
|
236
|
-
if not dbuser.ed25519_private_key:
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
235
|
+
# if not dbuser.ed25519_private_key:
|
236
|
+
# priv, publ = hutils.crypto.get_ed25519_private_public_pair()
|
237
|
+
# dbuser.ed25519_private_key = priv
|
238
|
+
# dbuser.ed25519_public_key = publ
|
239
|
+
|
241
240
|
dbuser.wg_pk = data.get('wg_pk', dbuser.wg_pk)
|
242
241
|
dbuser.wg_pub = data.get('wg_pub', dbuser.wg_pub)
|
243
242
|
dbuser.wg_psk = data.get('wg_psk', dbuser.wg_psk)
|
244
|
-
|
245
|
-
|
246
|
-
|
243
|
+
|
244
|
+
# if not dbuser.wg_pk:
|
245
|
+
# dbuser.wg_pk, dbuser.wg_pub, dbuser.wg_psk = hutils.crypto.get_wg_private_public_psk_pair()
|
247
246
|
|
248
247
|
mode = data.get('mode', UserMode.no_reset)
|
249
248
|
if mode == 'disable':
|
@@ -270,7 +269,7 @@ class User(BaseAccount):
|
|
270
269
|
'start_date': hutils.convert.date_to_json(self.start_date)if convert_date else self.start_date,
|
271
270
|
'current_usage_GB': self.current_usage_GB,
|
272
271
|
'last_reset_time': hutils.convert.date_to_json(self.last_reset_time) if convert_date else self.last_reset_time,
|
273
|
-
'added_by_uuid': self.admin.uuid,
|
272
|
+
'added_by_uuid': self.admin.uuid if self.admin else None,
|
274
273
|
'ed25519_private_key': self.ed25519_private_key,
|
275
274
|
'ed25519_public_key': self.ed25519_public_key,
|
276
275
|
'wg_pk': self.wg_pk,
|
@@ -302,5 +301,8 @@ class User(BaseAccount):
|
|
302
301
|
|
303
302
|
@event.listens_for(User, 'before_insert')
|
304
303
|
def on_user_insert(mapper, connection, target):
|
305
|
-
|
306
|
-
|
304
|
+
from hiddifypanel import hutils
|
305
|
+
hutils.model.gen_username(target)
|
306
|
+
hutils.model.gen_password(target)
|
307
|
+
hutils.model.gen_ed25519_keys(target)
|
308
|
+
hutils.model.gen_wg_keys(target)
|
@@ -208,7 +208,7 @@ class AdminstratorAdmin(AdminLTEModelView):
|
|
208
208
|
|
209
209
|
if g.account.mode != AdminMode.super_admin and model.mode == AdminMode.super_admin:
|
210
210
|
raise ValidationError("Sub-Admin can not have more power!!!!")
|
211
|
-
if
|
211
|
+
if g.account.mode == AdminMode.agent and model.mode != AdminMode.agent:
|
212
212
|
raise ValidationError("Sub-Admin can not have more power!!!!")
|
213
213
|
|
214
214
|
def on_model_delete(self, model):
|
@@ -199,9 +199,10 @@ class DomainAdmin(AdminLTEModelView):
|
|
199
199
|
raise ValidationError(
|
200
200
|
__("Domain IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode", server_ip=', '.join(list(map(str, ipv4_list))), domain_ip=dip)) # type: ignore
|
201
201
|
|
202
|
-
|
203
|
-
|
204
|
-
|
202
|
+
if domain_ip_is_same_as_panel and model.mode in [DomainType.cdn, DomainType.relay, DomainType.fake, DomainType.auto_cdn_ip]:
|
203
|
+
# # hutils.flask.flash(__(f"In CDN mode, Domain IP={dip} should be different to your ip={', '.join(list(map(str, ipv4_list)))}"), 'warning')
|
204
|
+
raise ValidationError(__("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
|
205
|
+
server_ip=', '.join(list(map(str, ipv4_list))), domain_ip=dip)) # type: ignore
|
205
206
|
|
206
207
|
# if model.mode in [DomainType.ss_faketls, DomainType.telegram_faketls]:
|
207
208
|
# if len(Domain.query.filter(Domain.mode==model.mode and Domain.id!=model.id).all())>0:
|
@@ -217,43 +218,28 @@ class DomainAdmin(AdminLTEModelView):
|
|
217
218
|
# if model.mode==DomainType.fake and model.cdn_ip!=myip:
|
218
219
|
# raise ValidationError(f"Specifying CDN IP is only valid for CDN mode")
|
219
220
|
|
220
|
-
# work_with_ids = form.work_with.data
|
221
|
-
# print(work_with_ids)
|
222
221
|
# # Update the many-to-many relationship
|
223
222
|
if len(model.show_domains) == Domain.query.count():
|
224
223
|
model.show_domains = []
|
225
|
-
# if model.alias and not g.is_commercial:
|
226
|
-
# model.alias= "@hiddify "+model.alias
|
227
|
-
# model.work_with = self.session.query(Domain).filter(
|
228
|
-
# Domain.id.in_(work_with_ids)).all()
|
229
224
|
|
230
225
|
if model.mode == DomainType.reality:
|
231
|
-
model.servernames = (model.domain).lower()
|
232
|
-
if not hutils.network.is_domain_reality_friendly(model.domain):
|
233
|
-
# hutils.flask.flash(_("Domain is not REALITY friendly!")+" "+d,'error')
|
234
|
-
# return render_template('config.html', form=form)
|
235
|
-
raise ValidationError(_("Domain is not REALITY friendly!") + " " + model.domain)
|
236
|
-
|
237
|
-
hiddify.debug_flash_if_not_in_the_same_asn(model.domain)
|
238
|
-
if False:
|
239
226
|
model.servernames = (model.servernames or model.domain).lower()
|
240
|
-
|
241
|
-
for v in [model.domain, model.servernames]:
|
242
|
-
|
227
|
+
for v in set([model.domain, model.servernames]):
|
243
228
|
for d in v.split(","):
|
244
229
|
if not d:
|
245
230
|
continue
|
246
|
-
|
247
231
|
if not hutils.network.is_domain_reality_friendly(d):
|
248
|
-
|
249
|
-
# return render_template('config.html', form=form)
|
250
|
-
raise ValidationError(_("Domain is not REALITY friendly!") + " " + d)
|
232
|
+
raise ValidationError(_("Domain is not REALITY friendly!")+f' {d}')
|
251
233
|
|
252
|
-
|
234
|
+
if not hutils.network.is_in_same_asn(d, ipv4_list[0]):
|
235
|
+
server_asn = hutils.network.get_ip_asn_name(ipv4_list[0])
|
236
|
+
domain_asn = hutils.network.get_ip_asn_name(dip) # type: ignore
|
237
|
+
msg = _("selected domain for REALITY is not in the same ASN. To better use of the protocol, it is better to find a domain in the same ASN.")+(f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}" if server_asn or domain_asn else "")
|
238
|
+
hutils.flask.flash(msg, 'warning')
|
253
239
|
|
254
240
|
for d in model.servernames.split(","):
|
255
|
-
if not
|
256
|
-
raise ValidationError(_("REALITY Fallback domain is not compaitble with server names!")
|
241
|
+
if not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
|
242
|
+
raise ValidationError(_("REALITY Fallback domain is not compaitble with server names!")+f' {d} != {model.domain}')
|
257
243
|
|
258
244
|
if (model.cdn_ip):
|
259
245
|
try:
|
@@ -35,7 +35,7 @@ class ProxyAdmin(FlaskView):
|
|
35
35
|
|
36
36
|
db.session.commit()
|
37
37
|
# print(cat,vs)
|
38
|
-
|
38
|
+
hutils.proxy.get_proxies.invalidate_all()
|
39
39
|
hiddify.check_need_reset(old_configs)
|
40
40
|
all_proxy_form = get_all_proxy_form(True)
|
41
41
|
|
@@ -55,7 +55,7 @@ class ProxyAdmin(FlaskView):
|
|
55
55
|
|
56
56
|
# print(cat,vs)
|
57
57
|
db.session.commit()
|
58
|
-
|
58
|
+
hutils.proxy.get_proxies.invalidate_all()
|
59
59
|
hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=False)
|
60
60
|
# if hconfig(ConfigEnum.parent_panel):
|
61
61
|
# hiddify_api.sync_child_to_parent()
|
@@ -94,7 +94,7 @@ def get_global_config_form(empty=False):
|
|
94
94
|
|
95
95
|
|
96
96
|
def get_all_proxy_form(empty=False):
|
97
|
-
proxies =
|
97
|
+
proxies = hutils.proxy.get_proxies(Child.current.id)
|
98
98
|
categories1 = sorted([c for c in {c.cdn: 1 for c in proxies}])
|
99
99
|
|
100
100
|
class DynamicForm(FlaskForm):
|
@@ -122,7 +122,6 @@ class SettingAdmin(FlaskView):
|
|
122
122
|
|
123
123
|
|
124
124
|
def get_config_form():
|
125
|
-
|
126
125
|
strconfigs = StrConfig.query.filter(StrConfig.child_id == Child.current.id).all()
|
127
126
|
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current.id).all()
|
128
127
|
bool_types = {c.key: 'bool' for c in boolconfigs}
|
@@ -154,7 +153,8 @@ def get_config_form():
|
|
154
153
|
if c.key in bool_types:
|
155
154
|
field = SwitchField(_(f'config.{c.key}.label'), default=c.value, description=_(f'config.{c.key}.description'))
|
156
155
|
elif c.key == ConfigEnum.core_type:
|
157
|
-
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("xray", _("Xray")), ("singbox", _(
|
156
|
+
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("xray", _("Xray")), ("singbox", _(
|
157
|
+
"SingBox"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
158
158
|
elif c.key == ConfigEnum.warp_mode:
|
159
159
|
field = wtf.SelectField(
|
160
160
|
_(f"config.{c.key}.label"), choices=[("disable", _("Disable")), ("all", _("All")), ("custom", _("Only Blocked and Local websites"))],
|
@@ -168,7 +168,8 @@ def get_config_form():
|
|
168
168
|
description=_(f"config.{c.key}.description"),
|
169
169
|
default=hconfig(c.key))
|
170
170
|
elif c.key == ConfigEnum.country:
|
171
|
-
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("ir", _("Iran")), ("zh", _(
|
171
|
+
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("ir", _("Iran")), ("zh", _(
|
172
|
+
"China")), ("other", _("Others"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
172
173
|
elif c.key == ConfigEnum.package_mode:
|
173
174
|
package_modes = [("release", _("Release")), ("beta", _("Beta"))]
|
174
175
|
if hconfig(c.key) == "develop":
|
@@ -193,9 +194,14 @@ def get_config_form():
|
|
193
194
|
elif c.key == ConfigEnum.telegram_lib:
|
194
195
|
# if hconfig(ConfigEnum.telegram_lib)=='python':
|
195
196
|
# continue6
|
196
|
-
libs = [
|
197
|
-
|
198
|
-
|
197
|
+
libs = [
|
198
|
+
("erlang", _("lib.telegram.erlang")),
|
199
|
+
("python", _("lib.telegram.python")),
|
200
|
+
("tgo", _("lib.telegram.go")),
|
201
|
+
# ("orig", _("lib.telegram.orignal")),
|
202
|
+
]
|
203
|
+
field = wtf.SelectField(_("config.telegram_lib.label"), choices=libs, description=_(
|
204
|
+
"config.telegram_lib.description"), default=hconfig(ConfigEnum.telegram_lib))
|
199
205
|
elif c.key == ConfigEnum.mux_protocol:
|
200
206
|
choices = [("smux", 'smux'), ("yamux", "yamux"), ("h2mux", "h2mux")]
|
201
207
|
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=choices, description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
@@ -203,11 +209,13 @@ def get_config_form():
|
|
203
209
|
elif c.key == ConfigEnum.warp_sites:
|
204
210
|
validators = [wtf.validators.Length(max=2048)]
|
205
211
|
render_kw = {'class': "ltr", 'maxlength': 2048}
|
206
|
-
field = wtf.TextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
212
|
+
field = wtf.TextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
213
|
+
description=_(f'config.{c.key}.description'), render_kw=render_kw)
|
207
214
|
elif c.key == ConfigEnum.branding_freetext:
|
208
215
|
validators = [wtf.validators.Length(max=2048)]
|
209
216
|
render_kw = {'class': "ltr", 'maxlength': 2048}
|
210
|
-
field = custom_widgets.CKTextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
217
|
+
field = custom_widgets.CKTextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
218
|
+
description=_(f'config.{c.key}.description'), render_kw=render_kw)
|
211
219
|
else:
|
212
220
|
render_kw = {'class': "ltr"}
|
213
221
|
validators = []
|
@@ -239,11 +247,13 @@ def get_config_form():
|
|
239
247
|
if c.key == ConfigEnum.telegram_bot_token:
|
240
248
|
validators.append(wtf.validators.Regexp("()|^([0-9]{8,12}:[a-zA-Z0-9_-]{30,40})|$", re.IGNORECASE, _("config.Invalid telegram bot token")))
|
241
249
|
if c.key == ConfigEnum.branding_site:
|
242
|
-
validators.append(wtf.validators.Regexp(
|
250
|
+
validators.append(wtf.validators.Regexp(
|
251
|
+
"()|(http(s|)://([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})/?.*)", re.IGNORECASE, _("config.Invalid brand link")))
|
243
252
|
# render_kw['required']=""
|
244
253
|
|
245
254
|
if 'secret' in c.key:
|
246
|
-
validators.append(wtf.validators.Regexp(
|
255
|
+
validators.append(wtf.validators.Regexp(
|
256
|
+
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", re.IGNORECASE, _('config.invalid uuid')))
|
247
257
|
render_kw['required'] = ""
|
248
258
|
|
249
259
|
if c.key == ConfigEnum.proxy_path:
|
@@ -274,7 +284,8 @@ def get_config_form():
|
|
274
284
|
if c.key == ConfigEnum.reality_public_key and g.account.mode in [AdminMode.super_admin]:
|
275
285
|
extra_info = f" <a href='{hurl_for('admin.Actions:change_reality_keys')}'>{_('Change')}</a>"
|
276
286
|
|
277
|
-
field = wtf.StringField(_(f'config.{c.key}.label'), validators, default=c.value,
|
287
|
+
field = wtf.StringField(_(f'config.{c.key}.label'), validators, default=c.value,
|
288
|
+
description=_(f'config.{c.key}.description') + extra_info, render_kw=render_kw)
|
278
289
|
setattr(CategoryForm, f'{c.key}', field)
|
279
290
|
|
280
291
|
multifield = wtf.FormField(CategoryForm, Markup('<i class="fa-solid fa-plus"></i> ' + _(f'config.{cat}.label')))
|
@@ -271,14 +271,16 @@ class UserAdmin(AdminLTEModelView):
|
|
271
271
|
active=g.account.max_active_users, total=g.account.max_users))
|
272
272
|
if old_user and old_user.uuid != model.uuid:
|
273
273
|
user_driver.remove_client(old_user)
|
274
|
-
if not model.ed25519_private_key:
|
275
|
-
priv, publ = hiddify.get_ed25519_private_public_pair()
|
276
|
-
model.ed25519_private_key = priv
|
277
|
-
model.ed25519_public_key = publ
|
278
|
-
if not model.wg_pk:
|
279
|
-
model.wg_pk, model.wg_pub, model.wg_psk = hiddify.get_wg_private_public_psk_pair()
|
280
|
-
# model.expiry_time=datetime.date.today()+datetime.timedelta(days=model.expiry_time)
|
281
274
|
|
275
|
+
# generated automatically
|
276
|
+
# if not model.ed25519_private_key:
|
277
|
+
# priv, publ = hutils.crypto.get_ed25519_private_public_pair()
|
278
|
+
# model.ed25519_private_key = priv
|
279
|
+
# model.ed25519_public_key = publ
|
280
|
+
# if not model.wg_pk:
|
281
|
+
# model.wg_pk, model.wg_pub, model.wg_psk = hutils.crypto.get_wg_private_public_psk_pair()
|
282
|
+
|
283
|
+
# model.expiry_time=datetime.date.today()+datetime.timedelta(days=model.expiry_time)
|
282
284
|
# if model.current_usage_GB < model.usage_limit_GB:
|
283
285
|
# xray_api.add_client(model.uuid)
|
284
286
|
# else:
|
hiddifypanel/panel/cf_api.py
CHANGED
@@ -2,7 +2,6 @@ from hiddifypanel.models import *
|
|
2
2
|
from hiddifypanel.panel.admin.adminlte import AdminLTEModelView
|
3
3
|
from flask_babel import gettext as __
|
4
4
|
from flask_babel import lazy_gettext as _
|
5
|
-
from hiddifypanel.panel import hiddify
|
6
5
|
from flask import g, redirect, Markup
|
7
6
|
from hiddifypanel.hutils.flask import hurl_for, flash
|
8
7
|
from hiddifypanel.auth import login_required
|
@@ -10,8 +9,8 @@ from flask_admin.model.template import EndpointLinkRowAction
|
|
10
9
|
from flask_admin.actions import action
|
11
10
|
from flask_admin.contrib.sqla import form, filters as sqla_filters, tools
|
12
11
|
# Define a custom field type for the related domains
|
13
|
-
|
14
12
|
from flask import current_app
|
13
|
+
from hiddifypanel import hutils
|
15
14
|
|
16
15
|
|
17
16
|
class ProxyDetailsAdmin(AdminLTEModelView):
|
@@ -30,7 +29,7 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
30
29
|
|
31
30
|
self.session.commit()
|
32
31
|
flash(_('%(count)s records were successfully disabled.', count=count), 'success')
|
33
|
-
|
32
|
+
hutils.proxy.get_proxies.invalidate_all()
|
34
33
|
|
35
34
|
@action('enable', 'Enable', 'Are you sure you want to enable selected proxies?')
|
36
35
|
def action_enable(self, ids):
|
@@ -39,7 +38,7 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
39
38
|
|
40
39
|
self.session.commit()
|
41
40
|
flash(_('%(count)s records were successfully enabled.', count=count), 'success')
|
42
|
-
|
41
|
+
hutils.proxy.get_proxies.invalidate_all()
|
43
42
|
|
44
43
|
# list_template = 'model/domain_list.html'
|
45
44
|
|
@@ -48,13 +47,13 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
48
47
|
def after_model_change(self, form, model, is_created):
|
49
48
|
# if hconfig(ConfigEnum.parent_panel):
|
50
49
|
# hiddify_api.sync_child_to_parent()
|
51
|
-
|
50
|
+
hutils.proxy.get_proxies.invalidate_all()
|
52
51
|
pass
|
53
52
|
|
54
53
|
def after_model_delete(self, model):
|
55
54
|
# if hconfig(ConfigEnum.parent_panel):
|
56
55
|
# hiddify_api.sync_child_to_parent()
|
57
|
-
|
56
|
+
hutils.proxy.get_proxies.invalidate_all()
|
58
57
|
pass
|
59
58
|
|
60
59
|
def is_accessible(self):
|
@@ -357,7 +357,7 @@ class AppAPI(MethodView):
|
|
357
357
|
ins_url = latest_url.split('releases/')[0] + f'releases/download/{version}/hiddify-clash-desktop_{version}_amd64.AppImage'
|
358
358
|
dto.install.append(self.__get_app_install_dto(AppInstallType.appimage, ins_url))
|
359
359
|
case Platform.mac:
|
360
|
-
ins_url = latest_url.split('releases/')[0] + f'releases/download/{version}/HiddifyClashDesktop_{version}
|
360
|
+
ins_url = latest_url.split('releases/')[0] + f'releases/download/{version}/HiddifyClashDesktop_{version}_x64.dmg'
|
361
361
|
dto.install.append(self.__get_app_install_dto(AppInstallType.dmg, ins_url))
|
362
362
|
else:
|
363
363
|
match platform:
|
@@ -3,13 +3,10 @@ from flask import g, request
|
|
3
3
|
from flask import current_app as app
|
4
4
|
from flask.views import MethodView
|
5
5
|
from hiddifypanel.auth import login_required
|
6
|
-
from hiddifypanel.models
|
7
|
-
from hiddifypanel.models.config_enum import ConfigEnum
|
8
|
-
from hiddifypanel.models.role import Role
|
6
|
+
from hiddifypanel.models import Proxy, Role, ConfigEnum, hconfig
|
9
7
|
from apiflask import Schema
|
10
8
|
from apiflask.fields import String
|
11
9
|
from hiddifypanel.panel.user.user import get_common_data
|
12
|
-
from hiddifypanel.panel.user import link_maker
|
13
10
|
from hiddifypanel import hutils
|
14
11
|
|
15
12
|
|
@@ -26,7 +23,7 @@ class ConfigSchema(Schema):
|
|
26
23
|
class AllConfigsAPI(MethodView):
|
27
24
|
decorators = [login_required({Role.user})]
|
28
25
|
|
29
|
-
@app.output(ConfigSchema(many=True))
|
26
|
+
@app.output(ConfigSchema(many=True)) # type: ignore
|
30
27
|
def get(self):
|
31
28
|
def create_item(name, domain, type, protocol, transport, security, link):
|
32
29
|
dto = ConfigSchema()
|
@@ -108,7 +105,7 @@ class AllConfigsAPI(MethodView):
|
|
108
105
|
)
|
109
106
|
)
|
110
107
|
|
111
|
-
for pinfo in
|
108
|
+
for pinfo in hutils.proxy.get_valid_proxies(c['domains']):
|
112
109
|
items.append(
|
113
110
|
create_item(
|
114
111
|
pinfo["name"].replace("_", " "),
|
@@ -117,7 +114,7 @@ class AllConfigsAPI(MethodView):
|
|
117
114
|
pinfo['proto'],
|
118
115
|
pinfo['transport'],
|
119
116
|
pinfo['l3'],
|
120
|
-
f"{
|
117
|
+
f"{hutils.proxy.xray.to_link(pinfo)}"
|
121
118
|
)
|
122
119
|
)
|
123
120
|
|
hiddifypanel/panel/common.py
CHANGED
@@ -9,6 +9,7 @@ from hiddifypanel import hutils
|
|
9
9
|
import hiddifypanel.auth as auth
|
10
10
|
from hiddifypanel.auth import current_account
|
11
11
|
from apiflask import APIFlask, HTTPError, abort
|
12
|
+
from hiddifypanel import hutils
|
12
13
|
|
13
14
|
|
14
15
|
def init_app(app: APIFlask):
|
@@ -21,6 +22,7 @@ def init_app(app: APIFlask):
|
|
21
22
|
app.jinja_env.globals['static_url_for'] = hutils.flask.static_url_for
|
22
23
|
app.jinja_env.globals['hurl_for'] = hutils.flask.hurl_for
|
23
24
|
app.jinja_env.globals['_gettext'] = lambda x: print("==========", x)
|
25
|
+
app.jinja_env.globals['proxy_stats_url'] = hutils.flask.get_proxy_stats_url
|
24
26
|
|
25
27
|
@app.after_request
|
26
28
|
def apply_no_robot(response):
|
@@ -42,7 +44,7 @@ def init_app(app: APIFlask):
|
|
42
44
|
has_update = False
|
43
45
|
else:
|
44
46
|
has_update = "dev" not in hiddifypanel.__version__ and f'{last_version}' != hiddifypanel.__version__
|
45
|
-
|
47
|
+
|
46
48
|
if not request.accept_mimetypes.accept_html:
|
47
49
|
if has_update:
|
48
50
|
return jsonify({
|
@@ -91,11 +93,11 @@ def init_app(app: APIFlask):
|
|
91
93
|
if 'proxy_path' not in values:
|
92
94
|
if force_path := g.get('force_proxy_path'):
|
93
95
|
values['proxy_path'] = force_path
|
94
|
-
elif hutils.flask.is_admin_role(current_account.role):
|
96
|
+
elif hutils.flask.is_admin_role(current_account.role): # type: ignore
|
95
97
|
values['proxy_path'] = hconfig(ConfigEnum.proxy_path_admin)
|
96
98
|
elif hutils.flask.is_user_panel_call():
|
97
99
|
values['proxy_path'] = hconfig(ConfigEnum.proxy_path_client)
|
98
|
-
elif current_account and hutils.flask.is_admin_role(current_account.role):
|
100
|
+
elif current_account and hutils.flask.is_admin_role(current_account.role): # type: ignore
|
99
101
|
values['proxy_path'] = hconfig(ConfigEnum.proxy_path_admin)
|
100
102
|
else:
|
101
103
|
values['proxy_path'] = g.proxy_path or "A"
|
hiddifypanel/panel/hiddify.py
CHANGED
@@ -68,52 +68,6 @@ def exec_command(cmd, cwd=None):
|
|
68
68
|
print(e)
|
69
69
|
|
70
70
|
|
71
|
-
@cache.cache(ttl=300)
|
72
|
-
def get_available_proxies(child_id):
|
73
|
-
proxies = Proxy.query.filter(Proxy.child_id == child_id).all()
|
74
|
-
proxies = [c for c in proxies if 'restls' not in c.transport]
|
75
|
-
# if not hconfig(ConfigEnum.tuic_enable, child_id):
|
76
|
-
# proxies = [c for c in proxies if c.proto != ProxyProto.tuic]
|
77
|
-
# if not hconfig(ConfigEnum.hysteria_enable, child_id):
|
78
|
-
# proxies = [c for c in proxies if c.proto != ProxyProto.hysteria2]
|
79
|
-
if not hconfig(ConfigEnum.shadowsocks2022_enable, child_id):
|
80
|
-
proxies = [c for c in proxies if 'shadowsocks' != c.transport]
|
81
|
-
|
82
|
-
if not hconfig(ConfigEnum.ssfaketls_enable, child_id):
|
83
|
-
proxies = [c for c in proxies if 'faketls' != c.transport]
|
84
|
-
if not hconfig(ConfigEnum.v2ray_enable, child_id):
|
85
|
-
proxies = [c for c in proxies if 'v2ray' != c.proto]
|
86
|
-
if not hconfig(ConfigEnum.shadowtls_enable, child_id):
|
87
|
-
proxies = [c for c in proxies if c.transport != 'shadowtls']
|
88
|
-
if not hconfig(ConfigEnum.ssr_enable, child_id):
|
89
|
-
proxies = [c for c in proxies if 'ssr' != c.proto]
|
90
|
-
if not hconfig(ConfigEnum.vmess_enable, child_id):
|
91
|
-
proxies = [c for c in proxies if 'vmess' not in c.proto]
|
92
|
-
if not hconfig(ConfigEnum.httpupgrade_enable, child_id):
|
93
|
-
proxies = [c for c in proxies if ProxyTransport.httpupgrade not in c.transport]
|
94
|
-
if not hconfig(ConfigEnum.ws_enable, child_id):
|
95
|
-
proxies = [c for c in proxies if ProxyTransport.WS not in c.transport]
|
96
|
-
|
97
|
-
if not hconfig(ConfigEnum.grpc_enable, child_id):
|
98
|
-
proxies = [c for c in proxies if ProxyTransport.grpc not in c.transport]
|
99
|
-
if not hconfig(ConfigEnum.kcp_enable, child_id):
|
100
|
-
proxies = [c for c in proxies if 'kcp' not in c.l3]
|
101
|
-
|
102
|
-
if not hconfig(ConfigEnum.http_proxy_enable, child_id):
|
103
|
-
proxies = [c for c in proxies if 'http' != c.l3]
|
104
|
-
|
105
|
-
if not Domain.query.filter(Domain.mode.in_([DomainType.cdn, DomainType.auto_cdn_ip])).first():
|
106
|
-
proxies = [c for c in proxies if c.cdn != "CDN"]
|
107
|
-
|
108
|
-
if not Domain.query.filter(Domain.mode.in_([DomainType.relay])).first():
|
109
|
-
proxies = [c for c in proxies if c.cdn != ProxyCDN.relay]
|
110
|
-
|
111
|
-
if not Domain.query.filter(Domain.mode.in_([DomainType.cdn, DomainType.auto_cdn_ip]), Domain.servernames != "", Domain.servernames != Domain.domain).first():
|
112
|
-
proxies = [c for c in proxies if 'Fake' not in c.cdn]
|
113
|
-
proxies = [c for c in proxies if not ('vless' == c.proto and ProxyTransport.tcp == c.transport and c.cdn == ProxyCDN.direct)]
|
114
|
-
return proxies
|
115
|
-
|
116
|
-
|
117
71
|
def quick_apply_users():
|
118
72
|
if hconfig(ConfigEnum.is_parent):
|
119
73
|
return
|
@@ -358,54 +312,10 @@ def generate_x25519_keys():
|
|
358
312
|
return {'private_key': priv_str, 'public_key': pub_str}
|
359
313
|
|
360
314
|
|
361
|
-
def get_hostkeys(dojson=False):
|
362
|
-
key_files = glob.glob(current_app.config['HIDDIFY_CONFIG_PATH'] + "/other/ssh/host_key/*_key.pub")
|
363
|
-
host_keys = []
|
364
|
-
for file_name in key_files:
|
365
|
-
with open(file_name, "r") as f:
|
366
|
-
host_key = f.read().strip()
|
367
|
-
host_key = host_key.split()
|
368
|
-
if len(host_key) > 2:
|
369
|
-
host_key = host_key[:2] # strip the hostname part
|
370
|
-
host_key = " ".join(host_key)
|
371
|
-
host_keys.append(host_key)
|
372
|
-
if dojson:
|
373
|
-
return json.dumps(host_keys)
|
374
|
-
return host_keys
|
375
|
-
|
376
|
-
|
377
315
|
def get_ssh_client_version(user):
|
378
316
|
return 'SSH-2.0-OpenSSH_7.4p1'
|
379
317
|
|
380
318
|
|
381
|
-
def get_ed25519_private_public_pair():
|
382
|
-
from cryptography.hazmat.primitives.asymmetric import ed25519
|
383
|
-
from cryptography.hazmat.primitives import serialization
|
384
|
-
privkey = ed25519.Ed25519PrivateKey.generate()
|
385
|
-
pubkey = privkey.public_key()
|
386
|
-
priv_bytes = privkey.private_bytes(
|
387
|
-
encoding=serialization.Encoding.PEM,
|
388
|
-
format=serialization.PrivateFormat.OpenSSH,
|
389
|
-
encryption_algorithm=serialization.NoEncryption(),
|
390
|
-
)
|
391
|
-
pub_bytes = pubkey.public_bytes(
|
392
|
-
encoding=serialization.Encoding.OpenSSH,
|
393
|
-
format=serialization.PublicFormat.OpenSSH,
|
394
|
-
)
|
395
|
-
return priv_bytes.decode(), pub_bytes.decode()
|
396
|
-
|
397
|
-
|
398
|
-
def get_wg_private_public_psk_pair():
|
399
|
-
try:
|
400
|
-
private_key = subprocess.run(["wg", "genkey"], capture_output=True, text=True, check=True).stdout.strip()
|
401
|
-
public_key = subprocess.run(["wg", "pubkey"], input=private_key, capture_output=True, text=True, check=True).stdout.strip()
|
402
|
-
psk = subprocess.run(["wg", "genpsk"], capture_output=True, text=True, check=True).stdout.strip()
|
403
|
-
return private_key, public_key, psk
|
404
|
-
except subprocess.CalledProcessError as e:
|
405
|
-
print(f"Error: {e}")
|
406
|
-
return None, None, None
|
407
|
-
|
408
|
-
|
409
319
|
def get_account_panel_link(account: BaseAccount, host: str, is_https: bool = True, prefere_path_only: bool = False, child_id=None):
|
410
320
|
if child_id is None:
|
411
321
|
child_id = Child.current.id
|