hiddifypanel 10.12.1__py3-none-any.whl → 10.15.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +15 -4
  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 +2 -0
  10. hiddifypanel/drivers/user_driver.py +12 -6
  11. hiddifypanel/drivers/wireguard_api.py +2 -0
  12. hiddifypanel/drivers/xray_api.py +14 -9
  13. hiddifypanel/hutils/__init__.py +1 -0
  14. hiddifypanel/hutils/convert.py +13 -2
  15. hiddifypanel/hutils/crypto.py +21 -2
  16. hiddifypanel/hutils/flask.py +19 -5
  17. hiddifypanel/hutils/importer/xui.py +5 -2
  18. hiddifypanel/hutils/node/__init__.py +3 -0
  19. hiddifypanel/hutils/node/api_client.py +76 -0
  20. hiddifypanel/hutils/node/child.py +147 -0
  21. hiddifypanel/hutils/node/parent.py +100 -0
  22. hiddifypanel/hutils/node/shared.py +65 -0
  23. hiddifypanel/hutils/proxy/shared.py +15 -3
  24. hiddifypanel/models/__init__.py +2 -2
  25. hiddifypanel/models/admin.py +14 -2
  26. hiddifypanel/models/base_account.py +3 -3
  27. hiddifypanel/models/child.py +30 -16
  28. hiddifypanel/models/config.py +39 -15
  29. hiddifypanel/models/config_enum.py +55 -8
  30. hiddifypanel/models/domain.py +28 -20
  31. hiddifypanel/models/parent_domain.py +2 -2
  32. hiddifypanel/models/proxy.py +13 -4
  33. hiddifypanel/models/report.py +2 -3
  34. hiddifypanel/models/usage.py +2 -2
  35. hiddifypanel/models/user.py +18 -9
  36. hiddifypanel/panel/admin/Actions.py +4 -6
  37. hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
  38. hiddifypanel/panel/admin/Dashboard.py +5 -10
  39. hiddifypanel/panel/admin/DomainAdmin.py +12 -11
  40. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  41. hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
  42. hiddifypanel/panel/admin/SettingAdmin.py +60 -21
  43. hiddifypanel/panel/admin/UserAdmin.py +10 -2
  44. hiddifypanel/panel/admin/templates/index.html +1 -1
  45. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  46. hiddifypanel/panel/cli.py +16 -16
  47. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
  48. hiddifypanel/panel/commercial/__init__.py +7 -5
  49. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  50. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  51. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  52. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
  53. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  54. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  55. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  56. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
  57. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  58. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  59. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  60. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  61. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  62. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  63. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  64. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  65. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  66. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  67. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  68. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  69. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  70. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  71. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  72. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  73. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  74. hiddifypanel/panel/common.py +21 -6
  75. hiddifypanel/panel/hiddify.py +9 -80
  76. hiddifypanel/panel/init_db.py +84 -38
  77. hiddifypanel/panel/usage.py +33 -18
  78. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  79. hiddifypanel/panel/user/templates/new.html +2 -2
  80. hiddifypanel/static/css/custom.css +13 -0
  81. hiddifypanel/static/images/hiddify.png +0 -0
  82. hiddifypanel/static/images/hiddify2.png +0 -0
  83. hiddifypanel/static/new/assets/hiddify-logo-7617d937.png +0 -0
  84. hiddifypanel/static/new/assets/{index-4510b616.js → index-ccb9873c.js} +2 -2
  85. hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
  86. hiddifypanel/static/new/i18n/en.json +6 -6
  87. hiddifypanel/static/new/i18n/fa.json +1 -1
  88. hiddifypanel/templates/admin-layout.html +24 -40
  89. hiddifypanel/templates/fake.html +22 -0
  90. hiddifypanel/templates/master.html +24 -42
  91. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  92. hiddifypanel/translations/en/LC_MESSAGES/messages.po +95 -5
  93. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  94. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +96 -4
  95. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  96. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
  97. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  98. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +91 -1
  99. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  100. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
  101. hiddifypanel/translations.i18n/en.json +61 -5
  102. hiddifypanel/translations.i18n/fa.json +60 -4
  103. hiddifypanel/translations.i18n/pt.json +63 -7
  104. hiddifypanel/translations.i18n/ru.json +57 -1
  105. hiddifypanel/translations.i18n/zh.json +58 -2
  106. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
  107. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +112 -94
  108. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  109. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  110. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  111. hiddifypanel/static/new/assets/hiddify-logo-7617d937_old.png +0 -0
  112. hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
  113. /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
  114. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
  115. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
  116. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
  117. {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/top_level.txt +0 -0
@@ -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()
@@ -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