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.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/auth.py +15 -4
- hiddifypanel/base.py +58 -50
- hiddifypanel/cache.py +43 -25
- hiddifypanel/database.py +9 -0
- hiddifypanel/drivers/abstract_driver.py +2 -0
- hiddifypanel/drivers/singbox_api.py +17 -15
- hiddifypanel/drivers/ssh_liberty_bridge_api.py +2 -0
- hiddifypanel/drivers/user_driver.py +12 -6
- hiddifypanel/drivers/wireguard_api.py +2 -0
- hiddifypanel/drivers/xray_api.py +14 -9
- hiddifypanel/hutils/__init__.py +1 -0
- hiddifypanel/hutils/convert.py +13 -2
- hiddifypanel/hutils/crypto.py +21 -2
- hiddifypanel/hutils/flask.py +19 -5
- hiddifypanel/hutils/importer/xui.py +5 -2
- hiddifypanel/hutils/node/__init__.py +3 -0
- hiddifypanel/hutils/node/api_client.py +76 -0
- hiddifypanel/hutils/node/child.py +147 -0
- hiddifypanel/hutils/node/parent.py +100 -0
- hiddifypanel/hutils/node/shared.py +65 -0
- hiddifypanel/hutils/proxy/shared.py +15 -3
- hiddifypanel/models/__init__.py +2 -2
- hiddifypanel/models/admin.py +14 -2
- hiddifypanel/models/base_account.py +3 -3
- hiddifypanel/models/child.py +30 -16
- hiddifypanel/models/config.py +39 -15
- hiddifypanel/models/config_enum.py +55 -8
- hiddifypanel/models/domain.py +28 -20
- hiddifypanel/models/parent_domain.py +2 -2
- hiddifypanel/models/proxy.py +13 -4
- hiddifypanel/models/report.py +2 -3
- hiddifypanel/models/usage.py +2 -2
- hiddifypanel/models/user.py +18 -9
- hiddifypanel/panel/admin/Actions.py +4 -6
- hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
- hiddifypanel/panel/admin/Dashboard.py +5 -10
- hiddifypanel/panel/admin/DomainAdmin.py +12 -11
- hiddifypanel/panel/admin/NodeAdmin.py +6 -2
- hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
- hiddifypanel/panel/admin/SettingAdmin.py +60 -21
- hiddifypanel/panel/admin/UserAdmin.py +10 -2
- hiddifypanel/panel/admin/templates/index.html +1 -1
- hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
- hiddifypanel/panel/cli.py +16 -16
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
- hiddifypanel/panel/commercial/__init__.py +7 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
- hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
- hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
- hiddifypanel/panel/common.py +21 -6
- hiddifypanel/panel/hiddify.py +9 -80
- hiddifypanel/panel/init_db.py +84 -38
- hiddifypanel/panel/usage.py +33 -18
- hiddifypanel/panel/user/templates/home/usage.html +1 -1
- hiddifypanel/panel/user/templates/new.html +2 -2
- hiddifypanel/static/css/custom.css +13 -0
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/images/hiddify2.png +0 -0
- hiddifypanel/static/new/assets/hiddify-logo-7617d937.png +0 -0
- hiddifypanel/static/new/assets/{index-4510b616.js → index-ccb9873c.js} +2 -2
- hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
- hiddifypanel/static/new/i18n/en.json +6 -6
- hiddifypanel/static/new/i18n/fa.json +1 -1
- hiddifypanel/templates/admin-layout.html +24 -40
- hiddifypanel/templates/fake.html +22 -0
- hiddifypanel/templates/master.html +24 -42
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +95 -5
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +96 -4
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +91 -1
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
- hiddifypanel/translations.i18n/en.json +61 -5
- hiddifypanel/translations.i18n/fa.json +60 -4
- hiddifypanel/translations.i18n/pt.json +63 -7
- hiddifypanel/translations.i18n/ru.json +57 -1
- hiddifypanel/translations.i18n/zh.json +58 -2
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +112 -94
- hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
- hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
- hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
- hiddifypanel/static/new/assets/hiddify-logo-7617d937_old.png +0 -0
- hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
- /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.12.1.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
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,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
|
|
hiddifypanel/models/__init__.py
CHANGED
@@ -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
|
hiddifypanel/models/admin.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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,
|
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
|
hiddifypanel/models/child.py
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import uuid
|
3
|
-
|
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
|
18
|
-
id =
|
19
|
-
name =
|
20
|
-
mode =
|
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 =
|
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) ->
|
62
|
+
def by_id(cls, id: int) -> 'Child':
|
59
63
|
return Child.query.filter(Child.id == id).first()
|
60
64
|
|
61
65
|
@classmethod
|
62
|
-
|
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
|
-
|
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
|