hiddifypanel 10.20.3__py3-none-any.whl → 10.30.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 (82) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/base.py +17 -8
  4. hiddifypanel/cache.py +2 -51
  5. hiddifypanel/drivers/wireguard_api.py +24 -5
  6. hiddifypanel/hutils/convert.py +1 -1
  7. hiddifypanel/hutils/flask.py +28 -2
  8. hiddifypanel/hutils/importer/xui.py +6 -7
  9. hiddifypanel/hutils/network/__init__.py +1 -0
  10. hiddifypanel/hutils/network/cf_api.py +84 -0
  11. hiddifypanel/hutils/network/net.py +26 -49
  12. hiddifypanel/hutils/node/child.py +25 -7
  13. hiddifypanel/hutils/node/parent.py +7 -7
  14. hiddifypanel/hutils/node/shared.py +19 -6
  15. hiddifypanel/hutils/proxy/clash.py +1 -1
  16. hiddifypanel/hutils/proxy/shared.py +1 -1
  17. hiddifypanel/hutils/proxy/singbox.py +2 -3
  18. hiddifypanel/hutils/proxy/xray.py +12 -10
  19. hiddifypanel/hutils/proxy/xrayjson.py +26 -49
  20. hiddifypanel/hutils/utils.py +47 -3
  21. hiddifypanel/models/__init__.py +1 -1
  22. hiddifypanel/models/admin.py +9 -2
  23. hiddifypanel/models/base_account.py +3 -1
  24. hiddifypanel/models/config.py +5 -7
  25. hiddifypanel/models/config_enum.py +18 -6
  26. hiddifypanel/models/domain.py +82 -118
  27. hiddifypanel/models/user.py +44 -24
  28. hiddifypanel/panel/admin/Actions.py +6 -11
  29. hiddifypanel/panel/admin/AdminstratorAdmin.py +3 -9
  30. hiddifypanel/panel/admin/Backup.py +5 -8
  31. hiddifypanel/panel/admin/Dashboard.py +3 -4
  32. hiddifypanel/panel/admin/DomainAdmin.py +20 -15
  33. hiddifypanel/panel/admin/ProxyAdmin.py +3 -10
  34. hiddifypanel/panel/admin/QuickSetup.py +1 -1
  35. hiddifypanel/panel/admin/SettingAdmin.py +7 -5
  36. hiddifypanel/panel/admin/Terminal.py +0 -1
  37. hiddifypanel/panel/admin/UserAdmin.py +4 -3
  38. hiddifypanel/panel/cli.py +36 -23
  39. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +2 -4
  40. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +7 -4
  41. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +17 -13
  42. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +4 -3
  43. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +28 -10
  44. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +2 -19
  45. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +27 -4
  46. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +28 -9
  47. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -21
  48. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +1 -1
  49. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +8 -4
  50. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +19 -3
  51. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +48 -42
  52. hiddifypanel/panel/commercial/telegrambot/Usage.py +1 -1
  53. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -1
  54. hiddifypanel/panel/commercial/telegrambot/information.py +1 -1
  55. hiddifypanel/panel/common.py +5 -11
  56. hiddifypanel/panel/hiddify.py +9 -20
  57. hiddifypanel/panel/init_db.py +31 -13
  58. hiddifypanel/panel/usage.py +38 -9
  59. hiddifypanel/panel/user/user.py +52 -32
  60. hiddifypanel/templates/admin-layout.html +2 -2
  61. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  62. hiddifypanel/translations/en/LC_MESSAGES/messages.po +80 -25
  63. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  64. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +74 -20
  65. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  66. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +60 -6
  67. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  68. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +158 -78
  69. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  70. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +60 -6
  71. hiddifypanel/translations.i18n/en.json +62 -22
  72. hiddifypanel/translations.i18n/fa.json +57 -17
  73. hiddifypanel/translations.i18n/pt.json +43 -3
  74. hiddifypanel/translations.i18n/ru.json +112 -72
  75. hiddifypanel/translations.i18n/zh.json +43 -3
  76. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/METADATA +2 -1
  77. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/RECORD +81 -81
  78. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/WHEEL +1 -1
  79. hiddifypanel/panel/cf_api.py +0 -37
  80. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/LICENSE.md +0 -0
  81. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/entry_points.txt +0 -0
  82. {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,16 @@
1
1
  from enum import auto
2
- from typing import List
3
-
4
- from urllib.parse import urlparse
5
-
2
+ from typing import Dict, List
6
3
  from flask import request
7
-
8
4
  from flask_babel import lazy_gettext as _
9
5
  from sqlalchemy.orm import backref
10
- from urllib.parse import urlparse
11
6
  from strenum import StrEnum
7
+ from sqlalchemy_serializer import SerializerMixin
12
8
 
13
- from hiddifypanel.database import db
14
9
 
10
+ from hiddifypanel.database import db
15
11
  from hiddifypanel.models.config import hconfig
16
12
  from .child import Child
17
13
  from hiddifypanel.models.config_enum import ConfigEnum
18
- from sqlalchemy.orm import backref
19
- from sqlalchemy_serializer import SerializerMixin
20
14
 
21
15
 
22
16
  class DomainType(StrEnum):
@@ -69,11 +63,11 @@ class Domain(db.Model, SerializerMixin):
69
63
  'mode': self.mode,
70
64
  'alias': self.alias,
71
65
  'sub_link_only': self.sub_link_only,
72
- 'child_unique_id': self.child.unique_id if self.child else '',
66
+ 'child_unique_id': self.child.unique_id if self.child else '', # type: ignore
73
67
  'cdn_ip': self.cdn_ip,
74
68
  'servernames': self.servernames,
75
69
  'grpc': self.grpc,
76
- 'show_domains': [dd.domain for dd in self.show_domains],
70
+ 'show_domains': [dd.domain for dd in self.show_domains], # type: ignore
77
71
  }
78
72
  if dump_child_id:
79
73
  data['child_id'] = self.child_id
@@ -124,110 +118,80 @@ class Domain(db.Model, SerializerMixin):
124
118
  # TODO: check validity of the range of the port
125
119
  return int(hconfig(ConfigEnum.reality_port, self.child_id)) + self.port_index
126
120
 
127
-
128
- def hdomains(mode):
129
- domains = Domain.query.filter(Domain.mode == mode).all()
130
- if domains:
131
- return [d.domain for d in domains]
132
- return []
133
-
134
-
135
- def hdomain(mode):
136
- domains = hdomains(mode)
137
- if domains:
138
- return domains[0]
139
- return None
140
-
141
-
142
- def get_hdomains():
143
- return {mode: hdomains(mode) for mode in DomainType}
144
-
145
-
146
- def get_domain(domain):
147
- return Domain.query.filter(Domain.domain == domain).first()
148
-
149
-
150
- def get_panel_link(child_id: int | None = None) -> Domain | None:
151
- if child_id is None:
152
- child_id = Child.current().id
153
- domains = Domain.query.filter(Domain.mode.in_(
154
- [DomainType.direct, DomainType.cdn, DomainType.worker, DomainType.relay, DomainType.auto_cdn_ip, DomainType.old_xtls_direct, DomainType.sub_link_only]),
155
- Domain.child_id == child_id
156
- ).all()
157
- if not domains:
158
- return None
159
- return domains[0]
160
-
161
-
162
- def get_panel_domains(always_add_ip=False, always_add_all_domains=False) -> List[Domain]:
163
- from hiddifypanel import hutils
164
- domains = []
165
- domains = Domain.query.filter(Domain.mode == DomainType.sub_link_only, Domain.child_id == Child.current().id).all()
166
- if not len(domains) or always_add_all_domains:
167
- domains = Domain.query.filter(Domain.mode.notin_([DomainType.fake, DomainType.reality])).all()
168
-
169
- if len(domains) == 0 and request:
170
- domains = [Domain(domain=request.host)]
171
- if len(domains) == 0 or always_add_ip:
172
- domains += [Domain(domain=hutils.network.get_ip_str(4))]
173
- return domains
174
-
175
-
176
- def get_proxy_domains(domain):
177
- db_domain = Domain.query.filter(Domain.domain == domain, Domain.child_id == Child.current().id).first()
178
- if not db_domain:
179
- db_domain = Domain(domain=domain, mode=DomainType.direct, cdn_ip='', show_domains=[])
180
- return get_proxy_domains_db(db_domain)
181
-
182
-
183
- def get_proxy_domains_db(db_domain):
184
-
185
- if not db_domain:
186
- domain = urlparse(request.base_url).hostname
187
- db_domain = Domain(domain=domain, mode=DomainType.direct, show_domains=[])
188
- # print("no domain")
189
- hutils.flask.flash(_("This domain does not exist in the panel!" + domain)) # type: ignore
190
-
191
- return db_domain.show_domains or Domain.query.all()
192
-
193
-
194
- def get_current_proxy_domains(force_domain=None):
195
- domain = force_domain or urlparse(request.base_url).hostname
196
- return get_proxy_domains(domain)
197
-
198
-
199
- def add_or_update_domain(commit=True, child_id=0, **domain):
200
- dbdomain = Domain.query.filter(Domain.domain == domain['domain']).first()
201
- if not dbdomain:
202
- dbdomain = Domain(domain=domain['domain'])
203
- db.session.add(dbdomain)
204
- dbdomain.child_id = child_id
205
-
206
- dbdomain.mode = domain['mode']
207
- if (str(domain.get('sub_link_only', False)).lower() == 'true'):
208
- dbdomain.mode = DomainType.sub_link_only
209
- dbdomain.cdn_ip = domain.get('cdn_ip', '')
210
- dbdomain.alias = domain.get('alias', '')
211
- dbdomain.grpc = domain.get('grpc', False)
212
- dbdomain.servernames = domain.get('servernames', '')
213
- show_domains = domain.get('show_domains', [])
214
- dbdomain.show_domains = Domain.query.filter(Domain.domain.in_(show_domains)).all()
215
- if commit:
216
- db.session.commit()
217
-
218
-
219
- def bulk_register_domains(domains, commit=True, remove=False, force_child_unique_id: str | None = None):
220
- from hiddifypanel.panel import hiddify
221
- child_ids = {}
222
- for domain in domains:
223
- child_id = hiddify.get_child(unique_id=force_child_unique_id)
224
- child_ids[child_id] = 1
225
- add_or_update_domain(commit=False, child_id=child_id, **domain)
226
- if remove and len(child_ids):
227
- dd = {d['domain']: 1 for d in domains}
228
- for d in Domain.query.filter(Domain.child_id.in_(child_ids)):
229
- if d.domain not in dd:
230
- db.session.delete(d)
231
-
232
- if commit:
233
- db.session.commit()
121
+ @classmethod
122
+ def by_mode(cls, mode: DomainType) -> List['Domain']:
123
+ domains = Domain.query.filter(Domain.mode == mode).all()
124
+ if domains:
125
+ return [d.domain for d in domains]
126
+ return []
127
+
128
+ @classmethod
129
+ def modes_and_domains(cls) -> Dict[DomainType, List['Domain']]:
130
+ return {mode: cls.by_mode(mode) for mode in DomainType}
131
+
132
+ @classmethod
133
+ def by_domain(cls, domain: str) -> 'Domain | None':
134
+ return Domain.query.filter(Domain.domain == domain).first()
135
+
136
+ @classmethod
137
+ def get_panel_link(cls, child_id: int | None = None) -> str | None:
138
+ if child_id is None:
139
+ child_id = Child.current().id # type: ignore
140
+ domains = Domain.query.filter(Domain.mode.in_(
141
+ [DomainType.direct, DomainType.cdn, DomainType.worker, DomainType.relay, DomainType.auto_cdn_ip, DomainType.old_xtls_direct, DomainType.sub_link_only]),
142
+ Domain.child_id == child_id
143
+ ).all()
144
+ if not domains:
145
+ return None
146
+ return domains[0].domain
147
+
148
+ @classmethod
149
+ def get_domains(cls, always_add_ip=False, always_add_all_domains=False) -> List['Domain']:
150
+ from hiddifypanel import hutils
151
+ domains = []
152
+ domains = Domain.query.filter(Domain.mode == DomainType.sub_link_only, Domain.child_id == Child.current().id).all()
153
+ if not len(domains) or always_add_all_domains:
154
+ domains = Domain.query.filter(Domain.mode.notin_([DomainType.fake, DomainType.reality])).all()
155
+
156
+ if len(domains) == 0 and request:
157
+ domains = [Domain(domain=request.host)] # type: ignore
158
+ if len(domains) == 0 or always_add_ip:
159
+ domains += [Domain(domain=hutils.network.get_ip_str(4))] # type: ignore
160
+ return domains
161
+
162
+ @classmethod
163
+ def add_or_update(cls, commit=True, child_id=0, **domain):
164
+ dbdomain = Domain.query.filter(Domain.domain == domain['domain']).first()
165
+ if not dbdomain:
166
+ dbdomain = Domain(domain=domain['domain']) # type: ignore
167
+ db.session.add(dbdomain)
168
+ dbdomain.child_id = child_id
169
+
170
+ dbdomain.mode = domain['mode']
171
+ if (str(domain.get('sub_link_only', False)).lower() == 'true'):
172
+ dbdomain.mode = DomainType.sub_link_only
173
+ dbdomain.cdn_ip = domain.get('cdn_ip', '')
174
+ dbdomain.alias = domain.get('alias', '')
175
+ dbdomain.grpc = domain.get('grpc', False)
176
+ dbdomain.servernames = domain.get('servernames', '')
177
+ show_domains = domain.get('show_domains', [])
178
+ dbdomain.show_domains = Domain.query.filter(Domain.domain.in_(show_domains)).all()
179
+ if commit:
180
+ db.session.commit()
181
+
182
+ @classmethod
183
+ def bulk_register(cls, domains, commit=True, remove=False, force_child_unique_id: str | None = None):
184
+ from hiddifypanel.panel import hiddify
185
+ child_ids = {}
186
+ for domain in domains:
187
+ child_id = hiddify.get_child(unique_id=force_child_unique_id)
188
+ child_ids[child_id] = 1
189
+ cls.add_or_update(commit=False, child_id=child_id, **domain)
190
+ if remove and len(child_ids):
191
+ dd = {d['domain']: 1 for d in domains}
192
+ for d in Domain.query.filter(Domain.child_id.in_(child_ids)):
193
+ if d.domain not in dd:
194
+ db.session.delete(d)
195
+
196
+ if commit:
197
+ db.session.commit()
@@ -65,7 +65,7 @@ class User(BaseAccount, SerializerMixin):
65
65
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
66
66
  last_online = db.Column(db.DateTime, nullable=False, default=datetime.datetime.min)
67
67
  # removed
68
- expiry_time = db.Column(db.Date, default=datetime.date.today() + relativedelta.relativedelta(months=6))
68
+ # expiry_time = db.Column(db.Date, default=datetime.date.today() + relativedelta.relativedelta(months=6))
69
69
  usage_limit = db.Column(db.BigInteger, default=1000 * ONE_GIG, nullable=False)
70
70
  package_days = db.Column(db.Integer, default=90, nullable=False)
71
71
  mode = db.Column(db.Enum(UserMode), default=UserMode.no_reset, nullable=False)
@@ -138,15 +138,23 @@ class User(BaseAccount, SerializerMixin):
138
138
  # print("start_date",user.start_date, "pack",package_mode_dic.get(user.mode,10000), "total",(datetime.date.today()-user.start_date).days)
139
139
  # if user.mode==UserMode.daily:
140
140
  # return 0
141
- res = True
142
141
  if not self.last_reset_time:
143
- res = True
142
+ return True
144
143
  elif not self.start_date or (datetime.date.today() - self.last_reset_time).days == 0:
145
- res = False
146
- else:
147
- res = ((datetime.date.today() - self.start_date).days % package_mode_dic.get(self.mode, 10000)) == 0
144
+ return False
145
+ return ((datetime.date.today() - self.start_date).days % package_mode_dic.get(self.mode, 10000)) == 0
146
+
147
+ def reset_usage(self, commit: bool = False):
148
+ '''Resets the user usages'''
149
+ self.last_reset_time = datetime.date.today()
150
+ self.current_usage_GB = 0
151
+
152
+ # there's no usage of UserDetail yet, but we reset it too
153
+ # if ud := UserDetail.query.filter(UserDetail.user_id == self.id).first():
154
+ # ud.current_usage_GB = 0
148
155
 
149
- return res
156
+ if commit:
157
+ db.session.commit()
150
158
 
151
159
  def days_to_reset(self):
152
160
  """
@@ -190,6 +198,8 @@ class User(BaseAccount, SerializerMixin):
190
198
 
191
199
  @classmethod
192
200
  def by_uuid(cls, uuid: str, create: bool = False) -> 'User':
201
+ if not isinstance(uuid, str):
202
+ uuid = str(uuid)
193
203
  account = User.query.filter(User.uuid == uuid).first()
194
204
  if not account and create:
195
205
  dbuser = User(uuid=uuid, name="unknown", added_by=AdminUser.current_admin_or_owner().id)
@@ -213,38 +223,44 @@ class User(BaseAccount, SerializerMixin):
213
223
  else:
214
224
  dbuser.added_by = 1
215
225
 
216
- if data.get('expiry_time', ''):
217
- last_reset_time = hutils.convert.json_to_date(data.get('last_reset_time', '')) or datetime.date.today()
218
-
219
- expiry_time = hutils.convert.json_to_date(data['expiry_time'])
220
- dbuser.start_date = last_reset_time
221
- dbuser.package_days = (expiry_time - last_reset_time).days # type: ignore
226
+ # if data.get('expiry_time', ''): #v4
227
+ # last_reset_time = hutils.convert.json_to_time(data.get('last_reset_time', '')) or datetime.date.today()
222
228
 
223
- elif 'package_days' in data:
229
+ # expiry_time = hutils.convert.json_to_date(data['expiry_time'])
230
+ # dbuser.start_date = last_reset_time
231
+ # dbuser.package_days = (expiry_time - last_reset_time).days # type: ignore
232
+ # el
233
+ if 'package_days' in data:
224
234
  dbuser.package_days = data['package_days']
225
235
  if data.get('start_date', ''):
226
236
  dbuser.start_date = hutils.convert.json_to_date(data['start_date'])
227
237
  else:
228
238
  dbuser.start_date = None
229
- dbuser.current_usage_GB = data.get('current_usage_GB', 0)
230
239
 
231
- dbuser.usage_limit_GB = data.get('usage_limit_GB', 1000)
240
+ if (c_GB := data.get('current_usage_GB')) is not None:
241
+ dbuser.current_usage_GB = c_GB
242
+ elif (c := data.get('current_usage')) is not None:
243
+ dbuser.current_usage = c
244
+ else:
245
+ dbuser.current_usage = 0
246
+
247
+ if (l_GB := data.get('usage_limit_GB')) is not None:
248
+ dbuser.usage_limit_GB = l_GB
249
+ elif (l := data.get('usage_limit')) is not None:
250
+ dbuser.usage_limit = l
251
+ else:
252
+ dbuser.usage_limit_GB = 1000
253
+
232
254
  dbuser.enable = data.get('enable', True)
255
+
233
256
  if data.get('ed25519_private_key', ''):
234
257
  dbuser.ed25519_private_key = data.get('ed25519_private_key', '')
235
258
  dbuser.ed25519_public_key = data.get('ed25519_public_key', '')
236
- # if not dbuser.ed25519_private_key:
237
- # priv, publ = hutils.crypto.get_ed25519_private_public_pair()
238
- # dbuser.ed25519_private_key = priv
239
- # dbuser.ed25519_public_key = publ
240
259
 
241
260
  dbuser.wg_pk = data.get('wg_pk', dbuser.wg_pk)
242
261
  dbuser.wg_pub = data.get('wg_pub', dbuser.wg_pub)
243
262
  dbuser.wg_psk = data.get('wg_psk', dbuser.wg_psk)
244
263
 
245
- # if not dbuser.wg_pk:
246
- # dbuser.wg_pk, dbuser.wg_pub, dbuser.wg_psk = hutils.crypto.get_wg_private_public_psk_pair()
247
-
248
264
  mode = data.get('mode', UserMode.no_reset)
249
265
  if mode == 'disable':
250
266
  mode = UserMode.no_reset
@@ -271,6 +287,9 @@ class User(BaseAccount, SerializerMixin):
271
287
  from hiddifypanel import hutils
272
288
  if dump_id:
273
289
  base['id'] = self.id
290
+ if not base.get('lang'):
291
+ from hiddifypanel.models import hconfig, ConfigEnum
292
+ base['lang'] = hconfig(ConfigEnum.lang)
274
293
  return {**base,
275
294
  'last_online': hutils.convert.time_to_json(self.last_online) if convert_date else self.last_online,
276
295
  'usage_limit_GB': self.usage_limit_GB,
@@ -278,7 +297,8 @@ class User(BaseAccount, SerializerMixin):
278
297
  'mode': self.mode,
279
298
  'start_date': hutils.convert.date_to_json(self.start_date)if convert_date else self.start_date,
280
299
  'current_usage_GB': self.current_usage_GB,
281
- 'last_reset_time': hutils.convert.date_to_json(self.last_reset_time) if convert_date else self.last_reset_time,
300
+ 'last_reset_time': hutils.convert.time_to_json(self.last_reset_time) if convert_date else self.last_reset_time,
301
+ # 'expiry_time': hutils.convert.date_to_json(self.expiry_time) if convert_date else self.expiry_time,
282
302
  'added_by_uuid': self.admin.uuid if self.admin else None,
283
303
  'ed25519_private_key': self.ed25519_private_key,
284
304
  'ed25519_public_key': self.ed25519_public_key,
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env python3
2
- import os
3
1
  import urllib.request
4
-
2
+ import json
5
3
  from flask_classful import FlaskView, route
6
4
  from flask import render_template, request, redirect, g
7
5
  from hiddifypanel.hutils.flask import hurl_for
@@ -62,8 +60,7 @@ class Actions(FlaskView):
62
60
  if int(hconfig(ConfigEnum.db_version)) < 9:
63
61
  return ("Please update your panel before this action.")
64
62
  if hutils.node.is_child():
65
- if not hutils.node.child.sync_with_parent():
66
- hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
63
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent)
67
64
 
68
65
  domain_changed = request.args.get("domain_changed", str(domain_changed)).lower() == "true"
69
66
  complete_install = request.args.get("complete_install", str(complete_install)).lower() == "true"
@@ -81,7 +78,7 @@ class Actions(FlaskView):
81
78
  admin_links = f"<h5 >{_('Admin Links')}</h5><ul>"
82
79
 
83
80
  admin_links += f"<li><span class='badge badge-danger'>{_('Not Secure')}</span>: <a class='badge ltr share-link' href='{hiddify.get_account_panel_link(g.account, server_ip,is_https=False)}'>{hiddify.get_account_panel_link(g.account, server_ip,is_https=False)}</a></li>"
84
- domains = get_panel_domains()
81
+ domains = Domain.get_domains()
85
82
  # domains=[*domains,f'{server_ip}.sslip.io']
86
83
 
87
84
  for d in domains:
@@ -182,12 +179,10 @@ class Actions(FlaskView):
182
179
 
183
180
  @ login_required(roles={Role.super_admin})
184
181
  def update_usage(self):
185
-
186
- import json
187
-
182
+ color = 'white' if g.darkmode else 'black'
188
183
  return render_template("result.html",
189
184
  out_type="info",
190
- out_msg=f'<pre class="ltr">{json.dumps(usage.update_local_usage(),indent=2)}</pre>',
185
+ out_msg=f'<pre class="ltr" style="color:{color};">{json.dumps(usage.update_local_usage(),indent=2)}</pre>',
191
186
  log_file_url=None
192
187
  )
193
188
 
@@ -197,4 +192,4 @@ def get_log_api_url():
197
192
 
198
193
 
199
194
  def get_domains():
200
- return [str(d.domain).replace("*", hutils.random.get_random_string(3, 6)) for d in get_panel_domains(always_add_all_domains=True, always_add_ip=False)]
195
+ return [str(d.domain).replace("*", hutils.random.get_random_string(3, 6)) for d in Domain.get_domains(always_add_all_domains=True, always_add_ip=False)]
@@ -87,7 +87,7 @@ class AdminstratorAdmin(AdminLTEModelView):
87
87
 
88
88
  def _ul_formatter(view, context, model, name):
89
89
 
90
- return Markup(" ".join([hiddify.get_html_user_link(model, d) for d in get_panel_domains()]))
90
+ return Markup(" ".join([hiddify.get_html_user_link(model, d) for d in Domain.get_domains()]))
91
91
 
92
92
  @property
93
93
  def can_create(self):
@@ -216,12 +216,6 @@ class AdminstratorAdmin(AdminLTEModelView):
216
216
  def on_model_delete(self, model):
217
217
  model.remove()
218
218
 
219
- def get_query_for_parent_admin(self):
220
- # WHAT IS THIS?
221
- admin_user_id = self.get_pk_value()
222
- sub_admins_ids = set(recursive_sub_admins_ids(AdminUser.query.get(admin_user_id)))
223
- return AdminUser.query.filter(AdminUser.id.in_(sub_admins_ids)).with_entities(AdminUser.id, AdminUser.name)
224
-
225
219
  def on_form_prefill(self, form, id=None):
226
220
 
227
221
  if g.account.mode != AdminMode.super_admin:
@@ -242,8 +236,8 @@ class AdminstratorAdmin(AdminLTEModelView):
242
236
 
243
237
  def after_model_change(self, form, model, is_created):
244
238
  if hutils.node.is_parent():
245
- hutils.node.parent.send_sync_req_to_childs()
239
+ hutils.node.run_node_op_in_bg(hutils.node.parent.request_childs_to_sync)
246
240
 
247
241
  def after_model_delete(self, model):
248
242
  if hutils.node.is_parent():
249
- hutils.node.parent.send_sync_req_to_childs()
243
+ hutils.node.run_node_op_in_bg(hutils.node.parent.request_childs_to_sync)
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env python3
2
- from flask import render_template, request, jsonify, redirect, g
1
+ from flask import render_template, request, jsonify, g
3
2
  from flask_wtf.file import FileField, FileRequired
4
3
  from flask_bootstrap import SwitchField
5
4
  from flask_babel import gettext as _
@@ -25,12 +24,10 @@ class Backup(FlaskView):
25
24
 
26
25
  # @route("/backupfile")
27
26
  def backupfile(self):
28
- response = jsonify(
29
- hiddify.dump_db_to_dict()
30
- )
31
- o = urlparse(request.base_url)
32
- domain = o.hostname
33
- response.headers.add('Content-disposition', f'attachment; filename=hiddify-{domain}-{datetime.now()}.json')
27
+ response = jsonify(hiddify.dump_db_to_dict())
28
+ domain = urlparse(request.base_url).hostname
29
+ filename = f'hiddify-{domain}-{datetime.now()}.json'
30
+ response.headers.add('Content-disposition', f'attachment; filename={filename}')
34
31
 
35
32
  return response
36
33
 
@@ -21,10 +21,9 @@ class Dashboard(FlaskView):
21
21
  if hconfig(ConfigEnum.first_setup):
22
22
  return redirect(hurl_for("admin.QuickSetup:index"))
23
23
 
24
- if hiddifypanel.__release_date__ + datetime.timedelta(days=20) < datetime.datetime.now():
24
+ if hutils.utils.is_panel_outdated():
25
25
  hutils.flask.flash(_('This version of hiddify panel is outdated. Please update it from admin area.'), "danger") # type: ignore
26
- bot = None
27
- # if hconfig(ConfigEnum.license):
26
+
28
27
  childs = None
29
28
  admin_id = request.args.get("admin_id") or g.account.id
30
29
  if admin_id not in g.account.recursive_sub_admins_ids():
@@ -44,7 +43,7 @@ class Dashboard(FlaskView):
44
43
  c.is_active = True
45
44
 
46
45
  def_user = None if len(User.query.all()) > 1 else User.query.filter(User.name == 'default').first()
47
- domains = get_panel_domains()
46
+ domains = Domain.get_domains()
48
47
  sslip_domains = [d.domain for d in domains if "sslip.io" in d.domain]
49
48
 
50
49
  if def_user and sslip_domains:
@@ -12,7 +12,7 @@ from hiddifypanel.panel.run_commander import Command, commander
12
12
  from wtforms.validators import Regexp, ValidationError
13
13
 
14
14
  from hiddifypanel.models import *
15
- from hiddifypanel.panel import hiddify, cf_api, custom_widgets
15
+ from hiddifypanel.panel import hiddify, custom_widgets
16
16
  from .adminlte import AdminLTEModelView
17
17
  from hiddifypanel import hutils
18
18
 
@@ -110,6 +110,9 @@ class DomainAdmin(AdminLTEModelView):
110
110
 
111
111
  def _domain_ip(view, context, model, name):
112
112
  dip = hutils.network.get_domain_ip(model.domain)
113
+ # The get_domain_ip function uses the socket library, which relies on the system DNS resolver. So it may sometimes use cached data, which is not desirable
114
+ if not dip:
115
+ dip = hutils.network.resolve_domain_with_api(model.domain)
113
116
  myip = hutils.network.get_ip(4)
114
117
  if myip == dip and model.mode == DomainType.direct:
115
118
  badge_type = ''
@@ -144,6 +147,7 @@ class DomainAdmin(AdminLTEModelView):
144
147
  # Pre-select the related domains in the checkbox list
145
148
  # form.show_domains = [d.id for d in Domain.query.all()]
146
149
 
150
+ # TODO: refactor this function
147
151
  def on_model_change(self, form, model, is_created):
148
152
  model.domain = model.domain.lower()
149
153
 
@@ -171,16 +175,16 @@ class DomainAdmin(AdminLTEModelView):
171
175
  raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
172
176
 
173
177
  skip_check = "*" in model.domain
174
- if hconfig(ConfigEnum.cloudflare) and model.mode != DomainType.fake:
178
+ if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.relay, DomainType.reality]:
175
179
  try:
176
180
  proxied = model.mode in [DomainType.cdn, DomainType.auto_cdn_ip]
177
- cf_api.add_or_update_domain(model.domain, str(ipv4_list[0]), "A", proxied=proxied)
181
+ hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv4_list[0]), "A", proxied=proxied)
178
182
  if ipv6_list:
179
- cf_api.add_or_update_domain(model.domain, str(ipv6_list[0]), "AAAA", proxied=proxied)
183
+ hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv6_list[0]), "AAAA", proxied=proxied)
180
184
 
181
185
  skip_check = True
182
186
  except Exception as e:
183
- raise ValidationError(__("Can not connect to Cloudflare.") + f' {e}')
187
+ raise ValidationError(__("cloudflare.error") + f' {e}')
184
188
  # elif model.mode==DomainType.auto_cdn_ip:
185
189
  # if model.alias and not model.alias.replace("_", "").isalnum():
186
190
  # hutils.flask.flash(__("Using alias with special charachters may cause problem in some clients like FairVPN."), 'warning')
@@ -233,19 +237,20 @@ class DomainAdmin(AdminLTEModelView):
233
237
  for d in v.split(","):
234
238
  if not d:
235
239
  continue
236
- if not hutils.network.is_domain_reality_friendly(d):
240
+ if not hutils.network.is_domain_reality_friendly(d): # the minimum requirement for the REALITY protocol is to have tls1.3 and h2
237
241
  raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
238
242
 
239
243
  if not hutils.network.is_in_same_asn(d, ipv4_list[0]):
240
- server_asn = hutils.network.get_ip_asn_name(ipv4_list[0])
241
- domain_asn = hutils.network.get_ip_asn_name(dip) # type: ignore
244
+ server_asn = hutils.network.get_ip_asn(ipv4_list[0])
245
+ domain_asn = hutils.network.get_ip_asn(dip) # type: ignore
242
246
  msg = _("selected domain for REALITY is not in the same ASN. To better use of the protocol, it is better to find a domain in the same ASN.") + \
243
247
  (f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}" if server_asn or domain_asn else "")
244
248
  hutils.flask.flash(msg, 'warning')
245
249
 
246
250
  for d in model.servernames.split(","):
247
251
  if not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
248
- raise ValidationError(_("REALITY Fallback domain is not compaitble with server names!") + f' {d} != {model.domain}')
252
+ msg = _("REALITY Fallback domain is not compaitble with server names!") + f' {d} != {model.domain}'
253
+ hutils.flask.flash(msg, 'warning')
249
254
 
250
255
  if (model.cdn_ip):
251
256
  try:
@@ -253,7 +258,7 @@ class DomainAdmin(AdminLTEModelView):
253
258
  except BaseException:
254
259
  raise ValidationError(_("Error in auto cdn format"))
255
260
 
256
- old_db_domain = get_domain(model.domain)
261
+ old_db_domain = Domain.by_domain(model.domain)
257
262
  if is_created or not old_db_domain or old_db_domain.mode != model.mode:
258
263
  # return hiddify.reinstall_action(complete_install=False, domain_changed=True)
259
264
  hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=True)
@@ -265,15 +270,16 @@ class DomainAdmin(AdminLTEModelView):
265
270
  def on_model_delete(self, model):
266
271
  if len(Domain.query.all()) <= 1:
267
272
  raise ValidationError(f"at least one domain should exist")
268
- # ShowDomain.query.filter_by(related_id == model.id).delete()
273
+ if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.reality, DomainType.relay]:
274
+ if not hutils.network.cf_api.delete_dns_record(model.domain):
275
+ hutils.flask.flash(_('cf-delete.failed'), 'warning') # type: ignore
269
276
  model.showed_by_domains = []
270
277
  # db.session.commit()
271
278
  hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=True)
272
279
 
273
280
  def after_model_delete(self, model):
274
281
  if hutils.node.is_child():
275
- if not hutils.node.child.sync_with_parent():
276
- hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
282
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.domains])
277
283
 
278
284
  def after_model_change(self, form, model, is_created):
279
285
  if hconfig(ConfigEnum.first_setup):
@@ -281,8 +287,7 @@ class DomainAdmin(AdminLTEModelView):
281
287
  if model.need_valid_ssl:
282
288
  commander(Command.get_cert, domain=model.domain)
283
289
  if hutils.node.is_child():
284
- if not hutils.node.child.sync_with_parent():
285
- hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
290
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.domains])
286
291
 
287
292
  def is_accessible(self):
288
293
  if login_required(roles={Role.super_admin, Role.admin})(lambda: True)() != True:
@@ -36,6 +36,8 @@ class ProxyAdmin(FlaskView):
36
36
  db.session.commit()
37
37
  # print(cat,vs)
38
38
  hutils.proxy.get_proxies.invalidate_all()
39
+ if hutils.node.is_child():
40
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.hconfigs])
39
41
  hiddify.check_need_reset(old_configs)
40
42
  all_proxy_form = get_all_proxy_form(True)
41
43
 
@@ -57,8 +59,7 @@ class ProxyAdmin(FlaskView):
57
59
  db.session.commit()
58
60
  hutils.proxy.get_proxies.invalidate_all()
59
61
  if hutils.node.is_child():
60
- if not hutils.node.child.sync_with_parent():
61
- hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
62
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.proxies])
62
63
  hutils.flask.flash_config_success(restart_mode=ApplyMode.apply, domain_changed=False)
63
64
  global_config_form = get_global_config_form(True)
64
65
  else:
@@ -66,14 +67,6 @@ class ProxyAdmin(FlaskView):
66
67
 
67
68
  return render_template('proxy.html', global_config_form=global_config_form, detailed_config_form=all_proxy_form)
68
69
 
69
- import flask_babel
70
-
71
- # form=HelloForm()
72
- # # return render('config.html',form=form)
73
- # return render_template('config.html',form=HelloForm())
74
- form = get_config_form()
75
- return render_template('config.html', form=form)
76
-
77
70
 
78
71
  def get_global_config_form(empty=False):
79
72
  boolconfigs = BoolConfig.query.all()
@@ -150,5 +150,5 @@ def validate_domain(form, field):
150
150
 
151
151
 
152
152
  def admin_link():
153
- domains = get_panel_domains()
153
+ domains = Domain.get_domains()
154
154
  return hiddify.get_account_panel_link(g.account, domains[0] if len(domains)else hutils.network.get_ip_str(4))