hiddifypanel 9.0.0.dev90__py3-none-any.whl → 10.5.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 +30 -9
- hiddifypanel/base.py +60 -52
- 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 +3 -1
- hiddifypanel/drivers/user_driver.py +12 -6
- hiddifypanel/drivers/wireguard_api.py +7 -2
- hiddifypanel/drivers/xray_api.py +14 -9
- hiddifypanel/hutils/__init__.py +4 -0
- hiddifypanel/hutils/convert.py +13 -2
- hiddifypanel/hutils/crypto.py +48 -0
- hiddifypanel/hutils/encode.py +4 -1
- hiddifypanel/hutils/flask.py +38 -5
- hiddifypanel/hutils/github_issue.py +1 -1
- hiddifypanel/hutils/importer/xui.py +5 -2
- hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
- hiddifypanel/hutils/network/auto_ip_selector.py +2 -0
- hiddifypanel/hutils/network/net.py +46 -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/__init__.py +5 -0
- hiddifypanel/hutils/proxy/clash.py +161 -0
- hiddifypanel/hutils/proxy/shared.py +434 -0
- hiddifypanel/hutils/proxy/singbox.py +339 -0
- hiddifypanel/hutils/proxy/xray.py +235 -0
- hiddifypanel/hutils/proxy/xrayjson.py +391 -0
- hiddifypanel/hutils/random.py +4 -0
- hiddifypanel/hutils/utils.py +4 -1
- hiddifypanel/models/__init__.py +2 -2
- hiddifypanel/models/admin.py +31 -17
- hiddifypanel/models/base_account.py +7 -7
- hiddifypanel/models/child.py +30 -16
- hiddifypanel/models/config.py +45 -16
- hiddifypanel/models/config_enum.py +68 -17
- hiddifypanel/models/domain.py +28 -20
- hiddifypanel/models/parent_domain.py +2 -2
- hiddifypanel/models/proxy.py +29 -20
- hiddifypanel/models/report.py +2 -3
- hiddifypanel/models/usage.py +2 -2
- hiddifypanel/models/user.py +33 -22
- hiddifypanel/panel/admin/Actions.py +13 -19
- hiddifypanel/panel/admin/AdminstratorAdmin.py +14 -3
- hiddifypanel/panel/admin/Dashboard.py +5 -10
- hiddifypanel/panel/admin/DomainAdmin.py +35 -48
- hiddifypanel/panel/admin/NodeAdmin.py +6 -2
- hiddifypanel/panel/admin/ProxyAdmin.py +6 -5
- hiddifypanel/panel/admin/QuickSetup.py +21 -20
- hiddifypanel/panel/admin/SettingAdmin.py +107 -62
- hiddifypanel/panel/admin/UserAdmin.py +22 -21
- hiddifypanel/panel/admin/templates/index.html +1 -1
- hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
- hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
- hiddifypanel/panel/admin/templates/result.html +2 -3
- hiddifypanel/panel/cf_api.py +1 -2
- hiddifypanel/panel/cli.py +16 -16
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +16 -12
- hiddifypanel/panel/commercial/__init__.py +7 -5
- hiddifypanel/panel/commercial/restapi/v1/__init__.py +1 -1
- hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -1
- hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
- 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 -25
- 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 -66
- 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/restapi/v2/user/apps_api.py +17 -23
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +23 -26
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
- hiddifypanel/panel/common.py +25 -8
- hiddifypanel/panel/common_bp/login.py +2 -2
- hiddifypanel/panel/hiddify.py +22 -185
- hiddifypanel/panel/init_db.py +102 -55
- hiddifypanel/panel/usage.py +33 -18
- 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/templates/home/usage.html +1 -1
- hiddifypanel/panel/user/templates/new.html +2 -2
- hiddifypanel/panel/user/user.py +56 -50
- hiddifypanel/static/css/custom.css +31 -0
- hiddifypanel/static/images/favicon.ico +0 -0
- hiddifypanel/static/images/hiddify-old.png +0 -0
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/images/hiddify2.png +0 -0
- hiddifypanel/static/new/assets/{index-1b891a7c.js → index-ccb9873c.js} +56 -56
- hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
- hiddifypanel/static/new/i18n/en.json +6 -6
- hiddifypanel/static/new/i18n/fa.json +2 -2
- hiddifypanel/templates/admin-layout.html +30 -43
- hiddifypanel/templates/fake.html +0 -4
- hiddifypanel/templates/flaskadmin-layout.html +7 -3
- hiddifypanel/templates/master.html +11 -6
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +2082 -1977
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +2035 -1924
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +1911 -1840
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2036 -1881
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1857 -1720
- hiddifypanel/translations.i18n/en.json +992 -933
- hiddifypanel/translations.i18n/fa.json +994 -935
- hiddifypanel/translations.i18n/pt.json +994 -935
- hiddifypanel/translations.i18n/ru.json +994 -935
- hiddifypanel/translations.i18n/zh.json +971 -912
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/WHEEL +1 -1
- 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/panel/user/link_maker.py +0 -1083
- hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -13,7 +15,6 @@ from flask_bootstrap import SwitchField
|
|
13
15
|
|
14
16
|
# from gettext import gettext as _
|
15
17
|
from flask_classful import FlaskView
|
16
|
-
from wtforms.fields import *
|
17
18
|
from flask_wtf import FlaskForm
|
18
19
|
|
19
20
|
|
@@ -21,6 +22,7 @@ from hiddifypanel.models import BoolConfig, StrConfig, ConfigEnum, hconfig, Conf
|
|
21
22
|
from hiddifypanel.models import *
|
22
23
|
from hiddifypanel.database import db
|
23
24
|
from hiddifypanel.panel import hiddify, custom_widgets
|
25
|
+
from hiddifypanel import __version__
|
24
26
|
|
25
27
|
|
26
28
|
class SettingAdmin(FlaskView):
|
@@ -37,26 +39,26 @@ class SettingAdmin(FlaskView):
|
|
37
39
|
reset_action = None
|
38
40
|
if form.validate_on_submit():
|
39
41
|
|
40
|
-
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current.id).all()
|
42
|
+
boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current().id).all()
|
41
43
|
bool_types = {c.key: 'bool' for c in boolconfigs}
|
42
44
|
|
43
45
|
old_configs = get_hconfigs()
|
44
46
|
changed_configs = {}
|
45
47
|
|
46
|
-
for
|
48
|
+
for category, c_items in form.data.items(): # [c for c in ConfigEnum]:
|
47
49
|
|
48
|
-
if isinstance(
|
50
|
+
if isinstance(c_items, dict):
|
49
51
|
for k in ConfigEnum:
|
50
|
-
if k.name not in
|
52
|
+
if k.name not in c_items:
|
51
53
|
continue
|
52
|
-
v =
|
54
|
+
v = c_items[k.name]
|
53
55
|
if k.type == str:
|
54
56
|
if "_domain" in k or "_fakedomain" in k:
|
55
57
|
v = v.lower()
|
56
58
|
|
57
59
|
if "port" in k:
|
58
60
|
for p in v.split(","):
|
59
|
-
for k2, v2 in
|
61
|
+
for k2, v2 in c_items.items():
|
60
62
|
if "port" in k2 and k.name != k2 and p in v2:
|
61
63
|
hutils.flask.flash(_("Port is already used! in") + f" {k2} {k}", 'error')
|
62
64
|
return render_template('config.html', form=form)
|
@@ -74,32 +76,63 @@ class SettingAdmin(FlaskView):
|
|
74
76
|
hutils.flask.flash(_("ProxyPath is already used! use different proxy path"), 'error') # type: ignore
|
75
77
|
return render_template('config.html', form=form)
|
76
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
|
+
|
77
91
|
for k, v in changed_configs.items():
|
78
92
|
set_hconfig(k, v, commit=False)
|
79
93
|
|
80
94
|
db.session.commit()
|
81
95
|
flask_babel.refresh()
|
82
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
|
+
|
83
107
|
from hiddifypanel.panel.commercial.telegrambot import register_bot
|
84
|
-
register_bot()
|
85
|
-
|
86
|
-
#
|
108
|
+
register_bot(set_hook=True)
|
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
|
+
|
87
127
|
reset_action = hiddify.check_need_reset(old_configs)
|
88
128
|
|
89
129
|
if old_configs[ConfigEnum.admin_lang] != hconfig(ConfigEnum.admin_lang):
|
90
|
-
|
91
130
|
form = get_config_form()
|
92
131
|
else:
|
93
132
|
hutils.flask.flash(_('config.validation-error'), 'danger') # type: ignore
|
94
133
|
|
95
134
|
return reset_action or render_template('config.html', form=form)
|
96
135
|
|
97
|
-
# # form=HelloForm()
|
98
|
-
# # # return render('config.html',form=form)
|
99
|
-
# # return render_template('config.html',form=HelloForm())
|
100
|
-
# form=get_config_form()
|
101
|
-
# return render_template('config.html',form=form)
|
102
|
-
|
103
136
|
def get_babel_string(self):
|
104
137
|
res = ""
|
105
138
|
strconfigs = StrConfig.query.all()
|
@@ -123,9 +156,8 @@ class SettingAdmin(FlaskView):
|
|
123
156
|
|
124
157
|
|
125
158
|
def get_config_form():
|
126
|
-
|
127
|
-
|
128
|
-
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()
|
129
161
|
bool_types = {c.key: 'bool' for c in boolconfigs}
|
130
162
|
|
131
163
|
configs = [*boolconfigs, *strconfigs]
|
@@ -135,7 +167,7 @@ def get_config_form():
|
|
135
167
|
|
136
168
|
class DynamicForm(FlaskForm):
|
137
169
|
pass
|
138
|
-
is_parent =
|
170
|
+
is_parent = hutils.node.is_parent()
|
139
171
|
|
140
172
|
for cat in ConfigCategory:
|
141
173
|
if cat == 'hidden':
|
@@ -146,71 +178,78 @@ def get_config_form():
|
|
146
178
|
continue
|
147
179
|
|
148
180
|
class CategoryForm(FlaskForm):
|
149
|
-
description_for_fieldset = wtf.
|
181
|
+
description_for_fieldset = wtf.TextAreaField("", description=_(f'config.{cat}.description'), render_kw={"class": "d-none"})
|
150
182
|
for c2 in cat_configs:
|
151
183
|
if not (c2 in configs_key):
|
152
184
|
continue
|
153
185
|
c = configs_key[c2]
|
186
|
+
if hutils.node.is_parent():
|
187
|
+
if c.key == ConfigEnum.parent_panel:
|
188
|
+
continue
|
154
189
|
extra_info = ''
|
155
190
|
if c.key in bool_types:
|
156
191
|
field = SwitchField(_(f'config.{c.key}.label'), default=c.value, description=_(f'config.{c.key}.description'))
|
157
192
|
elif c.key == ConfigEnum.core_type:
|
158
|
-
field = wtf.
|
193
|
+
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("xray", _("Xray")), ("singbox", _(
|
159
194
|
"SingBox"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
160
195
|
elif c.key == ConfigEnum.warp_mode:
|
161
|
-
field = wtf.
|
162
|
-
|
196
|
+
field = wtf.SelectField(
|
197
|
+
_(f"config.{c.key}.label"), choices=[("disable", _("Disable")), ("all", _("All")), ("custom", _("Only Blocked and Local websites"))],
|
198
|
+
description=_(f"config.{c.key}.description"),
|
199
|
+
default=hconfig(c.key))
|
163
200
|
|
164
201
|
elif c.key == ConfigEnum.lang or c.key == ConfigEnum.admin_lang:
|
165
|
-
field = wtf.
|
166
|
-
|
202
|
+
field = wtf.SelectField(
|
203
|
+
_(f"config.{c.key}.label"),
|
204
|
+
choices=[("en", _("lang.en")), ("fa", Markup(_("lang.fa"))), ("zh", _("lang.zh")), ("pt", _("lang.pt")), ("ru", _("lang.ru"))],
|
205
|
+
description=_(f"config.{c.key}.description"),
|
206
|
+
default=hconfig(c.key))
|
167
207
|
elif c.key == ConfigEnum.country:
|
168
|
-
field = wtf.
|
208
|
+
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("ir", _("Iran")), ("zh", _(
|
169
209
|
"China")), ("other", _("Others"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
170
210
|
elif c.key == ConfigEnum.package_mode:
|
171
211
|
package_modes = [("release", _("Release")), ("beta", _("Beta"))]
|
172
212
|
if hconfig(c.key) == "develop":
|
173
213
|
package_modes.append(("develop", _("Develop")))
|
174
|
-
field = wtf.
|
175
|
-
description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
214
|
+
field = wtf.SelectField(_(f"config.{c.key}.label"), choices=package_modes, description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
176
215
|
|
177
216
|
elif c.key == ConfigEnum.shadowsocks2022_method:
|
178
|
-
field = wtf.
|
179
|
-
("
|
180
|
-
("2022-blake3-chacha20-poly1305", "2022-blake3-chacha20-poly1305"),
|
181
|
-
|
217
|
+
field = wtf.SelectField(
|
218
|
+
_(f"config.{c.key}.label"),
|
219
|
+
choices=[("2022-blake3-aes-256-gcm", "2022-blake3-aes-256-gcm"), ("2022-blake3-chacha20-poly1305", "2022-blake3-chacha20-poly1305"),],
|
220
|
+
description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
182
221
|
|
183
222
|
elif c.key == ConfigEnum.utls:
|
184
|
-
field = wtf.
|
223
|
+
field = wtf.SelectField(
|
185
224
|
_(f"config.{c.key}.label"),
|
186
|
-
choices=[
|
187
|
-
|
188
|
-
|
189
|
-
("ios", "iOS"),
|
190
|
-
("android", "Android"),
|
191
|
-
("safari", "Safari"),
|
192
|
-
("firefox", "Firefox"),
|
193
|
-
('random', 'random'),
|
194
|
-
('randomized', 'randomized')],
|
225
|
+
choices=[
|
226
|
+
("none", "None"), ("chrome", "Chrome"), ("edge", "Edge"), ("ios", "iOS"), ("android", "Android"),
|
227
|
+
("safari", "Safari"), ("firefox", "Firefox"), ('random', 'random'), ('randomized', 'randomized')],
|
195
228
|
description=_(f"config.{c.key}.description"),
|
196
|
-
default=hconfig(c.key)
|
229
|
+
default=hconfig(c.key)
|
230
|
+
)
|
197
231
|
elif c.key == ConfigEnum.telegram_lib:
|
198
232
|
# if hconfig(ConfigEnum.telegram_lib)=='python':
|
199
233
|
# continue6
|
200
|
-
libs = [
|
201
|
-
|
202
|
-
|
234
|
+
libs = [
|
235
|
+
("erlang", _("lib.telegram.erlang")),
|
236
|
+
("python", _("lib.telegram.python")),
|
237
|
+
("tgo", _("lib.telegram.go")),
|
238
|
+
# ("orig", _("lib.telegram.orignal")),
|
239
|
+
]
|
240
|
+
field = wtf.SelectField(_("config.telegram_lib.label"), choices=libs, description=_(
|
203
241
|
"config.telegram_lib.description"), default=hconfig(ConfigEnum.telegram_lib))
|
204
242
|
elif c.key == ConfigEnum.mux_protocol:
|
205
243
|
choices = [("smux", 'smux'), ("yamux", "yamux"), ("h2mux", "h2mux")]
|
206
|
-
field = wtf.
|
207
|
-
description=_(f"config.{c.key}.description"), default=hconfig(c.key))
|
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
|
-
field = wtf.
|
213
|
-
|
251
|
+
field = wtf.TextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
|
252
|
+
description=_(f'config.{c.key}.description'), render_kw=render_kw)
|
214
253
|
elif c.key == ConfigEnum.branding_freetext:
|
215
254
|
validators = [wtf.validators.Length(max=2048)]
|
216
255
|
render_kw = {'class': "ltr", 'maxlength': 2048}
|
@@ -221,24 +260,27 @@ def get_config_form():
|
|
221
260
|
validators = []
|
222
261
|
if c.key == ConfigEnum.domain_fronting_domain:
|
223
262
|
validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})|$", re.IGNORECASE, _("config.Invalid domain")))
|
224
|
-
validators.append(
|
263
|
+
validators.append(hutils.flask.validate_domain_exist)
|
225
264
|
elif '_domain' in c.key or "_fakedomain" in c.key:
|
226
265
|
validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})$", re.IGNORECASE, _("config.Invalid domain")))
|
227
|
-
validators.append(
|
266
|
+
validators.append(hutils.flask.validate_domain_exist)
|
228
267
|
|
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
|
-
validators.append(wtf.validators.NoneOf(
|
232
|
-
|
270
|
+
validators.append(wtf.validators.NoneOf(
|
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")))
|
272
|
+
|
233
273
|
render_kw['required'] = ""
|
234
274
|
if len(c.value) < 3:
|
235
275
|
c.value = hutils.network.get_random_domains(1)[0]
|
276
|
+
|
236
277
|
# if c.key ==ConfigEnum.reality_short_ids:
|
237
278
|
# extra_info=f" <a target='_blank' href='{hurl_for('admin.Actions:get_some_random_reality_friendly_domain',test_domain=c.value)}'>"+_('Example Domains')+"</a>"
|
238
279
|
# if c.key ==ConfigEnum.reality_server_names:
|
239
280
|
# validators.append(wtf.validators.Regexp("^([\w-]+\.)+[\w-]+(,\s*([\w-]+\.)+[\w-]+)*$",re.IGNORECASE,_("Invalid REALITY hostnames")))
|
240
281
|
# gauge width gate lamp weasel jaguar minute enough few attitude endorse situate usdt trc20 doge bep20 trx doge ltc bnb eth btc bnb
|
241
282
|
# enjoy control list debris chronic few door broken way negative daring life season recipe profit switch bitter casual frame aunt plate brush aerobic display
|
283
|
+
|
242
284
|
if c.key == ConfigEnum.parent_panel:
|
243
285
|
validators.append(wtf.validators.Regexp("()|(http(s|)://([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})/.*)", re.IGNORECASE, _("Invalid admin link")))
|
244
286
|
if c.key == ConfigEnum.telegram_bot_token:
|
@@ -272,20 +314,23 @@ def get_config_form():
|
|
272
314
|
if c.key in [ConfigEnum.hysteria_up_mbps, ConfigEnum.hysteria_down_mbps, ConfigEnum.mux_max_connections, ConfigEnum.mux_min_streams, ConfigEnum.mux_max_streams,
|
273
315
|
ConfigEnum.mux_brutal_down_mbps, ConfigEnum.mux_brutal_up_mbps]:
|
274
316
|
validators.append(wtf.validators.Regexp("^\\d+$", re.IGNORECASE, _("config.Invalid! it should be a number only") + f' {c.key}'))
|
317
|
+
|
275
318
|
for val in validators:
|
276
319
|
if hasattr(val, "regex"):
|
277
320
|
render_kw['pattern'] = val.regex.pattern
|
278
321
|
render_kw['title'] = val.message
|
322
|
+
|
279
323
|
if c.key == ConfigEnum.reality_public_key and g.account.mode in [AdminMode.super_admin]:
|
280
324
|
extra_info = f" <a href='{hurl_for('admin.Actions:change_reality_keys')}'>{_('Change')}</a>"
|
281
|
-
|
282
|
-
|
325
|
+
|
326
|
+
field = wtf.StringField(_(f'config.{c.key}.label'), validators, default=c.value,
|
327
|
+
description=_(f'config.{c.key}.description') + extra_info, render_kw=render_kw)
|
283
328
|
setattr(CategoryForm, f'{c.key}', field)
|
284
329
|
|
285
|
-
multifield = wtf.
|
330
|
+
multifield = wtf.FormField(CategoryForm, Markup('<i class="fa-solid fa-plus"></i> ' + _(f'config.{cat}.label')))
|
286
331
|
|
287
332
|
setattr(DynamicForm, cat, multifield)
|
288
333
|
|
289
|
-
setattr(DynamicForm, "submit", wtf.
|
334
|
+
setattr(DynamicForm, "submit", wtf.SubmitField(_('Submit')))
|
290
335
|
|
291
336
|
return DynamicForm()
|
@@ -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
|
@@ -26,7 +28,7 @@ class UserAdmin(AdminLTEModelView):
|
|
26
28
|
column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days", "comment", 'last_online', "uuid", 'remaining_days']
|
27
29
|
column_searchable_list = ["uuid", "name"]
|
28
30
|
column_list = ["is_active", "name", "UserLinks", "current_usage", "remaining_days", "comment", 'last_online', 'mode', 'admin', "uuid"]
|
29
|
-
column_editable_list = ["comment", "name"]
|
31
|
+
column_editable_list = ["comment", "name", "uuid"]
|
30
32
|
form_extra_fields = {
|
31
33
|
'reset_days': SwitchField(_("Reset package days")),
|
32
34
|
'reset_usage': SwitchField(_("Reset package usage")),
|
@@ -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> '
|
@@ -152,8 +154,6 @@ class UserAdmin(AdminLTEModelView):
|
|
152
154
|
domains = [d for d in get_panel_domains() if d.domain != request.host]
|
153
155
|
return Markup(link + " ".join([hiddify.get_html_user_link(model, d) for d in domains]))
|
154
156
|
|
155
|
-
def _uuid_formatter(view, context, model, name):
|
156
|
-
return Markup(f"<span>{model.uuid}</span>")
|
157
157
|
# def _usage_formatter(view, context, model, name):
|
158
158
|
# return round(model.current_usage_GB,3)
|
159
159
|
|
@@ -201,7 +201,7 @@ class UserAdmin(AdminLTEModelView):
|
|
201
201
|
column_formatters = {
|
202
202
|
# 'name': _name_formatter,
|
203
203
|
'UserLinks': _ul_formatter,
|
204
|
-
'uuid': _uuid_formatter,
|
204
|
+
# 'uuid': _uuid_formatter,
|
205
205
|
'current_usage': _usage_formatter,
|
206
206
|
"remaining_days": _expire_formatter,
|
207
207
|
'last_online': _online_formatter,
|
@@ -273,14 +273,16 @@ class UserAdmin(AdminLTEModelView):
|
|
273
273
|
active=g.account.max_active_users, total=g.account.max_users))
|
274
274
|
if old_user and old_user.uuid != model.uuid:
|
275
275
|
user_driver.remove_client(old_user)
|
276
|
-
if not model.ed25519_private_key:
|
277
|
-
priv, publ = hiddify.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 = hiddify.get_wg_private_public_psk_pair()
|
282
|
-
# model.expiry_time=datetime.date.today()+datetime.timedelta(days=model.expiry_time)
|
283
276
|
|
277
|
+
# generated automatically
|
278
|
+
# if not model.ed25519_private_key:
|
279
|
+
# priv, publ = hutils.crypto.get_ed25519_private_public_pair()
|
280
|
+
# model.ed25519_private_key = priv
|
281
|
+
# model.ed25519_public_key = publ
|
282
|
+
# if not model.wg_pk:
|
283
|
+
# model.wg_pk, model.wg_pub, model.wg_psk = hutils.crypto.get_wg_private_public_psk_pair()
|
284
|
+
|
285
|
+
# model.expiry_time=datetime.date.today()+datetime.timedelta(days=model.expiry_time)
|
284
286
|
# if model.current_usage_GB < model.usage_limit_GB:
|
285
287
|
# xray_api.add_client(model.uuid)
|
286
288
|
# else:
|
@@ -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)
|
@@ -309,26 +317,19 @@ class UserAdmin(AdminLTEModelView):
|
|
309
317
|
|
310
318
|
if search:
|
311
319
|
from sqlalchemy import or_
|
312
|
-
|
313
|
-
# Assume 'name' is a field in your model you want to search in.
|
314
|
-
search_conditions = or_(self.model.name.contains(search), self.model.uuid.contains(search))
|
315
|
-
|
320
|
+
search_conditions = or_(self.model.name.contains(search), self.model.uuid == search)
|
316
321
|
query = query.filter(search_conditions)
|
317
322
|
|
318
323
|
data = query.all()
|
319
324
|
count = len(data)
|
320
|
-
|
321
|
-
# Sorting the data
|
322
325
|
data = sorted(data, key=lambda p: getattr(p, sort_column), reverse=sort_desc)
|
323
326
|
|
324
327
|
# Applying pagination
|
325
328
|
start = page * page_size
|
326
329
|
end = start + page_size
|
327
330
|
data = data[start: end]
|
328
|
-
|
329
331
|
res = count, data
|
330
332
|
else:
|
331
|
-
|
332
333
|
res = super().get_list(page, sort_column, sort_desc, search=search, filters=filters, page_size=page_size, *args, **kwargs)
|
333
334
|
return res
|
334
335
|
|
@@ -7,26 +7,37 @@
|
|
7
7
|
<summary>
|
8
8
|
{{_("Send Message to User's Telegram")}}
|
9
9
|
</summary>
|
10
|
-
<
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
<div style="margin-top: 10px;">
|
11
|
+
<button onclick="show_send_message('all')" type="button" class="btn hbtn bg-h-green">
|
12
|
+
{{_("All Users")}}
|
13
|
+
</button>
|
14
|
+
<button onclick="show_send_message('active')" type="button" class="btn hbtn bg-h-green" style="margin-left: 5px;margin-right: 5px;">
|
15
|
+
{{_("Active Users")}}
|
16
|
+
</button>
|
17
|
+
<button onclick="show_send_message('selected')" type="button" class="btn hbtn bg-h-green">
|
18
|
+
{{_("Seleted Users")}}
|
19
|
+
</button>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div style="margin-top: 10px;">
|
23
|
+
<button onclick="show_send_message('offline 1h')" type="button" class="btn hbtn bg-h-red">
|
24
|
+
{{_("Offline more than 1 hour")}}</button>
|
25
|
+
<button onclick="show_send_message('offline 1d')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;">
|
26
|
+
{{_("Offline more than 1 day")}}</button>
|
27
|
+
<button onclick="show_send_message('offline 1w')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;margin-right: 5px;">
|
28
|
+
{{_("Offline more than 1 week")}}
|
29
|
+
</button>
|
30
|
+
<button onclick="show_send_message('expired')" type="button" class="btn hbtn bg-h-red">
|
31
|
+
{{_("Expired Users")}}
|
32
|
+
</button>
|
33
|
+
</div>
|
22
34
|
</details>
|
23
35
|
</div>
|
24
36
|
{% endif %}
|
25
37
|
{{super()}}
|
26
38
|
|
27
39
|
<div class="callout callout-success">
|
28
|
-
{{_('User usage will be updated every 6 minutes. To update it now click <a href="%(link)s"
|
29
|
-
class="btn btn-info">here</a>',link=hurl_for("admin.Actions:update_usage"))}}
|
40
|
+
{{_('User usage will be updated every 6 minutes. To update it now click <a href="%(link)s" class="btn btn-info">here</a>',link=hurl_for("admin.Actions:update_usage"))}}
|
30
41
|
|
31
42
|
</div>
|
32
43
|
<style>
|
@@ -69,23 +80,36 @@
|
|
69
80
|
{% block tail_js%}
|
70
81
|
{{super()}}
|
71
82
|
<script>
|
83
|
+
function get_selected_users_id() {
|
84
|
+
let ids = [];
|
85
|
+
$('input[type="checkbox"][name="rowid"]:checked[value][class="action-checkbox"]').each(function () {
|
86
|
+
ids.push($(this).val());
|
87
|
+
});
|
88
|
+
return ids
|
89
|
+
}
|
72
90
|
function show_send_message(id) {
|
91
|
+
if (id == 'selected') {
|
92
|
+
id = get_selected_users_id();
|
93
|
+
if (id.length == 0) {
|
94
|
+
alert(`{{_("Please select at least one user")}}`);
|
95
|
+
return
|
96
|
+
}
|
97
|
+
}
|
73
98
|
bootbox.prompt({
|
74
99
|
title: '{{_("Please type your message to send to the telegram:")}}',
|
75
100
|
inputType: 'textarea',
|
76
101
|
locale: '{{get_locale()}}',
|
77
|
-
callback: function (
|
78
|
-
console.log(
|
79
|
-
if (!
|
102
|
+
callback: function (msg) {
|
103
|
+
console.log(msg);
|
104
|
+
if (!msg) return
|
80
105
|
// let dialog = bootbox.dialog({
|
81
106
|
// message: '<p><i class="fas fa-spin fa-spinner"></i> {{_("Sending")}}</p>'
|
82
107
|
// });
|
83
|
-
|
84
108
|
$.ajax({
|
85
109
|
type: "POST",
|
86
110
|
url: '{{hurl_for("api_v1.sendmsgresource")}}',
|
87
111
|
|
88
|
-
data: JSON.stringify({ 'text':
|
112
|
+
data: JSON.stringify({ 'text': msg, 'id': id }),
|
89
113
|
contentType: "application/json",
|
90
114
|
dataType: "json",
|
91
115
|
error: function (jqx, status, error) {
|
@@ -131,4 +155,4 @@
|
|
131
155
|
}
|
132
156
|
|
133
157
|
</script>
|
134
|
-
{% endblock %}
|
158
|
+
{% endblock %}
|
@@ -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 %}
|
@@ -40,8 +40,7 @@ page."),temp_link(),show=False)}}
|
|
40
40
|
<label class="col-6" id="process-title"></label> <label class="col-6" id="process-details"></label>
|
41
41
|
</div>
|
42
42
|
<div class="progress">
|
43
|
-
<div id="progress" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0"
|
44
|
-
aria-valuemax="100"></div>
|
43
|
+
<div id="progress" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
45
44
|
</div>
|
46
45
|
</div>
|
47
46
|
{% endif %}
|
@@ -50,7 +49,7 @@ page."),temp_link(),show=False)}}
|
|
50
49
|
{% if log_file_url %}
|
51
50
|
<div>
|
52
51
|
|
53
|
-
<details {% if log_file=="status.log" %}open="" {% endif %}>
|
52
|
+
<details {% if log_file=="status.log" or log_file=="restart.log" %}open="" {% endif %}>
|
54
53
|
|
55
54
|
<summary>
|
56
55
|
<div class="btn ">
|