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
@@ -10,6 +10,7 @@ from wtforms.validators import NumberRange
|
|
10
10
|
from flask_babel import lazy_gettext as _
|
11
11
|
from flask import g, request # type: ignore
|
12
12
|
from markupsafe import Markup
|
13
|
+
from sqlalchemy import desc
|
13
14
|
|
14
15
|
from hiddifypanel.hutils.flask import hurl_for
|
15
16
|
from wtforms.validators import Regexp, ValidationError
|
@@ -148,7 +149,7 @@ class UserAdmin(AdminLTEModelView):
|
|
148
149
|
href = f'{hiddify.get_account_panel_link(model, request.host, is_https=True)}#{hutils.encode.unicode_slug(model.name)}'
|
149
150
|
|
150
151
|
link = f"""<a target='_blank' class='share-link btn btn-xs btn-primary' data-copy='{href}' href='{href}'>
|
151
|
-
<i class='fa-solid fa-arrow-up-right-from-square'></i>
|
152
|
+
<i class='fa-solid fa-arrow-up-right-from-square'></i>
|
152
153
|
{_("Current Domain")} </a>"""
|
153
154
|
|
154
155
|
domains = [d for d in Domain.get_domains() if d.domain != request.host]
|
@@ -171,7 +172,7 @@ class UserAdmin(AdminLTEModelView):
|
|
171
172
|
</div>
|
172
173
|
""")
|
173
174
|
|
174
|
-
def _expire_formatter(view, context, model, name):
|
175
|
+
def _expire_formatter(view, context, model: User, name):
|
175
176
|
remaining = model.remaining_days
|
176
177
|
|
177
178
|
diff = datetime.timedelta(days=remaining)
|
@@ -231,7 +232,8 @@ class UserAdmin(AdminLTEModelView):
|
|
231
232
|
# delattr(form,'disable_user')
|
232
233
|
else:
|
233
234
|
remaining = form._obj.remaining_days # remaining_days(form._obj)
|
234
|
-
|
235
|
+
relative_remaining = hutils.convert.format_timedelta(datetime.timedelta(days=remaining))
|
236
|
+
msg = _("Remaining about %(relative)s, exactly %(days)s days", relative=relative_remaining, days=remaining)
|
235
237
|
form.reset_days.label.text += f" ({msg})"
|
236
238
|
usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
|
237
239
|
form.reset_usage.label.text += usr_usage
|
@@ -240,10 +242,18 @@ class UserAdmin(AdminLTEModelView):
|
|
240
242
|
|
241
243
|
form.usage_limit.label.text += usr_usage
|
242
244
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
245
|
+
# if form._obj.mode==UserMode.disable:
|
246
|
+
# delattr(form,'disable_user')
|
247
|
+
# form.disable_user.data=form._obj.mode==UserMode.disable
|
248
|
+
if form._obj.start_date:
|
249
|
+
started = form._obj.start_date - datetime.date.today()
|
250
|
+
msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
|
251
|
+
form.package_days.label.text += f" ({msg})"
|
252
|
+
if started.days <= 0:
|
253
|
+
exact_start = _("Started %(days)s days ago", days=-started.days)
|
254
|
+
else:
|
255
|
+
exact_start = _("Will Start in %(days)s days", days=started.days)
|
256
|
+
form.package_days.description += f" ({exact_start})"
|
247
257
|
|
248
258
|
def get_edit_form(self):
|
249
259
|
form = super().get_edit_form()
|
@@ -295,7 +305,7 @@ class UserAdmin(AdminLTEModelView):
|
|
295
305
|
def after_model_change(self, form, model, is_created):
|
296
306
|
if hconfig(ConfigEnum.first_setup):
|
297
307
|
set_hconfig(ConfigEnum.first_setup, False)
|
298
|
-
user = User.query.filter(User.uuid == model.uuid).first()
|
308
|
+
user = User.query.filter(User.uuid == model.uuid).first() or abort(404)
|
299
309
|
if user.is_active:
|
300
310
|
user_driver.add_client(model)
|
301
311
|
else:
|
@@ -314,6 +324,7 @@ class UserAdmin(AdminLTEModelView):
|
|
314
324
|
|
315
325
|
def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
|
316
326
|
res = None
|
327
|
+
self._auto_joins = {}
|
317
328
|
# print('aaa',args, kwargs)
|
318
329
|
if sort_column in ['remaining_days', 'is_active']:
|
319
330
|
query = self.get_query()
|
@@ -365,14 +376,12 @@ class UserAdmin(AdminLTEModelView):
|
|
365
376
|
abort(403)
|
366
377
|
|
367
378
|
query = query.filter(User.added_by.in_(admin.recursive_sub_admins_ids()))
|
368
|
-
|
379
|
+
query = query.order_by(desc(User.id))
|
369
380
|
return query
|
370
381
|
|
371
382
|
# Override get_count_query() to include the filter condition in the count query
|
372
383
|
def get_count_query(self):
|
373
384
|
# Get the base count query
|
374
|
-
query = self.get_query()
|
375
|
-
from sqlalchemy import func
|
376
385
|
|
377
386
|
# query = query.session.query(func.count(User.id))
|
378
387
|
query = super().get_count_query()
|
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
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import uuid
|
2
|
-
from apiflask.fields import String, Float, Enum, Date, Integer, Boolean
|
2
|
+
from apiflask.fields import String, Float, Enum, Date, Integer, Boolean, DateTime
|
3
3
|
from apiflask import Schema, fields
|
4
4
|
from typing import Any, Mapping
|
5
5
|
|
6
|
+
from marshmallow import ValidationError
|
7
|
+
|
6
8
|
from hiddifypanel.models import UserMode, Lang, AdminMode
|
7
9
|
from hiddifypanel import hutils
|
8
10
|
|
@@ -18,19 +20,24 @@ class FriendlyDateTime(fields.Field):
|
|
18
20
|
|
19
21
|
|
20
22
|
class FriendlyUUID(fields.Field):
|
23
|
+
|
21
24
|
def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
|
22
|
-
if value is None:
|
25
|
+
if value is None or not hutils.auth.is_uuid_valid(value):
|
23
26
|
return None
|
24
27
|
return str(value)
|
25
28
|
|
26
29
|
def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
|
27
|
-
if value is None:
|
30
|
+
if value is None or not hutils.auth.is_uuid_valid(value):
|
28
31
|
return None
|
29
32
|
try:
|
30
33
|
return str(uuid.UUID(value))
|
31
34
|
except ValueError:
|
32
35
|
self.fail('Invalid uuid')
|
33
36
|
|
37
|
+
def _validated(self, value):
|
38
|
+
if not hutils.auth.is_uuid_valid(value):
|
39
|
+
raise ValidationError('Invalid UUID')
|
40
|
+
|
34
41
|
|
35
42
|
class UserSchema(Schema):
|
36
43
|
uuid = FriendlyUUID(required=True, description="Unique identifier for the user")
|
@@ -66,8 +73,8 @@ class UserSchema(Schema):
|
|
66
73
|
allow_none=True,
|
67
74
|
description="The current data usage of the user in gigabytes"
|
68
75
|
)
|
69
|
-
last_reset_time =
|
70
|
-
format='%Y-%m-%d',
|
76
|
+
last_reset_time = FriendlyDateTime(
|
77
|
+
format='%Y-%m-%d %H:%M:%S',
|
71
78
|
description="The last time the user's data usage was reset, in a JSON-friendly format",
|
72
79
|
allow_none=True
|
73
80
|
)
|
@@ -121,20 +128,25 @@ class UserSchema(Schema):
|
|
121
128
|
|
122
129
|
lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
|
123
130
|
enable = Boolean(required=False, description="Whether the user is enabled or not")
|
131
|
+
is_active = Boolean(required=False, description="Whether the user is active for using hiddify")
|
124
132
|
|
125
133
|
|
126
|
-
class
|
134
|
+
class PostUserSchema(UserSchema):
|
127
135
|
def __init__(self, *args, **kwargs):
|
128
136
|
super().__init__(*args, **kwargs)
|
129
137
|
# the uuid is sent in the url path
|
130
138
|
self.fields['uuid'].required = False
|
139
|
+
self.fields['uuid'].allow_none = True
|
131
140
|
|
132
141
|
|
133
142
|
class PatchUserSchema(UserSchema):
|
134
143
|
def __init__(self, *args, **kwargs):
|
135
144
|
super().__init__(*args, **kwargs)
|
136
145
|
self.fields['uuid'].required = False
|
146
|
+
self.fields['uuid'].allow_none = True,
|
137
147
|
self.fields['name'].required = False
|
148
|
+
self.fields['name'].allow_none = True,
|
149
|
+
|
138
150
|
|
139
151
|
# endregion
|
140
152
|
|
@@ -144,7 +156,7 @@ class PatchUserSchema(UserSchema):
|
|
144
156
|
class AdminSchema(Schema):
|
145
157
|
name = String(required=True, description='The name of the admin')
|
146
158
|
comment = String(required=False, description='A comment related to the admin', allow_none=True)
|
147
|
-
uuid = FriendlyUUID(required=True, description='The unique identifier for the admin')
|
159
|
+
uuid = FriendlyUUID(required=False, allow_none=True, description='The unique identifier for the admin')
|
148
160
|
mode = Enum(AdminMode, required=True, description='The mode for the admin')
|
149
161
|
can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
|
150
162
|
parent_admin_uuid = FriendlyUUID(required=False, description='The unique identifier for the parent admin', allow_none=True,
|
@@ -156,13 +168,6 @@ class AdminSchema(Schema):
|
|
156
168
|
max_active_users = Integer(required=False, description='The maximum number of active users allowed', allow_none=True)
|
157
169
|
|
158
170
|
|
159
|
-
class PutAdminSchema(AdminSchema):
|
160
|
-
def __init__(self, *args, **kwargs):
|
161
|
-
super().__init__(*args, **kwargs)
|
162
|
-
# the uuid is sent in the url path
|
163
|
-
self.fields['uuid'].required = False
|
164
|
-
|
165
|
-
|
166
171
|
class PatchAdminSchema(AdminSchema):
|
167
172
|
def __init__(self, *args, **kwargs):
|
168
173
|
super().__init__(*args, **kwargs)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from flask import current_app as app, request
|
2
|
+
from flask import g
|
3
|
+
from flask.views import MethodView
|
4
|
+
from apiflask.fields import Dict
|
5
|
+
from apiflask import Schema
|
6
|
+
from hiddifypanel.models.usage import DailyUsage
|
7
|
+
from hiddifypanel.auth import login_required
|
8
|
+
from hiddifypanel.models import Role, DailyUsage
|
9
|
+
from hiddifypanel.panel import hiddify, usage
|
10
|
+
from hiddifypanel import hutils
|
11
|
+
import json
|
12
|
+
|
13
|
+
|
14
|
+
class UpdateUserUsageApi(MethodView):
|
15
|
+
decorators = [login_required({Role.super_admin})]
|
16
|
+
|
17
|
+
def get(self):
|
18
|
+
"""System: Update User Usage"""
|
19
|
+
return json.dumps(usage.update_local_usage(), indent=2)
|
20
|
+
|
21
|
+
|
22
|
+
class AllConfigsApi(MethodView):
|
23
|
+
decorators = [login_required({Role.super_admin})]
|
24
|
+
|
25
|
+
def get(self):
|
26
|
+
"""System: All Configs for configuration"""
|
27
|
+
return json.dumps(hiddify.all_configs_for_cli(), indent=2)
|
@@ -9,7 +9,7 @@ from hiddifypanel.drivers import user_driver
|
|
9
9
|
from hiddifypanel.panel import hiddify
|
10
10
|
|
11
11
|
from . import has_permission
|
12
|
-
from .schema import UserSchema,
|
12
|
+
from .schema import UserSchema, PostUserSchema, PatchUserSchema, SuccessfulSchema
|
13
13
|
|
14
14
|
|
15
15
|
class UserApi(MethodView):
|
@@ -17,30 +17,17 @@ class UserApi(MethodView):
|
|
17
17
|
|
18
18
|
@app.output(UserSchema) # type: ignore
|
19
19
|
def get(self, uuid):
|
20
|
+
"""User: Get details of a user"""
|
20
21
|
user = User.by_uuid(uuid) or abort(404, "User not found")
|
21
22
|
if not has_permission(user):
|
22
23
|
abort(403, "You don't have permission to access this user")
|
23
24
|
|
24
25
|
return user.to_schema() # type: ignore
|
25
26
|
|
26
|
-
@app.input(PutUserSchema, arg_name="data") # type: ignore
|
27
|
-
@app.output(SuccessfulSchema) # type: ignore
|
28
|
-
def put(self, uuid, data):
|
29
|
-
if User.by_uuid(uuid):
|
30
|
-
abort(400, 'The user exists')
|
31
|
-
data['uuid'] = uuid
|
32
|
-
|
33
|
-
if not data.get('added_by_uuid'):
|
34
|
-
data['added_by_uuid'] = g.account.uuid
|
35
|
-
|
36
|
-
dbuser = User.add_or_update(**data) or abort(502, "Unknown issue: User is not added")
|
37
|
-
user_driver.add_client(dbuser)
|
38
|
-
hiddify.quick_apply_users()
|
39
|
-
return {'status': 200, 'msg': 'ok'}
|
40
|
-
|
41
27
|
@app.input(PatchUserSchema, arg_name="data") # type: ignore
|
42
|
-
@app.output(
|
28
|
+
@app.output(UserSchema) # type: ignore
|
43
29
|
def patch(self, uuid, data):
|
30
|
+
"""User: Update a user"""
|
44
31
|
user = User.by_uuid(uuid) or abort(404, "user not found")
|
45
32
|
if not has_permission(user):
|
46
33
|
abort(403, "You don't have permission to access this user")
|
@@ -50,17 +37,20 @@ class UserApi(MethodView):
|
|
50
37
|
continue
|
51
38
|
if field not in data:
|
52
39
|
data[field] = getattr(user, field)
|
53
|
-
|
40
|
+
data['old_uuid'] = uuid
|
41
|
+
user_driver.remove_client(user)
|
54
42
|
dbuser = User.add_or_update(**data) or abort(502, "Unknown issue! User is not patched")
|
55
|
-
|
43
|
+
if dbuser.is_active:
|
44
|
+
user_driver.add_client(dbuser)
|
56
45
|
# the add_or_update doesn't update the uuid of User, so for now just delete old user after adding new
|
57
|
-
if user.uuid != data['uuid']:
|
58
|
-
|
46
|
+
# if user.uuid != data['uuid']:
|
47
|
+
# user.remove()
|
59
48
|
hiddify.quick_apply_users()
|
60
|
-
return
|
49
|
+
return dbuser.to_schema()
|
61
50
|
|
62
51
|
@app.output(SuccessfulSchema) # type: ignore
|
63
52
|
def delete(self, uuid):
|
53
|
+
"""User: Delete a User"""
|
64
54
|
user = User.by_uuid(uuid) or abort(404, "user not found")
|
65
55
|
if not has_permission(user):
|
66
56
|
abort(403, "You don't have permission to access this user")
|
@@ -6,7 +6,7 @@ from hiddifypanel.models.role import Role
|
|
6
6
|
from hiddifypanel.panel import hiddify
|
7
7
|
from hiddifypanel.drivers import user_driver
|
8
8
|
from hiddifypanel.models import User
|
9
|
-
from .user_api import UserSchema
|
9
|
+
from .user_api import UserSchema, PostUserSchema
|
10
10
|
from . import has_permission
|
11
11
|
|
12
12
|
|
@@ -15,5 +15,23 @@ class UsersApi(MethodView):
|
|
15
15
|
|
16
16
|
@app.output(UserSchema(many=True)) # type: ignore
|
17
17
|
def get(self):
|
18
|
+
"""User: List users of current admin"""
|
19
|
+
|
18
20
|
users = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no user")
|
19
21
|
return [user.to_schema() for user in users] # type: ignore
|
22
|
+
|
23
|
+
@app.input(PostUserSchema, arg_name="data") # type: ignore
|
24
|
+
@app.output(UserSchema) # type: ignore
|
25
|
+
def post(self, data):
|
26
|
+
"""User: Create a user"""
|
27
|
+
|
28
|
+
if data.get("uuid") and User.by_uuid(data['uuid']):
|
29
|
+
abort(400, 'The user exists')
|
30
|
+
|
31
|
+
if not data.get('added_by_uuid'):
|
32
|
+
data['added_by_uuid'] = g.account.uuid
|
33
|
+
|
34
|
+
dbuser = User.add_or_update(**data) or abort(502, "Unknown issue: User is not added")
|
35
|
+
user_driver.add_client(dbuser)
|
36
|
+
hiddify.quick_apply_users()
|
37
|
+
return dbuser.to_schema()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from apiflask import APIBlueprint
|
2
2
|
|
3
|
-
bp = APIBlueprint("api_child", __name__, url_prefix="/<proxy_path>/api/v2/child/", enable_openapi=
|
3
|
+
bp = APIBlueprint("api_child", __name__, url_prefix="/<proxy_path>/api/v2/child/", enable_openapi=False)
|
4
4
|
|
5
5
|
|
6
6
|
def init_app(app):
|
@@ -2,22 +2,34 @@ from flask.views import MethodView
|
|
2
2
|
|
3
3
|
from hiddifypanel.auth import login_required
|
4
4
|
from hiddifypanel.models import Role
|
5
|
+
from .schema import PongOutputSchema
|
6
|
+
from flask import current_app as app
|
5
7
|
|
6
8
|
|
7
9
|
class PingPongApi(MethodView):
|
8
10
|
decorators = [login_required(roles={Role.super_admin, Role.admin, Role.agent}, node_auth=True)]
|
9
11
|
|
12
|
+
@app.output(PongOutputSchema) # type: ignore
|
10
13
|
def get(self):
|
14
|
+
"""Ping Pong: Get"""
|
11
15
|
return {'msg': 'GET: PONG'}
|
12
16
|
|
17
|
+
@app.output(PongOutputSchema) # type: ignore
|
13
18
|
def post(self):
|
19
|
+
"""Ping Pong: Post"""
|
14
20
|
return {'msg': 'POST: PONG'}
|
15
21
|
|
22
|
+
@app.output(PongOutputSchema) # type: ignore
|
16
23
|
def patch(self):
|
24
|
+
"""Ping Pong: Patch"""
|
17
25
|
return {'msg': 'PATCH: PONG'}
|
18
26
|
|
27
|
+
@app.output(PongOutputSchema) # type: ignore
|
19
28
|
def delete(self):
|
29
|
+
"""Ping Pong: Delete"""
|
20
30
|
return {'msg': 'DELETE: PONG'}
|
21
31
|
|
32
|
+
@app.output(PongOutputSchema) # type: ignore
|
22
33
|
def put(self):
|
34
|
+
"""Ping Pong: Put"""
|
23
35
|
return {'msg': 'PUT: PONG'}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from apiflask import APIBlueprint
|
2
2
|
|
3
|
-
bp = APIBlueprint("api_parent", __name__, url_prefix="/<proxy_path>/api/v2/parent/", enable_openapi=
|
3
|
+
bp = APIBlueprint("api_parent", __name__, url_prefix="/<proxy_path>/api/v2/parent/", enable_openapi=False)
|
4
4
|
|
5
5
|
|
6
6
|
def init_app(app):
|
@@ -2,7 +2,7 @@ from apiflask import APIBlueprint
|
|
2
2
|
|
3
3
|
bp = APIBlueprint("api_user", __name__, url_prefix="/<proxy_path>/api/v2/user/", enable_openapi=True)
|
4
4
|
|
5
|
-
bp_uuid = APIBlueprint("
|
5
|
+
bp_uuid = APIBlueprint("api_user_by_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v2/user/", enable_openapi=True)
|
6
6
|
|
7
7
|
|
8
8
|
def init_app(app):
|
hiddifypanel/panel/hiddify.py
CHANGED
@@ -336,3 +336,34 @@ def get_backup_child_unique_id(backupdata: dict) -> str:
|
|
336
336
|
if len(backupdata.get('childs', [])) == 0:
|
337
337
|
return "self"
|
338
338
|
return backupdata['childs'][0]['unique_id']
|
339
|
+
|
340
|
+
|
341
|
+
def all_configs_for_cli():
|
342
|
+
valid_users = [u.to_dict(dump_id=True) for u in User.query.filter((User.usage_limit > User.current_usage)).all() if u.is_active]
|
343
|
+
host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
|
344
|
+
configs = {
|
345
|
+
"users": valid_users,
|
346
|
+
"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],
|
347
|
+
# "hconfigs": get_hconfigs(json=True),
|
348
|
+
"chconfigs": get_hconfigs_childs(host_child_ids, json=True)
|
349
|
+
}
|
350
|
+
|
351
|
+
def_user = None if len(User.query.all()) > 1 else User.query.filter(User.name == 'default').first()
|
352
|
+
domains = Domain.query.all()
|
353
|
+
sslip_domains = [d.domain for d in domains if "sslip.io" in d.domain]
|
354
|
+
|
355
|
+
configs['chconfigs'][0]['first_setup'] = def_user is not None and len(sslip_domains) > 0
|
356
|
+
server_ip = hutils.network.get_ip_str(4)
|
357
|
+
owner = AdminUser.get_super_admin()
|
358
|
+
configs['api_key'] = owner.uuid
|
359
|
+
configs['api_path'] = hconfig(ConfigEnum.proxy_path_admin)
|
360
|
+
configs['admin_path'] = get_account_panel_link(owner, server_ip, is_https=False, prefere_path_only=True)
|
361
|
+
configs['panel_links'] = []
|
362
|
+
configs['panel_links'].append(get_account_panel_link(owner, server_ip, is_https=False))
|
363
|
+
configs['panel_links'].append(get_account_panel_link(owner, server_ip))
|
364
|
+
domains = Domain.get_domains()
|
365
|
+
|
366
|
+
for d in domains:
|
367
|
+
configs['panel_links'].append(get_account_panel_link(owner, d.domain))
|
368
|
+
|
369
|
+
return configs
|
hiddifypanel/panel/init_db.py
CHANGED
@@ -671,7 +671,6 @@ def upgrade_database():
|
|
671
671
|
|
672
672
|
def init_db():
|
673
673
|
db.create_all()
|
674
|
-
cache.invalidate_all_cached_functions()
|
675
674
|
|
676
675
|
# set_hconfig(ConfigEnum.db_version, 71)
|
677
676
|
# temporary fix
|
@@ -680,6 +679,7 @@ def init_db():
|
|
680
679
|
db_version = int(hconfig(ConfigEnum.db_version) or 0)
|
681
680
|
if db_version == latest_db_version():
|
682
681
|
return
|
682
|
+
cache.invalidate_all_cached_functions()
|
683
683
|
migrate(db_version)
|
684
684
|
Child.query.filter(Child.id == 0).first().mode = ChildMode.virtual
|
685
685
|
# if db_version < 69:
|