hiddifypanel 10.30.6.dev0__py3-none-any.whl → 10.30.8.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 +16 -0
- hiddifypanel/base.py +8 -1
- hiddifypanel/drivers/singbox_api.py +2 -0
- hiddifypanel/drivers/ssh_liberty_bridge_api.py +10 -0
- hiddifypanel/drivers/user_driver.py +5 -2
- hiddifypanel/drivers/wireguard_api.py +34 -44
- hiddifypanel/drivers/xray_api.py +18 -4
- hiddifypanel/hutils/auth.py +1 -1
- hiddifypanel/hutils/proxy/xray.py +1 -1
- hiddifypanel/hutils/proxy/xrayjson.py +20 -22
- hiddifypanel/models/admin.py +14 -8
- hiddifypanel/models/base_account.py +14 -6
- hiddifypanel/models/user.py +38 -25
- hiddifypanel/panel/admin/DomainAdmin.py +1 -1
- hiddifypanel/panel/admin/UserAdmin.py +20 -11
- hiddifypanel/panel/cli.py +1 -27
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +3 -1
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +1 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +1 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +9 -20
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +14 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +19 -14
- hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +1 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/system_actions.py +27 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +12 -22
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +19 -1
- hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +12 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +5 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/user/__init__.py +1 -1
- hiddifypanel/panel/hiddify.py +31 -0
- hiddifypanel/panel/init_db.py +1 -1
- hiddifypanel/panel/usage.py +27 -16
- hiddifypanel/templates/fake.html +316 -0
- hiddifypanel/translations.i18n/en.json +74 -33
- hiddifypanel/translations.i18n/fa.json +80 -118
- hiddifypanel/translations.i18n/pt.json +68 -315
- hiddifypanel/translations.i18n/ru.json +73 -168
- hiddifypanel/translations.i18n/zh.json +68 -296
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/METADATA +1 -1
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/RECORD +48 -47
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/WHEEL +1 -1
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/top_level.txt +0 -0
hiddifypanel/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
10.30.
|
1
|
+
10.30.8.dev0
|
hiddifypanel/VERSION.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
__version__='10.30.
|
1
|
+
__version__='10.30.8.dev0'
|
2
2
|
from datetime import datetime
|
3
|
-
__release_date__= datetime.strptime('2024-07-
|
3
|
+
__release_date__= datetime.strptime('2024-07-10','%Y-%m-%d')
|
hiddifypanel/auth.py
CHANGED
@@ -98,8 +98,24 @@ def login_user(user: AdminUser | User, remember=False, duration=None, force=Fals
|
|
98
98
|
|
99
99
|
|
100
100
|
def login_required(roles: set[Role] | None = None, node_auth: bool = False):
|
101
|
+
|
102
|
+
def decorator(func):
|
103
|
+
from flask import has_app_context, current_app
|
104
|
+
# Conditionally apply x if has_app_context() is true
|
105
|
+
if has_app_context():
|
106
|
+
func = current_app.doc(security='Hiddify-API-Key')(func)
|
107
|
+
|
108
|
+
# Always apply y
|
109
|
+
func = login_required2(roles, node_auth)(func)
|
110
|
+
return func
|
111
|
+
return decorator
|
112
|
+
|
113
|
+
|
114
|
+
def login_required2(roles: set[Role] | None = None, node_auth: bool = False):
|
101
115
|
'''When both roles and node_auth is set, means authentication can be done by either uuid or unique_id'''
|
116
|
+
|
102
117
|
def wrapper(fn):
|
118
|
+
|
103
119
|
@wraps(fn)
|
104
120
|
def decorated_view(*args, **kwargs):
|
105
121
|
# print('xxxx', current_account)
|
hiddifypanel/base.py
CHANGED
@@ -37,7 +37,7 @@ def init_logger(app, cli):
|
|
37
37
|
|
38
38
|
def create_app(*args, cli=False, **config):
|
39
39
|
|
40
|
-
app = APIFlask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True, version='2.
|
40
|
+
app = APIFlask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True, version='2.2.0', title="Hiddify API",
|
41
41
|
openapi_blueprint_url_prefix="/<proxy_path>/api", docs_ui='elements', json_errors=False, enable_openapi=not cli)
|
42
42
|
# app = Flask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True)
|
43
43
|
|
@@ -72,6 +72,13 @@ def create_app(*args, cli=False, **config):
|
|
72
72
|
app.config['SESSION_REDIS'] = redis_client
|
73
73
|
app.config['SESSION_PERMANENT'] = True
|
74
74
|
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=10)
|
75
|
+
app.security_schemes = { # equals to use config SECURITY_SCHEMES
|
76
|
+
'Hiddify-API-Key': {
|
77
|
+
'type': 'apiKey',
|
78
|
+
'in': 'header',
|
79
|
+
'name': 'Hiddify-API-Key',
|
80
|
+
}
|
81
|
+
}
|
75
82
|
Session(app)
|
76
83
|
|
77
84
|
app.jinja_env.line_statement_prefix = '%'
|
@@ -4,6 +4,7 @@ from .abstract_driver import DriverABS
|
|
4
4
|
from flask import current_app
|
5
5
|
import json
|
6
6
|
from collections import defaultdict
|
7
|
+
from hiddifypanel.cache import cache
|
7
8
|
|
8
9
|
|
9
10
|
class SingboxApi(DriverABS):
|
@@ -18,6 +19,7 @@ class SingboxApi(DriverABS):
|
|
18
19
|
json_data = json.load(f)
|
19
20
|
return {u.split("@")[0]: 1 for u in json_data['experimental']['v2ray_api']['stats']['users']}
|
20
21
|
|
22
|
+
@cache.cache(ttl=300)
|
21
23
|
def get_inbound_tags(self):
|
22
24
|
try:
|
23
25
|
xray_client = self.get_singbox_client()
|
@@ -29,11 +29,21 @@ class SSHLibertyBridgeApi(DriverABS):
|
|
29
29
|
|
30
30
|
def remove_client(self, user):
|
31
31
|
redis_client = self.get_ssh_redis_client()
|
32
|
+
if user.ed25519_public_key is None:
|
33
|
+
members = redis_client.smembers(USERS_SET)
|
34
|
+
for member in members:
|
35
|
+
if member.startswith(user.uuid):
|
36
|
+
redis_client.srem(USERS_SET, member)
|
37
|
+
|
32
38
|
redis_client.srem(USERS_SET, f'{user.uuid}::{user.ed25519_public_key}')
|
33
39
|
redis_client.hdel(USERS_USAGE, f'{user.uuid}')
|
34
40
|
redis_client.save()
|
35
41
|
|
36
42
|
def get_all_usage(self, users):
|
43
|
+
redis_client = self.get_ssh_redis_client()
|
44
|
+
allusage = redis_client.hgetall(USERS_USAGE)
|
45
|
+
redis_client.delete(USERS_USAGE)
|
46
|
+
return {u: int(allusage.get(u.uuid) or 0) for u in users}
|
37
47
|
return {u: self.get_usage_imp(u.uuid) for u in users}
|
38
48
|
|
39
49
|
def get_usage_imp(self, client_uuid: str, reset: bool = True) -> int:
|
@@ -33,16 +33,19 @@ def get_enabled_users():
|
|
33
33
|
for driver in enabled_drivers():
|
34
34
|
try:
|
35
35
|
for u, v in driver.get_enabled_users().items():
|
36
|
+
# print(u, "enabled", v, driver)
|
36
37
|
if not v:
|
37
38
|
continue
|
38
39
|
d[u] += 1
|
39
40
|
total += 1
|
40
41
|
except Exception as e:
|
42
|
+
print(driver)
|
41
43
|
hiddify.error(f'ERROR! {driver.__class__.__name__} has error in get_enabled users')
|
42
|
-
|
44
|
+
# print(d, total)
|
43
45
|
res = defaultdict(bool)
|
44
46
|
for u, v in d.items():
|
45
|
-
res[u] = v >= total # ignore singbox
|
47
|
+
# res[u] = v >= total # ignore singbox
|
48
|
+
res[u] = v >= 1
|
46
49
|
return res
|
47
50
|
|
48
51
|
|
@@ -4,40 +4,25 @@ import os
|
|
4
4
|
from .abstract_driver import DriverABS
|
5
5
|
from hiddifypanel.models import User, hconfig, ConfigEnum
|
6
6
|
from hiddifypanel.panel.run_commander import Command, commander
|
7
|
+
import redis
|
8
|
+
|
9
|
+
|
10
|
+
USERS_USAGE = "wg:users-usage"
|
7
11
|
|
8
12
|
|
9
13
|
class WireguardApi(DriverABS):
|
14
|
+
def get_redis_client(self):
|
15
|
+
if not hasattr(self, 'redis_client'):
|
16
|
+
self.redis_client = redis.from_url('unix:///opt/hiddify-manager/other/redis/run.sock?db=1')
|
17
|
+
|
18
|
+
return self.redis_client
|
19
|
+
|
10
20
|
def is_enabled(self) -> bool:
|
11
21
|
return hconfig(ConfigEnum.wireguard_enable)
|
12
|
-
|
13
|
-
WG_LOCAL_USAGE_FILE_PATH = os.path.join('/opt/hiddify-manager/','hiddify-panel','wireguard_usages.json')
|
14
|
-
OLD_WG_LOCAL_USAGE_FILE_PATH = os.path.join('/opt/hiddify-manager/','hiddify-panel','hiddify_usages.json')
|
15
22
|
|
16
23
|
def __init__(self) -> None:
|
17
24
|
super().__init__()
|
18
25
|
|
19
|
-
if os.path.isfile(WireguardApi.OLD_WG_LOCAL_USAGE_FILE_PATH) and not os.path.isfile(WireguardApi.WG_LOCAL_USAGE_FILE_PATH):
|
20
|
-
os.rename(WireguardApi.OLD_WG_LOCAL_USAGE_FILE_PATH,WireguardApi.WG_LOCAL_USAGE_FILE_PATH)
|
21
|
-
|
22
|
-
if not self.is_usages_file_exists_and_json():
|
23
|
-
self.init_empty_usages_file()
|
24
|
-
# create empty local usage file
|
25
|
-
|
26
|
-
def is_usages_file_exists_and_json(self) -> bool:
|
27
|
-
if os.path.isfile(WireguardApi.WG_LOCAL_USAGE_FILE_PATH):
|
28
|
-
try:
|
29
|
-
# try to load it as a JSON
|
30
|
-
self.__get_local_usage()
|
31
|
-
return True
|
32
|
-
except json.decoder.JSONDecodeError:
|
33
|
-
os.remove(WireguardApi.WG_LOCAL_USAGE_FILE_PATH)
|
34
|
-
return False
|
35
|
-
return False
|
36
|
-
def init_empty_usages_file(self):
|
37
|
-
with open(WireguardApi.WG_LOCAL_USAGE_FILE_PATH, 'w+') as f:
|
38
|
-
json.dump({}, f)
|
39
|
-
|
40
|
-
|
41
26
|
def __get_wg_usages(self) -> dict:
|
42
27
|
raw_output = commander(Command.update_wg_usage, run_in_background=False)
|
43
28
|
data = {}
|
@@ -54,11 +39,13 @@ class WireguardApi(DriverABS):
|
|
54
39
|
return data
|
55
40
|
|
56
41
|
def __get_local_usage(self) -> dict:
|
57
|
-
|
58
|
-
|
59
|
-
return
|
42
|
+
usage_data = self.get_redis_client() .get(USERS_USAGE)
|
43
|
+
if usage_data:
|
44
|
+
return json.loads(usage_data)
|
45
|
+
|
46
|
+
return {}
|
60
47
|
|
61
|
-
def __sync_local_usages(self) -> dict:
|
48
|
+
def __sync_local_usages(self, users) -> dict:
|
62
49
|
local_usage = self.__get_local_usage()
|
63
50
|
wg_usage = self.__get_wg_usages()
|
64
51
|
res = {}
|
@@ -66,16 +53,18 @@ class WireguardApi(DriverABS):
|
|
66
53
|
for local_wg_pub in local_usage.copy().keys():
|
67
54
|
if local_wg_pub not in wg_usage:
|
68
55
|
del local_usage[local_wg_pub]
|
69
|
-
|
56
|
+
uuid_map = {u.wg_pub: u for u in users}
|
70
57
|
for wg_pub, wg_usage in wg_usage.items():
|
58
|
+
user = uuid_map.get(wg_pub)
|
59
|
+
uuid = user.uuid if user else None
|
71
60
|
if not local_usage.get(wg_pub):
|
72
|
-
local_usage[wg_pub] = wg_usage
|
61
|
+
local_usage[wg_pub] = {"uuid": uuid, "usage": wg_usage}
|
73
62
|
continue
|
74
|
-
res[wg_pub] = self.calculate_reset(local_usage[wg_pub], wg_usage)
|
75
|
-
local_usage[wg_pub] = wg_usage
|
63
|
+
res[wg_pub] = self.calculate_reset(local_usage[wg_pub]['usage'], wg_usage)
|
64
|
+
local_usage[wg_pub] = {"uuid": uuid, "usage": wg_usage}
|
65
|
+
|
66
|
+
self.get_redis_client().set(USERS_USAGE, json.dumps(local_usage))
|
76
67
|
|
77
|
-
with open(WireguardApi.WG_LOCAL_USAGE_FILE_PATH, 'w') as f:
|
78
|
-
json.dump(local_usage, f)
|
79
68
|
return res
|
80
69
|
|
81
70
|
def calculate_reset(self, last_usage: dict, current_usage: dict) -> dict:
|
@@ -94,15 +83,16 @@ class WireguardApi(DriverABS):
|
|
94
83
|
if not hconfig(ConfigEnum.wireguard_enable):
|
95
84
|
return {}
|
96
85
|
usages = self.__get_wg_usages()
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
enabled = {}
|
101
|
-
|
102
|
-
|
86
|
+
new_wg_pubs = set(usages.keys())
|
87
|
+
old_usages = self.__get_local_usage()
|
88
|
+
old_wg_pubs = set(old_usages.keys())
|
89
|
+
enabled = {u['uuid']: 1 for u in old_usages.values()}
|
90
|
+
not_included = new_wg_pubs - old_wg_pubs
|
91
|
+
if not_included:
|
92
|
+
users = User.query.filter(User.wg_pub.in_(not_included).all())
|
93
|
+
for u in users:
|
103
94
|
enabled[u.uuid] = 1
|
104
|
-
|
105
|
-
enabled[u.uuid] = 0
|
95
|
+
|
106
96
|
return enabled
|
107
97
|
|
108
98
|
def add_client(self, user):
|
@@ -114,7 +104,7 @@ class WireguardApi(DriverABS):
|
|
114
104
|
def get_all_usage(self, users, reset=True):
|
115
105
|
if not hconfig(ConfigEnum.wireguard_enable):
|
116
106
|
return {}
|
117
|
-
all_usages = self.__sync_local_usages()
|
107
|
+
all_usages = self.__sync_local_usages(users)
|
118
108
|
res = {}
|
119
109
|
for u in users:
|
120
110
|
if use := all_usages.get(u.wg_pub):
|
hiddifypanel/drivers/xray_api.py
CHANGED
@@ -2,6 +2,7 @@ import xtlsapi
|
|
2
2
|
from hiddifypanel.models import *
|
3
3
|
from .abstract_driver import DriverABS
|
4
4
|
from collections import defaultdict
|
5
|
+
from hiddifypanel.cache import cache
|
5
6
|
|
6
7
|
|
7
8
|
class XrayApi(DriverABS):
|
@@ -19,7 +20,18 @@ class XrayApi(DriverABS):
|
|
19
20
|
if "user>>>" not in use.name:
|
20
21
|
continue
|
21
22
|
uuid = use.name.split(">>>")[1].split("@")[0]
|
22
|
-
|
23
|
+
try:
|
24
|
+
t = "xtls"
|
25
|
+
protocol = "vless"
|
26
|
+
xray_client.add_client(t, f'{uuid}', f'{uuid}@hiddify.com', protocol=protocol, flow='xtls-rprx-vision', alter_id=0, cipher='chacha20_poly1305')
|
27
|
+
xray_client.remove_client(t, f'{uuid}@hiddify.com')
|
28
|
+
res[uuid] = 0
|
29
|
+
except xtlsapi.xtlsapi.exceptions.EmailAlreadyExists as e:
|
30
|
+
res[uuid] = 1
|
31
|
+
except Exception as e:
|
32
|
+
print(f"error {e}")
|
33
|
+
res[uuid] = 0
|
34
|
+
|
23
35
|
return res
|
24
36
|
|
25
37
|
# xray_client = self.get_xray_client()
|
@@ -40,15 +52,16 @@ class XrayApi(DriverABS):
|
|
40
52
|
# enabled[uuid] = e
|
41
53
|
# return enabled
|
42
54
|
|
55
|
+
# @cache.cache(ttl=300)
|
43
56
|
def get_inbound_tags(self):
|
44
57
|
try:
|
45
58
|
xray_client = self.get_xray_client()
|
46
|
-
inbounds =
|
59
|
+
inbounds = {inb.name.split(">>>")[1] for inb in xray_client.stats_query('inbound')}
|
47
60
|
print(f"Success in get inbound tags {inbounds}")
|
48
61
|
except Exception as e:
|
49
62
|
print(f"error in get inbound tags {e}")
|
50
|
-
inbounds =
|
51
|
-
return list(
|
63
|
+
inbounds = {}
|
64
|
+
return list(inbounds)
|
52
65
|
|
53
66
|
def add_client(self, user):
|
54
67
|
uuid = user.uuid
|
@@ -65,6 +78,7 @@ class XrayApi(DriverABS):
|
|
65
78
|
'v2ray': 'shadowsocks',
|
66
79
|
'kcp': 'vless',
|
67
80
|
'dispatcher': 'trojan',
|
81
|
+
'reality': 'vless'
|
68
82
|
}
|
69
83
|
|
70
84
|
def proto(t):
|
hiddifypanel/hutils/auth.py
CHANGED
@@ -82,7 +82,7 @@ def to_link(proxy: dict) -> str | dict:
|
|
82
82
|
return "ShadowTLS is Not Supported for this platform"
|
83
83
|
# return f'{baseurl}?plugin=v2ray-plugin&path={proxy["proxy_path"]}&host={proxy["fakedomain"]}&udp-over-tcp=true#{name_link}'
|
84
84
|
if proxy['proto'] == 'v2ray':
|
85
|
-
return f'{baseurl}?plugin=v2ray-plugin&mode=websocket&path={proxy["proxy_path"]}&host={proxy["
|
85
|
+
return f'{baseurl}?plugin=v2ray-plugin&mode=websocket&path={proxy["proxy_path"]}&host={proxy["sni"]}&tls&udp-over-tcp=true#{name_link}'
|
86
86
|
|
87
87
|
if proxy['proto'] == 'tuic':
|
88
88
|
baseurl = f'tuic://{proxy["uuid"]}:{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}?congestion_control=cubic&udp_relay_mode=native&sni={proxy["sni"]}&alpn=h3'
|
@@ -8,7 +8,7 @@ from hiddifypanel.models import hconfig, ConfigEnum
|
|
8
8
|
from .xray import is_muxable_agent, OUTBOUND_LEVEL
|
9
9
|
|
10
10
|
|
11
|
-
def configs_as_json(domains: list[Domain], user: User, expire_days: int,
|
11
|
+
def configs_as_json(domains: list[Domain], user: User, expire_days: int, remarks: str) -> str:
|
12
12
|
'''Returns xray configs as json'''
|
13
13
|
all_configs = []
|
14
14
|
|
@@ -35,50 +35,48 @@ def configs_as_json(domains: list[Domain], user: User, expire_days: int, remark
|
|
35
35
|
)
|
36
36
|
# endregion
|
37
37
|
|
38
|
-
# region show status (active/disable)
|
39
|
-
active = True
|
40
38
|
if not user.is_active:
|
39
|
+
# region show status (active/disable)
|
41
40
|
tag = '✖ ' + (hutils.encode.url_encode('بسته شما به پایان رسید') if hconfig(ConfigEnum.lang) == 'fa' else 'Package Ended')
|
42
41
|
# add user status
|
43
42
|
all_configs.append(
|
44
43
|
null_config(tag)
|
45
44
|
)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
if active:
|
45
|
+
# endregion
|
46
|
+
else:
|
50
47
|
# TODO: seperate codes to small functions
|
51
48
|
# TODO: check what are unsupported protocols in other apps
|
52
|
-
unsupported_protos =
|
53
|
-
unsupported_transport =
|
49
|
+
unsupported_protos = {}
|
50
|
+
unsupported_transport = {}
|
54
51
|
if g.user_agent.get('is_v2rayng'):
|
55
52
|
# TODO: ensure which protocols are not supported in v2rayng
|
56
|
-
unsupported_protos =
|
53
|
+
unsupported_protos = {ProxyProto.wireguard, ProxyProto.hysteria, ProxyProto.hysteria2,
|
54
|
+
ProxyProto.tuic, ProxyProto.ss, ProxyProto.ssr, ProxyProto.ssh}
|
57
55
|
if not hutils.flask.is_client_version(hutils.flask.ClientVersion.v2ryang, 1, 8, 18):
|
58
|
-
unsupported_transport =
|
56
|
+
unsupported_transport = {ProxyTransport.httpupgrade}
|
59
57
|
|
60
58
|
# multiple outbounds needs multiple whole base config not just one with multiple outbounds (at least for v2rayng)
|
61
59
|
# https://github.com/2dust/v2rayNG/pull/2827#issue-2127534078
|
62
60
|
outbounds = []
|
63
61
|
for proxy in hutils.proxy.get_valid_proxies(domains):
|
64
|
-
if
|
62
|
+
if proxy['proto'] in unsupported_protos:
|
65
63
|
continue
|
66
|
-
if
|
64
|
+
if proxy['transport'] in unsupported_transport:
|
67
65
|
continue
|
68
66
|
outbound = to_xray(proxy)
|
69
|
-
|
70
|
-
outbounds.append(outbound)
|
67
|
+
outbounds.append(outbound)
|
71
68
|
|
72
69
|
base_config = json.loads(render_template('base_xray_config.json.j2', remarks=remarks))
|
73
70
|
if len(outbounds) > 1:
|
74
71
|
for out in outbounds:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
base = copy.deepcopy(base_config)
|
73
|
+
base['remarks'] = out['tag']
|
74
|
+
base['outbounds'].insert(0, out)
|
75
|
+
# if all_configs:
|
76
|
+
# all_configs.insert(0, copy.deepcopy(base_config))
|
77
|
+
# else:
|
78
|
+
all_configs.append(base)
|
79
|
+
|
82
80
|
else: # single outbound
|
83
81
|
base_config['outbounds'].insert(0, outbounds[0])
|
84
82
|
all_configs = base_config
|
hiddifypanel/models/admin.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from enum import auto
|
2
|
-
import
|
2
|
+
from uuid import uuid4
|
3
3
|
from flask import g
|
4
4
|
from hiddifypanel.models.usage import DailyUsage
|
5
5
|
from sqlalchemy import event, Column, Integer, Enum, Boolean, ForeignKey
|
@@ -84,6 +84,9 @@ class AdminUser(BaseAccount, SerializerMixin):
|
|
84
84
|
uuid = str(uuid)
|
85
85
|
account = AdminUser.query.filter(AdminUser.uuid == uuid).first()
|
86
86
|
if not account and create:
|
87
|
+
from hiddifypanel import hutils
|
88
|
+
if not hutils.auth.is_uuid_valid(uuid):
|
89
|
+
uuid = str(uuid4())
|
87
90
|
dbuser = AdminUser(uuid=uuid, name="unknown", parent_admin_id=AdminUser.current_admin_or_owner().id)
|
88
91
|
db.session.add(dbuser)
|
89
92
|
db.session.commit()
|
@@ -103,11 +106,14 @@ class AdminUser(BaseAccount, SerializerMixin):
|
|
103
106
|
else:
|
104
107
|
parent_admin = cls.by_uuid(parent, create=True)
|
105
108
|
dbuser.parent_admin_id = parent_admin.id # type: ignore
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
109
|
+
if data.get('mode') is not None:
|
110
|
+
dbuser.mode = data.get('mode', AdminMode.agent)
|
111
|
+
if data.get('can_add_admin') is not None:
|
112
|
+
dbuser.can_add_admin = data['can_add_admin']
|
113
|
+
if data.get('max_users') is not None:
|
114
|
+
dbuser.max_users = data['max_users']
|
115
|
+
if data.get('max_active_users') is not None:
|
116
|
+
dbuser.max_active_users = data['max_active_users']
|
111
117
|
if commit:
|
112
118
|
db.session.commit()
|
113
119
|
return dbuser
|
@@ -158,10 +164,10 @@ class AdminUser(BaseAccount, SerializerMixin):
|
|
158
164
|
return str(self.name)
|
159
165
|
|
160
166
|
@staticmethod
|
161
|
-
def get_super_admin():
|
167
|
+
def get_super_admin() -> "AdminUser":
|
162
168
|
admin = AdminUser.by_id(1)
|
163
169
|
if not admin:
|
164
|
-
db.session.add(AdminUser(id=1, uuid=str(
|
170
|
+
db.session.add(AdminUser(id=1, uuid=str(uuid4()), name="Owner", mode=AdminMode.super_admin, comment=""))
|
165
171
|
db.session.commit()
|
166
172
|
|
167
173
|
db_execute("update admin_user set id=1 where name='Owner'", commit=True)
|
@@ -61,13 +61,21 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
|
|
61
61
|
return cls.query.filter(cls.username == username, cls.password == password).first()
|
62
62
|
|
63
63
|
@classmethod
|
64
|
-
def add_or_update(cls, commit: bool = True, **data):
|
65
|
-
db_account = cls.by_uuid(data
|
66
|
-
db_account.name = data.get('name', '')
|
67
|
-
db_account.comment = data.get('comment', '')
|
64
|
+
def add_or_update(cls, commit: bool = True, old_uuid=None, **data):
|
65
|
+
db_account: BaseAccount = cls.by_uuid(old_uuid or data.get('uuid'), create=True)
|
68
66
|
from hiddifypanel import hutils
|
69
|
-
|
70
|
-
|
67
|
+
if hutils.auth.is_uuid_valid(data.get('uuid')):
|
68
|
+
db_account.uuid = data['uuid']
|
69
|
+
|
70
|
+
if data.get('name') is not None:
|
71
|
+
db_account.name = data.get('name')
|
72
|
+
|
73
|
+
if data.get('comment') is not None:
|
74
|
+
db_account.comment = data.get('comment')
|
75
|
+
if data.get('telegram_id') is not None:
|
76
|
+
db_account.telegram_id = hutils.convert.to_int(data.get('telegram_id'))
|
77
|
+
if data.get('lang') is not None:
|
78
|
+
db_account.lang = data.get('lang')
|
71
79
|
if commit:
|
72
80
|
db.session.commit() # type: ignore
|
73
81
|
return db_account
|
hiddifypanel/models/user.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
from enum import auto
|
3
|
+
from uuid import uuid4
|
3
4
|
from hiddifypanel.models.role import Role
|
4
5
|
from dateutil import relativedelta
|
5
6
|
|
@@ -54,7 +55,8 @@ class UserDetail(db.Model, SerializerMixin):
|
|
54
55
|
|
55
56
|
@property
|
56
57
|
def devices(self):
|
57
|
-
return []
|
58
|
+
return []
|
59
|
+
# return [] if not self.connected_devices else self.connected_devices.split(",")
|
58
60
|
|
59
61
|
|
60
62
|
class User(BaseAccount, SerializerMixin):
|
@@ -122,13 +124,14 @@ class User(BaseAccount, SerializerMixin):
|
|
122
124
|
is_active = False
|
123
125
|
elif self.remaining_days < 0:
|
124
126
|
is_active = False
|
125
|
-
elif len(self.devices) > max(3, self.max_ips):
|
126
|
-
|
127
|
+
# elif len(self.devices) > max(3, self.max_ips):
|
128
|
+
# is_active = False
|
127
129
|
return is_active
|
128
130
|
|
129
131
|
@property
|
130
132
|
def devices(self):
|
131
133
|
res = {}
|
134
|
+
return res
|
132
135
|
for detail in UserDetail.query.filter(UserDetail.user_id == self.id):
|
133
136
|
for device in detail.devices:
|
134
137
|
res[device] = 1
|
@@ -202,6 +205,10 @@ class User(BaseAccount, SerializerMixin):
|
|
202
205
|
uuid = str(uuid)
|
203
206
|
account = User.query.filter(User.uuid == uuid).first()
|
204
207
|
if not account and create:
|
208
|
+
from hiddifypanel import hutils
|
209
|
+
if not hutils.auth.is_uuid_valid(uuid):
|
210
|
+
uuid = str(uuid4())
|
211
|
+
|
205
212
|
dbuser = User(uuid=uuid, name="unknown", added_by=AdminUser.current_admin_or_owner().id)
|
206
213
|
db.session.add(dbuser)
|
207
214
|
db.session.commit()
|
@@ -216,11 +223,11 @@ class User(BaseAccount, SerializerMixin):
|
|
216
223
|
@classmethod
|
217
224
|
def add_or_update(cls, commit: bool = True, **data):
|
218
225
|
from hiddifypanel import hutils
|
219
|
-
dbuser = super().add_or_update(commit=commit, **data)
|
226
|
+
dbuser: User = super().add_or_update(commit=commit, **data)
|
220
227
|
if data.get('added_by_uuid'):
|
221
228
|
admin = AdminUser.by_uuid(data.get('added_by_uuid'), create=True) or AdminUser.current_admin_or_owner() # type: ignore
|
222
229
|
dbuser.added_by = admin.id
|
223
|
-
|
230
|
+
elif not dbuser.added_by:
|
224
231
|
dbuser.added_by = 1
|
225
232
|
|
226
233
|
# if data.get('expiry_time', ''): #v4
|
@@ -230,45 +237,50 @@ class User(BaseAccount, SerializerMixin):
|
|
230
237
|
# dbuser.start_date = last_reset_time
|
231
238
|
# dbuser.package_days = (expiry_time - last_reset_time).days # type: ignore
|
232
239
|
# el
|
233
|
-
if 'package_days'
|
240
|
+
if data.get('package_days') is not None:
|
234
241
|
dbuser.package_days = data['package_days']
|
235
|
-
|
242
|
+
|
243
|
+
if data.get('start_date'):
|
236
244
|
dbuser.start_date = hutils.convert.json_to_date(data['start_date'])
|
237
|
-
|
245
|
+
elif 'start_date' in data and data['start_date'] is None:
|
238
246
|
dbuser.start_date = None
|
239
247
|
|
240
248
|
if (c_GB := data.get('current_usage_GB')) is not None:
|
241
249
|
dbuser.current_usage_GB = c_GB
|
242
250
|
elif (c := data.get('current_usage')) is not None:
|
243
251
|
dbuser.current_usage = c
|
244
|
-
|
252
|
+
elif dbuser.current_usage is None:
|
245
253
|
dbuser.current_usage = 0
|
246
254
|
|
247
255
|
if (l_GB := data.get('usage_limit_GB')) is not None:
|
248
256
|
dbuser.usage_limit_GB = l_GB
|
249
257
|
elif (l := data.get('usage_limit')) is not None:
|
250
258
|
dbuser.usage_limit = l
|
251
|
-
|
259
|
+
elif dbuser.usage_limit_GB is None:
|
252
260
|
dbuser.usage_limit_GB = 1000
|
253
261
|
|
254
|
-
|
262
|
+
if data.get('enable') is not None:
|
263
|
+
dbuser.enable = data['enable']
|
255
264
|
|
256
|
-
if data.get('ed25519_private_key', ''):
|
265
|
+
if data.get('ed25519_private_key', '') and data.get('ed25519_public_key', ''):
|
257
266
|
dbuser.ed25519_private_key = data.get('ed25519_private_key', '')
|
258
267
|
dbuser.ed25519_public_key = data.get('ed25519_public_key', '')
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
268
|
+
if data.get('wg_pk') is not None:
|
269
|
+
dbuser.wg_pk = data['wg_pk']
|
270
|
+
if data.get('wg_pub') is not None:
|
271
|
+
dbuser.wg_pub = data['wg_pub']
|
272
|
+
if data.get('wg_psk') is not None:
|
273
|
+
dbuser.wg_psk = data['wg_psk']
|
274
|
+
|
275
|
+
if data.get('mode') is not None or dbuser.mode is None:
|
276
|
+
mode = data.get('mode', UserMode.no_reset)
|
277
|
+
if mode == 'disable':
|
278
|
+
mode = UserMode.no_reset
|
279
|
+
dbuser.enable = False
|
280
|
+
dbuser.mode = mode
|
281
|
+
|
282
|
+
if data.get('last_online') is not None:
|
283
|
+
dbuser.last_online = hutils.convert.json_to_time(data.get('last_online')) or datetime.datetime.min
|
272
284
|
if commit:
|
273
285
|
db.session.commit()
|
274
286
|
return dbuser
|
@@ -305,6 +317,7 @@ class User(BaseAccount, SerializerMixin):
|
|
305
317
|
'wg_pk': self.wg_pk,
|
306
318
|
'wg_pub': self.wg_pub,
|
307
319
|
'wg_psk': self.wg_psk,
|
320
|
+
'is_active': self.is_active
|
308
321
|
}
|
309
322
|
|
310
323
|
# @staticmethod
|
@@ -114,7 +114,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
114
114
|
if not dip:
|
115
115
|
dip = hutils.network.resolve_domain_with_api(model.domain)
|
116
116
|
myip = hutils.network.get_ip(4)
|
117
|
-
if myip == dip and model.mode
|
117
|
+
if myip == dip and model.mode in [DomainType.direct, DomainType.sub_link_only]:
|
118
118
|
badge_type = ''
|
119
119
|
elif dip and model.mode != DomainType.direct and myip != dip:
|
120
120
|
badge_type = 'warning'
|