hiddifypanel 10.14.0__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 (107) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +15 -4
  4. hiddifypanel/base.py +11 -3
  5. hiddifypanel/cache.py +43 -25
  6. hiddifypanel/database.py +9 -0
  7. hiddifypanel/drivers/singbox_api.py +2 -14
  8. hiddifypanel/drivers/xray_api.py +0 -4
  9. hiddifypanel/hutils/__init__.py +1 -0
  10. hiddifypanel/hutils/convert.py +13 -2
  11. hiddifypanel/hutils/crypto.py +21 -2
  12. hiddifypanel/hutils/flask.py +18 -4
  13. hiddifypanel/hutils/importer/xui.py +5 -2
  14. hiddifypanel/hutils/node/__init__.py +3 -0
  15. hiddifypanel/hutils/node/api_client.py +76 -0
  16. hiddifypanel/hutils/node/child.py +147 -0
  17. hiddifypanel/hutils/node/parent.py +100 -0
  18. hiddifypanel/hutils/node/shared.py +65 -0
  19. hiddifypanel/hutils/proxy/shared.py +15 -3
  20. hiddifypanel/models/__init__.py +2 -2
  21. hiddifypanel/models/admin.py +14 -2
  22. hiddifypanel/models/base_account.py +3 -3
  23. hiddifypanel/models/child.py +30 -16
  24. hiddifypanel/models/config.py +39 -15
  25. hiddifypanel/models/config_enum.py +55 -8
  26. hiddifypanel/models/domain.py +28 -20
  27. hiddifypanel/models/parent_domain.py +2 -2
  28. hiddifypanel/models/proxy.py +13 -4
  29. hiddifypanel/models/report.py +2 -3
  30. hiddifypanel/models/usage.py +2 -2
  31. hiddifypanel/models/user.py +13 -4
  32. hiddifypanel/panel/admin/Actions.py +4 -6
  33. hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
  34. hiddifypanel/panel/admin/Dashboard.py +5 -10
  35. hiddifypanel/panel/admin/DomainAdmin.py +12 -11
  36. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  37. hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
  38. hiddifypanel/panel/admin/SettingAdmin.py +60 -21
  39. hiddifypanel/panel/admin/UserAdmin.py +10 -2
  40. hiddifypanel/panel/admin/templates/index.html +1 -1
  41. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  42. hiddifypanel/panel/cli.py +16 -16
  43. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
  44. hiddifypanel/panel/commercial/__init__.py +7 -5
  45. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  46. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  47. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  48. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
  49. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  50. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  51. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  52. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
  53. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  54. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  55. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  56. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  57. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  58. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  59. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  60. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  61. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  62. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  63. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  64. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  65. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  66. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  67. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  68. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  69. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  70. hiddifypanel/panel/common.py +21 -6
  71. hiddifypanel/panel/hiddify.py +9 -80
  72. hiddifypanel/panel/init_db.py +43 -12
  73. hiddifypanel/panel/usage.py +28 -15
  74. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  75. hiddifypanel/panel/user/templates/new.html +1 -1
  76. hiddifypanel/static/css/custom.css +13 -0
  77. hiddifypanel/static/images/hiddify.png +0 -0
  78. hiddifypanel/static/new/assets/{index-bce9b1a6.js → index-ccb9873c.js} +65 -65
  79. hiddifypanel/templates/admin-layout.html +24 -40
  80. hiddifypanel/templates/fake.html +298 -0
  81. hiddifypanel/templates/master.html +23 -41
  82. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  83. hiddifypanel/translations/en/LC_MESSAGES/messages.po +90 -0
  84. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  85. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +91 -1
  86. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  87. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
  88. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  89. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +90 -0
  90. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  91. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
  92. hiddifypanel/translations.i18n/en.json +56 -0
  93. hiddifypanel/translations.i18n/fa.json +57 -1
  94. hiddifypanel/translations.i18n/pt.json +63 -7
  95. hiddifypanel/translations.i18n/ru.json +56 -0
  96. hiddifypanel/translations.i18n/zh.json +58 -2
  97. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
  98. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +104 -86
  99. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  100. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  101. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  102. /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
  103. /hiddifypanel/static/{new/assets/hiddify-logo-noroz-559c8dcb.png → images/hiddify2.png} +0 -0
  104. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
  105. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
  106. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
  107. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from typing import List
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  from flask import request
7
- from sqlalchemy_serializer import SerializerMixin
7
+
8
8
  from flask_babel import lazy_gettext as _
9
9
  from sqlalchemy.orm import backref
10
10
  from urllib.parse import urlparse
@@ -40,7 +40,7 @@ ShowDomain = db.Table('show_domain',
40
40
  )
41
41
 
42
42
 
43
- class Domain(db.Model, SerializerMixin):
43
+ class Domain(db.Model):
44
44
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
45
45
  child_id = db.Column(db.Integer, db.ForeignKey('child.id'), default=0)
46
46
  domain = db.Column(db.String(200), nullable=False, unique=False)
@@ -67,7 +67,7 @@ class Domain(db.Model, SerializerMixin):
67
67
  'domain': self.domain.lower(),
68
68
  'mode': self.mode,
69
69
  'alias': self.alias,
70
- # 'sub_link_only':d.sub_link_only,
70
+ 'sub_link_only': self.sub_link_only,
71
71
  'child_unique_id': self.child.unique_id if self.child else '',
72
72
  'cdn_ip': self.cdn_ip,
73
73
  'servernames': self.servernames,
@@ -84,6 +84,15 @@ class Domain(db.Model, SerializerMixin):
84
84
 
85
85
  return data
86
86
 
87
+ @staticmethod
88
+ def from_schema(schema):
89
+ return schema.dump(Domain())
90
+
91
+ def to_schema(self):
92
+ domain_dict = self.to_dict()
93
+ from hiddifypanel.panel.commercial.restapi.v2.parent.schema import DomainSchema
94
+ return DomainSchema().load(domain_dict)
95
+
87
96
  @property
88
97
  def need_valid_ssl(self):
89
98
  return self.mode in [DomainType.direct, DomainType.cdn, DomainType.worker, DomainType.relay, DomainType.auto_cdn_ip, DomainType.old_xtls_direct, DomainType.sub_link_only]
@@ -137,14 +146,22 @@ def get_domain(domain):
137
146
  return Domain.query.filter(Domain.domain == domain).first()
138
147
 
139
148
 
149
+ def get_panel_link(child_id: int | None = None) -> Domain | None:
150
+ if child_id is None:
151
+ child_id = Child.current().id
152
+ domains = Domain.query.filter(Domain.mode.in_(
153
+ [DomainType.direct, DomainType.cdn, DomainType.worker, DomainType.relay, DomainType.auto_cdn_ip, DomainType.old_xtls_direct, DomainType.sub_link_only]),
154
+ Domain.child_id == child_id
155
+ ).all()
156
+ if not domains:
157
+ return None
158
+ return domains[0]
159
+
160
+
140
161
  def get_panel_domains(always_add_ip=False, always_add_all_domains=False) -> List[Domain]:
141
162
  from hiddifypanel import hutils
142
163
  domains = []
143
- # if hconfig(ConfigEnum.is_parent):
144
- # from .parent_domain import ParentDomain
145
- # domains = ParentDomain.query.all()
146
- # else:
147
- domains = Domain.query.filter(Domain.mode == DomainType.sub_link_only, Domain.child_id == Child.current.id).all()
164
+ domains = Domain.query.filter(Domain.mode == DomainType.sub_link_only, Domain.child_id == Child.current().id).all()
148
165
  if not len(domains) or always_add_all_domains:
149
166
  domains = Domain.query.filter(Domain.mode.notin_([DomainType.fake, DomainType.reality])).all()
150
167
 
@@ -156,11 +173,7 @@ def get_panel_domains(always_add_ip=False, always_add_all_domains=False) -> List
156
173
 
157
174
 
158
175
  def get_proxy_domains(domain):
159
- # if hconfig(ConfigEnum.is_parent):
160
- # from hiddifypanel.models.parent_domain import ParentDomain
161
- # db_domain = ParentDomain.query.filter(ParentDomain.domain == domain).first() or ParentDomain(domain=domain, show_domains=[])
162
- # else:
163
- db_domain = Domain.query.filter(Domain.domain == domain, Domain.child_id == Child.current.id).first()
176
+ db_domain = Domain.query.filter(Domain.domain == domain, Domain.child_id == Child.current().id).first()
164
177
  if not db_domain:
165
178
  db_domain = Domain(domain=domain, mode=DomainType.direct, cdn_ip='', show_domains=[])
166
179
  return get_proxy_domains_db(db_domain)
@@ -202,11 +215,11 @@ def add_or_update_domain(commit=True, child_id=0, **domain):
202
215
  db.session.commit()
203
216
 
204
217
 
205
- def bulk_register_domains(domains, commit=True, remove=False, override_child_unique_id=None):
218
+ def bulk_register_domains(domains, commit=True, remove=False, force_child_unique_id: str | None = None):
206
219
  from hiddifypanel.panel import hiddify
207
220
  child_ids = {}
208
221
  for domain in domains:
209
- child_id = hiddify.get_child(unique_id=None)
222
+ child_id = hiddify.get_child(unique_id=force_child_unique_id)
210
223
  child_ids[child_id] = 1
211
224
  add_or_update_domain(commit=False, child_id=child_id, **domain)
212
225
  if remove and len(child_ids):
@@ -215,10 +228,5 @@ def bulk_register_domains(domains, commit=True, remove=False, override_child_uni
215
228
  if d.domain not in dd:
216
229
  db.session.delete(d)
217
230
 
218
- # if commit:
219
- db.session.commit()
220
- for domain in domains:
221
- child_id = hiddify.get_child(unique_id=None)
222
- add_or_update_domain(commit=False, child_id=child_id, **domain)
223
231
  if commit:
224
232
  db.session.commit()
@@ -1,6 +1,6 @@
1
1
  # from hiddifypanel.models.domain import Domain
2
2
  # from sqlalchemy.orm import backref
3
- # from sqlalchemy_serializer import SerializerMixin
3
+ #
4
4
 
5
5
  # from hiddifypanel.database import db
6
6
 
@@ -10,7 +10,7 @@
10
10
  # )
11
11
 
12
12
 
13
- # class ParentDomain(db.Model, SerializerMixin):
13
+ # class ParentDomain(db.Model):
14
14
  # id = db.Column(db.Integer, primary_key=True, autoincrement=True)
15
15
  # domain = db.Column(db.String(200), nullable=False, unique=True)
16
16
  # alias = db.Column(db.String(200), nullable=False, unique=False)
@@ -1,4 +1,4 @@
1
- from sqlalchemy_serializer import SerializerMixin
1
+
2
2
  from strenum import StrEnum
3
3
  from enum import auto
4
4
  from sqlalchemy import Column, String, Integer, Boolean, Enum, ForeignKey
@@ -57,7 +57,7 @@ class ProxyL3(StrEnum):
57
57
  custom = auto()
58
58
 
59
59
 
60
- class Proxy(db.Model, SerializerMixin): # type: ignore
60
+ class Proxy(db.Model): # type: ignore
61
61
  id = Column(Integer, primary_key=True, autoincrement=True)
62
62
  child_id = Column(Integer, ForeignKey('child.id'), default=0)
63
63
  name = Column(String(200), nullable=False, unique=False)
@@ -102,10 +102,19 @@ class Proxy(db.Model, SerializerMixin): # type: ignore
102
102
  db.session.commit() # type: ignore
103
103
 
104
104
  @staticmethod
105
- def bulk_register(proxies, commit=True, override_child_unique_id=None):
105
+ def from_schema(schema):
106
+ return schema.dump(Proxy())
107
+
108
+ def to_schema(self):
109
+ proxy_dict = self.to_dict()
110
+ from hiddifypanel.panel.commercial.restapi.v2.parent.schema import ProxySchema
111
+ return ProxySchema().load(proxy_dict)
112
+
113
+ @staticmethod
114
+ def bulk_register(proxies, commit=True, force_child_unique_id: str | None = None):
106
115
  from hiddifypanel.panel import hiddify
107
116
  for proxy in proxies:
108
- child_id = hiddify.get_child(unique_id=None)
117
+ child_id = hiddify.get_child(unique_id=force_child_unique_id)
109
118
  Proxy.add_or_update(commit=False, child_id=child_id, **proxy)
110
119
  if commit:
111
120
  db.session.commit() # type: ignore
@@ -1,11 +1,10 @@
1
1
  import datetime
2
2
 
3
- from sqlalchemy_serializer import SerializerMixin
4
3
 
5
4
  from hiddifypanel.database import db
6
5
 
7
6
 
8
- class Report(db.Model, SerializerMixin):
7
+ class Report(db.Model):
9
8
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
10
9
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'), default=0, nullable=False)
11
10
  asn_id = db.Column(db.String(200), nullable=False, unique=False)
@@ -19,7 +18,7 @@ class Report(db.Model, SerializerMixin):
19
18
  details = db.relationship('ReportDetail', cascade="all,delete", backref='report', lazy='dynamic',)
20
19
 
21
20
 
22
- class ReportDetail(db.Model, SerializerMixin):
21
+ class ReportDetail(db.Model):
23
22
  report_id = db.Column(db.Integer, db.ForeignKey('report.id'), primary_key=True, )
24
23
  proxy_id = db.Column(db.Integer, db.ForeignKey('proxy.id'), primary_key=True, )
25
24
  ping = db.Column(db.Integer, default=-1)
@@ -3,12 +3,12 @@ from datetime import timedelta, date
3
3
 
4
4
  from flask import g
5
5
  from sqlalchemy import func
6
- from sqlalchemy_serializer import SerializerMixin
6
+
7
7
 
8
8
  from hiddifypanel.database import db
9
9
 
10
10
 
11
- class DailyUsage(db.Model, SerializerMixin):
11
+ class DailyUsage(db.Model):
12
12
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
13
13
  date = db.Column(db.Date, default=datetime.date.today(), index=True)
14
14
  usage = db.Column(db.BigInteger, default=0, nullable=False)
@@ -2,7 +2,7 @@ import datetime
2
2
  from enum import auto
3
3
  from hiddifypanel.models.role import Role
4
4
  from dateutil import relativedelta
5
- from sqlalchemy_serializer import SerializerMixin
5
+
6
6
  from strenum import StrEnum
7
7
  from sqlalchemy import event
8
8
 
@@ -35,7 +35,7 @@ package_mode_dic = {
35
35
  }
36
36
 
37
37
 
38
- class UserDetail(db.Model, SerializerMixin):
38
+ class UserDetail(db.Model):
39
39
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
40
40
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'), default=0, nullable=False)
41
41
  child_id = db.Column(db.Integer, db.ForeignKey('child.id'), default=0, nullable=False)
@@ -121,12 +121,12 @@ class User(BaseAccount):
121
121
  is_active = False
122
122
  elif self.remaining_days < 0:
123
123
  is_active = False
124
- elif len(self.ips) > max(3, self.max_ips):
124
+ elif len(self.devices) > max(3, self.max_ips):
125
125
  is_active = False
126
126
  return is_active
127
127
 
128
128
  @property
129
- def ips(self):
129
+ def devices(self):
130
130
  res = {}
131
131
  for detail in UserDetail.query.filter(UserDetail.user_id == self.id):
132
132
  for device in detail.devices:
@@ -256,6 +256,15 @@ class User(BaseAccount):
256
256
  db.session.commit()
257
257
  return dbuser
258
258
 
259
+ @staticmethod
260
+ def form_schema(schema):
261
+ return schema.dump(User())
262
+
263
+ def to_schema(self):
264
+ user_dict = self.to_dict()
265
+ from hiddifypanel.panel.commercial.restapi.v2.admin.user_api import UserSchema
266
+ return UserSchema().load(user_dict)
267
+
259
268
  def to_dict(self, convert_date=True, dump_id=False) -> dict:
260
269
  base = super().to_dict()
261
270
  from hiddifypanel import hutils
@@ -61,11 +61,9 @@ class Actions(FlaskView):
61
61
  def reinstall2(self, complete_install=True, domain_changed=False):
62
62
  if int(hconfig(ConfigEnum.db_version)) < 9:
63
63
  return ("Please update your panel before this action.")
64
- # if hconfig(ConfigEnum.parent_panel):
65
- # try:
66
- # hiddify_api.sync_child_to_parent()
67
- # except e as Exception:
68
- # hutils.flask.flash(_('can not sync child with parent panel')+" "+e)
64
+ if hutils.node.is_child():
65
+ if not hutils.node.child.sync_with_parent():
66
+ hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
69
67
 
70
68
  domain_changed = request.args.get("domain_changed", str(domain_changed)).lower() == "true"
71
69
  complete_install = request.args.get("complete_install", str(complete_install)).lower() == "true"
@@ -110,7 +108,7 @@ class Actions(FlaskView):
110
108
 
111
109
  @login_required(roles={Role.super_admin})
112
110
  def change_reality_keys(self):
113
- key = hiddify.generate_x25519_keys()
111
+ key = hutils.crypto.generate_x25519_keys()
114
112
  set_hconfig(ConfigEnum.reality_private_key, key['private_key'])
115
113
  set_hconfig(ConfigEnum.reality_public_key, key['public_key'])
116
114
  hutils.flask.flash_config_success(restart_mode=ApplyMode.restart, domain_changed=False)
@@ -7,7 +7,9 @@ from .adminlte import AdminLTEModelView
7
7
  from flask_babel import lazy_gettext as _
8
8
  from wtforms.validators import Regexp
9
9
  from flask_babel import gettext as __
10
- from flask import Markup, request # type: ignore
10
+ from flask import request # type: ignore
11
+ from markupsafe import Markup
12
+
11
13
  from flask import g
12
14
  import datetime
13
15
  from wtforms import SelectField
@@ -37,7 +39,7 @@ class SubAdminsField(SelectField):
37
39
 
38
40
  class AdminstratorAdmin(AdminLTEModelView):
39
41
  column_hide_backrefs = False
40
- column_list = ["name", 'UserLinks', 'mode', 'can_add_admin', 'max_active_users', 'max_users', 'online_users', 'comment']
42
+ column_list = ["name", 'UserLinks', 'mode', 'can_add_admin', 'max_active_users', 'max_users', 'online_users', 'comment',]
41
43
  form_columns = ["name", 'mode', 'can_add_admin', 'max_active_users', 'max_users', 'comment', "uuid"]
42
44
  list_template = 'model/admin_list.html'
43
45
  # column_editable_list = ['name']
@@ -215,6 +217,7 @@ class AdminstratorAdmin(AdminLTEModelView):
215
217
  model.remove()
216
218
 
217
219
  def get_query_for_parent_admin(self):
220
+ # WHAT IS THIS?
218
221
  admin_user_id = self.get_pk_value()
219
222
  sub_admins_ids = set(recursive_sub_admins_ids(AdminUser.query.get(admin_user_id)))
220
223
  return AdminUser.query.filter(AdminUser.id.in_(sub_admins_ids)).with_entities(AdminUser.id, AdminUser.name)
@@ -236,3 +239,11 @@ class AdminstratorAdmin(AdminLTEModelView):
236
239
  del form.max_users
237
240
  del form.max_active_users
238
241
  del form.can_add_admin
242
+
243
+ def after_model_change(self, form, model, is_created):
244
+ if hutils.node.is_parent():
245
+ hutils.node.parent.send_sync_req_to_childs()
246
+
247
+ def after_model_delete(self, model):
248
+ if hutils.node.is_parent():
249
+ hutils.node.parent.send_sync_req_to_childs()
@@ -18,9 +18,9 @@ class Dashboard(FlaskView):
18
18
 
19
19
  @login_required(roles={Role.super_admin, Role.admin, Role.agent})
20
20
  def index(self):
21
-
22
21
  if hconfig(ConfigEnum.first_setup):
23
22
  return redirect(hurl_for("admin.QuickSetup:index"))
23
+
24
24
  if hiddifypanel.__release_date__ + datetime.timedelta(days=20) < datetime.datetime.now():
25
25
  hutils.flask.flash(_('This version of hiddify panel is outdated. Please update it from admin area.'), "danger") # type: ignore
26
26
  bot = None
@@ -34,20 +34,15 @@ class Dashboard(FlaskView):
34
34
  user_query = User.query
35
35
  if admin_id:
36
36
  user_query = user_query.filter(User.added_by == admin_id)
37
- if hconfig(ConfigEnum.is_parent):
37
+ if hutils.node.is_parent():
38
38
  childs = Child.query.filter(Child.id != 0).all()
39
39
  for c in childs:
40
40
  c.is_active = False
41
41
  for d in c.domains:
42
- if d.mode == DomainType.fake:
43
- continue
44
- remote = hiddify.get_account_panel_link(g.account, d.domain, child_id=c.id)
45
- d.is_active = hutils.network.check_connection_to_remote(remote)
42
+ d.is_active = hutils.node.parent.is_child_domain_active(c, d)
46
43
  if d.is_active:
47
44
  c.is_active = True
48
45
 
49
- # return render_template('parent_dash.html',childs=childs,bot=bot)
50
- # try:
51
46
  def_user = None if len(User.query.all()) > 1 else User.query.filter(User.name == 'default').first()
52
47
  domains = get_panel_domains()
53
48
  sslip_domains = [d.domain for d in domains if "sslip.io" in d.domain]
@@ -56,13 +51,13 @@ class Dashboard(FlaskView):
56
51
  quick_setup = hurl_for("admin.QuickSetup:index")
57
52
  hutils.flask.flash((_('It seems that you have not setup the system completely. <a class="btn btn-success" href="%(quick_setup)s">Click here</a> to complete setup.',
58
53
  quick_setup=quick_setup)), 'warning') # type: ignore
59
- if hconfig(ConfigEnum.is_parent):
54
+ if hutils.node.is_parent():
60
55
  hutils.flask.flash(
61
56
  _("Please understand that parent panel is under test and the plan and the condition of use maybe change at anytime."), "danger") # type: ignore
62
57
  elif len(sslip_domains):
63
58
  hutils.flask.flash((_('It seems that you are using default domain (%(domain)s) which is not recommended.',
64
59
  domain=sslip_domains[0])), 'warning') # type: ignore
65
- if hconfig(ConfigEnum.is_parent):
60
+ if hutils.node.is_parent():
66
61
  hutils.flask.flash(
67
62
  _("Please understand that parent panel is under test and the plan and the condition of use maybe change at anytime."), "danger") # type: ignore
68
63
  elif def_user:
@@ -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: