hiddifypanel 10.14.0__py3-none-any.whl → 10.15.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/auth.py +15 -4
- hiddifypanel/base.py +11 -3
- hiddifypanel/cache.py +43 -25
- hiddifypanel/database.py +9 -0
- hiddifypanel/drivers/singbox_api.py +2 -14
- hiddifypanel/drivers/xray_api.py +0 -4
- hiddifypanel/hutils/__init__.py +1 -0
- hiddifypanel/hutils/convert.py +13 -2
- hiddifypanel/hutils/crypto.py +21 -2
- hiddifypanel/hutils/flask.py +18 -4
- hiddifypanel/hutils/importer/xui.py +5 -2
- hiddifypanel/hutils/node/__init__.py +3 -0
- hiddifypanel/hutils/node/api_client.py +76 -0
- hiddifypanel/hutils/node/child.py +147 -0
- hiddifypanel/hutils/node/parent.py +100 -0
- hiddifypanel/hutils/node/shared.py +65 -0
- hiddifypanel/hutils/proxy/shared.py +15 -3
- hiddifypanel/models/__init__.py +2 -2
- hiddifypanel/models/admin.py +14 -2
- hiddifypanel/models/base_account.py +3 -3
- hiddifypanel/models/child.py +30 -16
- hiddifypanel/models/config.py +39 -15
- hiddifypanel/models/config_enum.py +55 -8
- hiddifypanel/models/domain.py +28 -20
- hiddifypanel/models/parent_domain.py +2 -2
- hiddifypanel/models/proxy.py +13 -4
- hiddifypanel/models/report.py +2 -3
- hiddifypanel/models/usage.py +2 -2
- hiddifypanel/models/user.py +13 -4
- hiddifypanel/panel/admin/Actions.py +4 -6
- hiddifypanel/panel/admin/AdminstratorAdmin.py +13 -2
- hiddifypanel/panel/admin/Dashboard.py +5 -10
- hiddifypanel/panel/admin/DomainAdmin.py +12 -11
- hiddifypanel/panel/admin/NodeAdmin.py +6 -2
- hiddifypanel/panel/admin/ProxyAdmin.py +4 -3
- hiddifypanel/panel/admin/SettingAdmin.py +60 -21
- hiddifypanel/panel/admin/UserAdmin.py +10 -2
- hiddifypanel/panel/admin/templates/index.html +1 -1
- hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
- hiddifypanel/panel/cli.py +16 -16
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +10 -5
- hiddifypanel/panel/commercial/__init__.py +7 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -35
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
- hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -73
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
- hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
- hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
- hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
- hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
- hiddifypanel/panel/common.py +21 -6
- hiddifypanel/panel/hiddify.py +9 -80
- hiddifypanel/panel/init_db.py +43 -12
- hiddifypanel/panel/usage.py +28 -15
- hiddifypanel/panel/user/templates/home/usage.html +1 -1
- hiddifypanel/panel/user/templates/new.html +1 -1
- hiddifypanel/static/css/custom.css +13 -0
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/new/assets/{index-bce9b1a6.js → index-ccb9873c.js} +65 -65
- hiddifypanel/templates/admin-layout.html +24 -40
- hiddifypanel/templates/fake.html +298 -0
- hiddifypanel/templates/master.html +23 -41
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +90 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +91 -1
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +98 -6
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +90 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +92 -2
- hiddifypanel/translations.i18n/en.json +56 -0
- hiddifypanel/translations.i18n/fa.json +57 -1
- hiddifypanel/translations.i18n/pt.json +63 -7
- hiddifypanel/translations.i18n/ru.json +56 -0
- hiddifypanel/translations.i18n/zh.json +58 -2
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/METADATA +47 -47
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/RECORD +104 -86
- hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
- hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
- hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
- /hiddifypanel/static/images/{hiddify1.png → hiddify-old.png} +0 -0
- /hiddifypanel/static/{new/assets/hiddify-logo-noroz-559c8dcb.png → images/hiddify2.png} +0 -0
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.14.0.dist-info → hiddifypanel-10.15.0.dev0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,9 @@ from flask_babel import gettext as __
|
|
8
8
|
from .adminlte import AdminLTEModelView
|
9
9
|
from wtforms.validators import NumberRange
|
10
10
|
from flask_babel import lazy_gettext as _
|
11
|
-
from flask import
|
11
|
+
from flask import g, request # type: ignore
|
12
|
+
from markupsafe import Markup
|
13
|
+
|
12
14
|
from hiddifypanel.hutils.flask import hurl_for
|
13
15
|
from wtforms.validators import Regexp, ValidationError
|
14
16
|
from flask import current_app
|
@@ -126,7 +128,7 @@ class UserAdmin(AdminLTEModelView):
|
|
126
128
|
def _enable_formatter(view, context, model, name):
|
127
129
|
if model.is_active:
|
128
130
|
link = '<i class="fa-solid fa-circle-check text-success"></i> '
|
129
|
-
elif len(model.
|
131
|
+
elif len(model.devices):
|
130
132
|
link = '<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
|
131
133
|
else:
|
132
134
|
link = '<i class="fa-solid fa-circle-xmark text-danger"></i> '
|
@@ -297,10 +299,16 @@ class UserAdmin(AdminLTEModelView):
|
|
297
299
|
user_driver.remove_client(model)
|
298
300
|
hiddify.quick_apply_users()
|
299
301
|
|
302
|
+
if hutils.node.is_parent():
|
303
|
+
hutils.node.parent.request_childs_to_sync()
|
304
|
+
|
300
305
|
def after_model_delete(self, model):
|
301
306
|
user_driver.remove_client(model)
|
302
307
|
hiddify.quick_apply_users()
|
303
308
|
|
309
|
+
if hutils.node.is_parent():
|
310
|
+
hutils.node.parent.request_childs_to_sync()
|
311
|
+
|
304
312
|
def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
|
305
313
|
res = None
|
306
314
|
# print('aaa',args, kwargs)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% macro admin_btn(child,domain) -%}
|
2
2
|
<div class="btn-group">
|
3
|
-
<a href="{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
|
3
|
+
<a href="{{hiddify.get_account_panel_link(g.account,domain,child_id=child.id)}}" class="btn btn-xs btn-{{" success" if child.is_active else "warning" }} orig-link ltr" target="_blank">{{domain}}</a>
|
4
4
|
</div>
|
5
5
|
{%- endmacro -%}
|
6
6
|
|
@@ -28,7 +28,6 @@
|
|
28
28
|
<td>
|
29
29
|
{% if not child.is_active %}
|
30
30
|
<form method="post" action="remove_child">
|
31
|
-
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
32
31
|
<input type="hidden" name="child_id" value="{{child.id}}" />
|
33
32
|
<button type="submit" class="btn btn-xs btn-danger"><i class="fa fa-trash"></i></a>
|
34
33
|
</form>
|
@@ -37,9 +36,8 @@
|
|
37
36
|
<td class="text-center"><span class="btn btn-xs badge-{{" success" if child.is_active else "warning" }}"> {{icon('solid','check') if child.is_active else icon('solid','triangle-exclamation')}}</span>
|
38
37
|
</td>
|
39
38
|
<td>
|
40
|
-
|
41
39
|
{% for d in child.domains %}
|
42
|
-
{% if d.mode !="fake" %}
|
40
|
+
{% if d.mode !="fake" and d.mode != "reality"%}
|
43
41
|
{{admin_btn(child,d)}}
|
44
42
|
{% endif %}
|
45
43
|
{% endfor %}
|
hiddifypanel/panel/cli.py
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
import datetime
|
2
2
|
import uuid
|
3
|
-
|
3
|
+
import json
|
4
|
+
import os
|
4
5
|
import click
|
5
6
|
from dateutil import relativedelta
|
7
|
+
|
8
|
+
|
6
9
|
from hiddifypanel import hutils
|
7
10
|
|
8
11
|
from hiddifypanel.models import *
|
9
12
|
from hiddifypanel.panel import hiddify, usage
|
10
13
|
from hiddifypanel.database import db
|
11
14
|
from hiddifypanel.panel.init_db import init_db
|
12
|
-
from flask import g
|
13
15
|
|
14
16
|
|
15
17
|
def drop_db():
|
@@ -24,29 +26,28 @@ def downgrade():
|
|
24
26
|
ConfigEnum.hysteria_port, ConfigEnum.ssh_server_enable, ConfigEnum.ssh_server_port, ConfigEnum.ssh_server_redis_url])).delete()
|
25
27
|
Proxy.query.filter(Proxy.l3.in_([ProxyL3.ssh, ProxyL3.h3_quic, ProxyL3.custom])).delete()
|
26
28
|
db.session.commit()
|
27
|
-
import os
|
28
29
|
os.rename("/opt/hiddify-manager/hiddify-panel/hiddifypanel.db.old", "/opt/hiddify-manager/hiddify-panel/hiddifypanel.db")
|
29
30
|
|
30
31
|
|
31
32
|
def backup():
|
32
33
|
dbdict = hiddify.dump_db_to_dict()
|
33
|
-
import json
|
34
|
-
import os
|
35
34
|
os.makedirs('backup', exist_ok=True)
|
36
35
|
dst = f'backup/{datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")}.json'
|
37
36
|
with open(dst, 'w') as fp:
|
38
37
|
json.dump(dbdict, fp, indent=4, sort_keys=True, default=str)
|
39
38
|
|
40
39
|
if hconfig(ConfigEnum.telegram_bot_token):
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
from hiddifypanel.panel.commercial.telegrambot import bot, register_bot
|
41
|
+
if not bot.token:
|
42
|
+
register_bot(True)
|
43
|
+
|
44
|
+
with open(dst, 'rb') as document:
|
45
|
+
for admin in AdminUser.query.filter(AdminUser.mode == AdminMode.super_admin, AdminUser.telegram_id is not None).all():
|
44
46
|
caption = ("Backup \n" + admin_links())
|
45
47
|
bot.send_document(admin.telegram_id, document, visible_file_name=dst.replace("backup/", ""), caption=caption[:min(len(caption), 1000)])
|
46
48
|
|
47
49
|
|
48
50
|
def all_configs():
|
49
|
-
import json
|
50
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]
|
51
52
|
host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
|
52
53
|
configs = {
|
@@ -111,8 +112,8 @@ def admin_path():
|
|
111
112
|
print(hiddify.get_account_panel_link(admin, domain, prefere_path_only=True))
|
112
113
|
|
113
114
|
|
114
|
-
def get_this_host_domains():
|
115
|
-
|
115
|
+
# def get_this_host_domains():
|
116
|
+
# current_child_ids
|
116
117
|
|
117
118
|
|
118
119
|
def hysteria_domain_port():
|
@@ -142,12 +143,11 @@ def init_app(app):
|
|
142
143
|
@ app.cli.command()
|
143
144
|
@ click.option("--domain", "-d")
|
144
145
|
def add_domain(domain):
|
145
|
-
|
146
|
-
|
147
|
-
if table.query.filter(table.domain == domain).first():
|
146
|
+
# TODO: Fix this
|
147
|
+
if Domain.query.filter(Domain.domain == domain).first():
|
148
148
|
return "Domain already exist."
|
149
|
-
d =
|
150
|
-
if not
|
149
|
+
d = Domain(domain=domain)
|
150
|
+
if not hutils.node.is_parent():
|
151
151
|
d.mode = DomainType.direct
|
152
152
|
db.session.add(d)
|
153
153
|
db.session.commit()
|
@@ -1,8 +1,11 @@
|
|
1
1
|
from hiddifypanel.models import *
|
2
|
+
from hiddifypanel.panel import hiddify
|
2
3
|
from hiddifypanel.panel.admin.adminlte import AdminLTEModelView
|
3
4
|
from flask_babel import gettext as __
|
4
5
|
from flask_babel import lazy_gettext as _
|
5
|
-
from flask import g, redirect
|
6
|
+
from flask import g, redirect
|
7
|
+
from markupsafe import Markup
|
8
|
+
|
6
9
|
from hiddifypanel.hutils.flask import hurl_for, flash
|
7
10
|
from hiddifypanel.auth import login_required
|
8
11
|
from flask_admin.model.template import EndpointLinkRowAction
|
@@ -45,14 +48,16 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
45
48
|
# form_overrides = {'work_with': Select2Field}
|
46
49
|
|
47
50
|
def after_model_change(self, form, model, is_created):
|
48
|
-
|
49
|
-
|
51
|
+
if hutils.node.is_child():
|
52
|
+
if not hutils.node.child.sync_with_parent():
|
53
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
50
54
|
hutils.proxy.get_proxies.invalidate_all()
|
51
55
|
pass
|
52
56
|
|
53
57
|
def after_model_delete(self, model):
|
54
|
-
|
55
|
-
|
58
|
+
if hutils.node.is_child():
|
59
|
+
if not hutils.node.child.sync_with_parent():
|
60
|
+
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
56
61
|
hutils.proxy.get_proxies.invalidate_all()
|
57
62
|
pass
|
58
63
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
from .ProxyDetailsAdmin import ProxyDetailsAdmin
|
2
|
-
# from .CommercialSettings import CommercialSettings
|
3
|
-
from hiddifypanel.panel import hiddify
|
4
1
|
from hiddifypanel.models import *
|
5
2
|
from hiddifypanel.database import db
|
6
3
|
from hiddifypanel import Events, hutils
|
@@ -13,10 +10,14 @@ def init_app(app):
|
|
13
10
|
restapi_v1.init_app(app)
|
14
11
|
from .restapi.v2 import admin as api_v2_admin
|
15
12
|
from .restapi.v2 import user as api_v2_user
|
16
|
-
from .restapi.v2 import
|
13
|
+
from .restapi.v2 import parent as api_v2_parent
|
14
|
+
from .restapi.v2 import child as api_v2_child
|
15
|
+
from .restapi.v2 import panel as api_v2_panel
|
16
|
+
api_v2_parent.init_app(app)
|
17
17
|
api_v2_admin.init_app(app)
|
18
18
|
api_v2_user.init_app(app)
|
19
|
-
|
19
|
+
api_v2_child.init_app(app)
|
20
|
+
api_v2_panel.init_app(app)
|
20
21
|
return
|
21
22
|
|
22
23
|
|
@@ -53,6 +54,7 @@ Events.config_changed.subscribe(config_changed_event)
|
|
53
54
|
def admin_prehook(flaskadmin, admin_bp):
|
54
55
|
# from .ParentDomainAdmin import ParentDomainAdmin
|
55
56
|
# flaskadmin.add_view(ParentDomainAdmin(ParentDomain, db.session))
|
57
|
+
from .ProxyDetailsAdmin import ProxyDetailsAdmin
|
56
58
|
flaskadmin.add_view(ProxyDetailsAdmin(Proxy, db.session))
|
57
59
|
# CommercialSettings.register(admin_bp)
|
58
60
|
|
@@ -7,13 +7,13 @@ from hiddifypanel.models.admin import AdminUser
|
|
7
7
|
from hiddifypanel.models.config_enum import ConfigEnum, Lang
|
8
8
|
from hiddifypanel.models.config import hconfig
|
9
9
|
from hiddifypanel.models.role import Role
|
10
|
-
from .
|
10
|
+
from .schema import AdminSchema
|
11
11
|
|
12
12
|
|
13
13
|
class AdminInfoApi(MethodView):
|
14
14
|
decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
|
15
15
|
|
16
|
-
@app.output(AdminSchema)
|
16
|
+
@app.output(AdminSchema) # type: ignore
|
17
17
|
def get(self):
|
18
18
|
# admin = AdminUser.by_uuid(g.account.uuid) or abort(404, "user not found")
|
19
19
|
admin = g.account or abort(404, "user not found")
|
@@ -2,7 +2,6 @@ from apiflask import Schema, fields, abort
|
|
2
2
|
from flask.views import MethodView
|
3
3
|
from hiddifypanel import hutils
|
4
4
|
from hiddifypanel.models.role import Role
|
5
|
-
from hiddifypanel.panel import hiddify
|
6
5
|
from flask import current_app as app, make_response, g, request
|
7
6
|
import os
|
8
7
|
from ansi2html import Ansi2HTMLConverter
|
@@ -10,13 +9,13 @@ from hiddifypanel.auth import login_required
|
|
10
9
|
from hiddifypanel.models import *
|
11
10
|
|
12
11
|
|
13
|
-
class
|
12
|
+
class AdminInputLogfileSchema(Schema):
|
14
13
|
file = fields.String(description="The log file name", required=True)
|
15
14
|
|
16
15
|
|
17
16
|
class AdminLogApi(MethodView):
|
18
|
-
@app.input(
|
19
|
-
@app.output(fields.String(description="The html of the log", many=True))
|
17
|
+
@app.input(AdminInputLogfileSchema, arg_name="data", location='form') # type: ignore
|
18
|
+
@app.output(fields.String(description="The html of the log", many=True)) # type: ignore
|
20
19
|
@login_required({Role.super_admin})
|
21
20
|
def post(self, data):
|
22
21
|
file_name = data.get('file') or abort(400, "Parameter issue: 'file'")
|
@@ -40,7 +39,7 @@ class AdminLogApi(MethodView):
|
|
40
39
|
return resp
|
41
40
|
|
42
41
|
def options(self):
|
43
|
-
domain = request.args.get("domain")
|
42
|
+
# domain = request.args.get("domain")
|
44
43
|
# Domain.query.filter(Domain.domain == domain).first() or abort(404)
|
45
44
|
if g.proxy_path != hconfig(ConfigEnum.proxy_path_admin):
|
46
45
|
abort(403)
|
@@ -1,53 +1,26 @@
|
|
1
|
-
|
2
|
-
from apiflask.fields import Integer, String, UUID, Boolean, Enum
|
3
1
|
from flask import current_app as app
|
4
2
|
from flask import g
|
5
3
|
from flask.views import MethodView
|
6
|
-
from apiflask import Schema
|
7
4
|
from apiflask import abort
|
8
5
|
from hiddifypanel.auth import login_required
|
9
6
|
from hiddifypanel.models import *
|
10
|
-
from hiddifypanel.panel import hiddify
|
11
|
-
from hiddifypanel.models import AdminMode, Lang
|
12
|
-
|
13
|
-
from . import SuccessfulSchema, has_permission
|
14
|
-
|
15
|
-
|
16
|
-
class AdminSchema(Schema):
|
17
|
-
name = String(required=True, description='The name of the admin')
|
18
|
-
comment = String(required=False, description='A comment related to the admin', allow_none=True)
|
19
|
-
uuid = UUID(required=True, description='The unique identifier for the admin')
|
20
|
-
mode = Enum(AdminMode, required=True, description='The mode for the admin')
|
21
|
-
can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
|
22
|
-
parent_admin_uuid = UUID(description='The unique identifier for the parent admin', allow_none=True,
|
23
|
-
# validate=OneOf([p.uuid for p in AdminUser.query.all()])
|
24
|
-
)
|
25
|
-
telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
|
26
|
-
lang = Enum(Lang, required=True)
|
27
|
-
|
28
7
|
|
29
|
-
|
30
|
-
|
31
|
-
super().__init__(*args, **kwargs)
|
32
|
-
self.fields['name'].required = False
|
33
|
-
self.fields['mode'].required = False
|
34
|
-
self.fields['lang'].required = False
|
35
|
-
self.fields['can_add_admin'].required = False
|
36
|
-
pass
|
8
|
+
from . import has_permission
|
9
|
+
from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
|
37
10
|
|
38
11
|
|
39
12
|
class AdminUserApi(MethodView):
|
40
13
|
decorators = [login_required({Role.super_admin, Role.admin})]
|
41
14
|
|
42
|
-
@app.output(AdminSchema)
|
15
|
+
@app.output(AdminSchema) # type: ignore
|
43
16
|
def get(self, uuid):
|
44
17
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
45
18
|
if not has_permission(admin):
|
46
19
|
abort(403, "You don't have permission to access this admin")
|
47
|
-
return admin.to_dict()
|
20
|
+
return admin.to_dict() # type: ignore
|
48
21
|
|
49
|
-
@app.input(PatchAdminSchema, arg_name='data')
|
50
|
-
@app.output(SuccessfulSchema)
|
22
|
+
@app.input(PatchAdminSchema, arg_name='data') # type: ignore
|
23
|
+
@app.output(SuccessfulSchema) # type: ignore
|
51
24
|
def patch(self, uuid, data):
|
52
25
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
53
26
|
if not has_permission(admin):
|
@@ -60,10 +33,10 @@ class AdminUserApi(MethodView):
|
|
60
33
|
AdminUser.add_or_update(**data)
|
61
34
|
return {'status': 200, 'msg': 'ok'}
|
62
35
|
|
63
|
-
@app.output(SuccessfulSchema)
|
36
|
+
@app.output(SuccessfulSchema) # type: ignore
|
64
37
|
def delete(self, uuid):
|
65
38
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
66
39
|
if not has_permission(admin):
|
67
40
|
abort(403, "You don't have permission to access this admin")
|
68
|
-
admin.remove()
|
41
|
+
admin.remove() # type: ignore
|
69
42
|
return {'status': 200, 'msg': 'ok'}
|
@@ -11,13 +11,13 @@ from hiddifypanel.models import AdminUser
|
|
11
11
|
class AdminUsersApi(MethodView):
|
12
12
|
decorators = [login_required({Role.super_admin, Role.admin})]
|
13
13
|
|
14
|
-
@app.output(AdminSchema(many=True))
|
14
|
+
@app.output(AdminSchema(many=True)) # type: ignore
|
15
15
|
def get(self):
|
16
16
|
admins = AdminUser.query.filter(AdminUser.id.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no admin")
|
17
17
|
return [admin.to_dict() for admin in admins] # type: ignore
|
18
18
|
|
19
|
-
@app.input(AdminSchema, arg_name='data')
|
20
|
-
@app.output(AdminSchema)
|
19
|
+
@app.input(AdminSchema, arg_name='data') # type: ignore
|
20
|
+
@app.output(AdminSchema) # type: ignore
|
21
21
|
def put(self, data):
|
22
22
|
uuid = data.get('uuid') or abort(422, "Parameter issue: 'uuid'")
|
23
23
|
admin = AdminUser.by_uuid(uuid) # type: ignore
|
@@ -31,4 +31,4 @@ class AdminUsersApi(MethodView):
|
|
31
31
|
abort(403, "You don't have permission to access this admin")
|
32
32
|
|
33
33
|
dbadmin = AdminUser.add_or_update(**data) or abort(502, "Unknown issue: Admin is not added")
|
34
|
-
return dbadmin.to_dict()
|
34
|
+
return dbadmin.to_dict() # type: ignore
|
@@ -0,0 +1,157 @@
|
|
1
|
+
import uuid
|
2
|
+
from apiflask.fields import String, Float, Enum, Date, Integer, Boolean
|
3
|
+
from apiflask import Schema, fields
|
4
|
+
from typing import Any, Mapping
|
5
|
+
|
6
|
+
from hiddifypanel.models import UserMode, Lang, AdminMode
|
7
|
+
from hiddifypanel import hutils
|
8
|
+
|
9
|
+
# region user api
|
10
|
+
|
11
|
+
|
12
|
+
class FriendlyDateTime(fields.Field):
|
13
|
+
def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
|
14
|
+
return hutils.convert.time_to_json(value)
|
15
|
+
|
16
|
+
def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
|
17
|
+
return hutils.convert.json_to_time(value)
|
18
|
+
|
19
|
+
|
20
|
+
class FriendlyUUID(fields.Field):
|
21
|
+
def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
|
22
|
+
if value is None:
|
23
|
+
return None
|
24
|
+
return str(value)
|
25
|
+
|
26
|
+
def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
|
27
|
+
if value is None:
|
28
|
+
return None
|
29
|
+
try:
|
30
|
+
return uuid.UUID(value)
|
31
|
+
except ValueError:
|
32
|
+
self.fail('Invalid uuid')
|
33
|
+
|
34
|
+
|
35
|
+
class UserSchema(Schema):
|
36
|
+
uuid = FriendlyUUID(required=True, description="Unique identifier for the user")
|
37
|
+
name = String(required=True, description="Name of the user")
|
38
|
+
|
39
|
+
usage_limit_GB = Float(
|
40
|
+
required=False,
|
41
|
+
allow_none=True,
|
42
|
+
description="The data usage limit for the user in gigabytes"
|
43
|
+
)
|
44
|
+
package_days = Integer(
|
45
|
+
required=False,
|
46
|
+
allow_none=True,
|
47
|
+
description="The number of days in the user's package"
|
48
|
+
)
|
49
|
+
mode = Enum(UserMode,
|
50
|
+
required=False,
|
51
|
+
allow_none=True,
|
52
|
+
description="The mode of the user's account, which dictates access level or type"
|
53
|
+
)
|
54
|
+
last_online = FriendlyDateTime(
|
55
|
+
format="%Y-%m-%d %H:%M:%S",
|
56
|
+
allow_none=True,
|
57
|
+
description="The last time the user was online, converted to a JSON-friendly format"
|
58
|
+
)
|
59
|
+
start_date = Date(
|
60
|
+
format='%Y-%m-%d',
|
61
|
+
allow_none=True,
|
62
|
+
description="The start date of the user's package, in a JSON-friendly format"
|
63
|
+
)
|
64
|
+
current_usage_GB = Float(
|
65
|
+
required=False,
|
66
|
+
allow_none=True,
|
67
|
+
description="The current data usage of the user in gigabytes"
|
68
|
+
)
|
69
|
+
last_reset_time = Date(
|
70
|
+
format='%Y-%m-%d',
|
71
|
+
description="The last time the user's data usage was reset, in a JSON-friendly format",
|
72
|
+
allow_none=True
|
73
|
+
)
|
74
|
+
comment = String(
|
75
|
+
missing=None,
|
76
|
+
allow_none=True,
|
77
|
+
description="An optional comment about the user"
|
78
|
+
)
|
79
|
+
added_by_uuid = FriendlyUUID(
|
80
|
+
required=False,
|
81
|
+
description="UUID of the admin who added this user",
|
82
|
+
allow_none=True,
|
83
|
+
# validate=OneOf([p.uuid for p in AdminUser.query.all()])
|
84
|
+
)
|
85
|
+
telegram_id = Integer(
|
86
|
+
required=False,
|
87
|
+
description="The Telegram ID associated with the user",
|
88
|
+
allow_none=True
|
89
|
+
)
|
90
|
+
ed25519_private_key = String(
|
91
|
+
required=False,
|
92
|
+
allow_none=True,
|
93
|
+
description="If empty, it will be created automatically, The user's private key using the Ed25519 algorithm"
|
94
|
+
)
|
95
|
+
ed25519_public_key = String(
|
96
|
+
required=False,
|
97
|
+
allow_none=True,
|
98
|
+
description="If empty, it will be created automatically,The user's public key using the Ed25519 algorithm"
|
99
|
+
)
|
100
|
+
wg_pk = String(
|
101
|
+
required=False,
|
102
|
+
allow_none=True,
|
103
|
+
description="If empty, it will be created automatically, The user's WireGuard private key"
|
104
|
+
)
|
105
|
+
|
106
|
+
wg_pub = String(
|
107
|
+
required=False,
|
108
|
+
allow_none=True,
|
109
|
+
description="If empty, it will be created automatically, The user's WireGuard public key"
|
110
|
+
)
|
111
|
+
wg_psk = String(
|
112
|
+
required=False,
|
113
|
+
allow_none=True,
|
114
|
+
description="If empty, it will be created automatically, The user's WireGuard preshared key"
|
115
|
+
)
|
116
|
+
|
117
|
+
lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
|
118
|
+
|
119
|
+
|
120
|
+
class PatchUserSchema(UserSchema):
|
121
|
+
def __init__(self, *args, **kwargs):
|
122
|
+
super().__init__(*args, **kwargs)
|
123
|
+
self.fields['name'].required = False
|
124
|
+
pass
|
125
|
+
|
126
|
+
# endregion
|
127
|
+
|
128
|
+
# region admin api
|
129
|
+
|
130
|
+
|
131
|
+
class AdminSchema(Schema):
|
132
|
+
name = String(required=True, description='The name of the admin')
|
133
|
+
comment = String(required=False, description='A comment related to the admin', allow_none=True)
|
134
|
+
uuid = FriendlyUUID(required=True, description='The unique identifier for the admin')
|
135
|
+
mode = Enum(AdminMode, required=True, description='The mode for the admin')
|
136
|
+
can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
|
137
|
+
parent_admin_uuid = FriendlyUUID(description='The unique identifier for the parent admin', allow_none=True,
|
138
|
+
# validate=OneOf([p.uuid for p in AdminUser.query.all()])
|
139
|
+
)
|
140
|
+
telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
|
141
|
+
lang = Enum(Lang, required=True)
|
142
|
+
|
143
|
+
|
144
|
+
class PatchAdminSchema(AdminSchema):
|
145
|
+
def __init__(self, *args, **kwargs):
|
146
|
+
super().__init__(*args, **kwargs)
|
147
|
+
self.fields['name'].required = False
|
148
|
+
self.fields['mode'].required = False
|
149
|
+
self.fields['lang'].required = False
|
150
|
+
self.fields['can_add_admin'].required = False
|
151
|
+
pass
|
152
|
+
# endregion
|
153
|
+
|
154
|
+
|
155
|
+
class SuccessfulSchema(Schema):
|
156
|
+
status = Integer()
|
157
|
+
msg = String()
|
@@ -10,7 +10,7 @@ from hiddifypanel.panel import hiddify
|
|
10
10
|
from hiddifypanel import hutils
|
11
11
|
|
12
12
|
|
13
|
-
class
|
13
|
+
class ServerStatusOutputSchema(Schema):
|
14
14
|
stats = Dict(required=True, description="System stats")
|
15
15
|
usage_history = Dict(required=True, description="System usage history")
|
16
16
|
|
@@ -18,9 +18,9 @@ class ServerStatus(Schema):
|
|
18
18
|
class AdminServerStatusApi(MethodView):
|
19
19
|
decorators = [login_required({Role.super_admin, Role.admin, Role.agent})]
|
20
20
|
|
21
|
-
@app.output(
|
21
|
+
@app.output(ServerStatusOutputSchema) # type: ignore
|
22
22
|
def get(self):
|
23
|
-
dto =
|
23
|
+
dto = ServerStatusOutputSchema()
|
24
24
|
dto.stats = { # type: ignore
|
25
25
|
'system': hutils.system.system_stats(),
|
26
26
|
'top5': hutils.system.top_processes()
|