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.
Files changed (48) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +16 -0
  4. hiddifypanel/base.py +8 -1
  5. hiddifypanel/drivers/singbox_api.py +2 -0
  6. hiddifypanel/drivers/ssh_liberty_bridge_api.py +10 -0
  7. hiddifypanel/drivers/user_driver.py +5 -2
  8. hiddifypanel/drivers/wireguard_api.py +34 -44
  9. hiddifypanel/drivers/xray_api.py +18 -4
  10. hiddifypanel/hutils/auth.py +1 -1
  11. hiddifypanel/hutils/proxy/xray.py +1 -1
  12. hiddifypanel/hutils/proxy/xrayjson.py +20 -22
  13. hiddifypanel/models/admin.py +14 -8
  14. hiddifypanel/models/base_account.py +14 -6
  15. hiddifypanel/models/user.py +38 -25
  16. hiddifypanel/panel/admin/DomainAdmin.py +1 -1
  17. hiddifypanel/panel/admin/UserAdmin.py +20 -11
  18. hiddifypanel/panel/cli.py +1 -27
  19. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +3 -1
  20. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +1 -0
  21. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +1 -0
  22. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +9 -20
  23. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +14 -0
  24. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +19 -14
  25. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +1 -0
  26. hiddifypanel/panel/commercial/restapi/v2/admin/system_actions.py +27 -0
  27. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +12 -22
  28. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +19 -1
  29. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +1 -1
  30. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +12 -0
  31. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +5 -0
  32. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +1 -1
  33. hiddifypanel/panel/commercial/restapi/v2/user/__init__.py +1 -1
  34. hiddifypanel/panel/hiddify.py +31 -0
  35. hiddifypanel/panel/init_db.py +1 -1
  36. hiddifypanel/panel/usage.py +27 -16
  37. hiddifypanel/templates/fake.html +316 -0
  38. hiddifypanel/translations.i18n/en.json +74 -33
  39. hiddifypanel/translations.i18n/fa.json +80 -118
  40. hiddifypanel/translations.i18n/pt.json +68 -315
  41. hiddifypanel/translations.i18n/ru.json +73 -168
  42. hiddifypanel/translations.i18n/zh.json +68 -296
  43. {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/METADATA +1 -1
  44. {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/RECORD +48 -47
  45. {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/WHEEL +1 -1
  46. {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/LICENSE.md +0 -0
  47. {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/entry_points.txt +0 -0
  48. {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
- msg = _("Remaining: ") + hutils.convert.format_timedelta(datetime.timedelta(days=remaining))
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
- # if form._obj.mode==UserMode.disable:
244
- # delattr(form,'disable_user')
245
- # form.disable_user.data=form._obj.mode==UserMode.disable
246
- form.package_days.label.text += f" ({msg})"
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
- 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]
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
@@ -15,6 +15,7 @@ class AdminInfoApi(MethodView):
15
15
 
16
16
  @app.output(AdminSchema) # type: ignore
17
17
  def get(self):
18
+ """Current Admin Info"""
18
19
  admin = g.account or abort(404, "user not found")
19
20
 
20
21
  dto = AdminSchema()
@@ -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, PutAdminSchema, PatchAdminSchema, SuccessfulSchema
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(SuccessfulSchema) # type: ignore
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
- _ = AdminUser.add_or_update(True, **data) or abort(502, "Unknown issue: Admin is not patched")
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
- if admin.uuid != data['uuid']:
51
- admin.remove()
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 = Date(
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 PutUserSchema(UserSchema):
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)
@@ -20,6 +20,7 @@ class AdminServerStatusApi(MethodView):
20
20
 
21
21
  @app.output(ServerStatusOutputSchema) # type: ignore
22
22
  def get(self):
23
+ """System: ServerStatus"""
23
24
  dto = ServerStatusOutputSchema()
24
25
  dto.stats = { # type: ignore
25
26
  'system': hutils.system.system_stats(),
@@ -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, PutUserSchema, PatchUserSchema, SuccessfulSchema
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(SuccessfulSchema) # type: ignore
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
- user_driver.add_client(dbuser)
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
- user.remove()
46
+ # if user.uuid != data['uuid']:
47
+ # user.remove()
59
48
  hiddify.quick_apply_users()
60
- return {'status': 200, 'msg': 'ok'}
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=True)
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'}
@@ -5,3 +5,8 @@ from apiflask import Schema, fields
5
5
  class PanelInfoOutputSchema(Schema):
6
6
  version = fields.String(description="The panel version")
7
7
  # endregion
8
+
9
+
10
+ class PongOutputSchema(Schema):
11
+ msg = fields.String(description="Pong Response")
12
+ # endregion
@@ -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=True)
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("api_user_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v2/user/", enable_openapi=True)
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):
@@ -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
@@ -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: