hiddifypanel 10.14.0__py3-none-any.whl → 10.15.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 (106) 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 +19 -5
  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/master.html +23 -41
  81. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  82. hiddifypanel/translations/en/LC_MESSAGES/messages.po +90 -0
  83. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  84. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +91 -1
  85. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  86. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
  87. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  88. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +90 -0
  89. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  90. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
  91. hiddifypanel/translations.i18n/en.json +56 -0
  92. hiddifypanel/translations.i18n/fa.json +57 -1
  93. hiddifypanel/translations.i18n/pt.json +63 -7
  94. hiddifypanel/translations.i18n/ru.json +56 -0
  95. hiddifypanel/translations.i18n/zh.json +58 -2
  96. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/METADATA +47 -47
  97. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/RECORD +103 -85
  98. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  99. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  100. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  101. /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
  102. /hiddifypanel/static/{new/assets/hiddify-logo-noroz-559c8dcb.png → images/hiddify2.png} +0 -0
  103. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/LICENSE.md +0 -0
  104. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/WHEEL +0 -0
  105. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/entry_points.txt +0 -0
  106. {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,9 @@ from flask_babel import gettext as __
8
8
  from .adminlte import AdminLTEModelView
9
9
  from wtforms.validators import NumberRange
10
10
  from flask_babel import lazy_gettext as _
11
- from flask import Markup, g, request # type: ignore
11
+ from flask import g, request # type: ignore
12
+ from markupsafe import Markup
13
+
12
14
  from hiddifypanel.hutils.flask import hurl_for
13
15
  from wtforms.validators import Regexp, ValidationError
14
16
  from flask import current_app
@@ -126,7 +128,7 @@ class UserAdmin(AdminLTEModelView):
126
128
  def _enable_formatter(view, context, model, name):
127
129
  if model.is_active:
128
130
  link = '<i class="fa-solid fa-circle-check text-success"></i> '
129
- elif len(model.ips):
131
+ elif len(model.devices):
130
132
  link = '<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
131
133
  else:
132
134
  link = '<i class="fa-solid fa-circle-xmark text-danger"></i> '
@@ -297,10 +299,16 @@ class UserAdmin(AdminLTEModelView):
297
299
  user_driver.remove_client(model)
298
300
  hiddify.quick_apply_users()
299
301
 
302
+ if hutils.node.is_parent():
303
+ hutils.node.parent.request_childs_to_sync()
304
+
300
305
  def after_model_delete(self, model):
301
306
  user_driver.remove_client(model)
302
307
  hiddify.quick_apply_users()
303
308
 
309
+ if hutils.node.is_parent():
310
+ hutils.node.parent.request_childs_to_sync()
311
+
304
312
  def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
305
313
  res = None
306
314
  # print('aaa',args, kwargs)
@@ -148,7 +148,7 @@
148
148
  </div>
149
149
 
150
150
  </div>
151
- {% if hconfig(ConfigEnum.is_parent) %}
151
+ {% if hutils.node.is_parent() %}
152
152
  {% include 'parent_dash.html' %}
153
153
  {% endif %}
154
154
  {% endblock %}
@@ -1,6 +1,6 @@
1
1
  {% macro admin_btn(child,domain) -%}
2
2
  <div class="btn-group">
3
- <a href="{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
3
+ <a href="{{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
4
4
  </div>
5
5
  {%- endmacro -%}
6
6
 
@@ -28,7 +28,6 @@
28
28
  <td>
29
29
  {% if not child.is_active %}
30
30
  <form method="post" action="remove_child">
31
- <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
32
31
  <input type="hidden" name="child_id" value="{{child.id}}" />
33
32
  <button type="submit" class="btn btn-xs btn-danger"><i class="fa fa-trash"></i></a>
34
33
  </form>
@@ -37,9 +36,8 @@
37
36
  <td class="text-center"><span class="btn btn-xs badge-{{" success" if child.is_active else "warning" }}">&nbsp{{icon('solid','check') if child.is_active else icon('solid','triangle-exclamation')}}</span>
38
37
  </td>
39
38
  <td>
40
-
41
39
  {% for d in child.domains %}
42
- {% if d.mode !="fake" %}
40
+ {% if d.mode !="fake" and d.mode != "reality"%}
43
41
  {{admin_btn(child,d)}}
44
42
  {% endif %}
45
43
  {% endfor %}
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,8 +1,11 @@
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 flask import g, redirect, Markup
6
+ from flask import g, redirect
7
+ from markupsafe import Markup
8
+
6
9
  from hiddifypanel.hutils.flask import hurl_for, flash
7
10
  from hiddifypanel.auth import login_required
8
11
  from flask_admin.model.template import EndpointLinkRowAction
@@ -45,14 +48,16 @@ class ProxyDetailsAdmin(AdminLTEModelView):
45
48
  # form_overrides = {'work_with': Select2Field}
46
49
 
47
50
  def after_model_change(self, form, model, is_created):
48
- # if hconfig(ConfigEnum.parent_panel):
49
- # hiddify_api.sync_child_to_parent()
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
50
54
  hutils.proxy.get_proxies.invalidate_all()
51
55
  pass
52
56
 
53
57
  def after_model_delete(self, model):
54
- # if hconfig(ConfigEnum.parent_panel):
55
- # hiddify_api.sync_child_to_parent()
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
56
61
  hutils.proxy.get_proxies.invalidate_all()
57
62
  pass
58
63
 
@@ -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
 
@@ -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,53 +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
-
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)
27
-
28
7
 
29
- class PatchAdminSchema(AdminSchema):
30
- def __init__(self, *args, **kwargs):
31
- super().__init__(*args, **kwargs)
32
- self.fields['name'].required = False
33
- self.fields['mode'].required = False
34
- self.fields['lang'].required = False
35
- self.fields['can_add_admin'].required = False
36
- pass
8
+ from . import has_permission
9
+ from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
37
10
 
38
11
 
39
12
  class AdminUserApi(MethodView):
40
13
  decorators = [login_required({Role.super_admin, Role.admin})]
41
14
 
42
- @app.output(AdminSchema)
15
+ @app.output(AdminSchema) # type: ignore
43
16
  def get(self, uuid):
44
17
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
45
18
  if not has_permission(admin):
46
19
  abort(403, "You don't have permission to access this admin")
47
- return admin.to_dict()
20
+ return admin.to_dict() # type: ignore
48
21
 
49
- @app.input(PatchAdminSchema, arg_name='data')
50
- @app.output(SuccessfulSchema)
22
+ @app.input(PatchAdminSchema, arg_name='data') # type: ignore
23
+ @app.output(SuccessfulSchema) # type: ignore
51
24
  def patch(self, uuid, data):
52
25
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
53
26
  if not has_permission(admin):
@@ -60,10 +33,10 @@ class AdminUserApi(MethodView):
60
33
  AdminUser.add_or_update(**data)
61
34
  return {'status': 200, 'msg': 'ok'}
62
35
 
63
- @app.output(SuccessfulSchema)
36
+ @app.output(SuccessfulSchema) # type: ignore
64
37
  def delete(self, uuid):
65
38
  admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
66
39
  if not has_permission(admin):
67
40
  abort(403, "You don't have permission to access this admin")
68
- admin.remove()
41
+ admin.remove() # type: ignore
69
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()