hiddifypanel 9.0.0.dev90__py3-none-any.whl → 10.5.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 (152) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +30 -9
  4. hiddifypanel/base.py +60 -52
  5. hiddifypanel/cache.py +43 -25
  6. hiddifypanel/database.py +9 -0
  7. hiddifypanel/drivers/abstract_driver.py +2 -0
  8. hiddifypanel/drivers/singbox_api.py +17 -15
  9. hiddifypanel/drivers/ssh_liberty_bridge_api.py +3 -1
  10. hiddifypanel/drivers/user_driver.py +12 -6
  11. hiddifypanel/drivers/wireguard_api.py +7 -2
  12. hiddifypanel/drivers/xray_api.py +14 -9
  13. hiddifypanel/hutils/__init__.py +4 -0
  14. hiddifypanel/hutils/convert.py +13 -2
  15. hiddifypanel/hutils/crypto.py +48 -0
  16. hiddifypanel/hutils/encode.py +4 -1
  17. hiddifypanel/hutils/flask.py +38 -5
  18. hiddifypanel/hutils/github_issue.py +1 -1
  19. hiddifypanel/hutils/importer/xui.py +5 -2
  20. hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
  21. hiddifypanel/hutils/network/auto_ip_selector.py +2 -0
  22. hiddifypanel/hutils/network/net.py +46 -2
  23. hiddifypanel/hutils/node/__init__.py +3 -0
  24. hiddifypanel/hutils/node/api_client.py +76 -0
  25. hiddifypanel/hutils/node/child.py +147 -0
  26. hiddifypanel/hutils/node/parent.py +100 -0
  27. hiddifypanel/hutils/node/shared.py +65 -0
  28. hiddifypanel/hutils/proxy/__init__.py +5 -0
  29. hiddifypanel/hutils/proxy/clash.py +161 -0
  30. hiddifypanel/hutils/proxy/shared.py +434 -0
  31. hiddifypanel/hutils/proxy/singbox.py +339 -0
  32. hiddifypanel/hutils/proxy/xray.py +235 -0
  33. hiddifypanel/hutils/proxy/xrayjson.py +391 -0
  34. hiddifypanel/hutils/random.py +4 -0
  35. hiddifypanel/hutils/utils.py +4 -1
  36. hiddifypanel/models/__init__.py +2 -2
  37. hiddifypanel/models/admin.py +31 -17
  38. hiddifypanel/models/base_account.py +7 -7
  39. hiddifypanel/models/child.py +30 -16
  40. hiddifypanel/models/config.py +45 -16
  41. hiddifypanel/models/config_enum.py +68 -17
  42. hiddifypanel/models/domain.py +28 -20
  43. hiddifypanel/models/parent_domain.py +2 -2
  44. hiddifypanel/models/proxy.py +29 -20
  45. hiddifypanel/models/report.py +2 -3
  46. hiddifypanel/models/usage.py +2 -2
  47. hiddifypanel/models/user.py +33 -22
  48. hiddifypanel/panel/admin/Actions.py +13 -19
  49. hiddifypanel/panel/admin/AdminstratorAdmin.py +14 -3
  50. hiddifypanel/panel/admin/Dashboard.py +5 -10
  51. hiddifypanel/panel/admin/DomainAdmin.py +35 -48
  52. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  53. hiddifypanel/panel/admin/ProxyAdmin.py +6 -5
  54. hiddifypanel/panel/admin/QuickSetup.py +21 -20
  55. hiddifypanel/panel/admin/SettingAdmin.py +107 -62
  56. hiddifypanel/panel/admin/UserAdmin.py +22 -21
  57. hiddifypanel/panel/admin/templates/index.html +1 -1
  58. hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
  59. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  60. hiddifypanel/panel/admin/templates/result.html +2 -3
  61. hiddifypanel/panel/cf_api.py +1 -2
  62. hiddifypanel/panel/cli.py +16 -16
  63. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +16 -12
  64. hiddifypanel/panel/commercial/__init__.py +7 -5
  65. hiddifypanel/panel/commercial/restapi/v1/__init__.py +1 -1
  66. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -1
  67. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
  68. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  69. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  70. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  71. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -25
  72. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  73. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  74. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  75. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -66
  76. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  77. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  78. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  79. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  80. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  81. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  82. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  83. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  84. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  85. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  86. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  87. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  88. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  89. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  90. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  91. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  92. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +17 -23
  93. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +23 -26
  94. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  95. hiddifypanel/panel/common.py +25 -8
  96. hiddifypanel/panel/common_bp/login.py +2 -2
  97. hiddifypanel/panel/hiddify.py +22 -185
  98. hiddifypanel/panel/init_db.py +102 -55
  99. hiddifypanel/panel/usage.py +33 -18
  100. hiddifypanel/panel/user/__init__.py +0 -1
  101. hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
  102. hiddifypanel/panel/user/templates/all_configs.txt +2 -2
  103. hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
  104. hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
  105. hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
  106. hiddifypanel/panel/user/templates/clash_config.yml +4 -4
  107. hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
  108. hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
  109. hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
  110. hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
  111. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  112. hiddifypanel/panel/user/templates/new.html +2 -2
  113. hiddifypanel/panel/user/user.py +56 -50
  114. hiddifypanel/static/css/custom.css +31 -0
  115. hiddifypanel/static/images/favicon.ico +0 -0
  116. hiddifypanel/static/images/hiddify-old.png +0 -0
  117. hiddifypanel/static/images/hiddify.png +0 -0
  118. hiddifypanel/static/images/hiddify2.png +0 -0
  119. hiddifypanel/static/new/assets/{index-1b891a7c.js → index-ccb9873c.js} +56 -56
  120. hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
  121. hiddifypanel/static/new/i18n/en.json +6 -6
  122. hiddifypanel/static/new/i18n/fa.json +2 -2
  123. hiddifypanel/templates/admin-layout.html +30 -43
  124. hiddifypanel/templates/fake.html +0 -4
  125. hiddifypanel/templates/flaskadmin-layout.html +7 -3
  126. hiddifypanel/templates/master.html +11 -6
  127. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  128. hiddifypanel/translations/en/LC_MESSAGES/messages.po +2082 -1977
  129. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  130. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +2035 -1924
  131. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  132. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +1911 -1840
  133. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  134. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2036 -1881
  135. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1857 -1720
  137. hiddifypanel/translations.i18n/en.json +992 -933
  138. hiddifypanel/translations.i18n/fa.json +994 -935
  139. hiddifypanel/translations.i18n/pt.json +994 -935
  140. hiddifypanel/translations.i18n/ru.json +994 -935
  141. hiddifypanel/translations.i18n/zh.json +971 -912
  142. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
  143. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
  144. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/WHEEL +1 -1
  145. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  146. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  147. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  148. hiddifypanel/panel/user/link_maker.py +0 -1083
  149. hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
  150. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
  151. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
  152. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/top_level.txt +0 -0
hiddifypanel/panel/cli.py CHANGED
@@ -1,15 +1,17 @@
1
1
  import datetime
2
2
  import uuid
3
-
3
+ import json
4
+ import os
4
5
  import click
5
6
  from dateutil import relativedelta
7
+
8
+
6
9
  from hiddifypanel import hutils
7
10
 
8
11
  from hiddifypanel.models import *
9
12
  from hiddifypanel.panel import hiddify, usage
10
13
  from hiddifypanel.database import db
11
14
  from hiddifypanel.panel.init_db import init_db
12
- from flask import g
13
15
 
14
16
 
15
17
  def drop_db():
@@ -24,29 +26,28 @@ def downgrade():
24
26
  ConfigEnum.hysteria_port, ConfigEnum.ssh_server_enable, ConfigEnum.ssh_server_port, ConfigEnum.ssh_server_redis_url])).delete()
25
27
  Proxy.query.filter(Proxy.l3.in_([ProxyL3.ssh, ProxyL3.h3_quic, ProxyL3.custom])).delete()
26
28
  db.session.commit()
27
- import os
28
29
  os.rename("/opt/hiddify-manager/hiddify-panel/hiddifypanel.db.old", "/opt/hiddify-manager/hiddify-panel/hiddifypanel.db")
29
30
 
30
31
 
31
32
  def backup():
32
33
  dbdict = hiddify.dump_db_to_dict()
33
- import json
34
- import os
35
34
  os.makedirs('backup', exist_ok=True)
36
35
  dst = f'backup/{datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")}.json'
37
36
  with open(dst, 'w') as fp:
38
37
  json.dump(dbdict, fp, indent=4, sort_keys=True, default=str)
39
38
 
40
39
  if hconfig(ConfigEnum.telegram_bot_token):
41
- for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
42
- from hiddifypanel.panel.commercial.telegrambot import bot
43
- with open(dst, 'rb') as document:
40
+ from hiddifypanel.panel.commercial.telegrambot import bot, register_bot
41
+ if not bot.token:
42
+ register_bot(True)
43
+
44
+ with open(dst, 'rb') as document:
45
+ for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
44
46
  caption = ("Backup \n" + admin_links())
45
47
  bot.send_document(admin.telegram_id, document, visible_file_name=dst.replace("backup/", ""), caption=caption[:min(len(caption), 1000)])
46
48
 
47
49
 
48
50
  def all_configs():
49
- import json
50
51
  valid_users = [u.to_dict(dump_id=True) for u in User.query.filter((User.usage_limit > User.current_usage)).all() if u.is_active]
51
52
  host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
52
53
  configs = {
@@ -111,8 +112,8 @@ def admin_path():
111
112
  print(hiddify.get_account_panel_link(admin, domain, prefere_path_only=True))
112
113
 
113
114
 
114
- def get_this_host_domains():
115
- current_child_ids
115
+ # def get_this_host_domains():
116
+ # current_child_ids
116
117
 
117
118
 
118
119
  def hysteria_domain_port():
@@ -142,12 +143,11 @@ def init_app(app):
142
143
  @ app.cli.command()
143
144
  @ click.option("--domain", "-d")
144
145
  def add_domain(domain):
145
- table = ParentDomain if hconfig(ConfigEnum.is_parent) else Domain
146
-
147
- if table.query.filter(table.domain == domain).first():
146
+ # TODO: Fix this
147
+ if Domain.query.filter(Domain.domain == domain).first():
148
148
  return "Domain already exist."
149
- d = table(domain=domain)
150
- if not hconfig(ConfigEnum.is_parent):
149
+ d = Domain(domain=domain)
150
+ if not hutils.node.is_parent():
151
151
  d.mode = DomainType.direct
152
152
  db.session.add(d)
153
153
  db.session.commit()
@@ -1,24 +1,26 @@
1
1
  from hiddifypanel.models import *
2
+ from hiddifypanel.panel import hiddify
2
3
  from hiddifypanel.panel.admin.adminlte import AdminLTEModelView
3
4
  from flask_babel import gettext as __
4
5
  from flask_babel import lazy_gettext as _
5
- from hiddifypanel.panel import hiddify
6
- from flask import g, redirect, Markup
6
+ from flask import g, redirect
7
+ from markupsafe import Markup
8
+
7
9
  from hiddifypanel.hutils.flask import hurl_for, flash
8
10
  from hiddifypanel.auth import login_required
9
11
  from flask_admin.model.template import EndpointLinkRowAction
10
12
  from flask_admin.actions import action
11
13
  from flask_admin.contrib.sqla import form, filters as sqla_filters, tools
12
14
  # Define a custom field type for the related domains
13
-
14
15
  from flask import current_app
16
+ from hiddifypanel import hutils
15
17
 
16
18
 
17
19
  class ProxyDetailsAdmin(AdminLTEModelView):
18
20
 
19
21
  column_hide_backrefs = True
20
22
  can_create = False
21
- form_excluded_columns = ['child']
23
+ form_excluded_columns = ['child', 'proto', 'transport', 'cdn']
22
24
  column_exclude_list = ['child']
23
25
  column_searchable_list = ['name', 'proto', 'transport', 'l3', 'cdn']
24
26
  column_editable_list = ['name']
@@ -30,7 +32,7 @@ class ProxyDetailsAdmin(AdminLTEModelView):
30
32
 
31
33
  self.session.commit()
32
34
  flash(_('%(count)s records were successfully disabled.', count=count), 'success')
33
- hiddify.get_available_proxies.invalidate_all()
35
+ hutils.proxy.get_proxies.invalidate_all()
34
36
 
35
37
  @action('enable', 'Enable', 'Are you sure you want to enable selected proxies?')
36
38
  def action_enable(self, ids):
@@ -39,22 +41,24 @@ class ProxyDetailsAdmin(AdminLTEModelView):
39
41
 
40
42
  self.session.commit()
41
43
  flash(_('%(count)s records were successfully enabled.', count=count), 'success')
42
- hiddify.get_available_proxies.invalidate_all()
44
+ hutils.proxy.get_proxies.invalidate_all()
43
45
 
44
46
  # list_template = 'model/domain_list.html'
45
47
 
46
48
  # form_overrides = {'work_with': Select2Field}
47
49
 
48
50
  def after_model_change(self, form, model, is_created):
49
- # if hconfig(ConfigEnum.parent_panel):
50
- # hiddify_api.sync_child_to_parent()
51
- hiddify.get_available_proxies.invalidate_all()
51
+ if hutils.node.is_child():
52
+ if not hutils.node.child.sync_with_parent():
53
+ hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
54
+ hutils.proxy.get_proxies.invalidate_all()
52
55
  pass
53
56
 
54
57
  def after_model_delete(self, model):
55
- # if hconfig(ConfigEnum.parent_panel):
56
- # hiddify_api.sync_child_to_parent()
57
- hiddify.get_available_proxies.invalidate_all()
58
+ if hutils.node.is_child():
59
+ if not hutils.node.child.sync_with_parent():
60
+ hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
61
+ hutils.proxy.get_proxies.invalidate_all()
58
62
  pass
59
63
 
60
64
  def is_accessible(self):
@@ -1,6 +1,3 @@
1
- from .ProxyDetailsAdmin import ProxyDetailsAdmin
2
- # from .CommercialSettings import CommercialSettings
3
- from hiddifypanel.panel import hiddify
4
1
  from hiddifypanel.models import *
5
2
  from hiddifypanel.database import db
6
3
  from hiddifypanel import Events, hutils
@@ -13,10 +10,14 @@ def init_app(app):
13
10
  restapi_v1.init_app(app)
14
11
  from .restapi.v2 import admin as api_v2_admin
15
12
  from .restapi.v2 import user as api_v2_user
16
- from .restapi.v2 import hello as api_v2_hello
13
+ from .restapi.v2 import parent as api_v2_parent
14
+ from .restapi.v2 import child as api_v2_child
15
+ from .restapi.v2 import panel as api_v2_panel
16
+ api_v2_parent.init_app(app)
17
17
  api_v2_admin.init_app(app)
18
18
  api_v2_user.init_app(app)
19
- api_v2_hello.init_app(app)
19
+ api_v2_child.init_app(app)
20
+ api_v2_panel.init_app(app)
20
21
  return
21
22
 
22
23
 
@@ -53,6 +54,7 @@ Events.config_changed.subscribe(config_changed_event)
53
54
  def admin_prehook(flaskadmin, admin_bp):
54
55
  # from .ParentDomainAdmin import ParentDomainAdmin
55
56
  # flaskadmin.add_view(ParentDomainAdmin(ParentDomain, db.session))
57
+ from .ProxyDetailsAdmin import ProxyDetailsAdmin
56
58
  flaskadmin.add_view(ProxyDetailsAdmin(Proxy, db.session))
57
59
  # CommercialSettings.register(admin_bp)
58
60
 
@@ -10,7 +10,7 @@ from . import tgbot
10
10
  from .tgmsg import SendMsgResource
11
11
  from .resources import *
12
12
  bp = APIBlueprint("api_v1", __name__, url_prefix="/<proxy_path>/api/v1/", tag="api_v1", enable_openapi=False)
13
- bp_uuid = APIBlueprint("api_v1_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v1", template_folder="templates", enable_openapi=False)
13
+ bp_uuid = APIBlueprint("api_v1_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v1/", template_folder="templates", enable_openapi=False)
14
14
  api = Api(bp)
15
15
  api_uuid = Api(bp_uuid)
16
16
 
@@ -34,7 +34,7 @@ def register_bot(set_hook=False):
34
34
 
35
35
  user_secret = AdminUser.get_super_admin_uuid()
36
36
  if set_hook:
37
- bot.set_webhook(url=f"https://{domain}/{admin_proxy_path}/{user_secret}/api/v1/tgbot/",)
37
+ bot.set_webhook(url=f"https://{domain}/{admin_proxy_path}/{user_secret}/api/v1/tgbot/")
38
38
  except Exception as e:
39
39
  print(e)
40
40
  import traceback
@@ -39,30 +39,34 @@ class SendMsgResource(Resource):
39
39
  else:
40
40
  return {'msg': 'error', 'res': res}
41
41
 
42
- def get_users_by_identifier(self, identifier: str) -> List[User]:
42
+ def get_users_by_identifier(self, identifier: str | list) -> List[User]:
43
43
  '''Returns all users that match the identifier for sending a message to them'''
44
44
  # when we are here we must have g.account but ...
45
45
  if not hasattr(g, 'account'):
46
46
  return []
47
+
47
48
  query = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids()))
48
49
  query = query.filter(User.telegram_id is not None, User.telegram_id != 0)
49
50
 
50
- if hutils.convert.is_int(identifier):
51
+ # user selected many ids as users identifier
52
+ if isinstance(identifier, list):
53
+ return query.filter(User.id.in_(identifier)).all()
54
+
55
+ if hutils.convert.is_int(identifier): # type: ignore
51
56
  return [query.filter(User.id == int(identifier)).first() or abort(404, 'The user not found')] # type: ignore
52
- elif identifier == 'all':
57
+ if identifier == 'all':
53
58
  return query.all()
54
- elif identifier == 'expired':
59
+ if identifier == 'expired':
55
60
  return [u for u in query.all() if not u.is_active]
56
- elif identifier == 'active':
61
+ if identifier == 'active':
57
62
  return [u for u in query.all() if u.is_active]
58
- elif identifier == 'offline 1h':
63
+ if identifier == 'offline 1h':
59
64
  h1 = datetime.datetime.now() - datetime.timedelta(hours=1)
60
65
  return [u for u in query.all() if u.is_active and u.last_online < h1]
61
- elif identifier == 'offline 1d':
66
+ if identifier == 'offline 1d':
62
67
  d1 = datetime.datetime.now() - datetime.timedelta(hours=24)
63
68
  return [u for u in query.all() if u.is_active and u.last_online < d1]
64
- elif identifier == 'offline 1w':
69
+ if identifier == 'offline 1w':
65
70
  d7 = datetime.datetime.now() - datetime.timedelta(days=7)
66
71
  return [u for u in query.all() if u.is_active and u.last_online < d7]
67
- else:
68
- return []
72
+ return []
@@ -32,8 +32,3 @@ def has_permission(model) -> bool:
32
32
  if not g.account.uuid != AdminUser.get_super_admin_uuid() and model.added_by != g.account.id: # type: ignore
33
33
  return False
34
34
  return True
35
-
36
-
37
- class SuccessfulSchema(Schema):
38
- status = Integer()
39
- msg = String()
@@ -7,13 +7,13 @@ from hiddifypanel.models.admin import AdminUser
7
7
  from hiddifypanel.models.config_enum import ConfigEnum, Lang
8
8
  from hiddifypanel.models.config import hconfig
9
9
  from hiddifypanel.models.role import Role
10
- from .admin_user_api import AdminSchema
10
+ from .schema import AdminSchema
11
11
 
12
12
 
13
13
  class AdminInfoApi(MethodView):
14
14
  decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
15
15
 
16
- @app.output(AdminSchema)
16
+ @app.output(AdminSchema) # type: ignore
17
17
  def get(self):
18
18
  # admin = AdminUser.by_uuid(g.account.uuid) or abort(404, "user not found")
19
19
  admin = g.account or abort(404, "user not found")
@@ -2,7 +2,6 @@ from apiflask import Schema, fields, abort
2
2
  from flask.views import MethodView
3
3
  from hiddifypanel import hutils
4
4
  from hiddifypanel.models.role import Role
5
- from hiddifypanel.panel import hiddify
6
5
  from flask import current_app as app, make_response, g, request
7
6
  import os
8
7
  from ansi2html import Ansi2HTMLConverter
@@ -10,13 +9,13 @@ from hiddifypanel.auth import login_required
10
9
  from hiddifypanel.models import *
11
10
 
12
11
 
13
- class AdminLogfileSchema(Schema):
12
+ class AdminInputLogfileSchema(Schema):
14
13
  file = fields.String(description="The log file name", required=True)
15
14
 
16
15
 
17
16
  class AdminLogApi(MethodView):
18
- @app.input(AdminLogfileSchema, arg_name="data", location='form')
19
- @app.output(fields.String(description="The html of the log", many=True))
17
+ @app.input(AdminInputLogfileSchema, arg_name="data", location='form') # type: ignore
18
+ @app.output(fields.String(description="The html of the log", many=True)) # type: ignore
20
19
  @login_required({Role.super_admin})
21
20
  def post(self, data):
22
21
  file_name = data.get('file') or abort(400, "Parameter issue: 'file'")
@@ -40,7 +39,7 @@ class AdminLogApi(MethodView):
40
39
  return resp
41
40
 
42
41
  def options(self):
43
- domain = request.args.get("domain")
42
+ # domain = request.args.get("domain")
44
43
  # Domain.query.filter(Domain.domain == domain).first() or abort(404)
45
44
  if g.proxy_path != hconfig(ConfigEnum.proxy_path_admin):
46
45
  abort(403)
@@ -1,43 +1,26 @@
1
-
2
- from apiflask.fields import Integer, String, UUID, Boolean, Enum
3
1
  from flask import current_app as app
4
2
  from flask import g
5
3
  from flask.views import MethodView
6
- from apiflask import Schema
7
4
  from apiflask import abort
8
5
  from hiddifypanel.auth import login_required
9
6
  from hiddifypanel.models import *
10
- from hiddifypanel.panel import hiddify
11
- from hiddifypanel.models import AdminMode, Lang
12
-
13
- from . import SuccessfulSchema, has_permission
14
-
15
7
 
16
- class AdminSchema(Schema):
17
- name = String(required=True, description='The name of the admin')
18
- comment = String(required=False, description='A comment related to the admin', allow_none=True)
19
- uuid = UUID(required=True, description='The unique identifier for the admin')
20
- mode = Enum(AdminMode, required=True, description='The mode for the admin')
21
- can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
22
- parent_admin_uuid = UUID(description='The unique identifier for the parent admin', allow_none=True,
23
- # validate=OneOf([p.uuid for p in AdminUser.query.all()])
24
- )
25
- telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
26
- lang = Enum(Lang, required=True)
8
+ from . import has_permission
9
+ from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
27
10
 
28
11
 
29
12
  class AdminUserApi(MethodView):
30
13
  decorators = [login_required({Role.super_admin, Role.admin})]
31
14
 
32
- @app.output(AdminSchema)
15
+ @app.output(AdminSchema) # type: ignore
33
16
  def get(self, uuid):
34
17
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
35
18
  if not has_permission(admin):
36
19
  abort(403, "You don't have permission to access this admin")
37
- return admin.to_dict()
20
+ return admin.to_dict() # type: ignore
38
21
 
39
- @app.input(AdminSchema, arg_name='data')
40
- @app.output(SuccessfulSchema)
22
+ @app.input(PatchAdminSchema, arg_name='data') # type: ignore
23
+ @app.output(SuccessfulSchema) # type: ignore
41
24
  def patch(self, uuid, data):
42
25
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
43
26
  if not has_permission(admin):
@@ -50,10 +33,10 @@ class AdminUserApi(MethodView):
50
33
  AdminUser.add_or_update(**data)
51
34
  return {'status': 200, 'msg': 'ok'}
52
35
 
53
- @app.output(SuccessfulSchema)
36
+ @app.output(SuccessfulSchema) # type: ignore
54
37
  def delete(self, uuid):
55
38
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
56
39
  if not has_permission(admin):
57
40
  abort(403, "You don't have permission to access this admin")
58
- admin.remove()
41
+ admin.remove() # type: ignore
59
42
  return {'status': 200, 'msg': 'ok'}
@@ -11,13 +11,13 @@ from hiddifypanel.models import AdminUser
11
11
  class AdminUsersApi(MethodView):
12
12
  decorators = [login_required({Role.super_admin, Role.admin})]
13
13
 
14
- @app.output(AdminSchema(many=True))
14
+ @app.output(AdminSchema(many=True)) # type: ignore
15
15
  def get(self):
16
16
  admins = AdminUser.query.filter(AdminUser.id.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no admin")
17
17
  return [admin.to_dict() for admin in admins] # type: ignore
18
18
 
19
- @app.input(AdminSchema, arg_name='data')
20
- @app.output(AdminSchema)
19
+ @app.input(AdminSchema, arg_name='data') # type: ignore
20
+ @app.output(AdminSchema) # type: ignore
21
21
  def put(self, data):
22
22
  uuid = data.get('uuid') or abort(422, "Parameter issue: 'uuid'")
23
23
  admin = AdminUser.by_uuid(uuid) # type: ignore
@@ -31,4 +31,4 @@ class AdminUsersApi(MethodView):
31
31
  abort(403, "You don't have permission to access this admin")
32
32
 
33
33
  dbadmin = AdminUser.add_or_update(**data) or abort(502, "Unknown issue: Admin is not added")
34
- return dbadmin.to_dict()
34
+ return dbadmin.to_dict() # type: ignore
@@ -0,0 +1,157 @@
1
+ import uuid
2
+ from apiflask.fields import String, Float, Enum, Date, Integer, Boolean
3
+ from apiflask import Schema, fields
4
+ from typing import Any, Mapping
5
+
6
+ from hiddifypanel.models import UserMode, Lang, AdminMode
7
+ from hiddifypanel import hutils
8
+
9
+ # region user api
10
+
11
+
12
+ class FriendlyDateTime(fields.Field):
13
+ def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
14
+ return hutils.convert.time_to_json(value)
15
+
16
+ def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
17
+ return hutils.convert.json_to_time(value)
18
+
19
+
20
+ class FriendlyUUID(fields.Field):
21
+ def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
22
+ if value is None:
23
+ return None
24
+ return str(value)
25
+
26
+ def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
27
+ if value is None:
28
+ return None
29
+ try:
30
+ return uuid.UUID(value)
31
+ except ValueError:
32
+ self.fail('Invalid uuid')
33
+
34
+
35
+ class UserSchema(Schema):
36
+ uuid = FriendlyUUID(required=True, description="Unique identifier for the user")
37
+ name = String(required=True, description="Name of the user")
38
+
39
+ usage_limit_GB = Float(
40
+ required=False,
41
+ allow_none=True,
42
+ description="The data usage limit for the user in gigabytes"
43
+ )
44
+ package_days = Integer(
45
+ required=False,
46
+ allow_none=True,
47
+ description="The number of days in the user's package"
48
+ )
49
+ mode = Enum(UserMode,
50
+ required=False,
51
+ allow_none=True,
52
+ description="The mode of the user's account, which dictates access level or type"
53
+ )
54
+ last_online = FriendlyDateTime(
55
+ format="%Y-%m-%d %H:%M:%S",
56
+ allow_none=True,
57
+ description="The last time the user was online, converted to a JSON-friendly format"
58
+ )
59
+ start_date = Date(
60
+ format='%Y-%m-%d',
61
+ allow_none=True,
62
+ description="The start date of the user's package, in a JSON-friendly format"
63
+ )
64
+ current_usage_GB = Float(
65
+ required=False,
66
+ allow_none=True,
67
+ description="The current data usage of the user in gigabytes"
68
+ )
69
+ last_reset_time = Date(
70
+ format='%Y-%m-%d',
71
+ description="The last time the user's data usage was reset, in a JSON-friendly format",
72
+ allow_none=True
73
+ )
74
+ comment = String(
75
+ missing=None,
76
+ allow_none=True,
77
+ description="An optional comment about the user"
78
+ )
79
+ added_by_uuid = FriendlyUUID(
80
+ required=False,
81
+ description="UUID of the admin who added this user",
82
+ allow_none=True,
83
+ # validate=OneOf([p.uuid for p in AdminUser.query.all()])
84
+ )
85
+ telegram_id = Integer(
86
+ required=False,
87
+ description="The Telegram ID associated with the user",
88
+ allow_none=True
89
+ )
90
+ ed25519_private_key = String(
91
+ required=False,
92
+ allow_none=True,
93
+ description="If empty, it will be created automatically, The user's private key using the Ed25519 algorithm"
94
+ )
95
+ ed25519_public_key = String(
96
+ required=False,
97
+ allow_none=True,
98
+ description="If empty, it will be created automatically,The user's public key using the Ed25519 algorithm"
99
+ )
100
+ wg_pk = String(
101
+ required=False,
102
+ allow_none=True,
103
+ description="If empty, it will be created automatically, The user's WireGuard private key"
104
+ )
105
+
106
+ wg_pub = String(
107
+ required=False,
108
+ allow_none=True,
109
+ description="If empty, it will be created automatically, The user's WireGuard public key"
110
+ )
111
+ wg_psk = String(
112
+ required=False,
113
+ allow_none=True,
114
+ description="If empty, it will be created automatically, The user's WireGuard preshared key"
115
+ )
116
+
117
+ lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
118
+
119
+
120
+ class PatchUserSchema(UserSchema):
121
+ def __init__(self, *args, **kwargs):
122
+ super().__init__(*args, **kwargs)
123
+ self.fields['name'].required = False
124
+ pass
125
+
126
+ # endregion
127
+
128
+ # region admin api
129
+
130
+
131
+ class AdminSchema(Schema):
132
+ name = String(required=True, description='The name of the admin')
133
+ comment = String(required=False, description='A comment related to the admin', allow_none=True)
134
+ uuid = FriendlyUUID(required=True, description='The unique identifier for the admin')
135
+ mode = Enum(AdminMode, required=True, description='The mode for the admin')
136
+ can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
137
+ parent_admin_uuid = FriendlyUUID(description='The unique identifier for the parent admin', allow_none=True,
138
+ # validate=OneOf([p.uuid for p in AdminUser.query.all()])
139
+ )
140
+ telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
141
+ lang = Enum(Lang, required=True)
142
+
143
+
144
+ class PatchAdminSchema(AdminSchema):
145
+ def __init__(self, *args, **kwargs):
146
+ super().__init__(*args, **kwargs)
147
+ self.fields['name'].required = False
148
+ self.fields['mode'].required = False
149
+ self.fields['lang'].required = False
150
+ self.fields['can_add_admin'].required = False
151
+ pass
152
+ # endregion
153
+
154
+
155
+ class SuccessfulSchema(Schema):
156
+ status = Integer()
157
+ msg = String()
@@ -10,7 +10,7 @@ from hiddifypanel.panel import hiddify
10
10
  from hiddifypanel import hutils
11
11
 
12
12
 
13
- class ServerStatus(Schema):
13
+ class ServerStatusOutputSchema(Schema):
14
14
  stats = Dict(required=True, description="System stats")
15
15
  usage_history = Dict(required=True, description="System usage history")
16
16
 
@@ -18,9 +18,9 @@ class ServerStatus(Schema):
18
18
  class AdminServerStatusApi(MethodView):
19
19
  decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
20
20
 
21
- @app.output(ServerStatus) # type: ignore
21
+ @app.output(ServerStatusOutputSchema) # type: ignore
22
22
  def get(self):
23
- dto = ServerStatus()
23
+ dto = ServerStatusOutputSchema()
24
24
  dto.stats = { # type: ignore
25
25
  'system': hutils.system.system_stats(),
26
26
  'top5': hutils.system.top_processes()