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.
Files changed (152) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +30 -9
  4. hiddifypanel/base.py +60 -52
  5. hiddifypanel/cache.py +43 -25
  6. hiddifypanel/database.py +9 -0
  7. hiddifypanel/drivers/abstract_driver.py +2 -0
  8. hiddifypanel/drivers/singbox_api.py +17 -15
  9. hiddifypanel/drivers/ssh_liberty_bridge_api.py +3 -1
  10. hiddifypanel/drivers/user_driver.py +12 -6
  11. hiddifypanel/drivers/wireguard_api.py +7 -2
  12. hiddifypanel/drivers/xray_api.py +14 -9
  13. hiddifypanel/hutils/__init__.py +4 -0
  14. hiddifypanel/hutils/convert.py +13 -2
  15. hiddifypanel/hutils/crypto.py +48 -0
  16. hiddifypanel/hutils/encode.py +4 -1
  17. hiddifypanel/hutils/flask.py +38 -5
  18. hiddifypanel/hutils/github_issue.py +1 -1
  19. hiddifypanel/hutils/importer/xui.py +5 -2
  20. hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
  21. hiddifypanel/hutils/network/auto_ip_selector.py +2 -0
  22. hiddifypanel/hutils/network/net.py +46 -2
  23. hiddifypanel/hutils/node/__init__.py +3 -0
  24. hiddifypanel/hutils/node/api_client.py +76 -0
  25. hiddifypanel/hutils/node/child.py +147 -0
  26. hiddifypanel/hutils/node/parent.py +100 -0
  27. hiddifypanel/hutils/node/shared.py +65 -0
  28. hiddifypanel/hutils/proxy/__init__.py +5 -0
  29. hiddifypanel/hutils/proxy/clash.py +161 -0
  30. hiddifypanel/hutils/proxy/shared.py +434 -0
  31. hiddifypanel/hutils/proxy/singbox.py +339 -0
  32. hiddifypanel/hutils/proxy/xray.py +235 -0
  33. hiddifypanel/hutils/proxy/xrayjson.py +391 -0
  34. hiddifypanel/hutils/random.py +4 -0
  35. hiddifypanel/hutils/utils.py +4 -1
  36. hiddifypanel/models/__init__.py +2 -2
  37. hiddifypanel/models/admin.py +31 -17
  38. hiddifypanel/models/base_account.py +7 -7
  39. hiddifypanel/models/child.py +30 -16
  40. hiddifypanel/models/config.py +45 -16
  41. hiddifypanel/models/config_enum.py +68 -17
  42. hiddifypanel/models/domain.py +28 -20
  43. hiddifypanel/models/parent_domain.py +2 -2
  44. hiddifypanel/models/proxy.py +29 -20
  45. hiddifypanel/models/report.py +2 -3
  46. hiddifypanel/models/usage.py +2 -2
  47. hiddifypanel/models/user.py +33 -22
  48. hiddifypanel/panel/admin/Actions.py +13 -19
  49. hiddifypanel/panel/admin/AdminstratorAdmin.py +14 -3
  50. hiddifypanel/panel/admin/Dashboard.py +5 -10
  51. hiddifypanel/panel/admin/DomainAdmin.py +35 -48
  52. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  53. hiddifypanel/panel/admin/ProxyAdmin.py +6 -5
  54. hiddifypanel/panel/admin/QuickSetup.py +21 -20
  55. hiddifypanel/panel/admin/SettingAdmin.py +107 -62
  56. hiddifypanel/panel/admin/UserAdmin.py +22 -21
  57. hiddifypanel/panel/admin/templates/index.html +1 -1
  58. hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
  59. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  60. hiddifypanel/panel/admin/templates/result.html +2 -3
  61. hiddifypanel/panel/cf_api.py +1 -2
  62. hiddifypanel/panel/cli.py +16 -16
  63. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +16 -12
  64. hiddifypanel/panel/commercial/__init__.py +7 -5
  65. hiddifypanel/panel/commercial/restapi/v1/__init__.py +1 -1
  66. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -1
  67. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
  68. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  69. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  70. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  71. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -25
  72. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  73. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  74. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  75. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -66
  76. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  77. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  78. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  79. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  80. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  81. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  82. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  83. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  84. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  85. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  86. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  87. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  88. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  89. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  90. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  91. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  92. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +17 -23
  93. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +23 -26
  94. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  95. hiddifypanel/panel/common.py +25 -8
  96. hiddifypanel/panel/common_bp/login.py +2 -2
  97. hiddifypanel/panel/hiddify.py +22 -185
  98. hiddifypanel/panel/init_db.py +102 -55
  99. hiddifypanel/panel/usage.py +33 -18
  100. hiddifypanel/panel/user/__init__.py +0 -1
  101. hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
  102. hiddifypanel/panel/user/templates/all_configs.txt +2 -2
  103. hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
  104. hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
  105. hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
  106. hiddifypanel/panel/user/templates/clash_config.yml +4 -4
  107. hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
  108. hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
  109. hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
  110. hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
  111. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  112. hiddifypanel/panel/user/templates/new.html +2 -2
  113. hiddifypanel/panel/user/user.py +56 -50
  114. hiddifypanel/static/css/custom.css +31 -0
  115. hiddifypanel/static/images/favicon.ico +0 -0
  116. hiddifypanel/static/images/hiddify-old.png +0 -0
  117. hiddifypanel/static/images/hiddify.png +0 -0
  118. hiddifypanel/static/images/hiddify2.png +0 -0
  119. hiddifypanel/static/new/assets/{index-1b891a7c.js → index-ccb9873c.js} +56 -56
  120. hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
  121. hiddifypanel/static/new/i18n/en.json +6 -6
  122. hiddifypanel/static/new/i18n/fa.json +2 -2
  123. hiddifypanel/templates/admin-layout.html +30 -43
  124. hiddifypanel/templates/fake.html +0 -4
  125. hiddifypanel/templates/flaskadmin-layout.html +7 -3
  126. hiddifypanel/templates/master.html +11 -6
  127. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  128. hiddifypanel/translations/en/LC_MESSAGES/messages.po +2082 -1977
  129. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  130. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +2035 -1924
  131. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  132. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +1911 -1840
  133. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  134. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2036 -1881
  135. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1857 -1720
  137. hiddifypanel/translations.i18n/en.json +992 -933
  138. hiddifypanel/translations.i18n/fa.json +994 -935
  139. hiddifypanel/translations.i18n/pt.json +994 -935
  140. hiddifypanel/translations.i18n/ru.json +994 -935
  141. hiddifypanel/translations.i18n/zh.json +971 -912
  142. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
  143. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
  144. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/WHEEL +1 -1
  145. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  146. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  147. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  148. hiddifypanel/panel/user/link_maker.py +0 -1083
  149. hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
  150. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
  151. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
  152. {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, Markup, g # type: ignore
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 cat, vs in form.data.items(): # [c for c in ConfigEnum]:
48
+ for category, c_items in form.data.items(): # [c for c in ConfigEnum]:
47
49
 
48
- if isinstance(vs, dict):
50
+ if isinstance(c_items, dict):
49
51
  for k in ConfigEnum:
50
- if k.name not in vs:
52
+ if k.name not in c_items:
51
53
  continue
52
- v = vs[k.name]
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 vs.items():
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
- # if hconfig(ConfigEnum.parent_panel):
86
- # hiddify_api.sync_child_to_parent()
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
- strconfigs = StrConfig.query.filter(StrConfig.child_id == Child.current.id).all()
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 = hconfig(ConfigEnum.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.fields.TextAreaField("", description=_(f'config.{cat}.description'), render_kw={"class": "d-none"})
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.fields.SelectField(_(f"config.{c.key}.label"), choices=[("xray", _("Xray")), ("singbox", _(
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.fields.SelectField(_(f"config.{c.key}.label"), choices=[("disable", _("Disable")), ("all", _("All")), ("custom", _(
162
- "Only Blocked and Local websites"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
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.fields.SelectField(_(f"config.{c.key}.label"), choices=[("en", _("lang.en")), ("fa", Markup(_("lang.fa"))), ("zh", _(
166
- "lang.zh")), ("pt", _("lang.pt")), ("ru", _("lang.ru"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
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.fields.SelectField(_(f"config.{c.key}.label"), choices=[("ir", _("Iran")), ("zh", _(
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.fields.SelectField(_(f"config.{c.key}.label"), choices=package_modes,
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.fields.SelectField(_(f"config.{c.key}.label"), choices=[
179
- ("2022-blake3-aes-256-gcm", "2022-blake3-aes-256-gcm"),
180
- ("2022-blake3-chacha20-poly1305", "2022-blake3-chacha20-poly1305"),
181
- ], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
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.fields.SelectField(
223
+ field = wtf.SelectField(
185
224
  _(f"config.{c.key}.label"),
186
- choices=[("none", "None"),
187
- ("chrome", "Chrome"),
188
- ("edge", "Edge"),
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 = [("python", _("lib.telegram.python")), ("tgo", _("lib.telegram.go")),
201
- ("orig", _("lib.telegram.orignal")), ("erlang", _("lib.telegram.erlang"))]
202
- field = wtf.fields.SelectField(_("config.telegram_lib.label"), choices=libs, description=_(
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.fields.SelectField(_(f"config.{c.key}.label"), choices=choices,
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.fields.TextAreaField(_(f'config.{c.key}.label'), validators, default=c.value,
213
- description=_(f'config.{c.key}.description'), render_kw=render_kw)
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(hiddify.validate_domain_exist)
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(hiddify.validate_domain_exist)
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([cc.value.lower() for cc in StrConfig.query.filter(StrConfig.child_id == Child.current.id).all(
232
- ) if cc.key != c.key and "fakedomain" in cc.key and cc.key != ConfigEnum.decoy_domain], _("config.Domain already used")))
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
- field = wtf.fields.StringField(_(f'config.{c.key}.label'), validators, default=c.value,
282
- description=_(f'config.{c.key}.description') + extra_info, render_kw=render_kw)
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.fields.FormField(CategoryForm, Markup('<i class="fa-solid fa-plus"></i>&nbsp' + _(f'config.{cat}.label')))
330
+ multifield = wtf.FormField(CategoryForm, Markup('<i class="fa-solid fa-plus"></i>&nbsp' + _(f'config.{cat}.label')))
286
331
 
287
332
  setattr(DynamicForm, cat, multifield)
288
333
 
289
- setattr(DynamicForm, "submit", wtf.fields.SubmitField(_('Submit')))
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 Markup, g, request # type: ignore
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.ips):
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
 
@@ -148,7 +148,7 @@
148
148
  </div>
149
149
 
150
150
  </div>
151
- {% if hconfig(ConfigEnum.is_parent) %}
151
+ {% if hutils.node.is_parent() %}
152
152
  {% include 'parent_dash.html' %}
153
153
  {% endif %}
154
154
  {% endblock %}
@@ -7,26 +7,37 @@
7
7
  <summary>
8
8
  {{_("Send Message to User's Telegram")}}
9
9
  </summary>
10
- <button onclick="show_send_message('all')" type="button" class="btn hbtn bg-h-blue">{{_("All Users")}}</button>
11
- <button onclick="show_send_message('expired')" type="button" class="btn hbtn bg-h-red">
12
- {{_("Expired Users")}}</button>
13
- <button onclick="show_send_message('active')" type="button" class="btn hbtn bg-h-green">
14
- {{_("Active Users")}}</button>
15
- <br>
16
- <button onclick="show_send_message('offline 1h')" type="button" class="btn hbtn bg-h-orange">
17
- {{_("Offline more than 1 hour")}}</button>
18
- <button onclick="show_send_message('offline 1d')" type="button" class="btn hbtn bg-h-red">
19
- {{_("Offline more than 1 day")}}</button>
20
- <button onclick="show_send_message('offline 1w')" type="button" class="btn hbtn bg-h-grey">
21
- {{_("Offline more than 1 week")}}</button>
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 (result) {
78
- console.log(result);
79
- if (!result) return
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': result, 'id': id }),
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" }}">&nbsp{{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 ">
@@ -1,6 +1,5 @@
1
1
  import CloudFlare
2
-
3
- from hiddifypanel.models import *
2
+ from hiddifypanel.models import hconfig, ConfigEnum
4
3
 
5
4
 
6
5
  def add_or_update_domain(domain, ip, dns_type="A", proxied=True):