hiddifypanel 9.0.0.dev92__py3-none-any.whl → 10.5.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 +30 -9
- hiddifypanel/base.py +58 -50
- hiddifypanel/cache.py +43 -25
- hiddifypanel/database.py +9 -0
- hiddifypanel/drivers/abstract_driver.py +2 -0
- hiddifypanel/drivers/singbox_api.py +17 -15
- hiddifypanel/drivers/ssh_liberty_bridge_api.py +3 -1
- hiddifypanel/drivers/user_driver.py +12 -6
- hiddifypanel/drivers/wireguard_api.py +7 -2
- hiddifypanel/drivers/xray_api.py +14 -9
- hiddifypanel/hutils/__init__.py +4 -0
- hiddifypanel/hutils/convert.py +13 -2
- hiddifypanel/hutils/crypto.py +48 -0
- hiddifypanel/hutils/encode.py +4 -1
- hiddifypanel/hutils/flask.py +38 -5
- hiddifypanel/hutils/github_issue.py +1 -1
- hiddifypanel/hutils/importer/xui.py +5 -2
- hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
- hiddifypanel/hutils/network/auto_ip_selector.py +2 -0
- hiddifypanel/hutils/network/net.py +46 -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/__init__.py +5 -0
- hiddifypanel/hutils/proxy/clash.py +161 -0
- hiddifypanel/hutils/proxy/shared.py +434 -0
- hiddifypanel/hutils/proxy/singbox.py +339 -0
- hiddifypanel/hutils/proxy/xray.py +235 -0
- hiddifypanel/hutils/proxy/xrayjson.py +391 -0
- hiddifypanel/hutils/random.py +4 -0
- hiddifypanel/hutils/utils.py +4 -1
- hiddifypanel/models/__init__.py +2 -2
- hiddifypanel/models/admin.py +31 -17
- hiddifypanel/models/base_account.py +7 -7
- hiddifypanel/models/child.py +30 -16
- hiddifypanel/models/config.py +45 -16
- hiddifypanel/models/config_enum.py +68 -17
- hiddifypanel/models/domain.py +28 -20
- hiddifypanel/models/parent_domain.py +2 -2
- hiddifypanel/models/proxy.py +29 -20
- hiddifypanel/models/report.py +2 -3
- hiddifypanel/models/usage.py +2 -2
- hiddifypanel/models/user.py +33 -22
- hiddifypanel/panel/admin/Actions.py +13 -19
- hiddifypanel/panel/admin/AdminstratorAdmin.py +14 -3
- hiddifypanel/panel/admin/Dashboard.py +5 -10
- hiddifypanel/panel/admin/DomainAdmin.py +35 -48
- hiddifypanel/panel/admin/NodeAdmin.py +6 -2
- hiddifypanel/panel/admin/ProxyAdmin.py +6 -5
- hiddifypanel/panel/admin/QuickSetup.py +21 -20
- hiddifypanel/panel/admin/SettingAdmin.py +107 -62
- hiddifypanel/panel/admin/UserAdmin.py +22 -21
- hiddifypanel/panel/admin/templates/index.html +1 -1
- hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
- hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
- hiddifypanel/panel/admin/templates/result.html +2 -3
- hiddifypanel/panel/cf_api.py +1 -2
- hiddifypanel/panel/cli.py +16 -16
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +16 -12
- hiddifypanel/panel/commercial/__init__.py +7 -5
- hiddifypanel/panel/commercial/restapi/v1/__init__.py +1 -1
- hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -1
- hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
- 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 -25
- 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 -66
- 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/restapi/v2/user/apps_api.py +17 -23
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +23 -26
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
- hiddifypanel/panel/common.py +25 -8
- hiddifypanel/panel/common_bp/login.py +2 -2
- hiddifypanel/panel/hiddify.py +22 -185
- hiddifypanel/panel/init_db.py +102 -55
- hiddifypanel/panel/usage.py +33 -18
- hiddifypanel/panel/user/__init__.py +0 -1
- hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
- hiddifypanel/panel/user/templates/all_configs.txt +2 -2
- hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
- hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
- hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
- hiddifypanel/panel/user/templates/clash_config.yml +4 -4
- hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
- hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
- hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
- hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
- hiddifypanel/panel/user/templates/home/usage.html +1 -1
- hiddifypanel/panel/user/templates/new.html +2 -2
- hiddifypanel/panel/user/user.py +56 -50
- hiddifypanel/static/css/custom.css +31 -0
- hiddifypanel/static/images/favicon.ico +0 -0
- hiddifypanel/static/images/hiddify-old.png +0 -0
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/images/hiddify2.png +0 -0
- hiddifypanel/static/new/assets/{index-1b891a7c.js → index-ccb9873c.js} +56 -56
- hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
- hiddifypanel/static/new/i18n/en.json +6 -6
- hiddifypanel/static/new/i18n/fa.json +2 -2
- hiddifypanel/templates/admin-layout.html +30 -43
- hiddifypanel/templates/fake.html +0 -4
- hiddifypanel/templates/flaskadmin-layout.html +7 -3
- hiddifypanel/templates/master.html +11 -6
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +2082 -1977
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +2035 -1924
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +1911 -1848
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2019 -1874
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1873 -1742
- hiddifypanel/translations.i18n/en.json +992 -933
- hiddifypanel/translations.i18n/fa.json +994 -935
- hiddifypanel/translations.i18n/pt.json +1031 -972
- hiddifypanel/translations.i18n/ru.json +994 -935
- hiddifypanel/translations.i18n/zh.json +971 -912
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/WHEEL +1 -1
- 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/panel/user/link_maker.py +0 -1083
- hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/top_level.txt +0 -0
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,24 +1,26 @@
|
|
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
|
6
|
-
from
|
6
|
+
from flask import g, redirect
|
7
|
+
from markupsafe import Markup
|
8
|
+
|
7
9
|
from hiddifypanel.hutils.flask import hurl_for, flash
|
8
10
|
from hiddifypanel.auth import login_required
|
9
11
|
from flask_admin.model.template import EndpointLinkRowAction
|
10
12
|
from flask_admin.actions import action
|
11
13
|
from flask_admin.contrib.sqla import form, filters as sqla_filters, tools
|
12
14
|
# Define a custom field type for the related domains
|
13
|
-
|
14
15
|
from flask import current_app
|
16
|
+
from hiddifypanel import hutils
|
15
17
|
|
16
18
|
|
17
19
|
class ProxyDetailsAdmin(AdminLTEModelView):
|
18
20
|
|
19
21
|
column_hide_backrefs = True
|
20
22
|
can_create = False
|
21
|
-
form_excluded_columns = ['child']
|
23
|
+
form_excluded_columns = ['child', 'proto', 'transport', 'cdn']
|
22
24
|
column_exclude_list = ['child']
|
23
25
|
column_searchable_list = ['name', 'proto', 'transport', 'l3', 'cdn']
|
24
26
|
column_editable_list = ['name']
|
@@ -30,7 +32,7 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
30
32
|
|
31
33
|
self.session.commit()
|
32
34
|
flash(_('%(count)s records were successfully disabled.', count=count), 'success')
|
33
|
-
|
35
|
+
hutils.proxy.get_proxies.invalidate_all()
|
34
36
|
|
35
37
|
@action('enable', 'Enable', 'Are you sure you want to enable selected proxies?')
|
36
38
|
def action_enable(self, ids):
|
@@ -39,22 +41,24 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
39
41
|
|
40
42
|
self.session.commit()
|
41
43
|
flash(_('%(count)s records were successfully enabled.', count=count), 'success')
|
42
|
-
|
44
|
+
hutils.proxy.get_proxies.invalidate_all()
|
43
45
|
|
44
46
|
# list_template = 'model/domain_list.html'
|
45
47
|
|
46
48
|
# form_overrides = {'work_with': Select2Field}
|
47
49
|
|
48
50
|
def after_model_change(self, form, model, is_created):
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
54
|
+
hutils.proxy.get_proxies.invalidate_all()
|
52
55
|
pass
|
53
56
|
|
54
57
|
def after_model_delete(self, model):
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
61
|
+
hutils.proxy.get_proxies.invalidate_all()
|
58
62
|
pass
|
59
63
|
|
60
64
|
def is_accessible(self):
|
@@ -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
|
|
@@ -10,7 +10,7 @@ from . import tgbot
|
|
10
10
|
from .tgmsg import SendMsgResource
|
11
11
|
from .resources import *
|
12
12
|
bp = APIBlueprint("api_v1", __name__, url_prefix="/<proxy_path>/api/v1/", tag="api_v1", enable_openapi=False)
|
13
|
-
bp_uuid = APIBlueprint("api_v1_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v1", template_folder="templates", enable_openapi=False)
|
13
|
+
bp_uuid = APIBlueprint("api_v1_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v1/", template_folder="templates", enable_openapi=False)
|
14
14
|
api = Api(bp)
|
15
15
|
api_uuid = Api(bp_uuid)
|
16
16
|
|
@@ -34,7 +34,7 @@ def register_bot(set_hook=False):
|
|
34
34
|
|
35
35
|
user_secret = AdminUser.get_super_admin_uuid()
|
36
36
|
if set_hook:
|
37
|
-
bot.set_webhook(url=f"https://{domain}/{admin_proxy_path}/{user_secret}/api/v1/tgbot/"
|
37
|
+
bot.set_webhook(url=f"https://{domain}/{admin_proxy_path}/{user_secret}/api/v1/tgbot/")
|
38
38
|
except Exception as e:
|
39
39
|
print(e)
|
40
40
|
import traceback
|
@@ -39,30 +39,34 @@ class SendMsgResource(Resource):
|
|
39
39
|
else:
|
40
40
|
return {'msg': 'error', 'res': res}
|
41
41
|
|
42
|
-
def get_users_by_identifier(self, identifier: str) -> List[User]:
|
42
|
+
def get_users_by_identifier(self, identifier: str | list) -> List[User]:
|
43
43
|
'''Returns all users that match the identifier for sending a message to them'''
|
44
44
|
# when we are here we must have g.account but ...
|
45
45
|
if not hasattr(g, 'account'):
|
46
46
|
return []
|
47
|
+
|
47
48
|
query = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids()))
|
48
49
|
query = query.filter(User.telegram_id is not None, User.telegram_id != 0)
|
49
50
|
|
50
|
-
|
51
|
+
# user selected many ids as users identifier
|
52
|
+
if isinstance(identifier, list):
|
53
|
+
return query.filter(User.id.in_(identifier)).all()
|
54
|
+
|
55
|
+
if hutils.convert.is_int(identifier): # type: ignore
|
51
56
|
return [query.filter(User.id == int(identifier)).first() or abort(404, 'The user not found')] # type: ignore
|
52
|
-
|
57
|
+
if identifier == 'all':
|
53
58
|
return query.all()
|
54
|
-
|
59
|
+
if identifier == 'expired':
|
55
60
|
return [u for u in query.all() if not u.is_active]
|
56
|
-
|
61
|
+
if identifier == 'active':
|
57
62
|
return [u for u in query.all() if u.is_active]
|
58
|
-
|
63
|
+
if identifier == 'offline 1h':
|
59
64
|
h1 = datetime.datetime.now() - datetime.timedelta(hours=1)
|
60
65
|
return [u for u in query.all() if u.is_active and u.last_online < h1]
|
61
|
-
|
66
|
+
if identifier == 'offline 1d':
|
62
67
|
d1 = datetime.datetime.now() - datetime.timedelta(hours=24)
|
63
68
|
return [u for u in query.all() if u.is_active and u.last_online < d1]
|
64
|
-
|
69
|
+
if identifier == 'offline 1w':
|
65
70
|
d7 = datetime.datetime.now() - datetime.timedelta(days=7)
|
66
71
|
return [u for u in query.all() if u.is_active and u.last_online < d7]
|
67
|
-
|
68
|
-
return []
|
72
|
+
return []
|
@@ -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,43 +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
7
|
|
16
|
-
|
17
|
-
|
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)
|
8
|
+
from . import has_permission
|
9
|
+
from .schema import AdminSchema, PatchAdminSchema, SuccessfulSchema
|
27
10
|
|
28
11
|
|
29
12
|
class AdminUserApi(MethodView):
|
30
13
|
decorators = [login_required({Role.super_admin, Role.admin})]
|
31
14
|
|
32
|
-
@app.output(AdminSchema)
|
15
|
+
@app.output(AdminSchema) # type: ignore
|
33
16
|
def get(self, uuid):
|
34
17
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
35
18
|
if not has_permission(admin):
|
36
19
|
abort(403, "You don't have permission to access this admin")
|
37
|
-
return admin.to_dict()
|
20
|
+
return admin.to_dict() # type: ignore
|
38
21
|
|
39
|
-
@app.input(
|
40
|
-
@app.output(SuccessfulSchema)
|
22
|
+
@app.input(PatchAdminSchema, arg_name='data') # type: ignore
|
23
|
+
@app.output(SuccessfulSchema) # type: ignore
|
41
24
|
def patch(self, uuid, data):
|
42
25
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
43
26
|
if not has_permission(admin):
|
@@ -50,10 +33,10 @@ class AdminUserApi(MethodView):
|
|
50
33
|
AdminUser.add_or_update(**data)
|
51
34
|
return {'status': 200, 'msg': 'ok'}
|
52
35
|
|
53
|
-
@app.output(SuccessfulSchema)
|
36
|
+
@app.output(SuccessfulSchema) # type: ignore
|
54
37
|
def delete(self, uuid):
|
55
38
|
admin = AdminUser.by_uuid(uuid) or abort(404, "admin not found")
|
56
39
|
if not has_permission(admin):
|
57
40
|
abort(403, "You don't have permission to access this admin")
|
58
|
-
admin.remove()
|
41
|
+
admin.remove() # type: ignore
|
59
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()
|