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
@@ -1,94 +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
- )
70
-
71
-
72
- class PatchUserSchema(UserSchema):
73
- def __init__(self, *args, **kwargs):
74
- super().__init__(*args, **kwargs)
75
- self.fields['name'].required = False
76
- pass
11
+ from . import has_permission
12
+ from .schema import UserSchema, PatchUserSchema, SuccessfulSchema
77
13
 
78
14
 
79
15
  class UserApi(MethodView):
80
16
  decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
81
17
 
82
- @app.output(UserSchema)
18
+ @app.output(UserSchema) # type: ignore
83
19
  def get(self, uuid):
84
20
  user = User.by_uuid(uuid) or abort(404, "user not found")
85
21
  if not has_permission(user):
86
22
  abort(403, "You don't have permission to access this user")
87
23
 
88
- return user.to_dict(False)
24
+ return user.to_dict(False) # type: ignore
89
25
 
90
- @app.input(PatchUserSchema, arg_name="data")
91
- @app.output(SuccessfulSchema)
26
+ @app.input(PatchUserSchema, arg_name="data") # type: ignore
27
+ @app.output(SuccessfulSchema) # type: ignore
92
28
  def patch(self, uuid, data):
93
29
  user = User.by_uuid(uuid) or abort(404, "user not found")
94
30
  if not has_permission(user):
@@ -104,11 +40,11 @@ class UserApi(MethodView):
104
40
  hiddify.quick_apply_users()
105
41
  return {'status': 200, 'msg': 'ok'}
106
42
 
107
- @app.output(SuccessfulSchema)
43
+ @app.output(SuccessfulSchema) # type: ignore
108
44
  def delete(self, uuid):
109
45
  user = User.by_uuid(uuid) or abort(404, "user not found")
110
46
  if not has_permission(user):
111
47
  abort(403, "You don't have permission to access this user")
112
- user.remove()
48
+ user.remove() # type: ignore
113
49
  hiddify.quick_apply_users()
114
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