hiddifypanel 9.0.0.dev92__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 +58 -50
  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 -1848
  133. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  134. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2019 -1874
  135. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1873 -1742
  137. hiddifypanel/translations.i18n/en.json +992 -933
  138. hiddifypanel/translations.i18n/fa.json +994 -935
  139. hiddifypanel/translations.i18n/pt.json +1031 -972
  140. hiddifypanel/translations.i18n/ru.json +994 -935
  141. hiddifypanel/translations.i18n/zh.json +971 -912
  142. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
  143. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
  144. {hiddifypanel-9.0.0.dev92.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.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
  151. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
  152. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/top_level.txt +0 -0
@@ -1,87 +1,30 @@
1
1
  from flask import g
2
2
  from flask.views import MethodView
3
3
  from flask import current_app as app
4
- from apiflask import abort, Schema
4
+ from apiflask import abort
5
5
  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
9
  from hiddifypanel.panel import hiddify
10
- from apiflask.fields import UUID, String, Float, Enum, Date, Time, Integer
11
10
 
12
- from . import SuccessfulSchema, has_permission
13
-
14
-
15
- class UserSchema(Schema):
16
- uuid = UUID(required=True, description="Unique identifier for the user")
17
- name = String(required=True, description="Name of the user")
18
-
19
- usage_limit_GB = Float(
20
- required=False,
21
- description="The data usage limit for the user in gigabytes"
22
- )
23
- package_days = Integer(
24
- required=False,
25
- description="The number of days in the user's package"
26
- )
27
- mode = Enum(UserMode,
28
- required=False,
29
- description="The mode of the user's account, which dictates access level or type"
30
- )
31
- last_online = Time(
32
- format="%Y-%m-%d %H:%M:%S",
33
- description="The last time the user was online, converted to a JSON-friendly format"
34
- )
35
- start_date = Date(
36
- format='%Y-%m-%d',
37
- description="The start date of the user's package, in a JSON-friendly format"
38
- )
39
- current_usage_GB = Float(
40
- required=False,
41
- description="The current data usage of the user in gigabytes"
42
- )
43
- last_reset_time = Date(
44
- format='%Y-%m-%d',
45
- description="The last time the user's data usage was reset, in a JSON-friendly format"
46
- )
47
- comment = String(
48
- missing=None,
49
- description="An optional comment about the user"
50
- )
51
- added_by_uuid = UUID(
52
- required=False,
53
- description="UUID of the admin who added this user",
54
- allow_none=True,
55
- # validate=OneOf([p.uuid for p in AdminUser.query.all()])
56
- )
57
- telegram_id = Integer(
58
- required=False,
59
- description="The Telegram ID associated with the user",
60
- allow_none=True
61
- )
62
- ed25519_private_key = String(
63
- required=False,
64
- description="If empty, it will be created automatically, The user's private key using the Ed25519 algorithm"
65
- )
66
- ed25519_public_key = String(
67
- required=False,
68
- description="If empty, it will be created automatically,The user's public key using the Ed25519 algorithm"
69
- )
11
+ from . import has_permission
12
+ from .schema import UserSchema, PatchUserSchema, SuccessfulSchema
70
13
 
71
14
 
72
15
  class UserApi(MethodView):
73
16
  decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
74
17
 
75
- @app.output(UserSchema)
18
+ @app.output(UserSchema) # type: ignore
76
19
  def get(self, uuid):
77
20
  user = User.by_uuid(uuid) or abort(404, "user not found")
78
21
  if not has_permission(user):
79
22
  abort(403, "You don't have permission to access this user")
80
23
 
81
- return user.to_dict(False)
24
+ return user.to_dict(False) # type: ignore
82
25
 
83
- @app.input(UserSchema, arg_name="data")
84
- @app.output(SuccessfulSchema)
26
+ @app.input(PatchUserSchema, arg_name="data") # type: ignore
27
+ @app.output(SuccessfulSchema) # type: ignore
85
28
  def patch(self, uuid, data):
86
29
  user = User.by_uuid(uuid) or abort(404, "user not found")
87
30
  if not has_permission(user):
@@ -97,11 +40,11 @@ class UserApi(MethodView):
97
40
  hiddify.quick_apply_users()
98
41
  return {'status': 200, 'msg': 'ok'}
99
42
 
100
- @app.output(SuccessfulSchema)
43
+ @app.output(SuccessfulSchema) # type: ignore
101
44
  def delete(self, uuid):
102
45
  user = User.by_uuid(uuid) or abort(404, "user not found")
103
46
  if not has_permission(user):
104
47
  abort(403, "You don't have permission to access this user")
105
- user.remove()
48
+ user.remove() # type: ignore
106
49
  hiddify.quick_apply_users()
107
50
  return {'status': 200, 'msg': 'ok'}
@@ -22,7 +22,7 @@ class UsersApi(MethodView):
22
22
  @app.output(UserSchema) # type: ignore
23
23
  def put(self, data):
24
24
  uuid = data.get('uuid') or abort(422, "Parameter issue: 'uuid'")
25
- user = User.by_uuid(uuid)
25
+ user = User.by_uuid(uuid) # type: ignore
26
26
 
27
27
  if not data.get('added_by_uuid'):
28
28
  data['added_by_uuid'] = g.account.uuid
@@ -0,0 +1,18 @@
1
+ from apiflask import APIBlueprint
2
+
3
+ bp = APIBlueprint("api_child", __name__, url_prefix="/<proxy_path>/api/v2/child/", enable_openapi=True)
4
+
5
+
6
+ def init_app(app):
7
+ with app.app_context():
8
+ from .register_parent_api import RegisterWithParentApi
9
+ from .sync_parent_api import SyncWithParentApi
10
+ from .actions import ApplyConfig, Restart, Status, UpdateUsage, Install
11
+ bp.add_url_rule('/sync-parent/', view_func=SyncWithParentApi)
12
+ bp.add_url_rule('/register-parent/', view_func=RegisterWithParentApi)
13
+ bp.add_url_rule('/status/', view_func=Status)
14
+ bp.add_url_rule('/restart/', view_func=Restart)
15
+ bp.add_url_rule('/apply-config/', view_func=ApplyConfig)
16
+ bp.add_url_rule('/install/', view_func=Install)
17
+ bp.add_url_rule('/update-usage/', view_func=UpdateUsage)
18
+ app.register_blueprint(bp)
@@ -0,0 +1,63 @@
1
+ from apiflask import fields, Schema
2
+ from flask import current_app as app
3
+ from flask import g
4
+ from flask.views import MethodView
5
+ from loguru import logger
6
+
7
+ from hiddifypanel.models.child import Child
8
+ from hiddifypanel.panel.run_commander import commander, Command
9
+ from hiddifypanel.auth import login_required
10
+
11
+
12
+ class Status(MethodView):
13
+ decorators = [login_required(node_auth=True)]
14
+
15
+ def post(self):
16
+ logger.info(f"Status action called by parent: {Child.node.unique_id}")
17
+ commander(Command.status)
18
+ return {'status': 200, 'msg': 'ok'}
19
+
20
+
21
+ class UpdateUsage(MethodView):
22
+ decorators = [login_required(node_auth=True)]
23
+
24
+ def post(self):
25
+ logger.info(f"Update usage action called by parent: {Child.node.unique_id}")
26
+ commander(Command.update_usage)
27
+ return {'status': 200, 'msg': 'ok'}
28
+
29
+
30
+ class Restart(MethodView):
31
+ decorators = [login_required(node_auth=True)]
32
+
33
+ def post(self):
34
+ logger.info(f"Restart action called by parent: {Child.node.unique_id}")
35
+ commander(Command.restart_services)
36
+ return {'status': 200, 'msg': 'ok'}
37
+
38
+
39
+ class ApplyConfig(MethodView):
40
+ decorators = [login_required(node_auth=True)]
41
+
42
+ def post(self):
43
+ logger.info(f"Apply config action called by parent: {Child.node.unique_id}")
44
+ commander(Command.apply)
45
+ return {'status': 200, 'msg': 'ok'}
46
+
47
+
48
+ class InstallSchema(Schema):
49
+ full = fields.Boolean(description="full install", required=True, default=True)
50
+
51
+
52
+ class Install(MethodView):
53
+ decorators = [login_required(node_auth=True)]
54
+
55
+ @app.input(InstallSchema, arg_name="data") # type: ignore
56
+ def post(self, data: dict):
57
+ if data.get('full'):
58
+ logger.info(f"Install action called by parent: {Child.node.unique_id}, full=True")
59
+ commander(Command.install)
60
+ else:
61
+ logger.info(f"Install action called by parent: {Child.node.unique_id}, full=False")
62
+ commander(Command.apply)
63
+ return {'status': 200, 'msg': 'ok'}
@@ -0,0 +1,34 @@
1
+ from apiflask import abort, fields, Schema
2
+ from flask.views import MethodView
3
+ from flask import current_app as app, g
4
+ from flask_babel import lazy_gettext as _
5
+ from loguru import logger
6
+
7
+ from hiddifypanel.auth import login_required
8
+ from hiddifypanel.models import set_hconfig, ConfigEnum, PanelMode, Role
9
+ from hiddifypanel import hutils
10
+
11
+ from .schema import RegisterWithParentInputSchema
12
+
13
+
14
+ class RegisterWithParentApi(MethodView):
15
+ decorators = [login_required({Role.super_admin})]
16
+
17
+ # TODO: incomplete (not used)
18
+ @app.input(RegisterWithParentInputSchema, arg_name='data') # type: ignore
19
+ def post(self, data):
20
+ logger.info(f"Registering panel with parent called by {data['parent_unique_id']}")
21
+ if hutils.node.is_parent() or hutils.node.is_child():
22
+ logger.error("The panel is not in standalone mode nor in child")
23
+ abort(400, 'The panel is not in standalone mode nor in child')
24
+
25
+ set_hconfig(ConfigEnum.parent_panel, data['parent_panel']) # type: ignore
26
+
27
+ if not hutils.node.child.register_to_parent(data['name'], data['apikey']):
28
+ logger.error("Child registration to parent failed")
29
+ set_hconfig(ConfigEnum.parent_panel, '') # type: ignore
30
+ abort(400, _('child.register-failed')) # type: ignore
31
+
32
+ set_hconfig(ConfigEnum.panel_mode, PanelMode.child) # type: ignore
33
+ logger.info("Registered panel with parent, panel mode is now child")
34
+ return {'status': 200, 'msg': 'ok'}
@@ -0,0 +1,7 @@
1
+ from apiflask import Schema, fields
2
+
3
+
4
+ class RegisterWithParentInputSchema(Schema):
5
+ parent_panel = fields.String(required=True, description="The parent panel url")
6
+ name = fields.String(required=True, description="The child's name in the parent panel")
7
+ apikey = fields.String(description="The parent's apikey")
@@ -0,0 +1,21 @@
1
+ from apiflask import abort
2
+ from flask.views import MethodView
3
+ from flask_babel import lazy_gettext as _
4
+ from flask import g
5
+ from loguru import logger
6
+
7
+ from hiddifypanel.models.child import Child
8
+ from hiddifypanel.auth import login_required
9
+ from hiddifypanel import hutils
10
+
11
+
12
+ class SyncWithParentApi(MethodView):
13
+ decorators = [login_required(node_auth=True)]
14
+
15
+ def post(self):
16
+ logger.info(f"Syncing panel with parent called by {Child.node.unique_id}")
17
+ if not hutils.node.child.sync_with_parent():
18
+ logger.error("Sync with parent failed")
19
+ abort(400, _('child.sync-failed')) # type: ignore
20
+ logger.success(f"Synced panel with parent {Child.node.unique_id}")
21
+ return {'status': 200, 'msg': 'ok'}
@@ -0,0 +1,13 @@
1
+ from apiflask import APIBlueprint
2
+
3
+ bp = APIBlueprint("api_panel", __name__, url_prefix="/<proxy_path>/api/v2/panel/", enable_openapi=True)
4
+
5
+
6
+ def init_app(app):
7
+
8
+ with app.app_context():
9
+ from .info import PanelInfoApi
10
+ from .ping_pong import PingPongApi
11
+ bp.add_url_rule('/info/', view_func=PanelInfoApi)
12
+ bp.add_url_rule('/ping/', view_func=PingPongApi)
13
+ app.register_blueprint(bp)
@@ -0,0 +1,18 @@
1
+ from flask.views import MethodView
2
+ from flask import current_app as app
3
+
4
+ from hiddifypanel.auth import login_required
5
+ from hiddifypanel.models import Role
6
+ from hiddifypanel import __version__
7
+
8
+ from .schema import PanelInfoOutputSchema
9
+
10
+
11
+ class PanelInfoApi(MethodView):
12
+ decorators = [login_required(roles={Role.super_admin, Role.admin, Role.agent}, node_auth=True)]
13
+
14
+ @app.output(PanelInfoOutputSchema) # type: ignore
15
+ def get(self):
16
+ res = PanelInfoOutputSchema()
17
+ res.version = __version__ # type: ignore
18
+ return res
@@ -0,0 +1,23 @@
1
+ from flask.views import MethodView
2
+
3
+ from hiddifypanel.auth import login_required
4
+ from hiddifypanel.models import Role
5
+
6
+
7
+ class PingPongApi(MethodView):
8
+ decorators = [login_required(roles={Role.super_admin, Role.admin, Role.agent}, node_auth=True)]
9
+
10
+ def get(self):
11
+ return {'msg': 'GET: PONG'}
12
+
13
+ def post(self):
14
+ return {'msg': 'POST: PONG'}
15
+
16
+ def patch(self):
17
+ return {'msg': 'PATCH: PONG'}
18
+
19
+ def delete(self):
20
+ return {'msg': 'DELETE: PONG'}
21
+
22
+ def put(self):
23
+ return {'msg': 'PUT: PONG'}
@@ -0,0 +1,7 @@
1
+ from apiflask import Schema, fields
2
+ # region info
3
+
4
+
5
+ class PanelInfoOutputSchema(Schema):
6
+ version = fields.String(description="The panel version")
7
+ # endregion
@@ -0,0 +1,16 @@
1
+ from apiflask import APIBlueprint
2
+
3
+ bp = APIBlueprint("api_parent", __name__, url_prefix="/<proxy_path>/api/v2/parent/", enable_openapi=True)
4
+
5
+
6
+ def init_app(app):
7
+ with app.app_context():
8
+ from .register_api import RegisterApi
9
+ from .sync_api import SyncApi
10
+ from .usage_api import UsageApi
11
+ from .status_api import StatusApi
12
+ bp.add_url_rule('/status/', view_func=StatusApi)
13
+ bp.add_url_rule('/register/', view_func=RegisterApi)
14
+ bp.add_url_rule('/sync/', view_func=SyncApi)
15
+ bp.add_url_rule('/usage/', view_func=UsageApi)
16
+ app.register_blueprint(bp)
@@ -0,0 +1,65 @@
1
+ from apiflask import abort
2
+ from hiddifypanel.database import db
3
+ from flask import current_app as app
4
+ from flask.views import MethodView
5
+ from loguru import logger
6
+
7
+ from hiddifypanel.models import *
8
+ from hiddifypanel.auth import login_required
9
+
10
+ from .schema import RegisterInputSchema, RegisterOutputSchema
11
+
12
+
13
+ class RegisterApi(MethodView):
14
+ decorators = [login_required({Role.super_admin})]
15
+
16
+ @app.input(RegisterInputSchema, arg_name='data') # type: ignore
17
+ @app.output(RegisterOutputSchema) # type: ignore
18
+ def put(self, data):
19
+ from hiddifypanel import hutils
20
+ logger.info("Register child panel with unique_id: {}", data['unique_id'])
21
+ if hutils.node.is_child():
22
+ logger.error("The panel in child, not a parent nor standalone")
23
+ abort(400, 'The panel in child, not a parent nor standalone')
24
+
25
+ unique_id = data['unique_id']
26
+ name = data['name']
27
+ mode = data['mode']
28
+
29
+ child = Child.query.filter(Child.unique_id == unique_id).first()
30
+ if not child:
31
+ logger.info("Adding new child with unique_id: {}", unique_id)
32
+ child = Child(unique_id=unique_id, name=name, mode=mode)
33
+ db.session.add(child) # type: ignore
34
+ db.session.commit() # type: ignore
35
+ child = Child.query.filter(Child.unique_id == unique_id).first()
36
+
37
+ try:
38
+ # add data
39
+ logger.info("Adding admin users...")
40
+ AdminUser.bulk_register(data['panel_data']['admin_users'], commit=False)
41
+ logger.info("Adding users...")
42
+ User.bulk_register(data['panel_data']['users'], commit=False)
43
+ logger.info("Adding domains...")
44
+ bulk_register_domains(data['panel_data']['domains'], commit=False, force_child_unique_id=child.unique_id)
45
+ logger.info("Adding hconfigs...")
46
+ bulk_register_configs(data['panel_data']['hconfigs'], commit=False, froce_child_unique_id=child.unique_id)
47
+ logger.info("Adding proxies...")
48
+ Proxy.bulk_register(data['panel_data']['proxies'], commit=False, force_child_unique_id=child.unique_id)
49
+ db.session.commit() # type: ignore
50
+ except Exception as err:
51
+ with logger.contextualize(error=err):
52
+ logger.error("Error while registering data")
53
+ abort(400, str(err))
54
+
55
+ if not hutils.node.is_parent():
56
+ logger.info("Setting panel to parent mode")
57
+ set_hconfig(ConfigEnum.panel_mode, PanelMode.parent) # type: ignore
58
+
59
+ logger.info("Returning register output")
60
+ res = RegisterOutputSchema()
61
+ res.users = [u.to_schema() for u in User.query.all()] # type: ignore
62
+ res.admin_users = [a.to_schema() for a in AdminUser.query.all()] # type: ignore
63
+ res.parent_unique_id = hconfig(ConfigEnum.unique_id)
64
+
65
+ return res
@@ -0,0 +1,115 @@
1
+ from apiflask import fields, Schema
2
+ from marshmallow import ValidationError
3
+
4
+ from hiddifypanel.models import DomainType, ProxyProto, ProxyL3, ProxyTransport, ProxyCDN, ConfigEnum, ChildMode
5
+ from hiddifypanel.panel.commercial.restapi.v2.admin.schema import UserSchema, AdminSchema
6
+
7
+
8
+ def hconfig_key_validator(value):
9
+ if value not in [c.name for c in ConfigEnum]:
10
+ raise ValidationError(f"{value} is not a valid hconfig key.")
11
+ return value
12
+
13
+
14
+ class DomainSchema(Schema):
15
+ child_unique_id = fields.String(description="The child's unique id")
16
+ domain = fields.String(required=True, description="The domain name")
17
+ alias = fields.String(description="The domain alias", allow_none=True)
18
+ sub_link_only = fields.Boolean(required=True, description="Is the domain sub link only")
19
+ mode = fields.Enum(DomainType, required=True, description="The domain type")
20
+ cdn_ip = fields.String(description="The cdn ip", allow_none=True)
21
+ grpc = fields.Boolean(required=True, description="Is the domain grpc")
22
+ servernames = fields.String(description="The servernames", allow_none=True)
23
+ show_domains = fields.List(fields.String(), desciption="The list of domains to show")
24
+
25
+
26
+ class ProxySchema(Schema):
27
+ child_unique_id = fields.String(description="The child's unique id")
28
+ name = fields.String(required=True, description="The proxy name")
29
+ enable = fields.Boolean(required=True, description="Is the proxy enabled")
30
+ proto = fields.Enum(ProxyProto, required=True, description="The proxy protocol")
31
+ l3 = fields.Enum(ProxyL3, required=True, description="The proxy l3")
32
+ transport = fields.Enum(ProxyTransport, required=True, description="The proxy transport")
33
+ cdn = fields.Enum(ProxyCDN, required=True, description="The proxy cdn")
34
+
35
+
36
+ class StringOrBooleanField(fields.Field):
37
+ def _deserialize(self, value, attr, data, **kwargs):
38
+ if isinstance(value, (str, bool)):
39
+ return str(value)
40
+ else:
41
+ raise ValidationError("Value must be a string or a boolean.")
42
+
43
+ def _serialize(self, value, attr, obj, **kwargs):
44
+ return value
45
+
46
+
47
+ class HConfigSchema(Schema):
48
+ child_unique_id = fields.String(description="The child's unique id")
49
+ key = fields.String(required=True, description="The config key", validate=hconfig_key_validator) # type: ignore
50
+ value = StringOrBooleanField(required=True, description="The config value")
51
+
52
+
53
+ # region usage
54
+ class UsageData(Schema):
55
+ uuid = fields.UUID(required=True, desciption="The user uuid")
56
+ usage = fields.Integer(required=True, description="The user usage in bytes")
57
+ devices = fields.List(fields.String(required=True, description="The user connected devices"))
58
+
59
+
60
+ class UsageInputOutputSchema(Schema):
61
+ usages = fields.List(fields.Nested(UsageData), required=True, description="The list of usages")
62
+ # endregion
63
+
64
+
65
+ # region sync
66
+ class SyncInputSchema(Schema):
67
+ # users = fields.List(fields.Nested(UserSchema),required=True,description="The list of users")
68
+ domains = fields.List(fields.Nested(DomainSchema), required=True, description="The list of domains")
69
+ proxies = fields.List(fields.Nested(ProxySchema), required=True, description="The list of proxies")
70
+ # parent_domains = fields.List(fields.Nested(ParentDomainSchema),required=True,description="The list of parent domains")
71
+ # admin_users = fields.List(fields.Nested(AdminSchema),required=True,description="The list of admin users")
72
+ hconfigs = fields.List(fields.Nested(HConfigSchema), required=True, description="The list of configs")
73
+
74
+
75
+ class SyncOutputSchema(Schema):
76
+ users = fields.List(fields.Nested(UserSchema), required=True, description="The list of users")
77
+ admin_users = fields.List(fields.Nested(AdminSchema), required=True, description="The list of admin users")
78
+
79
+ # endregion
80
+
81
+
82
+ # region child status
83
+ class ChildStatusInputSchema(Schema):
84
+ child_unique_id = fields.String(required=True, description="The child's unique id")
85
+
86
+
87
+ class ChildStatusOutputSchema(Schema):
88
+ existance = fields.Boolean(required=True, description="Whether child exists")
89
+
90
+ # end region
91
+
92
+
93
+ # region register
94
+
95
+ class RegisterDataSchema(Schema):
96
+ users = fields.List(fields.Nested(UserSchema), required=True, description="The list of users")
97
+ domains = fields.List(fields.Nested(DomainSchema), required=True, description="The list of domains")
98
+ proxies = fields.List(fields.Nested(ProxySchema), required=True, description="The list of proxies")
99
+ admin_users = fields.List(fields.Nested(AdminSchema), required=True, description="The list of admin users")
100
+ hconfigs = fields.List(fields.Nested(HConfigSchema), required=True, description="The list of configs")
101
+
102
+
103
+ class RegisterInputSchema(Schema):
104
+ panel_data = fields.Nested(RegisterDataSchema, required=True, description="The child's data")
105
+ unique_id = fields.String(required=True, description="The child's unique id")
106
+ name = fields.String(required=True, description="The child's name")
107
+ mode = fields.Enum(ChildMode, required=True, description="The child's mode")
108
+
109
+
110
+ class RegisterOutputSchema(Schema):
111
+ parent_unique_id = fields.String(description="The parent's unique id")
112
+ users = fields.List(fields.Nested(UserSchema), required=True, description="The list of users")
113
+ admin_users = fields.List(fields.Nested(AdminSchema), required=True, description="The list of admin users")
114
+
115
+ # endregion
@@ -0,0 +1,26 @@
1
+ from flask.views import MethodView
2
+ from flask import current_app as app
3
+ from loguru import logger
4
+
5
+ from hiddifypanel.models import Child, Role
6
+ from hiddifypanel.auth import login_required
7
+
8
+ from .schema import ChildStatusInputSchema, ChildStatusOutputSchema
9
+
10
+
11
+ class StatusApi(MethodView):
12
+ decorators = [login_required(node_auth=True)]
13
+
14
+ @app.input(ChildStatusInputSchema, arg_name='data') # type: ignore
15
+ @app.output(ChildStatusOutputSchema) # type: ignore
16
+ def post(self, data):
17
+ logger.info(f"Checking the existence of child with unique_id: {data['child_unique_id']}")
18
+ res = ChildStatusOutputSchema()
19
+ res.existance = False # type: ignore
20
+
21
+ child = Child.query.filter(Child.unique_id == data['child_unique_id']).first()
22
+ if child:
23
+ logger.info(f"Child with unique_id: {data['child_unique_id']} exists")
24
+ res.existance = True # type: ignore
25
+
26
+ return res
@@ -0,0 +1,53 @@
1
+
2
+ from flask.views import MethodView
3
+ from flask import current_app as app
4
+ from flask import g
5
+ from apiflask import abort
6
+
7
+ from hiddifypanel.models.user import User
8
+ from hiddifypanel.database import db
9
+ from hiddifypanel.models.child import Child
10
+ from loguru import logger
11
+ from hiddifypanel.models import *
12
+ from hiddifypanel.auth import login_required
13
+ from .schema import SyncInputSchema, SyncOutputSchema
14
+
15
+
16
+ class SyncApi(MethodView):
17
+ decorators = [login_required(node_auth=True)]
18
+
19
+ @app.input(SyncInputSchema, arg_name='data') # type: ignore
20
+ @app.output(SyncOutputSchema) # type: ignore
21
+ def put(self, data):
22
+ from hiddifypanel import hutils
23
+ unique_id = Child.node.unique_id
24
+
25
+ logger.info(f"Sync child with unique_id: {unique_id}")
26
+ if not hutils.node.is_parent():
27
+ logger.error("Not a parent")
28
+ abort(400, "Not a parent")
29
+
30
+ child = Child.query.filter(Child.unique_id == unique_id).first()
31
+ if not child:
32
+ logger.error("The child does not exist")
33
+ abort(404, "The child does not exist")
34
+
35
+ try:
36
+ logger.info("Syncing domains...")
37
+ bulk_register_domains(data['domains'], commit=False, force_child_unique_id=child.unique_id)
38
+ logger.info("Syncing hconfigs...")
39
+ bulk_register_configs(data['hconfigs'], commit=False, froce_child_unique_id=child.unique_id)
40
+ logger.info("Syncing proxies...")
41
+ Proxy.bulk_register(data['proxies'], commit=False, force_child_unique_id=child.unique_id)
42
+ db.session.commit() # type: ignore
43
+ except Exception as err:
44
+ with logger.contextualize(error=err):
45
+ logger.error(f"Error while syncing data")
46
+ abort(400, str(err))
47
+
48
+ res = SyncOutputSchema()
49
+ res.users = [u.to_schema() for u in User.query.all()] # type: ignore
50
+ res.admin_users = [a.to_schema() for a in AdminUser.query.all()] # type: ignore
51
+
52
+ logger.info("Returning sync output")
53
+ return res
@@ -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