hiddifypanel 10.80.0__py3-none-any.whl → 10.80.0.dev1__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 (69) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +1 -1
  3. hiddifypanel/base.py +129 -43
  4. hiddifypanel/cache.py +1 -1
  5. hiddifypanel/database.py +0 -7
  6. hiddifypanel/hutils/flask.py +3 -3
  7. hiddifypanel/hutils/proxy/__init__.py +0 -1
  8. hiddifypanel/hutils/proxy/clash.py +2 -2
  9. hiddifypanel/hutils/proxy/shared.py +6 -6
  10. hiddifypanel/hutils/proxy/singbox.py +1 -1
  11. hiddifypanel/hutils/proxy/xray.py +2 -2
  12. hiddifypanel/hutils/proxy/xrayjson.py +7 -10
  13. hiddifypanel/models/config.py +1 -4
  14. hiddifypanel/models/config_enum.py +2 -2
  15. hiddifypanel/models/proxy.py +1 -1
  16. hiddifypanel/panel/__init__.py +8 -8
  17. hiddifypanel/panel/admin/AdminstratorAdmin.py +8 -7
  18. hiddifypanel/panel/admin/DomainAdmin.py +98 -132
  19. hiddifypanel/panel/admin/QuickSetup.py +8 -8
  20. hiddifypanel/panel/admin/UserAdmin.py +33 -58
  21. hiddifypanel/panel/admin/templates/index.html +4 -6
  22. hiddifypanel/panel/admin/templates/model/user_list.html +3 -11
  23. hiddifypanel/panel/cli.py +2 -11
  24. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -19
  25. hiddifypanel/panel/commercial/restapi/v2/admin/system_actions.py +1 -5
  26. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +1 -2
  27. hiddifypanel/panel/common.py +1 -1
  28. hiddifypanel/panel/init_db.py +16 -19
  29. hiddifypanel/panel/usage.py +3 -13
  30. hiddifypanel/panel/user/user.py +18 -12
  31. hiddifypanel/templates/fake.html +320 -0
  32. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  33. hiddifypanel/translations/en/LC_MESSAGES/messages.po +13 -82
  34. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  35. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +12 -81
  36. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  37. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +4 -73
  38. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  39. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +9 -79
  40. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  41. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +4 -73
  42. hiddifypanel/translations.i18n/en.json +7 -50
  43. hiddifypanel/translations.i18n/fa.json +6 -49
  44. hiddifypanel/translations.i18n/fr.json +2 -2
  45. hiddifypanel/translations.i18n/my.json +2 -2
  46. hiddifypanel/translations.i18n/pt.json +4 -47
  47. hiddifypanel/translations.i18n/ru.json +7 -50
  48. hiddifypanel/translations.i18n/zh.json +4 -47
  49. {hiddifypanel-10.80.0.dist-info → hiddifypanel-10.80.0.dev1.dist-info}/METADATA +6 -13
  50. {hiddifypanel-10.80.0.dist-info → hiddifypanel-10.80.0.dev1.dist-info}/RECORD +53 -69
  51. hiddifypanel/apps/__init__.py +0 -0
  52. hiddifypanel/apps/asgi_app.py +0 -7
  53. hiddifypanel/apps/celery_app.py +0 -3
  54. hiddifypanel/apps/wsgi_app.py +0 -5
  55. hiddifypanel/base_setup.py +0 -82
  56. hiddifypanel/celery.py +0 -45
  57. hiddifypanel/hutils/proxy/wireguard.py +0 -34
  58. hiddifypanel/panel/hlogger.py +0 -32
  59. hiddifypanel/panel/node/__init__.py +0 -9
  60. hiddifypanel/panel/node/a.py +0 -14
  61. hiddifypanel/panel/node/hello.py +0 -14
  62. hiddifypanel/panel/node/test.proto +0 -13
  63. hiddifypanel/panel/node/test_grpc.py +0 -40
  64. hiddifypanel/panel/node/test_pb2.py +0 -40
  65. hiddifypanel/panel/node/test_pb2.pyi +0 -17
  66. hiddifypanel/panel/node/test_pb2_grpc.py +0 -97
  67. {hiddifypanel-10.80.0.dist-info → hiddifypanel-10.80.0.dev1.dist-info}/LICENSE.md +0 -0
  68. {hiddifypanel-10.80.0.dist-info → hiddifypanel-10.80.0.dev1.dist-info}/WHEEL +0 -0
  69. {hiddifypanel-10.80.0.dist-info → hiddifypanel-10.80.0.dev1.dist-info}/entry_points.txt +0 -0
@@ -16,7 +16,6 @@ from hiddifypanel.panel import hiddify, custom_widgets
16
16
  from .adminlte import AdminLTEModelView
17
17
  from hiddifypanel import hutils
18
18
 
19
- from loguru import logger
20
19
  from flask import current_app
21
20
  # Define a custom field type for the related domains
22
21
 
@@ -153,165 +152,132 @@ class DomainAdmin(AdminLTEModelView):
153
152
 
154
153
  # TODO: refactor this function
155
154
  def on_model_change(self, form, model, is_created):
156
- # Sanitize domain input
157
- model.domain = (model.domain or '').lower().strip()
158
-
159
- # Basic validation
155
+ model.domain = (model.domain or '').lower()
160
156
  if model.domain == '' and model.mode != DomainType.fake:
161
157
  raise ValidationError(_("domain.empty.allowed_for_fake_only"))
158
+ configs = get_hconfigs()
159
+ for c in configs:
160
+ if "domain" in c and c not in [ConfigEnum.decoy_domain, ConfigEnum.reality_fallback_domain] and c.category != 'hidden':
161
+ if model.domain == configs[c]:
162
+ raise ValidationError(_("You have used this domain in: ") + _(f"config.{c}.label"))
163
+
164
+ for td in Domain.query.filter(Domain.mode == DomainType.reality, Domain.domain != model.domain).all():
165
+ # print(td)
166
+ if td.servernames and (model.domain in td.servernames.split(",")):
167
+ raise ValidationError(_("You have used this domain in: ") + _(f"config.reality_server_names.label") + td.domain)
168
+
169
+ if is_created and Domain.query.filter(Domain.domain == model.domain, Domain.child_id == model.child_id).count() > 1:
170
+ raise ValidationError(_("You have used this domain in: "))
162
171
 
163
- self._validate_not_used_before(model,is_created)
164
172
  ipv4_list = hutils.network.get_ips(4)
165
173
  ipv6_list = hutils.network.get_ips(6)
166
- server_ips = [*ipv4_list, *ipv6_list]
167
174
 
168
- if not server_ips:
175
+ if not ipv4_list and not ipv6_list:
169
176
  raise ValidationError(_("Couldn't find your ip addresses"))
170
177
 
171
- # Validate domain based on mode
172
178
  if "*" in model.domain and model.mode not in [DomainType.cdn, DomainType.auto_cdn_ip]:
173
179
  raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
174
180
 
175
- cloudflare_updated=self._update_cloudflare(model, ipv4_list,ipv6_list)
176
-
177
-
178
- self._validate_domain_ips(model, server_ips)
179
-
180
- # Handle CDN IP settings
181
- if model.mode == DomainType.direct and model.cdn_ip:
182
- model.cdn_ip = ""
183
- raise ValidationError(_("Specifying CDN IP is only valid for CDN mode"))
184
-
185
- if model.mode == DomainType.fake and not model.cdn_ip:
186
- model.cdn_ip = str(server_ips[0])
187
-
188
- if model.cdn_ip:
189
- try:
190
- hutils.network.auto_ip_selector.get_clean_ip(str(model.cdn_ip))
191
- except Exception:
192
- raise ValidationError(_("Error in auto cdn format"))
193
-
194
- # Update show domains
195
- if len(model.show_domains) == Domain.query.count():
196
- model.show_domains = []
197
-
198
- # Handle mode-specific settings
199
- if model.mode == DomainType.old_xtls_direct and not hconfig(ConfigEnum.xtls_enable):
200
- set_hconfig(ConfigEnum.xtls_enable, True)
201
- hutils.proxy.get_proxies().invalidate_all()
202
- elif model.mode == DomainType.reality:
203
- self._validate_reality_settings(model, server_ips)
204
-
205
- # Signal config update if needed
206
- old_db_domain = Domain.by_domain(model.domain)
207
- if is_created or not old_db_domain or old_db_domain.mode != model.mode:
208
- # return hiddify.reinstall_action(complete_install=False, domain_changed=True)
209
- hutils.flask.flash_config_success(restart_mode=ApplyMode.apply_config, domain_changed=True)
210
-
211
-
212
-
213
- def _update_cloudflare(self, model, ipv4_list,ipv6_list):
181
+ skip_check = "*" in model.domain or model.domain == ""
214
182
  if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.relay, DomainType.reality]:
215
183
  try:
216
184
  proxied = model.mode in [DomainType.cdn, DomainType.auto_cdn_ip]
217
- if ipv4_list:
218
- hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv4_list[0]), "A", proxied=proxied)
185
+ hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv4_list[0]), "A", proxied=proxied)
219
186
  if ipv6_list:
220
187
  hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv6_list[0]), "AAAA", proxied=proxied)
221
- return True
188
+
189
+ skip_check = True
222
190
  except Exception as e:
223
191
  raise ValidationError(__("cloudflare.error") + f' {e}')
224
- return False
192
+ # elif model.mode==DomainType.auto_cdn_ip:
193
+ # if model.alias and not model.alias.replace("_", "").isalnum():
194
+ # hutils.flask.flash(__("Using alias with special charachters may cause problem in some clients like FairVPN."), 'warning')
195
+ # raise ValidationError(_("You have to add your cloudflare api key to use this feature: "))
225
196
 
226
- def _validate_reality_settings(self, model, server_ips):
227
- """Validate REALITY protocol settings with proper error handling"""
228
- if not hconfig(ConfigEnum.reality_enable):
229
- set_hconfig(ConfigEnum.reality_enable, True)
230
- hutils.proxy.get_proxies().invalidate_all()
197
+ dips = hutils.network.get_domain_ips(model.domain)
198
+ server_ips = [*ipv4_list, *ipv6_list]
199
+ if model.sub_link_only:
200
+ if not dips:
201
+ raise ValidationError(_("Domain can not be resolved! there is a problem in your domain")) # type: ignore
202
+ elif not skip_check:
203
+ if not dips:
204
+ raise ValidationError(_("Domain can not be resolved! there is a problem in your domain")) # type: ignore
205
+
206
+ domain_ip_is_same_as_panel = False
207
+
208
+ for mip in server_ips:
209
+ domain_ip_is_same_as_panel |= mip in dips
210
+ server_ips_str = ', '.join(list(map(str, server_ips)))
211
+ dips_str = ', '.join(list(map(str, dips)))
212
+
213
+ if model.mode == DomainType.direct and not domain_ip_is_same_as_panel:
214
+ # hutils.flask.flash(__(f"Domain IP={dip} is not matched with your ip={', '.join(list(map(str, ipv4_list)))} which is required in direct mode"),category='error')
215
+ raise ValidationError(
216
+ __("Domain IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode", server_ip=server_ips_str, domain_ip=dips_str)) # type: ignore
217
+
218
+ if domain_ip_is_same_as_panel and model.mode in [DomainType.cdn, DomainType.relay, DomainType.fake, DomainType.auto_cdn_ip]:
219
+ # # hutils.flask.flash(__(f"In CDN mode, Domain IP={dip} should be different to your ip={', '.join(list(map(str, ipv4_list)))}"), 'warning')
220
+ raise ValidationError(__("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
221
+ server_ip=server_ips_str, domain_ip=dips_str)) # type: ignore
222
+
223
+ # if model.mode in [DomainType.ss_faketls, DomainType.telegram_faketls]:
224
+ # if len(Domain.query.filter(Domain.mode==model.mode and Domain.id!=model.id).all())>0:
225
+ # ValidationError(f"another {model.mode} is exist")
226
+
227
+ model.domain = model.domain.lower()
228
+ if model.mode == DomainType.direct and model.cdn_ip:
229
+ model.cdn_ip = ""
230
+ raise ValidationError(f"Specifying CDN IP is only valid for CDN mode")
231
231
 
232
- model.servernames = (model.servernames or model.domain).lower().strip()
233
- domains_to_check = set()
234
- for v in [model.domain, model.servernames]:
235
- domains_to_check.update(d.strip() for d in v.split(",") if d.strip())
232
+ if model.mode == DomainType.fake and not model.cdn_ip:
233
+ model.cdn_ip = str(server_ips[0])
236
234
 
237
- for d in domains_to_check:
238
- # Check REALITY compatibility
239
- if not hutils.network.is_domain_reality_friendly(d):
240
- raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
235
+ # if model.mode==DomainType.fake and model.cdn_ip!=myip:
236
+ # raise ValidationError(f"Specifying CDN IP is only valid for CDN mode")
241
237
 
242
- try:
243
- if not hutils.network.is_in_same_asn(d, server_ips[0]):
244
- domain_ips = hutils.network.get_domain_ips(d)
245
- if domain_ips:
246
- dip = next(iter(domain_ips))
238
+ # # Update the many-to-many relationship
239
+ if len(model.show_domains) == Domain.query.count():
240
+ model.show_domains = []
241
+ if model.mode == DomainType.old_xtls_direct:
242
+ if not hconfig(ConfigEnum.xtls_enable):
243
+ set_hconfig(ConfigEnum.xtls_enable, True)
244
+ hutils.proxy.get_proxies().invalidate_all()
245
+ elif model.mode == DomainType.reality:
246
+ if not hconfig(ConfigEnum.reality_enable):
247
+ set_hconfig(ConfigEnum.reality_enable, True)
248
+ hutils.proxy.get_proxies().invalidate_all()
249
+ model.servernames = (model.servernames or model.domain).lower()
250
+ for v in set([model.domain, model.servernames]):
251
+ for d in v.split(","):
252
+ if not d:
253
+ continue
254
+ if not hutils.network.is_domain_reality_friendly(d): # the minimum requirement for the REALITY protocol is to have tls1.3 and h2
255
+ raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
256
+
257
+ if not hutils.network.is_in_same_asn(d, server_ips[0]):
258
+ dip = next(iter(dips))
247
259
  server_asn = hutils.network.get_ip_asn(server_ips[0])
248
- domain_asn = hutils.network.get_ip_asn(dip)
249
- msg = _("domain.reality.asn_issue")
250
- if server_asn or domain_asn:
251
- msg += f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}"
260
+ domain_asn = hutils.network.get_ip_asn(dip) # type: ignore
261
+ msg = _("domain.reality.asn_issue") + \
262
+ (f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}" if server_asn or domain_asn else "")
252
263
  hutils.flask.flash(msg, 'warning')
253
- except Exception as e:
254
- logger.warning(f"ASN check failed for domain {d}: {str(e)}")
255
-
256
- # Check fallback compatibility
257
- for d in model.servernames.split(","):
258
- if d.strip() and not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
259
- msg = _("REALITY Fallback domain is not compatible with server names!") + f' {d} != {model.domain}'
260
- hutils.flask.flash(msg, 'warning')
261
264
 
265
+ for d in model.servernames.split(","):
266
+ if not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
267
+ msg = _("REALITY Fallback domain is not compaitble with server names!") + f' {d} != {model.domain}'
268
+ hutils.flask.flash(msg, 'warning')
262
269
 
263
- def _validate_not_used_before(self, model,is_created):
264
- configs = get_hconfigs()
265
- for c in configs:
266
- if "domain" in c and c not in [ConfigEnum.decoy_domain, ConfigEnum.reality_fallback_domain] and c.category != 'hidden':
267
- if model.domain == configs[c]:
268
- raise ValidationError(_("You have used this domain in: ") + _(f"config.{c}.label"))
269
-
270
- for td in Domain.query.filter(Domain.mode == DomainType.reality, Domain.domain != model.domain).all():
271
- # print(td)
272
- if td.servernames and (model.domain in td.servernames.split(",")):
273
- raise ValidationError(_("You have used this domain in: ") + _(f"config.reality_server_names.label") + td.domain)
270
+ if (model.cdn_ip):
271
+ try:
272
+ hutils.network.auto_ip_selector.get_clean_ip(str(model.cdn_ip))
273
+ except BaseException:
274
+ raise ValidationError(_("Error in auto cdn format"))
274
275
 
275
- if is_created and Domain.query.filter(Domain.domain == model.domain, Domain.child_id == model.child_id).count() > 1:
276
- raise ValidationError(_("You have used this domain in: "))
276
+ old_db_domain = Domain.by_domain(model.domain)
277
+ if is_created or not old_db_domain or old_db_domain.mode != model.mode:
278
+ # return hiddify.reinstall_action(complete_install=False, domain_changed=True)
279
+ hutils.flask.flash_config_success(restart_mode=ApplyMode.apply_config, domain_changed=True)
277
280
 
278
- def _validate_domain_ips(self, model, server_ips):
279
- """Validate domain IP resolution and matching"""
280
-
281
- # Skip validation for wildcard or empty domains
282
- if (model.domain.startswith('*') or not model.domain) and model.mode not in [DomainType.direct]:
283
- return True
284
- if model.mode in [DomainType.fake, DomainType.reality, DomainType.relay]:
285
- return True
286
- # Resolve domain IPs with timeout
287
- try:
288
- dips = hutils.network.get_domain_ips(model.domain)
289
- except Exception as e:
290
- logger.error(f"Error resolving domain {model.domain}: {str(e)}")
291
- raise ValidationError(_("Domain cannot be resolved! Please check DNS settings"))
292
-
293
- # Validate resolution success
294
- if not dips:
295
- raise ValidationError(_("Domain cannot be resolved! Please check DNS settings"))
296
-
297
- # Check IP matching based on mode
298
- domain_ip_matches_server = any(ip in dips for ip in server_ips)
299
- server_ips_str = ', '.join(map(str, server_ips))
300
- dips_str = ', '.join(map(str, dips))
301
-
302
- if not domain_ip_matches_server and model.mode in [DomainType.direct]:
303
- raise ValidationError(
304
- __("Domain IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode",
305
- server_ip=server_ips_str, domain_ip=dips_str))
306
-
307
- if domain_ip_matches_server and model.mode in [DomainType.cdn, DomainType.relay, DomainType.fake, DomainType.auto_cdn_ip]:
308
- raise ValidationError(
309
- __("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
310
- server_ip=server_ips_str, domain_ip=dips_str))
311
-
312
- return True
313
-
314
-
315
281
  # def after_model_change(self,form, model, is_created):
316
282
  # if model.show_domains.count==0:
317
283
  # db.session.bulk_save_objects(ShowDomain(model.id,model.id))
@@ -72,7 +72,7 @@ def get_lang_form(empty=False):
72
72
  default=hconfig(ConfigEnum.admin_lang))
73
73
  # lang=wtf.SelectField(_("config.lang.label"),choices=[("en",_("lang.en")),("fa",_("lang.fa"))],description=_("config.lang.description"),default=hconfig(ConfigEnum.lang))
74
74
  country = wtf.SelectField(
75
- _("config.country.label"), choices=[("ir", _("Iran")), ("zh", _("China")), ("ru", _("Russia")), ("other", "Others")],
75
+ _("config.country.label"), choices=[("ir", _("Iran")), ("zh", _("China")), ("other", "Others")],
76
76
  description=_("config.country.description"),
77
77
  default=hconfig(ConfigEnum.country))
78
78
  lang_submit = wtf.SubmitField(_('Submit'))
@@ -251,11 +251,11 @@ def validate_domain(form, field):
251
251
  if dip is None:
252
252
  raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
253
253
 
254
- myips = hutils.network.get_ips()
255
- # Fixed: Changed from get_ip(4) to get_ip(6)
256
- if dip not in myips:
254
+ myip = hutils.network.get_ip(4)
255
+ myip6 = hutils.network.get_ip(4)
256
+ if dip and myip != dip and (not myip6 or myip6 != dip):
257
257
  raise ValidationError(_("Domain (%(domain)s)-> IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode",
258
- server_ip=myips, domain_ip=dip, domain=domain))
258
+ server_ip=myip, domain_ip=dip, domain=domain))
259
259
 
260
260
 
261
261
  def validate_domain_cdn(form, field):
@@ -266,10 +266,10 @@ def validate_domain_cdn(form, field):
266
266
  if dip is None:
267
267
  raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
268
268
 
269
- myips = hutils.network.get_ips()
270
- if dip in myips:
269
+ myip = hutils.network.get_ip(4)
270
+ if myip == dip:
271
271
  raise ValidationError(_("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
272
- server_ip=myips, domain_ip=dip, domain=domain))
272
+ server_ip=myip, domain_ip=dip, domain=domain))
273
273
 
274
274
 
275
275
  def admin_link():
@@ -27,7 +27,7 @@ from hiddifypanel import hutils
27
27
  class UserAdmin(AdminLTEModelView):
28
28
  column_default_sort = ('id', False) # Sort by username in ascending order
29
29
 
30
- column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days", "max_ips", "comment", 'last_online', "uuid"]
30
+ column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days","max_ips", "comment", 'last_online', "uuid", 'remaining_days']
31
31
  column_searchable_list = ["uuid", "name"]
32
32
  column_list = ["is_active", "name", "UserLinks", "current_usage", "remaining_days", "comment", 'last_online', 'mode', 'admin', "uuid"]
33
33
  column_editable_list = ["comment", "name", "uuid"]
@@ -131,7 +131,7 @@ class UserAdmin(AdminLTEModelView):
131
131
  if model.is_active:
132
132
  link = '<i class="fa-solid fa-circle-check text-success"></i> '
133
133
  elif len(model.devices):
134
- link = f'<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
134
+ link = '<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
135
135
  else:
136
136
  link = '<i class="fa-solid fa-circle-xmark text-danger"></i> '
137
137
 
@@ -161,8 +161,8 @@ class UserAdmin(AdminLTEModelView):
161
161
 
162
162
  def _usage_formatter(view, context, model, name):
163
163
  u = round(model.current_usage_GB, 3)
164
- t = max(round(model.usage_limit_GB, 3), 0.001) # Prevent division by zero
165
- rate = min(round(u * 100 / t), 100) # Cap at 100%
164
+ t = round(model.usage_limit_GB, 3)
165
+ rate = round(u * 100 / (t + 0.000001))
166
166
  state = "danger" if u >= t else ('warning' if rate > 80 else 'success')
167
167
  color = "#ff7e7e" if u >= t else ('#ffc107' if rate > 80 else '#9ee150')
168
168
  return Markup(f"""
@@ -225,45 +225,38 @@ class UserAdmin(AdminLTEModelView):
225
225
 
226
226
  def on_form_prefill(self, form, id=None):
227
227
  # print("================",form._obj.start_date)
228
- if form._obj is None:
229
- return
230
-
231
- if id is None or form._obj.start_date is None or form._obj.current_usage==0:
228
+ if id is None or form._obj is None or form._obj.start_date is None or form._obj.current_usage==0:
232
229
  msg = _("Package not started yet.")
233
230
  # form.reset['class']="d-none"
234
- if form._obj.start_date is None:
235
- if hasattr(form, 'reset_days'):
231
+ if form._obj.start_date is None:
236
232
  delattr(form, 'reset_days')
233
+ if form._obj.current_usage==0:
234
+ delattr(form, 'reset_usage')
235
+ # delattr(form,'disable_user')
237
236
  else:
238
- remaining = form._obj.remaining_days
237
+ remaining = form._obj.remaining_days # remaining_days(form._obj)
239
238
  relative_remaining = hutils.convert.format_timedelta(datetime.timedelta(days=remaining))
240
239
  msg = _("Remaining about %(relative)s, exactly %(days)s days", relative=relative_remaining, days=remaining)
241
240
  form.reset_days.label.text += f" ({msg})"
241
+ usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
242
+ form.reset_usage.label.text += usr_usage
243
+ form.reset_usage.data = False
242
244
  form.reset_days.data = False
243
245
 
244
- # Handle reset_usage field
245
- if form._obj.current_usage == 0:
246
- if hasattr(form, 'reset_usage'):
247
- delattr(form, 'reset_usage')
248
- else:
249
- usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
250
- if hasattr(form, 'reset_usage'):
251
- form.reset_usage.label.text += usr_usage
252
- form.reset_usage.data = False
253
-
254
- if hasattr(form, 'usage_limit'):
255
- form.usage_limit.label.text += usr_usage
256
-
257
- # Handle package days info
258
- if form._obj.start_date and hasattr(form, 'package_days'):
259
- started = form._obj.start_date - datetime.date.today()
260
- msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
261
- form.package_days.label.text += f" ({msg})"
262
- if started.days <= 0:
263
- exact_start = _("Started %(days)s days ago", days=-started.days)
264
- else:
265
- exact_start = _("Will Start in %(days)s days", days=started.days)
266
- form.package_days.description += f" ({exact_start})"
246
+ form.usage_limit.label.text += usr_usage
247
+
248
+ # if form._obj.mode==UserMode.disable:
249
+ # delattr(form,'disable_user')
250
+ # form.disable_user.data=form._obj.mode==UserMode.disable
251
+ if form._obj.start_date:
252
+ started = form._obj.start_date - datetime.date.today()
253
+ msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
254
+ form.package_days.label.text += f" ({msg})"
255
+ if started.days <= 0:
256
+ exact_start = _("Started %(days)s days ago", days=-started.days)
257
+ else:
258
+ exact_start = _("Will Start in %(days)s days", days=started.days)
259
+ form.package_days.description += f" ({exact_start})"
267
260
 
268
261
  def get_edit_form(self):
269
262
  form = super().get_edit_form()
@@ -274,44 +267,26 @@ class UserAdmin(AdminLTEModelView):
274
267
  return form
275
268
 
276
269
  def on_model_change(self, form, model, is_created):
277
- # Validate max_ips
278
- try:
279
- model.max_ips = max(3, min(int(model.max_ips or 10000), 10000))
280
- except (ValueError, TypeError):
281
- model.max_ips = 1000
282
-
283
- # Show donation message
270
+ model.max_ips = max(3, model.max_ips or 10000)
284
271
  if len(User.query.all()) % 4 == 0:
285
272
  hutils.flask.flash(('<div id="show-modal-donation"></div>'), ' d-none')
286
-
287
- # Validate UUID
288
273
  if not re.match("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", model.uuid):
289
274
  raise ValidationError('Invalid UUID e.g.,' + str(uuid.uuid4()))
290
-
291
- # Handle reset flags
292
275
  if hasattr(form, 'reset_usage') and form.reset_usage.data:
293
276
  model.current_usage_GB = 0
294
-
277
+ # if model.telegram_id and model.telegram_id != '0' and not re.match(r"^[1-9]\d*$", model.telegram_id):
278
+ # raise ValidationError('Invalid Telegram ID')
279
+ # if form.disable_user.data:
280
+ # model.mode=UserMode.disable
295
281
  if hasattr(form, 'reset_days') and form.reset_days.data:
296
282
  model.start_date = None
297
-
298
- # Validate package days
299
- try:
300
- model.package_days = min(int(model.package_days), 10000)
301
- except (ValueError, TypeError):
302
- model.package_days = 10000
303
-
304
- # Handle user ownership
283
+ model.package_days = min(model.package_days, 10000)
305
284
  old_user = User.by_id(model.id)
306
285
  if not model.added_by or model.added_by == 1:
307
286
  model.added_by = g.account.id
308
-
309
- # Validate user limits
310
287
  if not g.account.can_have_more_users():
311
288
  raise ValidationError(_('You have too much users! You can have only %(active)s active users and %(total)s users',
312
289
  active=g.account.max_active_users, total=g.account.max_users))
313
-
314
- # Handle UUID changes
315
290
  if old_user and old_user.uuid != model.uuid:
316
291
  user_driver.remove_client(old_user)
317
292
 
@@ -192,12 +192,10 @@
192
192
  }
193
193
 
194
194
  function update_from_json(data) {
195
- // Use local variables instead of globals
196
- const usage_history = data['usage_history'];
197
- const onlines = usage_history['m5']['online'];
198
- const total_users = usage_history['total']['users'];
199
- const stats = data['stats'];
200
-
195
+ usage_history = data['usage_history']
196
+ onlines = usage_history['m5']['online']
197
+ total_users = usage_history['total']['users']
198
+ stats = data['stats']
201
199
  info_box("today", "fa-solid fa-calendar", "Today Usage",
202
200
  ((usage_history['today']['usage'] / Math.pow(1024, 3)).toFixed(1)) + " GB",
203
201
  usage_history['today']['online'] / Math.max(1, total_users) * 100,
@@ -121,17 +121,9 @@
121
121
  $(document).Toasts('create', {
122
122
  class: 'bg-danger',
123
123
  position: "{{'topRight' if get_locale()=='fa' else 'topLeft'}}",
124
- title: $('<div>').text(status + " " + error).html(),
125
- body: $('<div>').text(JSON.stringify(jqx)).html(),
126
- autohide: true,
127
- delay: 5000
128
- });
129
-
130
- console.error('API Error:', {
131
- status: status,
132
- error: error,
133
- details: jqx
134
- });
124
+ title: status + " " + error,
125
+ body: JSON.stringify(jqx)
126
+ })
135
127
  },
136
128
  success: function (data) {
137
129
  // dialog.modal('hide');
hiddifypanel/panel/cli.py CHANGED
@@ -13,7 +13,6 @@ from hiddifypanel.panel import hiddify, usage
13
13
  from hiddifypanel.database import db
14
14
  from hiddifypanel.panel.init_db import init_db
15
15
 
16
- from loguru import logger
17
16
 
18
17
  def drop_db():
19
18
  """Cleans database"""
@@ -30,12 +29,7 @@ def downgrade():
30
29
  os.rename("/opt/hiddify-manager/hiddify-panel/hiddifypanel.db.old", "/opt/hiddify-manager/hiddify-panel/hiddifypanel.db")
31
30
 
32
31
 
33
- from celery import shared_task
34
32
  def backup():
35
- backup_task()
36
-
37
- @shared_task(ignore_result=False)
38
- def backup_task():
39
33
  dbdict = hiddify.dump_db_to_dict()
40
34
  os.makedirs('backup', exist_ok=True)
41
35
  dst = f'backup/{datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")}.json'
@@ -48,12 +42,9 @@ def backup_task():
48
42
  register_bot(True)
49
43
 
50
44
  with open(dst, 'rb') as document:
51
- for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None,AdminUser.telegram_id!=0).all():
45
+ for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
52
46
  caption = ("Backup \n" + admin_links())
53
- try:
54
- bot.send_document(admin.telegram_id, document, visible_file_name=dst.replace("backup/", ""), caption=caption[:1000])
55
- except Exception as e:
56
- logger.exception(e)
47
+ bot.send_document(admin.telegram_id, document, visible_file_name=dst.replace("backup/", ""), caption=caption[:min(len(caption), 1000)])
57
48
 
58
49
 
59
50
  def all_configs():
@@ -2,7 +2,6 @@ import telebot
2
2
  from flask import request
3
3
  from apiflask import abort
4
4
  from flask_restful import Resource
5
- import time
6
5
 
7
6
  from hiddifypanel.models import *
8
7
  from hiddifypanel import Events
@@ -12,24 +11,7 @@ logger = telebot.logger
12
11
 
13
12
  class ExceptionHandler(telebot.ExceptionHandler):
14
13
  def handle(self, exception):
15
- """Improved error handling for Telegram bot exceptions"""
16
- error_msg = str(exception)
17
- logger.error(f"Telegram bot error: {error_msg}")
18
-
19
- try:
20
- # Attempt recovery based on error type
21
- if "webhook" in error_msg.lower():
22
- if hasattr(bot, 'remove_webhook'):
23
- bot.remove_webhook()
24
- logger.info("Removed webhook due to error")
25
- elif "connection" in error_msg.lower():
26
- # Wait and retry for connection issues
27
- time.sleep(5)
28
- return True # Indicates retry
29
- except Exception as e:
30
- logger.error(f"Error during recovery attempt: {str(e)}")
31
-
32
- return False # Don't retry for unknown errors
14
+ logger.error(exception)
33
15
 
34
16
 
35
17
  bot = telebot.TeleBot("1:2", parse_mode="HTML", threaded=False, exception_handler=ExceptionHandler())
@@ -1,5 +1,3 @@
1
- import asyncio
2
- import time
3
1
  from flask import current_app as app, request
4
2
  from flask import g
5
3
  from flask.views import MethodView
@@ -18,9 +16,7 @@ class UpdateUserUsageApi(MethodView):
18
16
 
19
17
  def get(self):
20
18
  """System: Update User Usage"""
21
- # time.sleep(5)
22
-
23
- return json.dumps(usage.update_local_usage_not_lock(), indent=2)
19
+ return json.dumps(usage.update_local_usage(), indent=2)
24
20
 
25
21
 
26
22
  class AllConfigsApi(MethodView):
@@ -6,8 +6,7 @@ from hiddifypanel.auth import login_required
6
6
  from hiddifypanel.models import *
7
7
  from hiddifypanel.panel import hiddify
8
8
  from hiddifypanel.drivers import user_driver
9
-
10
-
9
+ from hiddifypanel.panel import hiddify
11
10
 
12
11
  from . import has_permission
13
12
  from .schema import UserSchema, PostUserSchema, PatchUserSchema, SuccessfulSchema
@@ -65,7 +65,7 @@ def init_app(app: APIFlask):
65
65
  'version': hiddifypanel.__version__,
66
66
  }), 500
67
67
 
68
- trace = traceback.format_exc()
68
+ trace = traceback.format_exc(e)
69
69
 
70
70
  # Create github issue link
71
71
  issue_link = hutils.github_issue.generate_github_issue_link_for_500_error(e, trace)