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
@@ -6,15 +6,18 @@ from datetime import datetime
6
6
  from dateutil.relativedelta import relativedelta
7
7
  from hiddifypanel import hutils
8
8
  from hiddifypanel.models import *
9
- from hiddifypanel.database import db
9
+ from hiddifypanel.database import db, db_execute
10
10
  import os
11
+ from sqlalchemy import text
11
12
 
12
13
 
13
14
  def __query_fetch_json(db, query: str, args: Tuple = ()) -> List[Dict[str, Any]]:
14
15
  try:
15
- db.execute(query, args) if args else db.execute(query)
16
+
17
+ db_execute(query, args)
16
18
  r = [dict((db.description[i][0], value)
17
19
  for i, value in enumerate(row)) for row in db.fetchall()]
20
+ connection.close()
18
21
  return r
19
22
  except Exception as err:
20
23
  raise err
@@ -0,0 +1,3 @@
1
+ from .shared import *
2
+ from . import child
3
+ from . import parent
@@ -0,0 +1,76 @@
1
+ from typing import Optional, Union, Type
2
+ from apiflask import Schema, fields
3
+ import traceback
4
+ import requests
5
+ from loguru import logger
6
+ from hiddifypanel.models import hconfig, ConfigEnum
7
+
8
+
9
+ class NodeApiErrorSchema(Schema):
10
+ msg = fields.String(required=True)
11
+ stacktrace = fields.String(required=True)
12
+ code = fields.Integer(required=True)
13
+ reason = fields.String(required=True)
14
+
15
+
16
+ class NodeApiClient():
17
+ def __init__(self, base_url: str, apikey: Optional[str] = None, max_retry: int = 3):
18
+ self.base_url = base_url if base_url.endswith('/') else base_url+'/'
19
+ self.max_retry = max_retry
20
+ self.headers = {'Hiddify-API-Key': apikey or hconfig(ConfigEnum.unique_id)}
21
+
22
+ def __call(self, method: str, path: str, payload: Optional[Schema], output_schema: Type[Union[Schema, dict]]) -> Union[dict, NodeApiErrorSchema]: # type: ignore
23
+ retry_count = 1
24
+ full_url = self.base_url + path.removeprefix('/')
25
+ while 1:
26
+ try:
27
+ # TODO: implement it with aiohttp
28
+
29
+ logger.trace(f"Attempting {method} request to node at {full_url}")
30
+
31
+ # send request
32
+ if payload:
33
+ response = requests.request(method, full_url, json=payload.dump(payload), headers=self.headers)
34
+ else:
35
+ response = requests.request(method, full_url, headers=self.headers)
36
+
37
+ # parse response
38
+ response.raise_for_status()
39
+ resp = response.json()
40
+ if not resp:
41
+ err = NodeApiErrorSchema()
42
+ err.msg = 'Empty response' # type: ignore
43
+ err.stacktrace = '' # type: ignore
44
+ err.code = response.status_code # type: ignore
45
+ err.reason = response.reason # type: ignore
46
+ with logger.contextualize(payload=payload):
47
+ logger.warning(f"Received empty response from {full_url} with method {method}")
48
+ return err
49
+
50
+ logger.trace(f"Successfully received response from {full_url}")
51
+ return resp if isinstance(output_schema, type(dict)) else output_schema().load(resp) # type: ignore
52
+
53
+ except requests.HTTPError as e:
54
+ if retry_count >= self.max_retry:
55
+ stack_trace = traceback.format_exc()
56
+ err = NodeApiErrorSchema()
57
+ err.msg = str(e) # type: ignore
58
+ err.stacktrace = stack_trace # type: ignore
59
+ err.code = response.status_code # type: ignore
60
+ err.reason = response.reason # type: ignore
61
+ with logger.contextualize(status_code=err.code, reason=err.reason, stack_trace=stack_trace, payload=payload):
62
+ logger.error(f"HTTP error after {self.max_retry} retries")
63
+ logger.exception(e)
64
+ return err
65
+
66
+ logger.warning(f"Error occurred: {e} from {full_url} with method {method}, retrying... ({retry_count}/{self.max_retry})")
67
+ retry_count += 1
68
+
69
+ def get(self, path: str, output: Type[Union[Schema, dict]]) -> Union[dict, NodeApiErrorSchema]:
70
+ return self.__call("GET", path, None, output)
71
+
72
+ def post(self, path: str, payload: Optional[Schema], output: Type[Union[Schema, dict]]) -> Union[dict, NodeApiErrorSchema]:
73
+ return self.__call("POST", path, payload, output)
74
+
75
+ def put(self, path: str, payload: Optional[Schema], output: Type[Union[Schema, dict]]) -> Union[dict, NodeApiErrorSchema]:
76
+ return self.__call("PUT", path, payload, output)
@@ -0,0 +1,147 @@
1
+ from loguru import logger
2
+ import socket
3
+
4
+ from hiddifypanel.models import AdminUser, User, hconfig, ConfigEnum, ChildMode, set_hconfig, Domain, Proxy, StrConfig, BoolConfig, Child, ChildMode
5
+ from hiddifypanel import hutils
6
+ from hiddifypanel.panel import hiddify
7
+ from hiddifypanel.panel import usage
8
+ from hiddifypanel.database import db
9
+ from hiddifypanel.cache import cache
10
+
11
+ # import schmeas
12
+ from hiddifypanel.panel.commercial.restapi.v2.parent.schema import *
13
+ from hiddifypanel.panel.commercial.restapi.v2.child.schema import *
14
+
15
+ from .api_client import NodeApiClient, NodeApiErrorSchema
16
+ # region private
17
+
18
+
19
+ def __get_register_data_for_api(name: str, mode: ChildMode) -> RegisterInputSchema:
20
+
21
+ register_data = RegisterInputSchema()
22
+ register_data.unique_id = hconfig(ConfigEnum.unique_id)
23
+ register_data.name = name # type: ignore
24
+ register_data.mode = mode # type: ignore
25
+
26
+ panel_data = RegisterDataSchema() # type: ignore
27
+ panel_data.admin_users = [admin_user.to_schema() for admin_user in AdminUser.query.all()] # type: ignore
28
+ panel_data.users = [user.to_schema() for user in User.query.all()] # type: ignore
29
+ panel_data.domains = [domain.to_schema() for domain in Domain.query.all()] # type: ignore
30
+ panel_data.proxies = [proxy.to_schema() for proxy in Proxy.query.all()] # type: ignore
31
+ panel_data.hconfigs = [*[u.to_schema() for u in StrConfig.query.all()], *[u.to_schema() for u in BoolConfig.query.all()]] # type: ignore
32
+ register_data.panel_data = panel_data
33
+
34
+ return register_data
35
+
36
+
37
+ def __get_sync_data_for_api() -> SyncInputSchema:
38
+ sync_data = SyncInputSchema()
39
+ sync_data.domains = [domain.to_schema() for domain in Domain.query.all()] # type: ignore
40
+ sync_data.proxies = [proxy.to_schema() for proxy in Proxy.query.all()] # type: ignore
41
+ sync_data.hconfigs = [*[u.to_schema() for u in StrConfig.query.all()], *[u.to_schema() for u in BoolConfig.query.all()]] # type: ignore
42
+
43
+ return sync_data
44
+
45
+
46
+ def __get_parent_panel_url() -> str:
47
+ url = 'https://' + f"{hconfig(ConfigEnum.parent_domain).removesuffix('/')}/{hconfig(ConfigEnum.parent_admin_proxy_path).removesuffix('/')}"
48
+ return url
49
+
50
+ # endregion
51
+
52
+
53
+ def is_registered() -> bool:
54
+ '''Checks if the current parent registered as a child'''
55
+ try:
56
+ logger.debug("Checking if current panel is registered with parent")
57
+ base_url = __get_parent_panel_url()
58
+ if not base_url:
59
+ return False
60
+ payload = ChildStatusInputSchema()
61
+ payload.child_unique_id = hconfig(ConfigEnum.unique_id)
62
+
63
+ res = NodeApiClient(base_url).post('/api/v2/parent/status/', payload, ChildStatusOutputSchema)
64
+ if isinstance(res, NodeApiErrorSchema):
65
+ logger.error(f"Error while checking if current panel is registered with parent: {res.msg}")
66
+ return False
67
+
68
+ if res['existance']:
69
+ return True
70
+ return False
71
+ except Exception as e:
72
+ logger.error(f"Error while checking if current panel is registered with parent")
73
+ logger.exception(e)
74
+ return False
75
+
76
+
77
+ def register_to_parent(name: str, apikey: str, mode: ChildMode = ChildMode.remote) -> bool:
78
+ # get parent link its format is "https://panel.hiddify.com/<admin_proxy_path>/"
79
+ p_url = __get_parent_panel_url()
80
+ if not p_url:
81
+ logger.error("Parent url is empty")
82
+ return False
83
+
84
+ payload = __get_register_data_for_api(name, mode)
85
+ res = NodeApiClient(p_url, apikey).put('/api/v2/parent/register/', payload, RegisterOutputSchema)
86
+ if isinstance(res, NodeApiErrorSchema):
87
+ logger.error(f"Error while registering to parent: {res.msg}")
88
+ return False
89
+
90
+ # TODO: change the bulk_register and such methods to accept models instead of dict
91
+ AdminUser.bulk_register(res['admin_users'], commit=False)
92
+ User.bulk_register(res['users'], commit=False)
93
+
94
+ # add new child as parent
95
+ db.session.add( # type: ignore
96
+ Child(unique_id=res['parent_unique_id'], name=socket.gethostname() or res['parent_unique_id'], mode=ChildMode.parent)
97
+ )
98
+
99
+ db.session.commit() # type: ignore
100
+
101
+ logger.success("Successfully registered to parent")
102
+ cache.invalidate_all_cached_functions()
103
+ return True
104
+
105
+
106
+ def sync_with_parent() -> bool:
107
+ # sync usage first
108
+ if not sync_users_usage_with_parent():
109
+ logger.error("Error while syncing with parent: Failed to sync users usage")
110
+ return False
111
+
112
+ p_url = __get_parent_panel_url()
113
+ if not p_url:
114
+ logger.error("Error while syncing with parent: Parent url is empty")
115
+ return False
116
+ payload = __get_sync_data_for_api()
117
+ res = NodeApiClient(p_url).put('/api/v2/parent/sync/', payload, SyncOutputSchema)
118
+ if isinstance(res, NodeApiErrorSchema):
119
+ logger.error(f"Error while syncing with parent: {res.msg}")
120
+ return False
121
+ AdminUser.bulk_register(res['admin_users'], commit=False, remove=True)
122
+ User.bulk_register(res['users'], commit=False, remove=True)
123
+ db.session.commit() # type: ignore
124
+ logger.success("Successfully synced with parent")
125
+ cache.invalidate_all_cached_functions()
126
+ return True
127
+
128
+
129
+ def sync_users_usage_with_parent() -> bool:
130
+ p_url = __get_parent_panel_url()
131
+ if not p_url:
132
+ logger.error("Parent url is empty")
133
+ return False
134
+
135
+ payload = hutils.node.get_users_usage_data_for_api()
136
+ if payload:
137
+ res = NodeApiClient(p_url).put('/api/v2/parent/usage/', payload, UsageInputOutputSchema) # type: ignore
138
+ if isinstance(res, NodeApiErrorSchema):
139
+ logger.error(f"Error while syncing users usage with parent: {res.msg}")
140
+ return False
141
+
142
+ # parse usages data
143
+ res = hutils.node.convert_usage_api_response_to_dict(res) # type: ignore
144
+ usage.add_users_usage_uuid(res, hiddify.get_child(None), True)
145
+ logger.success(f"Successfully synced users usage with parent: {res}")
146
+
147
+ return True
@@ -0,0 +1,100 @@
1
+ from flask import g
2
+ from flask_babel import lazy_gettext as _
3
+ from typing import List
4
+ from loguru import logger
5
+
6
+ from hiddifypanel.models import Child, AdminUser, ConfigEnum, Domain, ChildMode, hconfig, get_panel_link
7
+ from hiddifypanel import hutils
8
+ from hiddifypanel.panel.commercial.restapi.v2.child.schema import RegisterWithParentInputSchema
9
+ from .api_client import NodeApiClient, NodeApiErrorSchema
10
+
11
+ from hiddifypanel.cache import cache
12
+
13
+
14
+ def request_childs_to_sync():
15
+ for c in Child.query.filter(Child.id != 0).all():
16
+ if not request_child_to_sync(c):
17
+ logger.error(f'{c.name}: {_("parent.sync-req-failed")}')
18
+ hutils.flask.flash(f'{c.name}: ' + _('parent.sync-req-failed'), 'danger')
19
+
20
+
21
+ def request_child_to_sync(child: Child) -> bool:
22
+ '''Requests to a child to sync itself with the current panel'''
23
+ child_domain = get_panel_link(child.id)
24
+ if not child_domain:
25
+ logger.error(f"Child {child.name} has no valid domain")
26
+ return False
27
+
28
+ child_admin_proxy_path = hconfig(ConfigEnum.proxy_path_admin, child.id)
29
+ base_url = f'https://{child_domain}/{child_admin_proxy_path}'
30
+ path = '/api/v2/child/sync-parent/'
31
+ res = NodeApiClient(base_url).post(path, payload=None, output=dict)
32
+ if isinstance(res, NodeApiErrorSchema):
33
+ logger.error(f"Error while requesting child {child.name} to sync: {res.msg}")
34
+ return False
35
+ if res['msg'] == 'ok':
36
+ logger.success(f"Successfully requested child {child.name} to sync")
37
+ cache.invalidate_all_cached_functions()
38
+ return True
39
+
40
+ logger.error(f"Request to child {child.name} to sync failed")
41
+ return False
42
+
43
+ # before using this function should check child version
44
+
45
+
46
+ # TODO: not used
47
+ def request_chlid_to_register(name: str, child_link: str, apikey: str) -> bool:
48
+ '''Requests to a child to register itself with the current panel'''
49
+ if not child_link or not apikey:
50
+ logger.error("Child link or apikey is empty")
51
+ return False
52
+ domain = get_panel_link()
53
+ if not domain:
54
+ logger.error("Domain is empty")
55
+ return False
56
+ from hiddifypanel.panel import hiddify
57
+
58
+ payload = RegisterWithParentInputSchema()
59
+ payload.parent_panel = hiddify.get_account_panel_link(AdminUser.by_uuid(g.account.uuid), domain.domain) # type: ignore
60
+ payload.apikey = payload.name = hconfig(ConfigEnum.unique_id)
61
+
62
+ logger.debug(f"Requesting child {name} to register")
63
+ res = NodeApiClient(child_link, apikey).post('/api/v2/child/register-parent/', payload, dict)
64
+ if isinstance(res, NodeApiErrorSchema):
65
+ logger.error(f"Error while requesting child {name} to register: {res.msg}")
66
+ return False
67
+
68
+ if res['msg'] == 'ok':
69
+ logger.success(f"Successfully requested child {name} to register")
70
+ cache.invalidate_all_cached_functions()
71
+ return True
72
+
73
+ logger.error(f"Request to child {name} to register failed")
74
+ return False
75
+
76
+
77
+ def is_child_domain_active(child: Child, domain: Domain) -> bool:
78
+ '''Checks whether a child's domain is responsive'''
79
+ if not domain.need_valid_ssl:
80
+ return False
81
+ child_admin_proxy_path = hconfig(ConfigEnum.proxy_path_admin, child.id)
82
+ if not child_admin_proxy_path:
83
+ return False
84
+
85
+ return hutils.node.is_panel_active(domain.domain, child_admin_proxy_path)
86
+
87
+
88
+ def get_child_active_domains(child: Child) -> List[Domain]:
89
+ actives = []
90
+ for d in child.domains:
91
+ if is_child_domain_active(child, d):
92
+ actives.append(d)
93
+ return actives
94
+
95
+
96
+ def is_child_active(child: Child) -> bool:
97
+ for d in child.domains:
98
+ if is_child_domain_active(child, d):
99
+ return True
100
+ return False
@@ -0,0 +1,65 @@
1
+ from loguru import logger
2
+
3
+ from hiddifypanel.models import hconfig, ConfigEnum, PanelMode, User
4
+ from hiddifypanel.cache import cache
5
+ from hiddifypanel.panel.commercial.restapi.v2.parent.schema import UsageInputOutputSchema, UsageData
6
+ from hiddifypanel.panel.commercial.restapi.v2.panel.schema import PanelInfoOutputSchema
7
+ from .api_client import NodeApiClient, NodeApiErrorSchema
8
+
9
+
10
+ def is_child() -> bool:
11
+ return hconfig(ConfigEnum.panel_mode) == PanelMode.child
12
+
13
+
14
+ def is_parent() -> bool:
15
+ return hconfig(ConfigEnum.panel_mode) == PanelMode.parent
16
+
17
+ # region usage
18
+
19
+
20
+ def get_users_usage_data_for_api() -> UsageInputOutputSchema:
21
+ res = UsageInputOutputSchema()
22
+ res.usages = [] # type: ignore
23
+ for u in User.query.all():
24
+ usage_data = UsageData()
25
+ usage_data.uuid = u.uuid
26
+ usage_data.usage = u.current_usage
27
+ usage_data.devices = u.devices
28
+ res.usages.append(usage_data) # type: ignore
29
+ return res
30
+
31
+
32
+ def convert_usage_api_response_to_dict(data: dict) -> dict:
33
+ converted = {}
34
+ for i in data['usages']: # type: ignore
35
+ converted[str(i['uuid'])] = {
36
+ 'usage': i['usage'],
37
+ 'devices': ','.join(i['devices']) # type: ignore
38
+ }
39
+ return converted
40
+
41
+ # endregion
42
+
43
+
44
+ #@cache.cache(ttl=150)
45
+ def is_panel_active(domain: str, proxy_path: str,apikey:str|None = None) -> bool:
46
+ base_url = f'https://{domain}/{proxy_path}'
47
+ res = NodeApiClient(base_url,apikey).get('/api/v2/panel/ping/', dict)
48
+ if isinstance(res, NodeApiErrorSchema):
49
+ logger.error(f"Error while checking if panel is active: {res.msg}")
50
+ return False
51
+ if 'PONG' in res['msg']:
52
+ logger.debug(f"Panel is active: {res['msg']}")
53
+ return True
54
+ logger.debug("Panel is not active")
55
+ return False
56
+
57
+
58
+ #@cache.cache(300)
59
+ def get_panel_info(domain: str, proxy_path: str,apikey:str|None = None) -> dict | None:
60
+ base_url = f'https://{domain}/{proxy_path}'
61
+ res = NodeApiClient(base_url,apikey).get('/api/v2/panel/info/', PanelInfoOutputSchema)
62
+ if isinstance(res, NodeApiErrorSchema):
63
+ logger.error(f"Error while getting panel info from {domain}: {res.msg}")
64
+ return None
65
+ return res
@@ -5,7 +5,7 @@ import re
5
5
  import json
6
6
  from ipaddress import IPv4Address, IPv6Address
7
7
  from hiddifypanel.cache import cache
8
- from hiddifypanel.models import Proxy, ProxyProto, ProxyTransport, ProxyCDN, Domain, DomainType, ConfigEnum, hconfig, get_hconfigs
8
+ from hiddifypanel.models import Proxy, ProxyProto, ProxyL3, ProxyTransport, ProxyCDN, Domain, DomainType, ConfigEnum, hconfig, get_hconfigs
9
9
  from hiddifypanel import hutils
10
10
 
11
11
 
@@ -124,16 +124,28 @@ def get_proxies(child_id: int = 0, only_enabled=False) -> list['Proxy']:
124
124
  proxies = [c for c in proxies if 'ssr' != c.proto]
125
125
  if not hconfig(ConfigEnum.vmess_enable, child_id):
126
126
  proxies = [c for c in proxies if 'vmess' not in c.proto]
127
+ if not hconfig(ConfigEnum.vless_enable, child_id):
128
+ proxies = [c for c in proxies if 'vless' not in c.proto]
129
+ if not hconfig(ConfigEnum.trojan_enable, child_id):
130
+ proxies = [c for c in proxies if 'trojan' not in c.proto]
127
131
  if not hconfig(ConfigEnum.httpupgrade_enable, child_id):
128
132
  proxies = [c for c in proxies if ProxyTransport.httpupgrade not in c.transport]
129
133
  if not hconfig(ConfigEnum.ws_enable, child_id):
130
134
  proxies = [c for c in proxies if ProxyTransport.WS not in c.transport]
131
-
135
+ if not hconfig(ConfigEnum.xtls_enable, child_id):
136
+ proxies = [c for c in proxies if ProxyTransport.XTLS not in c.transport]
132
137
  if not hconfig(ConfigEnum.grpc_enable, child_id):
133
138
  proxies = [c for c in proxies if ProxyTransport.grpc not in c.transport]
139
+ if not hconfig(ConfigEnum.tcp_enable, child_id):
140
+ proxies = [c for c in proxies if 'tcp' not in c.transport]
141
+ if not hconfig(ConfigEnum.h2_enable, child_id):
142
+ proxies = [c for c in proxies if 'h2' not in c.transport and c.l3 not in [ProxyL3.tls_h2_h1, ProxyL3.tls_h2]]
134
143
  if not hconfig(ConfigEnum.kcp_enable, child_id):
135
144
  proxies = [c for c in proxies if 'kcp' not in c.l3]
136
-
145
+ if not hconfig(ConfigEnum.reality_enable, child_id):
146
+ proxies = [c for c in proxies if 'reality' not in c.l3]
147
+ if not hconfig(ConfigEnum.quic_enable, child_id):
148
+ proxies = [c for c in proxies if 'h3_quic' not in c.l3]
137
149
  if not hconfig(ConfigEnum.http_proxy_enable, child_id):
138
150
  proxies = [c for c in proxies if 'http' != c.l3]
139
151
 
@@ -1,10 +1,10 @@
1
1
  from .role import Role, AccountType
2
2
  from .child import Child, ChildMode
3
- from .config_enum import ConfigCategory, ConfigEnum, Lang, ApplyMode
3
+ from .config_enum import ConfigCategory, ConfigEnum, Lang, ApplyMode, PanelMode, LogLevel
4
4
  from .config import StrConfig, BoolConfig, get_hconfigs, hconfig, set_hconfig, add_or_update_config, bulk_register_configs, get_hconfigs_childs
5
5
 
6
6
  # from .parent_domain import ParentDomain
7
- from .domain import Domain, DomainType, ShowDomain, get_domain, get_current_proxy_domains, get_panel_domains, get_proxy_domains, get_proxy_domains_db, get_hdomains, hdomain, add_or_update_domain, bulk_register_domains
7
+ from .domain import Domain, DomainType, ShowDomain, get_domain, get_current_proxy_domains, get_panel_domains, get_proxy_domains, get_proxy_domains_db, get_hdomains, hdomain, add_or_update_domain, bulk_register_domains, get_panel_link
8
8
  from .proxy import Proxy, ProxyL3, ProxyCDN, ProxyProto, ProxyTransport
9
9
  from .user import User, UserMode, UserDetail, ONE_GIG
10
10
  from .admin import AdminUser, AdminMode
@@ -7,7 +7,7 @@ from strenum import StrEnum
7
7
  from apiflask import abort
8
8
  from flask_babel import gettext as __
9
9
  from flask_babel import lazy_gettext as _
10
- from hiddifypanel.database import db
10
+ from hiddifypanel.database import db, db_execute
11
11
  from hiddifypanel.models.role import Role
12
12
  from hiddifypanel.models.base_account import BaseAccount
13
13
 
@@ -50,6 +50,15 @@ class AdminUser(BaseAccount):
50
50
  return Role.agent
51
51
  return None
52
52
 
53
+ @staticmethod
54
+ def form_schema(schema):
55
+ return schema.dump(AdminUser())
56
+
57
+ def to_schema(self):
58
+ admin_dict = self.to_dict()
59
+ from hiddifypanel.panel.commercial.restapi.v2.admin.admin_user_api import AdminSchema
60
+ return AdminSchema().load(admin_dict)
61
+
53
62
  def get_id(self) -> str | None:
54
63
  return f'admin_{self.id}'
55
64
 
@@ -57,10 +66,12 @@ class AdminUser(BaseAccount):
57
66
  base = super().to_dict()
58
67
  if dump_id:
59
68
  base['id'] = self.id
69
+ from hiddifypanel.models import hconfig, ConfigEnum
60
70
  return {**base,
61
71
  'mode': self.mode,
62
72
  'can_add_admin': self.can_add_admin,
63
73
  'parent_admin_uuid': self.parent_admin.uuid if self.parent_admin else None,
74
+ 'lang': hconfig(ConfigEnum.admin_lang)
64
75
  }
65
76
 
66
77
  @classmethod
@@ -144,7 +155,8 @@ class AdminUser(BaseAccount):
144
155
  if not admin:
145
156
  db.session.add(AdminUser(id=1, uuid=str(uuid.uuid4()), name="Owner", mode=AdminMode.super_admin, comment=""))
146
157
  db.session.commit()
147
- db.engine.execute("update admin_user set id=1 where name='Owner'")
158
+
159
+ db_execute("update admin_user set id=1 where name='Owner'")
148
160
  admin = AdminUser.by_id(1)
149
161
 
150
162
  return admin
@@ -2,13 +2,13 @@ import datetime
2
2
  import uuid
3
3
  from hiddifypanel.models.role import Role
4
4
  from sqlalchemy import Column, String, BigInteger, Enum
5
- from sqlalchemy_serializer import SerializerMixin
5
+
6
6
  from flask_login import UserMixin as FlaskLoginUserMixin
7
7
  from hiddifypanel.models import Lang
8
8
  from hiddifypanel.database import db
9
9
 
10
10
 
11
- class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: ignore
11
+ class BaseAccount(db.Model, FlaskLoginUserMixin): # type: ignore
12
12
  __abstract__ = True
13
13
  uuid = Column(String(36), default=lambda: str(uuid.uuid4()), nullable=False, unique=True, index=True)
14
14
  name = Column(String(512), nullable=False, default='')
@@ -74,7 +74,7 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
74
74
  for u in accounts:
75
75
  cls.add_or_update(commit=False, **u)
76
76
  if remove:
77
- dd = {u['uuid']: 1 for u in accounts}
77
+ dd = {str(u['uuid']): 1 for u in accounts}
78
78
  for d in cls.query.all():
79
79
  if d.uuid not in dd:
80
80
  db.session.delete(d) # type: ignore
@@ -1,30 +1,34 @@
1
1
  from __future__ import annotations
2
2
  import uuid
3
- from sqlalchemy_serializer import SerializerMixin
3
+
4
+ from sqlalchemy import Column, Integer, String, Enum, text
4
5
  from enum import auto
5
6
  from strenum import StrEnum
6
7
  from flask import g, has_app_context
7
8
 
8
9
 
9
- from hiddifypanel.database import db
10
+ from hiddifypanel.database import db, db_execute
10
11
 
11
12
 
12
13
  class ChildMode(StrEnum):
13
14
  virtual = auto()
14
- remote = auto()
15
+ remote = auto() # it's child
16
+ parent = auto()
17
+
18
+ # the child model is node
15
19
 
16
20
 
17
- class Child(db.Model, SerializerMixin):
18
- id = db.Column(db.Integer, primary_key=True, autoincrement=True)
19
- name = db.Column(db.String(200), nullable=False, unique=True)
20
- mode = db.Column(db.Enum(ChildMode), nullable=False, default=ChildMode.virtual)
21
+ class Child(db.Model): # type: ignore
22
+ id = Column(Integer, primary_key=True, autoincrement=True)
23
+ name = Column(String(200), nullable=False, unique=False)
24
+ mode = Column(Enum(ChildMode), nullable=False, default=ChildMode.virtual)
21
25
  # ip = db.Column(db.String(200), nullable=False, unique=True)
22
- unique_id = db.Column(db.String(200), nullable=False, default=lambda: str(uuid.uuid4()), unique=True)
23
- domains = db.relationship('Domain', cascade="all,delete", backref='child')
24
- proxies = db.relationship('Proxy', cascade="all,delete", backref='child')
25
- boolconfigs = db.relationship('BoolConfig', cascade="all,delete", backref='child')
26
- strconfigs = db.relationship('StrConfig', cascade="all,delete", backref='child')
27
- dailyusages = db.relationship('DailyUsage', cascade="all,delete", backref='child')
26
+ unique_id = Column(String(200), nullable=False, default=lambda: str(uuid.uuid4()), unique=True)
27
+ domains = db.relationship('Domain', cascade="all,delete", backref='child') # type: ignore
28
+ proxies = db.relationship('Proxy', cascade="all,delete", backref='child') # type: ignore
29
+ boolconfigs = db.relationship('BoolConfig', cascade="all,delete", backref='child') # type: ignore
30
+ strconfigs = db.relationship('StrConfig', cascade="all,delete", backref='child') # type: ignore
31
+ dailyusages = db.relationship('DailyUsage', cascade="all,delete", backref='child') # type: ignore
28
32
 
29
33
  def to_dict(self):
30
34
  return {
@@ -55,11 +59,14 @@ class Child(db.Model, SerializerMixin):
55
59
  db.session.commit()
56
60
 
57
61
  @classmethod
58
- def by_id(cls, id: int) -> "Child":
62
+ def by_id(cls, id: int) -> 'Child':
59
63
  return Child.query.filter(Child.id == id).first()
60
64
 
61
65
  @classmethod
62
- @property
66
+ def by_unique_id(cls, unique_id: str) -> 'Child':
67
+ return Child.query.filter(Child.unique_id == unique_id).first()
68
+
69
+ @classmethod
63
70
  def current(cls) -> "Child":
64
71
  if has_app_context() and hasattr(g, "child"):
65
72
  return g.child
@@ -68,6 +75,13 @@ class Child(db.Model, SerializerMixin):
68
75
  tmp_uuid = str(uuid.uuid4())
69
76
  db.session.add(Child(id=0, unique_id=tmp_uuid, name="Root"))
70
77
  db.session.commit()
71
- db.engine.execute(f'update child set id=0 where unique_id="{tmp_uuid}"')
78
+ db_execute(f"update child set id=0 where unique_id='{tmp_uuid}'")
72
79
  child = Child.by_id(0)
80
+ print("child-=======", child)
73
81
  return child
82
+
83
+ @classmethod
84
+ @property
85
+ def node(cls) -> "Child | None":
86
+ if has_app_context() and hasattr(g, "node"):
87
+ return g.node