hiddifypanel 10.12.1__py3-none-any.whl → 10.15.0.dev0__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/auth.py +15 -4
- hiddifypanel/base.py +58 -50
- hiddifypanel/cache.py +43 -25
- hiddifypanel/database.py +9 -0
- hiddifypanel/drivers/abstract_driver.py +2 -0
- hiddifypanel/drivers/singbox_api.py +17 -15
- hiddifypanel/drivers/ssh_liberty_bridge_api.py +2 -0
- hiddifypanel/drivers/user_driver.py +12 -6
- hiddifypanel/drivers/wireguard_api.py +2 -0
- hiddifypanel/drivers/xray_api.py +14 -9
- hiddifypanel/hutils/__init__.py +1 -0
- hiddifypanel/hutils/convert.py +13 -2
- hiddifypanel/hutils/crypto.py +21 -2
- hiddifypanel/hutils/flask.py +19 -5
- hiddifypanel/hutils/importer/xui.py +5 -2
- hiddifypanel/hutils/node/__init__.py +3 -0
- hiddifypanel/hutils/node/api_client.py +76 -0
- hiddifypanel/hutils/node/child.py +147 -0
- hiddifypanel/hutils/node/parent.py +100 -0
- hiddifypanel/hutils/node/shared.py +65 -0
- hiddifypanel/hutils/proxy/shared.py +15 -3
- hiddifypanel/models/__init__.py +2 -2
- hiddifypanel/models/admin.py +14 -2
- hiddifypanel/models/base_account.py +3 -3
- hiddifypanel/models/child.py +30 -16
- hiddifypanel/models/config.py +39 -15
- hiddifypanel/models/config_enum.py +55 -8
- hiddifypanel/models/domain.py +28 -20
- hiddifypanel/models/parent_domain.py +2 -2
- hiddifypanel/models/proxy.py +13 -4
- hiddifypanel/models/report.py +2 -3
- hiddifypanel/models/usage.py +2 -2
- hiddifypanel/models/user.py +18 -9
- hiddifypanel/panel/admin/Actions.py +4 -6
- hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
- hiddifypanel/panel/admin/Dashboard.py +5 -10
- hiddifypanel/panel/admin/DomainAdmin.py +12 -11
- hiddifypanel/panel/admin/NodeAdmin.py +6 -2
- hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
- hiddifypanel/panel/admin/SettingAdmin.py +60 -21
- hiddifypanel/panel/admin/UserAdmin.py +10 -2
- hiddifypanel/panel/admin/templates/index.html +1 -1
- hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
- hiddifypanel/panel/cli.py +16 -16
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
- hiddifypanel/panel/commercial/__init__.py +7 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
- hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
- hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
- hiddifypanel/panel/common.py +21 -6
- hiddifypanel/panel/hiddify.py +9 -80
- hiddifypanel/panel/init_db.py +84 -38
- hiddifypanel/panel/usage.py +33 -18
- hiddifypanel/panel/user/templates/home/usage.html +1 -1
- hiddifypanel/panel/user/templates/new.html +2 -2
- hiddifypanel/static/css/custom.css +13 -0
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/images/hiddify2.png +0 -0
- hiddifypanel/static/new/assets/hiddify-logo-7617d937.png +0 -0
- hiddifypanel/static/new/assets/{index-4510b616.js → index-ccb9873c.js} +2 -2
- hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
- hiddifypanel/static/new/i18n/en.json +6 -6
- hiddifypanel/static/new/i18n/fa.json +1 -1
- hiddifypanel/templates/admin-layout.html +24 -40
- hiddifypanel/templates/fake.html +22 -0
- hiddifypanel/templates/master.html +24 -42
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +95 -5
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +96 -4
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +91 -1
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
- hiddifypanel/translations.i18n/en.json +61 -5
- hiddifypanel/translations.i18n/fa.json +60 -4
- hiddifypanel/translations.i18n/pt.json +63 -7
- hiddifypanel/translations.i18n/ru.json +57 -1
- hiddifypanel/translations.i18n/zh.json +58 -2
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +112 -94
- hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
- hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
- hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
- hiddifypanel/static/new/assets/hiddify-logo-7617d937_old.png +0 -0
- hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
- /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,9 @@ from hiddifypanel.auth import login_required, current_account
|
|
3
3
|
|
4
4
|
from hiddifypanel.models import *
|
5
5
|
import re
|
6
|
-
from flask import
|
6
|
+
from flask import g # type: ignore
|
7
|
+
from markupsafe import Markup
|
8
|
+
|
7
9
|
from flask_babel import gettext as __
|
8
10
|
from flask_babel import lazy_gettext as _
|
9
11
|
from hiddifypanel.panel.run_commander import Command, commander
|
@@ -229,7 +231,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
229
231
|
if not d:
|
230
232
|
continue
|
231
233
|
if not hutils.network.is_domain_reality_friendly(d):
|
232
|
-
raise ValidationError(_("Domain is not REALITY friendly!")+f' {d}')
|
234
|
+
raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
|
233
235
|
|
234
236
|
if not hutils.network.is_in_same_asn(d, ipv4_list[0]):
|
235
237
|
server_asn = hutils.network.get_ip_asn_name(ipv4_list[0])
|
@@ -240,7 +242,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
240
242
|
|
241
243
|
for d in model.servernames.split(","):
|
242
244
|
if not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
|
243
|
-
raise ValidationError(_("REALITY Fallback domain is not compaitble with server names!")+f' {d} != {model.domain}')
|
245
|
+
raise ValidationError(_("REALITY Fallback domain is not compaitble with server names!") + f' {d} != {model.domain}')
|
244
246
|
|
245
247
|
if (model.cdn_ip):
|
246
248
|
try:
|
@@ -266,19 +268,18 @@ class DomainAdmin(AdminLTEModelView):
|
|
266
268
|
hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=True)
|
267
269
|
|
268
270
|
def after_model_delete(self, model):
|
269
|
-
|
270
|
-
|
271
|
-
|
271
|
+
if hutils.node.is_child():
|
272
|
+
if not hutils.node.child.sync_with_parent():
|
273
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
272
274
|
|
273
275
|
def after_model_change(self, form, model, is_created):
|
274
276
|
if hconfig(ConfigEnum.first_setup):
|
275
277
|
set_hconfig(ConfigEnum.first_setup, False)
|
276
|
-
# if hconfig(ConfigEnum.parent_panel):
|
277
|
-
# hiddify_api.sync_child_to_parent()
|
278
278
|
if model.need_valid_ssl:
|
279
|
-
# hiddify.exec_command(f"sudo /opt/hiddify-manager/acme.sh/get_cert.sh {model.domain}")
|
280
|
-
# run get_cert.sh
|
281
279
|
commander(Command.get_cert, domain=model.domain)
|
280
|
+
if hutils.node.is_child():
|
281
|
+
if not hutils.node.child.sync_with_parent():
|
282
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
282
283
|
|
283
284
|
def is_accessible(self):
|
284
285
|
if login_required(roles={Role.super_admin, Role.admin})(lambda: True)() != True:
|
@@ -296,4 +297,4 @@ class DomainAdmin(AdminLTEModelView):
|
|
296
297
|
|
297
298
|
def get_query(self):
|
298
299
|
query = super().get_query()
|
299
|
-
return query.filter(Domain.child_id == Child.current.id)
|
300
|
+
return query.filter(Domain.child_id == Child.current().id)
|
@@ -4,7 +4,9 @@ from wtforms.validators import Regexp, ValidationError
|
|
4
4
|
from flask_babel import lazy_gettext as _
|
5
5
|
from .adminlte import AdminLTEModelView
|
6
6
|
from flask_babel import gettext as __
|
7
|
-
from flask import
|
7
|
+
from flask import g, request
|
8
|
+
from markupsafe import Markup
|
9
|
+
|
8
10
|
|
9
11
|
from hiddifypanel.auth import login_required
|
10
12
|
from hiddifypanel.panel import hiddify
|
@@ -40,7 +42,7 @@ class NodeAdmin(AdminLTEModelView):
|
|
40
42
|
def is_accessible(self):
|
41
43
|
if login_required(roles={Role.super_admin})(lambda: True)() != True:
|
42
44
|
return False
|
43
|
-
if Child.current.id != 0:
|
45
|
+
if Child.current().id != 0:
|
44
46
|
return False
|
45
47
|
return True
|
46
48
|
|
@@ -49,7 +51,9 @@ class NodeAdmin(AdminLTEModelView):
|
|
49
51
|
raise ValidationError(_("Remote nodes are not supported yet!"))
|
50
52
|
|
51
53
|
def after_model_change(self, form, model, is_created):
|
54
|
+
# deprecated
|
52
55
|
set_hconfig(ConfigEnum.is_parent, True)
|
56
|
+
set_hconfig(ConfigEnum.panel_mode, PanelMode.parent)
|
53
57
|
if is_created and model.mode == ChildMode.virtual:
|
54
58
|
# for k, v in get_hconfigs().items():
|
55
59
|
# set_hconfig(k, v, model.id)
|
@@ -56,9 +56,10 @@ class ProxyAdmin(FlaskView):
|
|
56
56
|
# print(cat,vs)
|
57
57
|
db.session.commit()
|
58
58
|
hutils.proxy.get_proxies.invalidate_all()
|
59
|
+
if hutils.node.is_child():
|
60
|
+
if not hutils.node.child.sync_with_parent():
|
61
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
59
62
|
hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=False)
|
60
|
-
# if hconfig(ConfigEnum.parent_panel):
|
61
|
-
# hiddify_api.sync_child_to_parent()
|
62
63
|
global_config_form = get_global_config_form(True)
|
63
64
|
else:
|
64
65
|
hutils.flask.flash((_('config.validation-error')), 'danger')
|
@@ -94,7 +95,7 @@ def get_global_config_form(empty=False):
|
|
94
95
|
|
95
96
|
|
96
97
|
def get_all_proxy_form(empty=False):
|
97
|
-
proxies = hutils.proxy.get_proxies(Child.current.id)
|
98
|
+
proxies = hutils.proxy.get_proxies(Child.current().id)
|
98
99
|
categories1 = sorted([c for c in {c.cdn: 1 for c in proxies}])
|
99
100
|
|
100
101
|
class DynamicForm(FlaskForm):
|
@@ -3,7 +3,9 @@ import flask_babel
|
|
3
3
|
import flask_babel
|
4
4
|
from flask_babel import lazy_gettext as _
|
5
5
|
# from flask_babelex import gettext as _
|
6
|
-
from flask import render_template,
|
6
|
+
from flask import render_template, g # type: ignore
|
7
|
+
from markupsafe import Markup
|
8
|
+
|
7
9
|
from hiddifypanel.hutils.flask import hurl_for
|
8
10
|
from flask import current_app as app
|
9
11
|
from hiddifypanel import hutils
|
@@ -20,6 +22,7 @@ from hiddifypanel.models import BoolConfig, StrConfig, ConfigEnum, hconfig, Conf
|
|
20
22
|
from hiddifypanel.models import *
|
21
23
|
from hiddifypanel.database import db
|
22
24
|
from hiddifypanel.panel import hiddify, custom_widgets
|
25
|
+
from hiddifypanel import __version__
|
23
26
|
|
24
27
|
|
25
28
|
class SettingAdmin(FlaskView):
|
@@ -36,26 +39,26 @@ class SettingAdmin(FlaskView):
|
|
36
39
|
reset_action = None
|
37
40
|
if form.validate_on_submit():
|
38
41
|
|
39
|
-
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current.id).all()
|
42
|
+
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current().id).all()
|
40
43
|
bool_types = {c.key: 'bool' for c in boolconfigs}
|
41
44
|
|
42
45
|
old_configs = get_hconfigs()
|
43
46
|
changed_configs = {}
|
44
47
|
|
45
|
-
for
|
48
|
+
for category, c_items in form.data.items(): # [c for c in ConfigEnum]:
|
46
49
|
|
47
|
-
if isinstance(
|
50
|
+
if isinstance(c_items, dict):
|
48
51
|
for k in ConfigEnum:
|
49
|
-
if k.name not in
|
52
|
+
if k.name not in c_items:
|
50
53
|
continue
|
51
|
-
v =
|
54
|
+
v = c_items[k.name]
|
52
55
|
if k.type == str:
|
53
56
|
if "_domain" in k or "_fakedomain" in k:
|
54
57
|
v = v.lower()
|
55
58
|
|
56
59
|
if "port" in k:
|
57
60
|
for p in v.split(","):
|
58
|
-
for k2, v2 in
|
61
|
+
for k2, v2 in c_items.items():
|
59
62
|
if "port" in k2 and k.name != k2 and p in v2:
|
60
63
|
hutils.flask.flash(_("Port is already used! in") + f" {k2} {k}", 'error')
|
61
64
|
return render_template('config.html', form=form)
|
@@ -73,32 +76,63 @@ class SettingAdmin(FlaskView):
|
|
73
76
|
hutils.flask.flash(_("ProxyPath is already used! use different proxy path"), 'error') # type: ignore
|
74
77
|
return render_template('config.html', form=form)
|
75
78
|
|
79
|
+
# validate parent_panel value
|
80
|
+
parent_apikey = ''
|
81
|
+
if p_p := changed_configs.get(ConfigEnum.parent_panel):
|
82
|
+
domain, proxy_path, uuid = hutils.flask.extract_parent_info_from_url(p_p)
|
83
|
+
if not domain or not proxy_path or not uuid or not hutils.node.is_panel_active(domain, proxy_path, uuid):
|
84
|
+
hutils.flask.flash(_('parent.invalid-parent-url'), 'danger') # type: ignore
|
85
|
+
return render_template('config.html', form=form)
|
86
|
+
else:
|
87
|
+
set_hconfig(ConfigEnum.parent_domain, domain)
|
88
|
+
set_hconfig(ConfigEnum.parent_admin_proxy_path, proxy_path)
|
89
|
+
parent_apikey = uuid
|
90
|
+
|
76
91
|
for k, v in changed_configs.items():
|
77
92
|
set_hconfig(k, v, commit=False)
|
78
93
|
|
79
94
|
db.session.commit()
|
80
95
|
flask_babel.refresh()
|
81
96
|
|
97
|
+
# set panel mode
|
98
|
+
p_mode = hconfig(ConfigEnum.panel_mode)
|
99
|
+
if p_mode != PanelMode.parent:
|
100
|
+
if hconfig(ConfigEnum.parent_panel):
|
101
|
+
if p_mode == PanelMode.standalone:
|
102
|
+
set_hconfig(ConfigEnum.panel_mode, PanelMode.child)
|
103
|
+
else:
|
104
|
+
if p_mode != PanelMode.standalone:
|
105
|
+
set_hconfig(ConfigEnum.panel_mode, PanelMode.standalone)
|
106
|
+
|
82
107
|
from hiddifypanel.panel.commercial.telegrambot import register_bot
|
83
108
|
register_bot(set_hook=True)
|
84
|
-
|
85
|
-
#
|
109
|
+
|
110
|
+
# sync with parent if needed
|
111
|
+
if hutils.node.is_child():
|
112
|
+
if hutils.node.child.is_registered():
|
113
|
+
if not hutils.node.child.sync_with_parent():
|
114
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
115
|
+
else: # TODO: it's just for debuging
|
116
|
+
hutils.flask.flash(_('child.sync-success')) # type: ignore
|
117
|
+
else:
|
118
|
+
name = hconfig(ConfigEnum.unique_id)
|
119
|
+
parent_info = hutils.node.get_panel_info(hconfig(ConfigEnum.parent_domain), hconfig(ConfigEnum.parent_admin_proxy_path), parent_apikey)
|
120
|
+
if parent_info.get('version') != __version__:
|
121
|
+
hutils.flask.flash(_('node.diff-version'), 'danger') # type: ignore
|
122
|
+
if not hutils.node.child.register_to_parent(name, parent_apikey, mode=ChildMode.remote):
|
123
|
+
hutils.flask.flash(_('child.register-failed'), 'danger') # type: ignore
|
124
|
+
else: # TODO: it's just for debuging
|
125
|
+
hutils.flask.flash(_('child.register-success')) # type: ignore
|
126
|
+
|
86
127
|
reset_action = hiddify.check_need_reset(old_configs)
|
87
128
|
|
88
129
|
if old_configs[ConfigEnum.admin_lang] != hconfig(ConfigEnum.admin_lang):
|
89
|
-
|
90
130
|
form = get_config_form()
|
91
131
|
else:
|
92
132
|
hutils.flask.flash(_('config.validation-error'), 'danger') # type: ignore
|
93
133
|
|
94
134
|
return reset_action or render_template('config.html', form=form)
|
95
135
|
|
96
|
-
# # form=HelloForm()
|
97
|
-
# # # return render('config.html',form=form)
|
98
|
-
# # return render_template('config.html',form=HelloForm())
|
99
|
-
# form=get_config_form()
|
100
|
-
# return render_template('config.html',form=form)
|
101
|
-
|
102
136
|
def get_babel_string(self):
|
103
137
|
res = ""
|
104
138
|
strconfigs = StrConfig.query.all()
|
@@ -122,8 +156,8 @@ class SettingAdmin(FlaskView):
|
|
122
156
|
|
123
157
|
|
124
158
|
def get_config_form():
|
125
|
-
strconfigs = StrConfig.query.filter(StrConfig.child_id == Child.current.id).all()
|
126
|
-
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current.id).all()
|
159
|
+
strconfigs = StrConfig.query.filter(StrConfig.child_id == Child.current().id).all()
|
160
|
+
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current().id).all()
|
127
161
|
bool_types = {c.key: 'bool' for c in boolconfigs}
|
128
162
|
|
129
163
|
configs = [*boolconfigs, *strconfigs]
|
@@ -133,7 +167,7 @@ def get_config_form():
|
|
133
167
|
|
134
168
|
class DynamicForm(FlaskForm):
|
135
169
|
pass
|
136
|
-
is_parent =
|
170
|
+
is_parent = hutils.node.is_parent()
|
137
171
|
|
138
172
|
for cat in ConfigCategory:
|
139
173
|
if cat == 'hidden':
|
@@ -149,6 +183,9 @@ def get_config_form():
|
|
149
183
|
if not (c2 in configs_key):
|
150
184
|
continue
|
151
185
|
c = configs_key[c2]
|
186
|
+
if hutils.node.is_parent():
|
187
|
+
if c.key == ConfigEnum.parent_panel:
|
188
|
+
continue
|
152
189
|
extra_info = ''
|
153
190
|
if c.key in bool_types:
|
154
191
|
field = SwitchField(_(f'config.{c.key}.label'), default=c.value, description=_(f'config.{c.key}.description'))
|
@@ -207,7 +244,9 @@ def get_config_form():
|
|
207
244
|
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=choices, description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
208
245
|
|
209
246
|
elif c.key == ConfigEnum.warp_sites:
|
210
|
-
validators = [wtf.validators.Length(max=2048)
|
247
|
+
validators = [wtf.validators.Length(max=2048),
|
248
|
+
wtf.validators.Regexp(r'^([\w.-]+)?(?:\n[\w.-]+)*$', re.IGNORECASE, _("config.invalid-pattern-for-warp-sites") + f' {c.key}')
|
249
|
+
]
|
211
250
|
render_kw = {'class': "ltr", 'maxlength': 2048}
|
212
251
|
field = wtf.TextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
213
252
|
description=_(f'config.{c.key}.description'), render_kw=render_kw)
|
@@ -229,7 +268,7 @@ def get_config_form():
|
|
229
268
|
if c.key != ConfigEnum.decoy_domain:
|
230
269
|
validators.append(wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain already used")))
|
231
270
|
validators.append(wtf.validators.NoneOf(
|
232
|
-
[cc.value.lower() for cc in StrConfig.query.filter(StrConfig.child_id == Child.current.id).all() if cc.key != c.key and "fakedomain" in cc.key and cc.key != ConfigEnum.decoy_domain], _("config.Domain already used")))
|
271
|
+
[cc.value.lower() for cc in StrConfig.query.filter(StrConfig.child_id == Child.current().id).all() if cc.key != c.key and "fakedomain" in cc.key and cc.key != ConfigEnum.decoy_domain], _("config.Domain already used")))
|
233
272
|
|
234
273
|
render_kw['required'] = ""
|
235
274
|
if len(c.value) < 3:
|
@@ -8,7 +8,9 @@ from flask_babel import gettext as __
|
|
8
8
|
from .adminlte import AdminLTEModelView
|
9
9
|
from wtforms.validators import NumberRange
|
10
10
|
from flask_babel import lazy_gettext as _
|
11
|
-
from flask import
|
11
|
+
from flask import g, request # type: ignore
|
12
|
+
from markupsafe import Markup
|
13
|
+
|
12
14
|
from hiddifypanel.hutils.flask import hurl_for
|
13
15
|
from wtforms.validators import Regexp, ValidationError
|
14
16
|
from flask import current_app
|
@@ -126,7 +128,7 @@ class UserAdmin(AdminLTEModelView):
|
|
126
128
|
def _enable_formatter(view, context, model, name):
|
127
129
|
if model.is_active:
|
128
130
|
link = '<i class="fa-solid fa-circle-check text-success"></i> '
|
129
|
-
elif len(model.
|
131
|
+
elif len(model.devices):
|
130
132
|
link = '<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
|
131
133
|
else:
|
132
134
|
link = '<i class="fa-solid fa-circle-xmark text-danger"></i> '
|
@@ -297,10 +299,16 @@ class UserAdmin(AdminLTEModelView):
|
|
297
299
|
user_driver.remove_client(model)
|
298
300
|
hiddify.quick_apply_users()
|
299
301
|
|
302
|
+
if hutils.node.is_parent():
|
303
|
+
hutils.node.parent.request_childs_to_sync()
|
304
|
+
|
300
305
|
def after_model_delete(self, model):
|
301
306
|
user_driver.remove_client(model)
|
302
307
|
hiddify.quick_apply_users()
|
303
308
|
|
309
|
+
if hutils.node.is_parent():
|
310
|
+
hutils.node.parent.request_childs_to_sync()
|
311
|
+
|
304
312
|
def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
|
305
313
|
res = None
|
306
314
|
# print('aaa',args, kwargs)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% macro admin_btn(child,domain) -%}
|
2
2
|
<div class="btn-group">
|
3
|
-
<a href="{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
|
3
|
+
<a href="{{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
|
4
4
|
</div>
|
5
5
|
{%- endmacro -%}
|
6
6
|
|
@@ -28,7 +28,6 @@
|
|
28
28
|
<td>
|
29
29
|
{% if not child.is_active %}
|
30
30
|
<form method="post" action="remove_child">
|
31
|
-
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
32
31
|
<input type="hidden" name="child_id" value="{{child.id}}" />
|
33
32
|
<button type="submit" class="btn btn-xs btn-danger"><i class="fa fa-trash"></i></a>
|
34
33
|
</form>
|
@@ -37,9 +36,8 @@
|
|
37
36
|
<td class="text-center"><span class="btn btn-xs badge-{{" success" if child.is_active else "warning" }}"> {{icon('solid','check') if child.is_active else icon('solid','triangle-exclamation')}}</span>
|
38
37
|
</td>
|
39
38
|
<td>
|
40
|
-
|
41
39
|
{% for d in child.domains %}
|
42
|
-
{% if d.mode !="fake" %}
|
40
|
+
{% if d.mode !="fake" and d.mode != "reality"%}
|
43
41
|
{{admin_btn(child,d)}}
|
44
42
|
{% endif %}
|
45
43
|
{% endfor %}
|
hiddifypanel/panel/cli.py
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
import datetime
|
2
2
|
import uuid
|
3
|
-
|
3
|
+
import json
|
4
|
+
import os
|
4
5
|
import click
|
5
6
|
from dateutil import relativedelta
|
7
|
+
|
8
|
+
|
6
9
|
from hiddifypanel import hutils
|
7
10
|
|
8
11
|
from hiddifypanel.models import *
|
9
12
|
from hiddifypanel.panel import hiddify, usage
|
10
13
|
from hiddifypanel.database import db
|
11
14
|
from hiddifypanel.panel.init_db import init_db
|
12
|
-
from flask import g
|
13
15
|
|
14
16
|
|
15
17
|
def drop_db():
|
@@ -24,29 +26,28 @@ def downgrade():
|
|
24
26
|
ConfigEnum.hysteria_port, ConfigEnum.ssh_server_enable, ConfigEnum.ssh_server_port, ConfigEnum.ssh_server_redis_url])).delete()
|
25
27
|
Proxy.query.filter(Proxy.l3.in_([ProxyL3.ssh, ProxyL3.h3_quic, ProxyL3.custom])).delete()
|
26
28
|
db.session.commit()
|
27
|
-
import os
|
28
29
|
os.rename("/opt/hiddify-manager/hiddify-panel/hiddifypanel.db.old", "/opt/hiddify-manager/hiddify-panel/hiddifypanel.db")
|
29
30
|
|
30
31
|
|
31
32
|
def backup():
|
32
33
|
dbdict = hiddify.dump_db_to_dict()
|
33
|
-
import json
|
34
|
-
import os
|
35
34
|
os.makedirs('backup', exist_ok=True)
|
36
35
|
dst = f'backup/{datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")}.json'
|
37
36
|
with open(dst, 'w') as fp:
|
38
37
|
json.dump(dbdict, fp, indent=4, sort_keys=True, default=str)
|
39
38
|
|
40
39
|
if hconfig(ConfigEnum.telegram_bot_token):
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
from hiddifypanel.panel.commercial.telegrambot import bot, register_bot
|
41
|
+
if not bot.token:
|
42
|
+
register_bot(True)
|
43
|
+
|
44
|
+
with open(dst, 'rb') as document:
|
45
|
+
for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
|
44
46
|
caption = ("Backup \n" + admin_links())
|
45
47
|
bot.send_document(admin.telegram_id, document, visible_file_name=dst.replace("backup/", ""), caption=caption[:min(len(caption), 1000)])
|
46
48
|
|
47
49
|
|
48
50
|
def all_configs():
|
49
|
-
import json
|
50
51
|
valid_users = [u.to_dict(dump_id=True) for u in User.query.filter((User.usage_limit > User.current_usage)).all() if u.is_active]
|
51
52
|
host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
|
52
53
|
configs = {
|
@@ -111,8 +112,8 @@ def admin_path():
|
|
111
112
|
print(hiddify.get_account_panel_link(admin, domain, prefere_path_only=True))
|
112
113
|
|
113
114
|
|
114
|
-
def get_this_host_domains():
|
115
|
-
|
115
|
+
# def get_this_host_domains():
|
116
|
+
# current_child_ids
|
116
117
|
|
117
118
|
|
118
119
|
def hysteria_domain_port():
|
@@ -142,12 +143,11 @@ def init_app(app):
|
|
142
143
|
@ app.cli.command()
|
143
144
|
@ click.option("--domain", "-d")
|
144
145
|
def add_domain(domain):
|
145
|
-
|
146
|
-
|
147
|
-
if table.query.filter(table.domain == domain).first():
|
146
|
+
# TODO: Fix this
|
147
|
+
if Domain.query.filter(Domain.domain == domain).first():
|
148
148
|
return "Domain already exist."
|
149
|
-
d =
|
150
|
-
if not
|
149
|
+
d = Domain(domain=domain)
|
150
|
+
if not hutils.node.is_parent():
|
151
151
|
d.mode = DomainType.direct
|
152
152
|
db.session.add(d)
|
153
153
|
db.session.commit()
|
@@ -1,8 +1,11 @@
|
|
1
1
|
from hiddifypanel.models import *
|
2
|
+
from hiddifypanel.panel import hiddify
|
2
3
|
from hiddifypanel.panel.admin.adminlte import AdminLTEModelView
|
3
4
|
from flask_babel import gettext as __
|
4
5
|
from flask_babel import lazy_gettext as _
|
5
|
-
from flask import g, redirect
|
6
|
+
from flask import g, redirect
|
7
|
+
from markupsafe import Markup
|
8
|
+
|
6
9
|
from hiddifypanel.hutils.flask import hurl_for, flash
|
7
10
|
from hiddifypanel.auth import login_required
|
8
11
|
from flask_admin.model.template import EndpointLinkRowAction
|
@@ -45,14 +48,16 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
45
48
|
# form_overrides = {'work_with': Select2Field}
|
46
49
|
|
47
50
|
def after_model_change(self, form, model, is_created):
|
48
|
-
|
49
|
-
|
51
|
+
if hutils.node.is_child():
|
52
|
+
if not hutils.node.child.sync_with_parent():
|
53
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
50
54
|
hutils.proxy.get_proxies.invalidate_all()
|
51
55
|
pass
|
52
56
|
|
53
57
|
def after_model_delete(self, model):
|
54
|
-
|
55
|
-
|
58
|
+
if hutils.node.is_child():
|
59
|
+
if not hutils.node.child.sync_with_parent():
|
60
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
56
61
|
hutils.proxy.get_proxies.invalidate_all()
|
57
62
|
pass
|
58
63
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
from .ProxyDetailsAdmin import ProxyDetailsAdmin
|
2
|
-
# from .CommercialSettings import CommercialSettings
|
3
|
-
from hiddifypanel.panel import hiddify
|
4
1
|
from hiddifypanel.models import *
|
5
2
|
from hiddifypanel.database import db
|
6
3
|
from hiddifypanel import Events, hutils
|
@@ -13,10 +10,14 @@ def init_app(app):
|
|
13
10
|
restapi_v1.init_app(app)
|
14
11
|
from .restapi.v2 import admin as api_v2_admin
|
15
12
|
from .restapi.v2 import user as api_v2_user
|
16
|
-
from .restapi.v2 import
|
13
|
+
from .restapi.v2 import parent as api_v2_parent
|
14
|
+
from .restapi.v2 import child as api_v2_child
|
15
|
+
from .restapi.v2 import panel as api_v2_panel
|
16
|
+
api_v2_parent.init_app(app)
|
17
17
|
api_v2_admin.init_app(app)
|
18
18
|
api_v2_user.init_app(app)
|
19
|
-
|
19
|
+
api_v2_child.init_app(app)
|
20
|
+
api_v2_panel.init_app(app)
|
20
21
|
return
|
21
22
|
|
22
23
|
|
@@ -53,6 +54,7 @@ Events.config_changed.subscribe(config_changed_event)
|
|
53
54
|
def admin_prehook(flaskadmin, admin_bp):
|
54
55
|
# from .ParentDomainAdmin import ParentDomainAdmin
|
55
56
|
# flaskadmin.add_view(ParentDomainAdmin(ParentDomain, db.session))
|
57
|
+
from .ProxyDetailsAdmin import ProxyDetailsAdmin
|
56
58
|
flaskadmin.add_view(ProxyDetailsAdmin(Proxy, db.session))
|
57
59
|
# CommercialSettings.register(admin_bp)
|
58
60
|
|
@@ -7,13 +7,13 @@ from hiddifypanel.models.admin import AdminUser
|
|
7
7
|
from hiddifypanel.models.config_enum import ConfigEnum, Lang
|
8
8
|
from hiddifypanel.models.config import hconfig
|
9
9
|
from hiddifypanel.models.role import Role
|
10
|
-
from .
|
10
|
+
from .schema import AdminSchema
|
11
11
|
|
12
12
|
|
13
13
|
class AdminInfoApi(MethodView):
|
14
14
|
decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
|
15
15
|
|
16
|
-
@app.output(AdminSchema)
|
16
|
+
@app.output(AdminSchema) # type: ignore
|
17
17
|
def get(self):
|
18
18
|
# admin = AdminUser.by_uuid(g.account.uuid) or abort(404, "user not found")
|
19
19
|
admin = g.account or abort(404, "user not found")
|
@@ -2,7 +2,6 @@ from apiflask import Schema, fields, abort
|
|
2
2
|
from flask.views import MethodView
|
3
3
|
from hiddifypanel import hutils
|
4
4
|
from hiddifypanel.models.role import Role
|
5
|
-
from hiddifypanel.panel import hiddify
|
6
5
|
from flask import current_app as app, make_response, g, request
|
7
6
|
import os
|
8
7
|
from ansi2html import Ansi2HTMLConverter
|
@@ -10,13 +9,13 @@ from hiddifypanel.auth import login_required
|
|
10
9
|
from hiddifypanel.models import *
|
11
10
|
|
12
11
|
|
13
|
-
class
|
12
|
+
class AdminInputLogfileSchema(Schema):
|
14
13
|
file = fields.String(description="The log file name", required=True)
|
15
14
|
|
16
15
|
|
17
16
|
class AdminLogApi(MethodView):
|
18
|
-
@app.input(
|
19
|
-
@app.output(fields.String(description="The html of the log", many=True))
|
17
|
+
@app.input(AdminInputLogfileSchema, arg_name="data", location='form') # type: ignore
|
18
|
+
@app.output(fields.String(description="The html of the log", many=True)) # type: ignore
|
20
19
|
@login_required({Role.super_admin})
|
21
20
|
def post(self, data):
|
22
21
|
file_name = data.get('file') or abort(400, "Parameter issue: 'file'")
|
@@ -40,7 +39,7 @@ class AdminLogApi(MethodView):
|
|
40
39
|
return resp
|
41
40
|
|
42
41
|
def options(self):
|
43
|
-
domain = request.args.get("domain")
|
42
|
+
# domain = request.args.get("domain")
|
44
43
|
# Domain.query.filter(Domain.domain == domain).first() or abort(404)
|
45
44
|
if g.proxy_path != hconfig(ConfigEnum.proxy_path_admin):
|
46
45
|
abort(403)
|
@@ -1,53 +1,26 @@
|
|
1
|
-
|
2
|
-
from apiflask.fields import Integer, String, UUID, Boolean, Enum
|
3
1
|
from flask import current_app as app
|
4
2
|
from flask import g
|
5
3
|
from flask.views import MethodView
|
6
|
-
from apiflask import Schema
|
7
4
|
from apiflask import abort
|
8
5
|
from hiddifypanel.auth import login_required
|
9
6
|
from hiddifypanel.models import *
|
10
|
-
from hiddifypanel.panel import hiddify
|
11
|
-
from hiddifypanel.models import AdminMode, Lang
|
12
|
-
|
13
|
-
from . import SuccessfulSchema, has_permission
|
14
|
-
|
15
|
-
|
16
|
-
class AdminSchema(Schema):
|
17
|
-
name = String(required=True, description='The name of the admin')
|
18
|
-
comment = String(required=False, description='A comment related to the admin', allow_none=True)
|
19
|
-
uuid = UUID(required=True, description='The unique identifier for the admin')
|
20
|
-
mode = Enum(AdminMode, required=True, description='The mode for the admin')
|
21
|
-
can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
|
22
|
-
parent_admin_uuid = UUID(description='The unique identifier for the parent admin', allow_none=True,
|
23
|
-
# validate=OneOf([p.uuid for p in AdminUser.query.all()])
|
24
|
-
)
|
25
|
-
telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
|
26
|
-
lang = Enum(Lang, required=True)
|
27
|
-
|
28
7
|
|
29
|
-
|
30
|
-
|
31
|
-
super().__init__(*args, **kwargs)
|
32
|
-
self.fields['name'].required = False
|
33
|
-
self.fields['mode'].required = False
|
34
|
-
self.fields['lang'].required = False
|
35
|
-
self.fields['can_add_admin'].required = False
|
36
|
-
pass
|
8
|
+
from . import has_permission
|
9
|
+
from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
|
37
10
|
|
38
11
|
|
39
12
|
class AdminUserApi(MethodView):
|
40
13
|
decorators = [login_required({Role.super_admin, Role.admin})]
|
41
14
|
|
42
|
-
@app.output(AdminSchema)
|
15
|
+
@app.output(AdminSchema) # type: ignore
|
43
16
|
def get(self, uuid):
|
44
17
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
45
18
|
if not has_permission(admin):
|
46
19
|
abort(403, "You don't have permission to access this admin")
|
47
|
-
return admin.to_dict()
|
20
|
+
return admin.to_dict() # type: ignore
|
48
21
|
|
49
|
-
@app.input(PatchAdminSchema, arg_name='data')
|
50
|
-
@app.output(SuccessfulSchema)
|
22
|
+
@app.input(PatchAdminSchema, arg_name='data') # type: ignore
|
23
|
+
@app.output(SuccessfulSchema) # type: ignore
|
51
24
|
def patch(self, uuid, data):
|
52
25
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
53
26
|
if not has_permission(admin):
|
@@ -60,10 +33,10 @@ class AdminUserApi(MethodView):
|
|
60
33
|
AdminUser.add_or_update(**data)
|
61
34
|
return {'status': 200, 'msg': 'ok'}
|
62
35
|
|
63
|
-
@app.output(SuccessfulSchema)
|
36
|
+
@app.output(SuccessfulSchema) # type: ignore
|
64
37
|
def delete(self, uuid):
|
65
38
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
66
39
|
if not has_permission(admin):
|
67
40
|
abort(403, "You don't have permission to access this admin")
|
68
|
-
admin.remove()
|
41
|
+
admin.remove() # type: ignore
|
69
42
|
return {'status': 200, 'msg': 'ok'}
|