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
@@ -0,0 +1,57 @@
1
+ from apiflask import abort
2
+ from flask.views import MethodView
3
+ from flask import current_app as app
4
+ from flask import g
5
+ from loguru import logger
6
+
7
+ from hiddifypanel.models import Child
8
+ from hiddifypanel.panel.usage import add_users_usage_uuid
9
+ from hiddifypanel.auth import login_required
10
+
11
+ from .schema import UsageInputOutputSchema
12
+
13
+
14
+ class UsageApi(MethodView):
15
+ decorators = [login_required(node_auth=True)]
16
+
17
+ @app.input(UsageInputOutputSchema, arg_name='data') # type: ignore
18
+ @app.output(UsageInputOutputSchema) # type: ignore
19
+ def put(self, data):
20
+ from hiddifypanel import hutils
21
+ child = Child.query.filter(Child.unique_id == Child.node.unique_id).first()
22
+ if not child:
23
+ logger.error("The child does not exist")
24
+ abort(400, "The child does not exist")
25
+
26
+ # parse request data
27
+ logger.debug(f"Received Usage data from child: {data}")
28
+ child_usages_data = hutils.node.convert_usage_api_response_to_dict(data)
29
+
30
+ # get current usage
31
+ logger.debug("Getting current usage data from parent")
32
+ parent_current_usages_data = hutils.node.convert_usage_api_response_to_dict(UsageInputOutputSchema().dump(hutils.node.get_users_usage_data_for_api())) # type: ignore
33
+
34
+ # calculate usages
35
+ logger.debug("Calculating increased usages")
36
+ increased_usages = self.__calculate_parent_increased_usages(child_usages_data, parent_current_usages_data)
37
+ logger.debug(f"Increased usages: {increased_usages}")
38
+
39
+ # add users usage
40
+ if increased_usages:
41
+ logger.info(f"Adding increased usages to parent: {increased_usages}")
42
+ add_users_usage_uuid(increased_usages, child.id)
43
+
44
+ return hutils.node.get_users_usage_data_for_api()
45
+
46
+ def __calculate_parent_increased_usages(self, child_usages_data: dict, parent_usages_data: dict) -> dict:
47
+ res = {}
48
+ for p_uuid, p_usage in parent_usages_data.items():
49
+ if child_usage := child_usages_data.get(p_uuid):
50
+ if child_usage['usage'] > 0:
51
+ usage_data = {
52
+ 'usage': child_usage['usage'] - p_usage['usage'],
53
+ 'devices': child_usage['devices'],
54
+ }
55
+ if usage_data['usage'] > 0:
56
+ res[p_uuid] = usage_data
57
+ return res
@@ -138,8 +138,7 @@ def create_package(call): # <- passes a CallbackQuery type object to your funct
138
138
  domain = int(splt[4])
139
139
  new_text = _("Please Wait...")
140
140
  bot.edit_message_text(new_text, call.message.chat.id, call.message.message_id, reply_markup=None)
141
- DT = (ParentDomain if hconfig(ConfigEnum.is_parent) else Domain)
142
- domain = DT.query.filter(DT.id == domain).first()
141
+ domain = Domain.query.filter(Domain.id == domain).first()
143
142
  from . import Usage
144
143
  admin_id = admin.id
145
144
  admin_name = admin.name
@@ -18,6 +18,8 @@ def init_app(app: APIFlask):
18
18
  app.jinja_env.globals['UserMode'] = UserMode
19
19
  app.jinja_env.globals['hconfig'] = hconfig
20
20
  app.jinja_env.globals['g'] = g
21
+ app.jinja_env.globals['hutils'] = hutils
22
+ app.jinja_env.globals['hiddify'] = hiddify
21
23
  app.jinja_env.globals['version'] = hiddifypanel.__version__
22
24
  app.jinja_env.globals['static_url_for'] = hutils.flask.static_url_for
23
25
  app.jinja_env.globals['hurl_for'] = hutils.flask.hurl_for
@@ -34,6 +36,12 @@ def init_app(app: APIFlask):
34
36
 
35
37
  @app.errorhandler(Exception)
36
38
  def internal_server_error(e):
39
+ if isinstance(e, Exception):
40
+ if hutils.flask.is_api_call(request.path):
41
+ return {
42
+ 'msg': str(e),
43
+ }, 500
44
+
37
45
  if hasattr(e, 'code') and e.code == 404:
38
46
  return jsonify({
39
47
  'message': 'Not Found',
@@ -68,6 +76,7 @@ def init_app(app: APIFlask):
68
76
  # print(request.headers)
69
77
  if not request.accept_mimetypes.accept_html:
70
78
  return app.error_callback(e)
79
+ # if it's interval server error
71
80
  if e.status_code == 500:
72
81
  trace = traceback.format_exc()
73
82
 
@@ -81,9 +90,17 @@ def init_app(app: APIFlask):
81
90
  has_update = "dev" not in hiddifypanel.__version__ and f'{last_version}' != hiddifypanel.__version__
82
91
 
83
92
  return render_template('500.html', error=e, trace=trace, has_update=has_update, last_version=last_version, issue_link=issue_link), 500
93
+
94
+ # if it's access denied error
84
95
  # if e.status_code in [400,401,403]:
85
96
  # return render_template('access-denied.html',error=e), e.status_code
86
97
 
98
+ # if it's api error
99
+ if hutils.flask.is_api_call(request.path):
100
+ return {
101
+ 'msg': e.message,
102
+ }, e.status_code
103
+
87
104
  return render_template('error.html', error=e), e.status_code
88
105
 
89
106
  @app.url_defaults
@@ -133,12 +150,6 @@ def init_app(app: APIFlask):
133
150
  g.child = Child.by_id(g.__child_id) or abort(404, "Child not found")
134
151
  g.account = current_account
135
152
 
136
- @app.before_first_request
137
- def first_request():
138
- import hiddifypanel.panel.commercial.telegrambot as telegrambot
139
- if (not telegrambot.bot) or (not telegrambot.bot.username): # type: ignore
140
- telegrambot.register_bot(set_hook=True)
141
-
142
153
  @app.before_request
143
154
  def base_middleware():
144
155
  if request.endpoint == 'static' or request.endpoint == "videos":
@@ -174,3 +185,7 @@ def init_app(app: APIFlask):
174
185
  return auth_before
175
186
 
176
187
  app.jinja_env.globals['generate_github_issue_link_for_admin_sidebar'] = hutils.github_issue.generate_github_issue_link_for_admin_sidebar
188
+ with app.app_context():
189
+ import hiddifypanel.panel.commercial.telegrambot as telegrambot
190
+ if (not telegrambot.bot) or (not telegrambot.bot.username): # type: ignore
191
+ telegrambot.register_bot(set_hook=True)
@@ -1,22 +1,16 @@
1
- import glob
2
1
  import re
3
- import json
4
2
  import subprocess
5
3
 
6
4
  from datetime import datetime
7
5
  from typing import Tuple
8
- from cryptography.hazmat.primitives import serialization
9
- from cryptography.hazmat.primitives.asymmetric import x25519
10
6
  from flask import current_app, g
11
7
  from flask_babel import lazy_gettext as _
12
- from flask_babel import gettext as __
13
8
  from datetime import timedelta
14
9
 
15
10
  from hiddifypanel.cache import cache
16
11
  from hiddifypanel.models import *
17
12
  from hiddifypanel.database import db
18
13
  from hiddifypanel.hutils.utils import *
19
- from hiddifypanel.Events import domain_changed
20
14
  from hiddifypanel import hutils
21
15
  from hiddifypanel.panel.run_commander import commander, Command
22
16
  import subprocess
@@ -69,23 +63,9 @@ def exec_command(cmd, cwd=None):
69
63
 
70
64
 
71
65
  def quick_apply_users():
72
- if hconfig(ConfigEnum.is_parent):
73
- return
74
- # from hiddifypanel.panel import usage
75
- # usage.update_local_usage()
76
- # return
77
- # for user in User.query.all():
78
- # if user.is_active:
79
- # xray_api.add_client(user.uuid)
80
- # else:
81
- # xray_api.remove_client(user.uuid)
82
-
83
- # exec_command("sudo /opt/hiddify-manager/install.sh apply_users --no-gui")
84
-
85
66
  # run install.sh apply_users
86
67
  commander(Command.apply_users)
87
68
 
88
- # time.sleep(1)
89
69
  return {"status": 'success'}
90
70
 
91
71
 
@@ -145,11 +125,12 @@ def check_need_reset(old_configs, do=False):
145
125
 
146
126
 
147
127
  def get_child(unique_id):
148
- child_id = Child.current.id
128
+ child_id = Child.current().id
149
129
  if unique_id is None or unique_id in ["self", "default", str(hconfig(ConfigEnum.unique_id))]:
150
130
  child_id = 0
151
131
  else:
152
132
  child = Child.query.filter(Child.unique_id == str(unique_id)).first()
133
+ # TODO: this doesn't work because name and mode fields are nullable
153
134
  if not child:
154
135
  child = Child(unique_id=str(unique_id))
155
136
  db.session.add(child)
@@ -164,7 +145,7 @@ def dump_db_to_dict():
164
145
  "users": [u.to_dict() for u in User.query.all()],
165
146
  "domains": [u.to_dict() for u in Domain.query.all()],
166
147
  "proxies": [u.to_dict() for u in Proxy.query.all()],
167
- "parent_domains": [] if not hconfig(ConfigEnum.license) else [u.to_dict() for u in ParentDomain.query.all()],
148
+ # "parent_domains": [] if not hconfig(ConfigEnum.license) else [u.to_dict() for u in ParentDomain.query.all()],
168
149
  'admin_users': [d.to_dict() for d in AdminUser.query.all()],
169
150
  "hconfigs": [*[u.to_dict() for u in BoolConfig.query.all()],
170
151
  *[u.to_dict() for u in StrConfig.query.all()]]
@@ -195,7 +176,7 @@ def set_db_from_json(json_data, override_child_unique_id=True, set_users=True, s
195
176
  # override root child unique id
196
177
  if override_child_unique_id:
197
178
  backup_child_unique_id = get_backup_child_unique_id(json_data)
198
- replace_backup_child_unique_id(json_data, backup_child_unique_id, Child.current.unique_id)
179
+ replace_backup_child_unique_id(json_data, backup_child_unique_id, Child.current().unique_id)
199
180
 
200
181
  # restore childs
201
182
  if set_child and 'childs' in json_data:
@@ -243,13 +224,12 @@ def set_db_from_json(json_data, override_child_unique_id=True, set_users=True, s
243
224
  if set_users and 'users' in json_data:
244
225
  User.bulk_register(json_data['users'], commit=False, remove=remove_users)
245
226
  if set_domains and 'domains' in json_data:
246
- bulk_register_domains(json_data['domains'], commit=False, remove=remove_domains, override_child_unique_id=override_child_unique_id)
247
- # if set_domains and 'parent_domains' in json_data:
248
- # ParentDomain.bulk_register(json_data['parent_domains'], commit=False, remove=remove_domains)
227
+ bulk_register_domains(json_data['domains'], commit=False, remove=remove_domains)
228
+
249
229
  if set_settings and 'hconfigs' in json_data:
250
- bulk_register_configs(json_data["hconfigs"], commit=True, override_child_unique_id=override_child_unique_id, override_unique_id=override_unique_id)
230
+ bulk_register_configs(json_data["hconfigs"], commit=True, override_unique_id=override_unique_id)
251
231
  if 'proxies' in json_data:
252
- Proxy.bulk_register(json_data['proxies'], commit=False, override_child_unique_id=override_child_unique_id)
232
+ Proxy.bulk_register(json_data['proxies'], commit=False)
253
233
 
254
234
  ids_without_parent = get_ids_without_parent({u.id: u.to_dict() for u in AdminUser.query.all()})
255
235
  owner = AdminUser.get_super_admin()
@@ -276,49 +256,13 @@ def get_domain_btn_link(domain):
276
256
  return res
277
257
 
278
258
 
279
- def debug_flash_if_not_in_the_same_asn(domain):
280
- from hiddifypanel.hutils.network.auto_ip_selector import IPASN
281
- ipv4 = hutils.network.get_ip_str(4)
282
- dip = hutils.network.get_domain_ip(domain)
283
- try:
284
- if IPASN:
285
- asn_ipv4 = IPASN.get(ipv4)
286
- asn_dip = IPASN.get(dip)
287
- # country_ipv4= ipcountry.get(ipv4)
288
- # country_dip= ipcountry.get(dip)
289
- if asn_ipv4.get('autonomous_system_organization') != asn_dip.get('autonomous_system_organization'):
290
- hutils.flask.flash(_("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.") +
291
- f"<br> Server ASN={asn_ipv4.get('autonomous_system_organization','unknown')}<br>{domain}_ASN={asn_dip.get('autonomous_system_organization','unknown')}", "warning")
292
- except BaseException:
293
- pass
294
-
295
-
296
- def generate_x25519_keys():
297
- priv = x25519.X25519PrivateKey.generate()
298
- pub = priv.public_key()
299
- priv_bytes = priv.private_bytes(
300
- encoding=serialization.Encoding.Raw,
301
- format=serialization.PrivateFormat.Raw,
302
- encryption_algorithm=serialization.NoEncryption()
303
- )
304
- pub_bytes = pub.public_bytes(
305
- encoding=serialization.Encoding.Raw,
306
- format=serialization.PublicFormat.Raw
307
- )
308
- import base64
309
- pub_str = base64.urlsafe_b64encode(pub_bytes).decode()[:-1]
310
- priv_str = base64.urlsafe_b64encode(priv_bytes).decode()[:-1]
311
-
312
- return {'private_key': priv_str, 'public_key': pub_str}
313
-
314
-
315
259
  def get_ssh_client_version(user):
316
260
  return 'SSH-2.0-OpenSSH_7.4p1'
317
261
 
318
262
 
319
263
  def get_account_panel_link(account: BaseAccount, host: str, is_https: bool = True, prefere_path_only: bool = False, child_id=None):
320
264
  if child_id is None:
321
- child_id = Child.current.id
265
+ child_id = Child.current().id
322
266
  is_admin = isinstance(account, AdminUser)
323
267
  basic_auth = False # is_admin #because safri does not support it.
324
268
 
@@ -363,11 +307,8 @@ def clone_model(model):
363
307
  for k in table.columns.keys():
364
308
  if k == "id":
365
309
  continue
366
- # if k in table.primary_key:
367
- # continue
368
310
  setattr(new_model, f'{k}', getattr(model, k))
369
311
 
370
- # data.pop('id')
371
312
  return new_model
372
313
 
373
314
 
@@ -392,18 +333,6 @@ def get_backup_child_unique_id(backupdata: dict) -> str:
392
333
  return "self"
393
334
  return backupdata['childs'][0]['unique_id']
394
335
 
395
- # for k, v in backupdata.items():
396
- # if k == 'admin_users' or k == 'users':
397
- # continue
398
- # if k == 'childs':
399
- # if len(v) < 1:
400
- # continue
401
- # return v[0]['unique_id']
402
- # else:
403
- # for item in v:
404
- # return item['child_unique_id']
405
- # return 'self'
406
-
407
336
 
408
337
  def is_hiddify_next_version(major_v: int = 0, minor_v: int = 0, patch_v: int = 0) -> bool:
409
338
  '''If the user agent version be equals or higher than parameters returns True'''
@@ -2,24 +2,50 @@ import datetime
2
2
  import json
3
3
  import os
4
4
  import random
5
- import string
6
5
  import sys
7
6
  import uuid
8
7
 
9
- from dateutil import relativedelta
10
8
 
11
9
  from hiddifypanel import Events, hutils
12
10
  from hiddifypanel.models import *
13
- from hiddifypanel.models import ConfigEnum, User, set_hconfig, ChildMode
14
11
  from hiddifypanel.panel import hiddify
15
- from hiddifypanel.database import db
12
+ from hiddifypanel.database import db, db_execute
16
13
  from hiddifypanel import hutils
17
-
14
+ from sqlalchemy import text
18
15
  from flask import g
19
16
 
20
17
  MAX_DB_VERSION = 90
21
18
 
22
19
 
20
+ def _v83(child_id):
21
+ set_hconfig(ConfigEnum.log_level, LogLevel.CRITICAL)
22
+
23
+
24
+ def _v82(child_id):
25
+ set_hconfig(ConfigEnum.vless_enable, True)
26
+ set_hconfig(ConfigEnum.trojan_enable, True)
27
+ set_hconfig(ConfigEnum.reality_enable, True)
28
+ set_hconfig(ConfigEnum.tcp_enable, True)
29
+ set_hconfig(ConfigEnum.quic_enable, True)
30
+ set_hconfig(ConfigEnum.xtls_enable, True)
31
+ set_hconfig(ConfigEnum.h2_enable, True)
32
+
33
+
34
+ def _v80(child_id):
35
+ set_hconfig(ConfigEnum.parent_domain, '')
36
+ set_hconfig(ConfigEnum.parent_admin_proxy_path, '')
37
+
38
+
39
+ def _v79(child_id):
40
+ set_hconfig(ConfigEnum.panel_mode, PanelMode.standalone)
41
+
42
+
43
+ def _v78(child_id):
44
+ # equalize panel unique id and root child unique id
45
+ root_child_unique_id = Child.query.filter(Child.name == "Root").first().unique_id
46
+ set_hconfig(ConfigEnum.unique_id, root_child_unique_id)
47
+
48
+
23
49
  def _v77(child_id):
24
50
  pass
25
51
 
@@ -229,7 +255,7 @@ def _v33():
229
255
 
230
256
  def _v31():
231
257
  add_config_if_not_exist(ConfigEnum.reality_short_ids, uuid.uuid4().hex[0:random.randint(1, 8) * 2])
232
- key_pair = hiddify.generate_x25519_keys()
258
+ key_pair = hutils.crypto.generate_x25519_keys()
233
259
  add_config_if_not_exist(ConfigEnum.reality_private_key, key_pair['private_key'])
234
260
  add_config_if_not_exist(ConfigEnum.reality_public_key, key_pair['public_key'])
235
261
  db.session.bulk_save_objects(get_proxy_rows_v1())
@@ -511,7 +537,7 @@ def make_proxy_rows(cfgs):
511
537
 
512
538
  def add_config_if_not_exist(key: ConfigEnum, val: str | int, child_id: int | None = None):
513
539
  if child_id is None:
514
- child_id = Child.current.id
540
+ child_id = Child.current().id
515
541
 
516
542
  old_val = hconfig(key, child_id)
517
543
  print(key, val, child_id, old_val)
@@ -522,14 +548,16 @@ def add_config_if_not_exist(key: ConfigEnum, val: str | int, child_id: int | Non
522
548
  def add_column(column):
523
549
  try:
524
550
  column_type = column.type.compile(db.engine.dialect)
525
- db.engine.execute(f'ALTER TABLE {column.table.name} ADD COLUMN {column.name} {column_type}')
551
+
552
+ db_execute(f'ALTER TABLE {column.table.name} ADD COLUMN {column.name} {column_type}')
526
553
  except BaseException:
527
554
  pass
528
555
 
529
556
 
530
- def execute(query):
557
+ def execute(query: str):
531
558
  try:
532
- db.engine.execute(query)
559
+
560
+ db_execute(query)
533
561
  except BaseException as e:
534
562
  print(e)
535
563
  pass
@@ -551,7 +579,8 @@ def add_new_enum_values():
551
579
  # Get the values in the enum column in the database
552
580
  # result = db.engine.execute(f"SELECT DISTINCT `{column_name}` FROM {table_name}")
553
581
  # db_values = {row[0] for row in result}
554
- result = db.engine.execute(f"SHOW COLUMNS FROM {table_name} LIKE '{column_name}';")
582
+
583
+ result = db_execute(f"SHOW COLUMNS FROM {table_name} LIKE '{column_name}';")
555
584
  db_values = []
556
585
 
557
586
  for row in result:
@@ -570,7 +599,7 @@ def add_new_enum_values():
570
599
  # Add the new value to the enum column in the database
571
600
  enumstr = ','.join([f"'{a}'" for a in [*existing_values, *old_values]])
572
601
 
573
- db.engine.execute(f"ALTER TABLE {table_name} MODIFY COLUMN `{column_name}` ENUM({enumstr});")
602
+ db_execute(text(f"ALTER TABLE {table_name} MODIFY COLUMN `{column_name}` ENUM({enumstr});"))
574
603
 
575
604
  db.session.commit()
576
605
 
@@ -674,6 +703,8 @@ def migrate(db_version):
674
703
  for column in table_obj.columns:
675
704
  add_column(column)
676
705
  Events.db_prehook.notify()
706
+ if db_version < 82:
707
+ execute('ALTER TABLE child DROP INDEX `name`;')
677
708
  if db_version < 77:
678
709
  execute('ALTER TABLE user_detail DROP COLUMN connected_ips;')
679
710
  execute('update user_detail set connected_devices="" where connected_devices IS NULL')
@@ -1,11 +1,13 @@
1
1
  from flask_babel import lazy_gettext as _
2
2
  from sqlalchemy import func
3
+ from typing import Dict
3
4
  import datetime
4
5
 
5
6
  from hiddifypanel.drivers import user_driver
6
7
  from hiddifypanel.models import *
7
8
  from hiddifypanel.panel import hiddify
8
9
  from hiddifypanel.database import db
10
+ from hiddifypanel import hutils
9
11
 
10
12
  to_gig_d = 1024**3
11
13
 
@@ -17,20 +19,18 @@ def update_local_usage():
17
19
  # return {"status": 'success', "comments":res}
18
20
 
19
21
 
20
- def add_users_usage_uuid(uuids_bytes, child_id):
22
+ def add_users_usage_uuid(uuids_bytes: Dict[str, Dict], child_id, sync=False):
21
23
  uuids_bytes = {u: v for u, v in uuids_bytes.items() if v}
22
- uuids = keys(uuids_bytes)
24
+ uuids = uuids_bytes.keys()
23
25
  users = User.query.filter(User.uuid.in_(uuids))
24
26
  dbusers_bytes = {u: uuids_bytes.get(u.uuid, 0) for u in users}
25
- _add_users_usage(dbusers_bytes, child_id)
27
+ _add_users_usage(dbusers_bytes, child_id, sync) # type: ignore
26
28
 
27
29
 
28
- def _add_users_usage(dbusers_bytes, child_id):
29
- # print(dbusers_bytes)
30
- if not hconfig(ConfigEnum.is_parent) and hconfig(ConfigEnum.parent_panel):
31
- from hiddifypanel.panel import hiddify_api
32
- hiddify_api.add_user_usage_to_parent(dbusers_bytes)
33
-
30
+ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
31
+ '''
32
+ sync: when enabled, it means we have received usages from the parent panel
33
+ '''
34
34
  res = {}
35
35
  have_change = False
36
36
  before_enabled_users = user_driver.get_enabled_users()
@@ -44,7 +44,7 @@ def _add_users_usage(dbusers_bytes, child_id):
44
44
  daily_usage[adm.id].online = User.query.filter(User.added_by == adm.id).filter(func.DATE(User.last_online) == today).count()
45
45
  # db.session.commit()
46
46
  userDetails = {p.user_id: p for p in UserDetail.query.filter(UserDetail.child_id == child_id).all()}
47
- for user, uinfo in dbusers_bytes.items():
47
+ for user, uinfo in users_usage_data.items():
48
48
  usage_bytes = uinfo['usage']
49
49
  devices = uinfo['devices']
50
50
  # user_active_before=user.is_active
@@ -68,11 +68,20 @@ def _add_users_usage(dbusers_bytes, child_id):
68
68
  if not isinstance(usage_bytes, int) or usage_bytes == 0:
69
69
  res[user.uuid] = "No usage"
70
70
  else:
71
- daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
71
+ if sync:
72
+ if daily_usage.get(user.added_by, daily_usage[1]).usage != usage_bytes:
73
+ daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
74
+ else:
75
+ daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
72
76
  in_gig = (usage_bytes) / to_gig_d
73
77
  res[user.uuid] = in_gig
74
- user.current_usage_GB += in_gig
75
- detail.current_usage_GB += in_gig
78
+ if sync:
79
+ if user.current_usage_GB != in_gig:
80
+ user.current_usage_GB = in_gig
81
+ # detail.current_usage_GB = in_gig
82
+ else:
83
+ user.current_usage_GB += in_gig
84
+ detail.current_usage_GB += in_gig
76
85
  user.last_online = datetime.datetime.now()
77
86
  detail.last_online = datetime.datetime.now()
78
87
 
@@ -85,15 +94,19 @@ def _add_users_usage(dbusers_bytes, child_id):
85
94
  have_change = True
86
95
  res[user.uuid] = f"{res[user.uuid]} !OUT of USAGE! Client Removed"
87
96
 
88
- db.session.commit()
97
+ db.session.commit() # type: ignore
89
98
  if have_change:
90
99
  hiddify.quick_apply_users()
91
100
 
101
+ # sync with the parent
102
+ if not sync:
103
+ if hutils.node.is_child():
104
+ hutils.node.child.sync_users_usage_with_parent()
92
105
  return {"status": 'success', "comments": res}
93
106
 
94
107
 
95
108
  def send_bot_message(user):
96
- if not (hconfig(ConfigEnum.telegram_bot_token) and not hconfig(ConfigEnum.parent_panel)):
109
+ if not (hconfig(ConfigEnum.telegram_bot_token) or not hutils.node.is_parent()):
97
110
  return
98
111
  if not user.telegram_id:
99
112
  return
@@ -23,7 +23,7 @@
23
23
  {% endif %}
24
24
  {% if not user.is_active %}
25
25
  {{_("User is inactive")}}
26
- {% if (user.ips|length)>user.max_ips: %}
26
+ {% if (user.devices|length)>user.max_ips: %}
27
27
  <br>
28
28
  <i class="fa-solid fa-users-slash text-danger"></i> {{_("Too many Connected IPs")}}
29
29
  {% endif %}
@@ -13,7 +13,7 @@
13
13
  <link rel="manifest" href="{{ url_for('common_bp.LoginView:create_pwa_manifest',secret_uuid=g.account.uuid or '')}}">
14
14
 
15
15
  <title>Hiddify | Panel</title>
16
- <script type="module" crossorigin src="../static/new/assets/index-bce9b1a6.js"></script>
16
+ <script type="module" crossorigin src="../static/new/assets/index-ccb9873c.js"></script>
17
17
  <link rel="stylesheet" href="../static/new/assets/index-fa00de9a.css">
18
18
  </head>
19
19
  <body>
@@ -555,4 +555,17 @@ td.col-uuid a {
555
555
 
556
556
  .select2-container {
557
557
  display: none;
558
+ }
559
+
560
+
561
+ #main-sidebar .nav-sidebar>.nav-item>.nav-link {
562
+ color: white;
563
+ }
564
+
565
+ #main-sidebar .nav-sidebar>.nav-item>.nav-link:hover {
566
+ background: #007bff;
567
+ }
568
+
569
+ .dark-mode #main-sidebar .nav-sidebar>.nav-item>.nav-link:hover {
570
+ background: #3f6791;
558
571
  }
Binary file