hiddifypanel 10.30.7.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/ssh_liberty_bridge_api.py +4 -0
- hiddifypanel/drivers/wireguard_api.py +34 -44
- hiddifypanel/drivers/xray_api.py +3 -3
- hiddifypanel/hutils/auth.py +1 -1
- hiddifypanel/models/admin.py +14 -8
- hiddifypanel/models/base_account.py +14 -6
- hiddifypanel/models/user.py +38 -25
- 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/templates/fake.html +316 -0
- {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/METADATA +1 -1
- {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/RECORD +36 -35
- {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/WHEEL +1 -1
- {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.30.7.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 = '%'
|
@@ -40,6 +40,10 @@ class SSHLibertyBridgeApi(DriverABS):
|
|
40
40
|
redis_client.save()
|
41
41
|
|
42
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}
|
43
47
|
return {u: self.get_usage_imp(u.uuid) for u in users}
|
44
48
|
|
45
49
|
def get_usage_imp(self, client_uuid: str, reset: bool = True) -> int:
|
@@ -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
@@ -56,12 +56,12 @@ class XrayApi(DriverABS):
|
|
56
56
|
def get_inbound_tags(self):
|
57
57
|
try:
|
58
58
|
xray_client = self.get_xray_client()
|
59
|
-
inbounds =
|
59
|
+
inbounds = {inb.name.split(">>>")[1] for inb in xray_client.stats_query('inbound')}
|
60
60
|
print(f"Success in get inbound tags {inbounds}")
|
61
61
|
except Exception as e:
|
62
62
|
print(f"error in get inbound tags {e}")
|
63
|
-
inbounds =
|
64
|
-
return list(
|
63
|
+
inbounds = {}
|
64
|
+
return list(inbounds)
|
65
65
|
|
66
66
|
def add_client(self, user):
|
67
67
|
uuid = user.uuid
|
hiddifypanel/hutils/auth.py
CHANGED
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
|
hiddifypanel/panel/cli.py
CHANGED
@@ -48,33 +48,7 @@ def backup():
|
|
48
48
|
|
49
49
|
|
50
50
|
def all_configs():
|
51
|
-
|
52
|
-
host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
|
53
|
-
configs = {
|
54
|
-
"users": valid_users,
|
55
|
-
"domains": [u.to_dict(dump_ports=True, dump_child_id=True) for u in Domain.query.filter(Domain.child_id.in_(host_child_ids)).all() if "*" not in u.domain],
|
56
|
-
# "hconfigs": get_hconfigs(json=True),
|
57
|
-
"chconfigs": get_hconfigs_childs(host_child_ids, json=True)
|
58
|
-
}
|
59
|
-
|
60
|
-
def_user = None if len(User.query.all()) > 1 else User.query.filter(User.name == 'default').first()
|
61
|
-
domains = Domain.query.all()
|
62
|
-
sslip_domains = [d.domain for d in domains if "sslip.io" in d.domain]
|
63
|
-
|
64
|
-
configs['chconfigs'][0]['first_setup'] = def_user is not None and len(sslip_domains) > 0
|
65
|
-
server_ip = hutils.network.get_ip_str(4)
|
66
|
-
owner = AdminUser.get_super_admin()
|
67
|
-
|
68
|
-
configs['admin_path'] = hiddify.get_account_panel_link(owner, server_ip, is_https=False, prefere_path_only=True)
|
69
|
-
configs['panel_links'] = []
|
70
|
-
configs['panel_links'].append(hiddify.get_account_panel_link(owner, server_ip, is_https=False))
|
71
|
-
configs['panel_links'].append(hiddify.get_account_panel_link(owner, server_ip))
|
72
|
-
domains = Domain.get_domains()
|
73
|
-
|
74
|
-
for d in domains:
|
75
|
-
configs['panel_links'].append(hiddify.get_account_panel_link(owner, d.domain))
|
76
|
-
|
77
|
-
print(json.dumps(configs, indent=4))
|
51
|
+
print(json.dumps(hiddify.all_configs_for_cli(), indent=4))
|
78
52
|
|
79
53
|
|
80
54
|
def update_usage():
|
@@ -13,12 +13,14 @@ def init_app(app):
|
|
13
13
|
from .admin_user_api import AdminUserApi
|
14
14
|
from .admin_users_api import AdminUsersApi
|
15
15
|
from .admin_log_api import AdminLogApi
|
16
|
+
from .system_actions import UpdateUserUsageApi, AllConfigsApi
|
16
17
|
bp.add_url_rule('/me/', view_func=AdminInfoApi) # type: ignore
|
17
18
|
bp.add_url_rule('/server_status/', view_func=AdminServerStatusApi) # type: ignore
|
18
19
|
bp.add_url_rule('/admin_user/<uuid:uuid>/', view_func=AdminUserApi) # type: ignore
|
19
20
|
bp.add_url_rule('/admin_user/', view_func=AdminUsersApi) # type: ignore
|
20
21
|
bp.add_url_rule('/log/', view_func=AdminLogApi) # type: ignore
|
21
|
-
|
22
|
+
bp.add_url_rule('/update_user_usage/', view_func=UpdateUserUsageApi) # type: ignore
|
23
|
+
bp.add_url_rule('/all-configs/', view_func=AllConfigsApi) # type: ignore
|
22
24
|
from .user_api import UserApi
|
23
25
|
from .users_api import UsersApi
|
24
26
|
bp.add_url_rule('/user/<uuid:uuid>/', view_func=UserApi) # type: ignore
|
@@ -18,6 +18,7 @@ class AdminLogApi(MethodView):
|
|
18
18
|
@app.output(fields.String(description="The html of the log", many=True)) # type: ignore
|
19
19
|
@login_required({Role.super_admin})
|
20
20
|
def post(self, data):
|
21
|
+
"""System: View Log file"""
|
21
22
|
file_name = data.get('file') or abort(400, "Parameter issue: 'file'")
|
22
23
|
log_dir = f"{app.config['HIDDIFY_CONFIG_PATH']}log/system/"
|
23
24
|
log_files = hutils.flask.list_dir_files(log_dir)
|
@@ -6,7 +6,7 @@ from hiddifypanel.auth import login_required
|
|
6
6
|
from hiddifypanel.models import *
|
7
7
|
|
8
8
|
from . import has_permission
|
9
|
-
from .schema import AdminSchema,
|
9
|
+
from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
|
10
10
|
|
11
11
|
|
12
12
|
class AdminUserApi(MethodView):
|
@@ -14,27 +14,16 @@ class AdminUserApi(MethodView):
|
|
14
14
|
|
15
15
|
@app.output(AdminSchema) # type: ignore
|
16
16
|
def get(self, uuid):
|
17
|
+
"""Admin: Get an admin"""
|
17
18
|
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
18
19
|
if not has_permission(admin):
|
19
20
|
abort(403, "you don't have permission to access this admin")
|
20
21
|
return admin.to_schema() # type: ignore
|
21
22
|
|
22
|
-
@app.input(PutAdminSchema, arg_name='data') # type: ignore
|
23
|
-
@app.output(SuccessfulSchema) # type: ignore
|
24
|
-
def put(self, uuid, data):
|
25
|
-
if AdminUser.by_uuid(uuid):
|
26
|
-
abort(400, "The admin exists")
|
27
|
-
data['uuid'] = uuid
|
28
|
-
|
29
|
-
if not data.get('added_by_uuid'):
|
30
|
-
data['added_by_uuid'] = g.account.uuid
|
31
|
-
|
32
|
-
_ = AdminUser.add_or_update(**data) or abort(502, "Unknown issue: Admin is not added")
|
33
|
-
return {'status': 200, 'msg': 'ok'}
|
34
|
-
|
35
23
|
@app.input(PatchAdminSchema, arg_name='data') # type: ignore
|
36
|
-
@app.output(
|
24
|
+
@app.output(AdminSchema) # type: ignore
|
37
25
|
def patch(self, uuid, data):
|
26
|
+
"""Admin: Update an admin"""
|
38
27
|
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
39
28
|
if not has_permission(admin):
|
40
29
|
abort(403, "You don't have permission to access this admin")
|
@@ -44,15 +33,15 @@ class AdminUserApi(MethodView):
|
|
44
33
|
continue
|
45
34
|
if field not in data:
|
46
35
|
data[field] = getattr(admin, field)
|
47
|
-
|
48
|
-
|
36
|
+
data['old_uuid'] = uuid
|
37
|
+
admin = AdminUser.add_or_update(True, **data) or abort(502, "Unknown issue: Admin is not patched")
|
49
38
|
# the add_or_update doesn't update the uuid of AdminUser, so for now just delete old admin after adding new
|
50
|
-
|
51
|
-
|
52
|
-
return {'status': 200, 'msg': 'ok'}
|
39
|
+
|
40
|
+
return admins
|
53
41
|
|
54
42
|
@app.output(SuccessfulSchema) # type: ignore
|
55
43
|
def delete(self, uuid):
|
44
|
+
"""Admin: Delete an admin"""
|
56
45
|
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
57
46
|
if not has_permission(admin):
|
58
47
|
abort(403, "You don't have permission to access this admin")
|
@@ -13,5 +13,19 @@ class AdminUsersApi(MethodView):
|
|
13
13
|
|
14
14
|
@app.output(AdminSchema(many=True)) # type: ignore
|
15
15
|
def get(self):
|
16
|
+
"""Admin: Get all admins"""
|
16
17
|
admins = AdminUser.query.filter(AdminUser.id.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no admin")
|
17
18
|
return [admin.to_schema() for admin in admins] # type: ignore
|
19
|
+
|
20
|
+
@app.input(AdminSchema, arg_name='data') # type: ignore
|
21
|
+
@app.output(AdminSchema) # type: ignore
|
22
|
+
def post(self, data):
|
23
|
+
"""Admin: Create an admin"""
|
24
|
+
if 'uuid' in data and AdminUser.by_uuid(data['uuid']):
|
25
|
+
abort(400, "The admin exists")
|
26
|
+
|
27
|
+
if not data.get('added_by_uuid'):
|
28
|
+
data['added_by_uuid'] = g.account.uuid
|
29
|
+
|
30
|
+
admin = AdminUser.add_or_update(**data) or abort(502, "Unknown issue: Admin is not added")
|
31
|
+
return admin
|