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.
Files changed (117) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +15 -4
  4. hiddifypanel/base.py +58 -50
  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 +2 -0
  10. hiddifypanel/drivers/user_driver.py +12 -6
  11. hiddifypanel/drivers/wireguard_api.py +2 -0
  12. hiddifypanel/drivers/xray_api.py +14 -9
  13. hiddifypanel/hutils/__init__.py +1 -0
  14. hiddifypanel/hutils/convert.py +13 -2
  15. hiddifypanel/hutils/crypto.py +21 -2
  16. hiddifypanel/hutils/flask.py +19 -5
  17. hiddifypanel/hutils/importer/xui.py +5 -2
  18. hiddifypanel/hutils/node/__init__.py +3 -0
  19. hiddifypanel/hutils/node/api_client.py +76 -0
  20. hiddifypanel/hutils/node/child.py +147 -0
  21. hiddifypanel/hutils/node/parent.py +100 -0
  22. hiddifypanel/hutils/node/shared.py +65 -0
  23. hiddifypanel/hutils/proxy/shared.py +15 -3
  24. hiddifypanel/models/__init__.py +2 -2
  25. hiddifypanel/models/admin.py +14 -2
  26. hiddifypanel/models/base_account.py +3 -3
  27. hiddifypanel/models/child.py +30 -16
  28. hiddifypanel/models/config.py +39 -15
  29. hiddifypanel/models/config_enum.py +55 -8
  30. hiddifypanel/models/domain.py +28 -20
  31. hiddifypanel/models/parent_domain.py +2 -2
  32. hiddifypanel/models/proxy.py +13 -4
  33. hiddifypanel/models/report.py +2 -3
  34. hiddifypanel/models/usage.py +2 -2
  35. hiddifypanel/models/user.py +18 -9
  36. hiddifypanel/panel/admin/Actions.py +4 -6
  37. hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
  38. hiddifypanel/panel/admin/Dashboard.py +5 -10
  39. hiddifypanel/panel/admin/DomainAdmin.py +12 -11
  40. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  41. hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
  42. hiddifypanel/panel/admin/SettingAdmin.py +60 -21
  43. hiddifypanel/panel/admin/UserAdmin.py +10 -2
  44. hiddifypanel/panel/admin/templates/index.html +1 -1
  45. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  46. hiddifypanel/panel/cli.py +16 -16
  47. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
  48. hiddifypanel/panel/commercial/__init__.py +7 -5
  49. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  50. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  51. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  52. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
  53. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  54. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  55. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  56. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
  57. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  58. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  59. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  60. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  61. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  62. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  63. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  64. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  65. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  66. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  67. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  68. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  69. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  70. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  71. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  72. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  73. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  74. hiddifypanel/panel/common.py +21 -6
  75. hiddifypanel/panel/hiddify.py +9 -80
  76. hiddifypanel/panel/init_db.py +84 -38
  77. hiddifypanel/panel/usage.py +33 -18
  78. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  79. hiddifypanel/panel/user/templates/new.html +2 -2
  80. hiddifypanel/static/css/custom.css +13 -0
  81. hiddifypanel/static/images/hiddify.png +0 -0
  82. hiddifypanel/static/images/hiddify2.png +0 -0
  83. hiddifypanel/static/new/assets/hiddify-logo-7617d937.png +0 -0
  84. hiddifypanel/static/new/assets/{index-4510b616.js → index-ccb9873c.js} +2 -2
  85. hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
  86. hiddifypanel/static/new/i18n/en.json +6 -6
  87. hiddifypanel/static/new/i18n/fa.json +1 -1
  88. hiddifypanel/templates/admin-layout.html +24 -40
  89. hiddifypanel/templates/fake.html +22 -0
  90. hiddifypanel/templates/master.html +24 -42
  91. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  92. hiddifypanel/translations/en/LC_MESSAGES/messages.po +95 -5
  93. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  94. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +96 -4
  95. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  96. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
  97. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  98. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +91 -1
  99. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  100. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
  101. hiddifypanel/translations.i18n/en.json +61 -5
  102. hiddifypanel/translations.i18n/fa.json +60 -4
  103. hiddifypanel/translations.i18n/pt.json +63 -7
  104. hiddifypanel/translations.i18n/ru.json +57 -1
  105. hiddifypanel/translations.i18n/zh.json +58 -2
  106. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
  107. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +112 -94
  108. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  109. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  110. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  111. hiddifypanel/static/new/assets/hiddify-logo-7617d937_old.png +0 -0
  112. hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
  113. /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
  114. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
  115. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
  116. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
  117. {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 Markup, g # type: ignore
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
- # if hconfig(ConfigEnum.parent_panel):
270
- # hiddify_api.sync_child_to_parent()
271
- pass
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 Markup, g, request
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, 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
@@ -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 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]:
46
49
 
47
- if isinstance(vs, dict):
50
+ if isinstance(c_items, dict):
48
51
  for k in ConfigEnum:
49
- if k.name not in vs:
52
+ if k.name not in c_items:
50
53
  continue
51
- v = vs[k.name]
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 vs.items():
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
- # if hconfig(ConfigEnum.parent_panel):
85
- # hiddify_api.sync_child_to_parent()
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 = hconfig(ConfigEnum.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 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
@@ -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> '
@@ -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)
@@ -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 %}
@@ -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 %}
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
- for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
42
- from hiddifypanel.panel.commercial.telegrambot import bot
43
- with open(dst, 'rb') as document:
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
- current_child_ids
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
- table = ParentDomain if hconfig(ConfigEnum.is_parent) else Domain
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 = table(domain=domain)
150
- if not hconfig(ConfigEnum.is_parent):
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, Markup
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
- # if hconfig(ConfigEnum.parent_panel):
49
- # hiddify_api.sync_child_to_parent()
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
- # if hconfig(ConfigEnum.parent_panel):
55
- # hiddify_api.sync_child_to_parent()
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 hello as api_v2_hello
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
- api_v2_hello.init_app(app)
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
 
@@ -32,8 +32,3 @@ def has_permission(model) -> bool:
32
32
  if not g.account.uuid != AdminUser.get_super_admin_uuid() and model.added_by != g.account.id: # type: ignore
33
33
  return False
34
34
  return True
35
-
36
-
37
- class SuccessfulSchema(Schema):
38
- status = Integer()
39
- msg = String()
@@ -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 .admin_user_api import AdminSchema
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 AdminLogfileSchema(Schema):
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(AdminLogfileSchema, arg_name="data", location='form')
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
- class PatchAdminSchema(AdminSchema):
30
- def __init__(self, *args, **kwargs):
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'}