hiddifypanel 10.20.3__py3-none-any.whl → 10.30.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/base.py +17 -8
- hiddifypanel/cache.py +2 -51
- hiddifypanel/drivers/wireguard_api.py +24 -5
- hiddifypanel/hutils/convert.py +1 -1
- hiddifypanel/hutils/flask.py +28 -2
- hiddifypanel/hutils/importer/xui.py +6 -7
- hiddifypanel/hutils/network/__init__.py +1 -0
- hiddifypanel/hutils/network/cf_api.py +84 -0
- hiddifypanel/hutils/network/net.py +26 -49
- hiddifypanel/hutils/node/child.py +25 -7
- hiddifypanel/hutils/node/parent.py +7 -7
- hiddifypanel/hutils/node/shared.py +19 -6
- hiddifypanel/hutils/proxy/clash.py +1 -1
- hiddifypanel/hutils/proxy/shared.py +1 -1
- hiddifypanel/hutils/proxy/singbox.py +2 -3
- hiddifypanel/hutils/proxy/xray.py +12 -10
- hiddifypanel/hutils/proxy/xrayjson.py +26 -49
- hiddifypanel/hutils/utils.py +47 -3
- hiddifypanel/models/__init__.py +1 -1
- hiddifypanel/models/admin.py +9 -2
- hiddifypanel/models/base_account.py +3 -1
- hiddifypanel/models/config.py +5 -7
- hiddifypanel/models/config_enum.py +18 -6
- hiddifypanel/models/domain.py +82 -118
- hiddifypanel/models/user.py +44 -24
- hiddifypanel/panel/admin/Actions.py +6 -11
- hiddifypanel/panel/admin/AdminstratorAdmin.py +3 -9
- hiddifypanel/panel/admin/Backup.py +5 -8
- hiddifypanel/panel/admin/Dashboard.py +3 -4
- hiddifypanel/panel/admin/DomainAdmin.py +20 -15
- hiddifypanel/panel/admin/ProxyAdmin.py +3 -10
- hiddifypanel/panel/admin/QuickSetup.py +1 -1
- hiddifypanel/panel/admin/SettingAdmin.py +7 -5
- hiddifypanel/panel/admin/Terminal.py +0 -1
- hiddifypanel/panel/admin/UserAdmin.py +4 -3
- hiddifypanel/panel/cli.py +36 -23
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +2 -4
- hiddifypanel/panel/commercial/restapi/v1/tgbot.py +7 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +17 -13
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +4 -3
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +28 -10
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +2 -19
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +27 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +28 -9
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -21
- hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +8 -4
- hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +19 -3
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +48 -42
- hiddifypanel/panel/commercial/telegrambot/Usage.py +1 -1
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -1
- hiddifypanel/panel/commercial/telegrambot/information.py +1 -1
- hiddifypanel/panel/common.py +5 -11
- hiddifypanel/panel/hiddify.py +9 -20
- hiddifypanel/panel/init_db.py +31 -13
- hiddifypanel/panel/usage.py +38 -9
- hiddifypanel/panel/user/user.py +52 -32
- hiddifypanel/templates/admin-layout.html +2 -2
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +80 -25
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +74 -20
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +60 -6
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +158 -78
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +60 -6
- hiddifypanel/translations.i18n/en.json +62 -22
- hiddifypanel/translations.i18n/fa.json +57 -17
- hiddifypanel/translations.i18n/pt.json +43 -3
- hiddifypanel/translations.i18n/ru.json +112 -72
- hiddifypanel/translations.i18n/zh.json +43 -3
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/METADATA +2 -1
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/RECORD +81 -81
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/WHEEL +1 -1
- hiddifypanel/panel/cf_api.py +0 -37
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.20.3.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from flask_bootstrap import SwitchField
|
|
16
16
|
# from gettext import gettext as _
|
17
17
|
from flask_classful import FlaskView
|
18
18
|
from flask_wtf import FlaskForm
|
19
|
+
from bleach import clean as bleach_clean
|
19
20
|
|
20
21
|
|
21
22
|
from hiddifypanel.models import BoolConfig, StrConfig, ConfigEnum, hconfig, ConfigCategory
|
@@ -66,7 +67,7 @@ class SettingAdmin(FlaskView):
|
|
66
67
|
return render_template('config.html', form=form)
|
67
68
|
if k == ConfigEnum.parent_panel and v != '':
|
68
69
|
# v=(v+"/").replace("/admin",'')
|
69
|
-
v = re.sub("(/admin/.*)", "/", v)
|
70
|
+
v = re.sub("(/admin/.*)", "/", v) + ("/" if not v.endswith("/") else "")
|
70
71
|
|
71
72
|
if old_configs[k] != v:
|
72
73
|
changed_configs[k] = v
|
@@ -91,6 +92,10 @@ class SettingAdmin(FlaskView):
|
|
91
92
|
parent_apikey = uuid
|
92
93
|
|
93
94
|
for k, v in changed_configs.items():
|
95
|
+
# html inputs santitizing
|
96
|
+
san_items = {ConfigEnum.branding_title, ConfigEnum.branding_site, ConfigEnum.branding_freetext}
|
97
|
+
if k in san_items:
|
98
|
+
v = bleach_clean(v)
|
94
99
|
set_hconfig(k, v, commit=False)
|
95
100
|
|
96
101
|
db.session.commit()
|
@@ -112,10 +117,7 @@ class SettingAdmin(FlaskView):
|
|
112
117
|
# sync with parent if needed
|
113
118
|
if hutils.node.is_child():
|
114
119
|
if hutils.node.child.is_registered():
|
115
|
-
|
116
|
-
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
117
|
-
else: # TODO: it's just for debuging
|
118
|
-
hutils.flask.flash(_('child.sync-success')) # type: ignore
|
120
|
+
hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.hconfigs])
|
119
121
|
else:
|
120
122
|
name = hconfig(ConfigEnum.unique_id)
|
121
123
|
parent_info = hutils.node.get_panel_info(hconfig(ConfigEnum.parent_domain), hconfig(ConfigEnum.parent_admin_proxy_path), parent_apikey)
|
@@ -151,7 +151,7 @@ class UserAdmin(AdminLTEModelView):
|
|
151
151
|
<i class='fa-solid fa-arrow-up-right-from-square'></i>
|
152
152
|
{_("Current Domain")} </a>"""
|
153
153
|
|
154
|
-
domains = [d for d in
|
154
|
+
domains = [d for d in Domain.get_domains() if d.domain != request.host]
|
155
155
|
return Markup(link + " ".join([hiddify.get_html_user_link(model, d) for d in domains]))
|
156
156
|
|
157
157
|
# def _usage_formatter(view, context, model, name):
|
@@ -300,14 +300,15 @@ class UserAdmin(AdminLTEModelView):
|
|
300
300
|
hiddify.quick_apply_users()
|
301
301
|
|
302
302
|
if hutils.node.is_parent():
|
303
|
-
hutils.node.parent.request_childs_to_sync
|
303
|
+
hutils.node.run_node_op_in_bg(hutils.node.parent.request_childs_to_sync)
|
304
|
+
|
304
305
|
|
305
306
|
def after_model_delete(self, model):
|
306
307
|
user_driver.remove_client(model)
|
307
308
|
hiddify.quick_apply_users()
|
308
309
|
|
309
310
|
if hutils.node.is_parent():
|
310
|
-
hutils.node.parent.request_childs_to_sync
|
311
|
+
hutils.node.run_node_op_in_bg(hutils.node.parent.request_childs_to_sync)
|
311
312
|
|
312
313
|
def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
|
313
314
|
res = None
|
hiddifypanel/panel/cli.py
CHANGED
@@ -53,7 +53,6 @@ def all_configs():
|
|
53
53
|
configs = {
|
54
54
|
"users": valid_users,
|
55
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
|
-
# "parent_domains": [hiddify.parent_domain_dict(u) for u in ParentDomain.query.all()],
|
57
56
|
# "hconfigs": get_hconfigs(json=True),
|
58
57
|
"chconfigs": get_hconfigs_childs(host_child_ids, json=True)
|
59
58
|
}
|
@@ -70,7 +69,7 @@ def all_configs():
|
|
70
69
|
configs['panel_links'] = []
|
71
70
|
configs['panel_links'].append(hiddify.get_account_panel_link(owner, server_ip, is_https=False))
|
72
71
|
configs['panel_links'].append(hiddify.get_account_panel_link(owner, server_ip))
|
73
|
-
domains =
|
72
|
+
domains = Domain.get_domains()
|
74
73
|
|
75
74
|
for d in domains:
|
76
75
|
configs['panel_links'].append(hiddify.get_account_panel_link(owner, d.domain))
|
@@ -82,18 +81,13 @@ def update_usage():
|
|
82
81
|
print(usage.update_local_usage())
|
83
82
|
|
84
83
|
|
85
|
-
def test():
|
86
|
-
print(ConfigEnum("auto_update1"))
|
87
|
-
|
88
|
-
|
89
84
|
def admin_links():
|
90
|
-
|
91
85
|
server_ip = hutils.network.get_ip_str(4)
|
92
86
|
owner = AdminUser.get_super_admin()
|
93
87
|
|
94
88
|
admin_links = f"Not Secure (do not use it - only if others not work):\n {hiddify.get_account_panel_link(owner, server_ip,is_https=True)}\n"
|
95
89
|
|
96
|
-
domains =
|
90
|
+
domains = Domain.get_domains()
|
97
91
|
admin_links += f"Secure:\n"
|
98
92
|
if not any([d for d in domains if 'sslip.io' not in d.domain]):
|
99
93
|
admin_links += f" (not signed) {hiddify.get_account_panel_link(owner, server_ip)}\n"
|
@@ -108,19 +102,15 @@ def admin_links():
|
|
108
102
|
def admin_path():
|
109
103
|
admin = AdminUser.get_super_admin()
|
110
104
|
# WTF is the owner and server_id?
|
111
|
-
domain =
|
105
|
+
domain = Domain.get_domains()[0]
|
112
106
|
print(hiddify.get_account_panel_link(admin, domain, prefere_path_only=True))
|
113
107
|
|
114
108
|
|
115
|
-
# def get_this_host_domains():
|
116
|
-
# current_child_ids
|
117
|
-
|
118
|
-
|
119
109
|
def hysteria_domain_port():
|
120
110
|
if not hconfig(ConfigEnum.hysteria_enable):
|
121
111
|
return
|
122
112
|
out = []
|
123
|
-
for
|
113
|
+
for domain in Domain.query.filter(Domain.mode.in_([DomainType.direct, DomainType.relay, DomainType.fake])).all():
|
124
114
|
out.append(f"{domain.domain}:{int(hconfig(ConfigEnum.hysteria_port))+domain.id}")
|
125
115
|
print(";".join(out))
|
126
116
|
|
@@ -129,26 +119,25 @@ def tuic_domain_port():
|
|
129
119
|
if not hconfig(ConfigEnum.tuic_enable):
|
130
120
|
return
|
131
121
|
out = []
|
132
|
-
for
|
122
|
+
for domain in Domain.query.filter(Domain.mode.in_([DomainType.direct, DomainType.relay, DomainType.fake])).all():
|
133
123
|
out.append(f"{domain}:{int(hconfig(ConfigEnum.tuic_port))+domain.id}")
|
134
124
|
print(";".join(out))
|
135
125
|
|
136
126
|
|
137
127
|
def init_app(app):
|
138
|
-
|
139
|
-
# print(app.config['SQLALCHEMY_DATABASE_URI'] )
|
140
|
-
for command in [hysteria_domain_port, tuic_domain_port, init_db, drop_db, all_configs, update_usage, test, admin_links, admin_path, backup, downgrade]:
|
128
|
+
for command in [hysteria_domain_port, tuic_domain_port, init_db, drop_db, all_configs, update_usage, admin_links, admin_path, backup, downgrade]:
|
141
129
|
app.cli.add_command(app.cli.command()(command))
|
142
130
|
|
143
131
|
@ app.cli.command()
|
144
132
|
@ click.option("--domain", "-d")
|
145
|
-
|
146
|
-
|
133
|
+
@ click.option("--mode", "-m")
|
134
|
+
def add_domain(domain, mode):
|
147
135
|
if Domain.query.filter(Domain.domain == domain).first():
|
148
136
|
return "Domain already exist."
|
149
|
-
d = Domain(
|
150
|
-
|
151
|
-
|
137
|
+
d = Domain()
|
138
|
+
d.domain = domain
|
139
|
+
d.mode = mode
|
140
|
+
d.sub_link_only = True if mode == DomainType.sub_link_only else False
|
152
141
|
db.session.add(d)
|
153
142
|
db.session.commit()
|
154
143
|
return "success"
|
@@ -236,3 +225,27 @@ def init_app(app):
|
|
236
225
|
print('success')
|
237
226
|
except Exception as e:
|
238
227
|
print(f'failed to import xui data: Error: {e}')
|
228
|
+
|
229
|
+
@ app.cli.command()
|
230
|
+
def tgbot_info():
|
231
|
+
if not hconfig(ConfigEnum.telegram_bot_token):
|
232
|
+
print('You didn\'t specified your telegram bot token')
|
233
|
+
return
|
234
|
+
|
235
|
+
from hiddifypanel.panel.commercial.telegrambot import bot, register_bot
|
236
|
+
if not bot.token:
|
237
|
+
register_bot(True)
|
238
|
+
info = bot.get_me().to_dict()
|
239
|
+
hook_data = bot.get_webhook_info()
|
240
|
+
hook_info = {
|
241
|
+
'url': hook_data.url,
|
242
|
+
'ip': hook_data.ip_address,
|
243
|
+
'last_error_msg': hook_data.last_error_message if hook_data.last_error_message else '',
|
244
|
+
'last_error_time': datetime.datetime.fromtimestamp(int(hook_data.last_error_date)).strftime('%Y-%m-%d %H:%M:%S') if hook_data.last_error_date else ''
|
245
|
+
}
|
246
|
+
|
247
|
+
output = {
|
248
|
+
'general': info,
|
249
|
+
'webhook': hook_info
|
250
|
+
}
|
251
|
+
print(json.dumps(output, indent=4))
|
@@ -44,15 +44,13 @@ class ProxyDetailsAdmin(AdminLTEModelView):
|
|
44
44
|
|
45
45
|
def after_model_change(self, form, model, is_created):
|
46
46
|
if hutils.node.is_child():
|
47
|
-
|
48
|
-
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
47
|
+
hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.proxies])
|
49
48
|
hutils.proxy.get_proxies.invalidate_all()
|
50
49
|
pass
|
51
50
|
|
52
51
|
def after_model_delete(self, model):
|
53
52
|
if hutils.node.is_child():
|
54
|
-
|
55
|
-
hutils.flask.flash(_('child.sync-failed'), 'danger') # type: ignore
|
53
|
+
hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.proxies])
|
56
54
|
hutils.proxy.get_proxies.invalidate_all()
|
57
55
|
pass
|
58
56
|
|
@@ -17,7 +17,7 @@ bot = telebot.TeleBot("", parse_mode="HTML", threaded=False, exception_handler=E
|
|
17
17
|
bot.username = ''
|
18
18
|
|
19
19
|
|
20
|
-
def register_bot(set_hook=False):
|
20
|
+
def register_bot(set_hook=False, remove_hook=False):
|
21
21
|
try:
|
22
22
|
global bot
|
23
23
|
token = hconfig(ConfigEnum.telegram_bot_token)
|
@@ -27,9 +27,12 @@ def register_bot(set_hook=False):
|
|
27
27
|
bot.username = bot.get_me().username
|
28
28
|
except BaseException:
|
29
29
|
pass
|
30
|
-
|
31
|
-
|
32
|
-
domain =
|
30
|
+
if remove_hook:
|
31
|
+
bot.remove_webhook()
|
32
|
+
domain = Domain.get_panel_link()
|
33
|
+
if not domain:
|
34
|
+
raise Exception('Cannot get valid domain for setting telegram bot webhook')
|
35
|
+
|
33
36
|
admin_proxy_path = hconfig(ConfigEnum.proxy_path_admin)
|
34
37
|
|
35
38
|
user_secret = AdminUser.get_super_admin_uuid()
|
@@ -1,7 +1,6 @@
|
|
1
|
-
from apiflask import APIBlueprint
|
2
|
-
from apiflask.fields import Integer, String
|
1
|
+
from apiflask import APIBlueprint
|
3
2
|
from flask import g
|
4
|
-
from hiddifypanel.models import AdminUser
|
3
|
+
from hiddifypanel.models import AdminUser, User
|
5
4
|
|
6
5
|
bp = APIBlueprint("api_admin", __name__, url_prefix="/<proxy_path>/api/v2/admin/", enable_openapi=True)
|
7
6
|
|
@@ -14,21 +13,26 @@ def init_app(app):
|
|
14
13
|
from .admin_user_api import AdminUserApi
|
15
14
|
from .admin_users_api import AdminUsersApi
|
16
15
|
from .admin_log_api import AdminLogApi
|
17
|
-
bp.add_url_rule('/me/', view_func=AdminInfoApi)
|
18
|
-
bp.add_url_rule('/server_status/', view_func=AdminServerStatusApi)
|
19
|
-
bp.add_url_rule('/admin_user/<uuid:uuid>/', view_func=AdminUserApi)
|
20
|
-
bp.add_url_rule('/admin_user/', view_func=AdminUsersApi)
|
21
|
-
bp.add_url_rule('/log/', view_func=AdminLogApi)
|
16
|
+
bp.add_url_rule('/me/', view_func=AdminInfoApi) # type: ignore
|
17
|
+
bp.add_url_rule('/server_status/', view_func=AdminServerStatusApi) # type: ignore
|
18
|
+
bp.add_url_rule('/admin_user/<uuid:uuid>/', view_func=AdminUserApi) # type: ignore
|
19
|
+
bp.add_url_rule('/admin_user/', view_func=AdminUsersApi) # type: ignore
|
20
|
+
bp.add_url_rule('/log/', view_func=AdminLogApi) # type: ignore
|
22
21
|
|
23
22
|
from .user_api import UserApi
|
24
23
|
from .users_api import UsersApi
|
25
|
-
bp.add_url_rule('/user/<uuid:uuid>/', view_func=UserApi)
|
26
|
-
bp.add_url_rule('/user/', view_func=UsersApi)
|
24
|
+
bp.add_url_rule('/user/<uuid:uuid>/', view_func=UserApi) # type: ignore
|
25
|
+
bp.add_url_rule('/user/', view_func=UsersApi) # type: ignore
|
27
26
|
app.register_blueprint(bp)
|
28
27
|
|
29
28
|
|
30
29
|
def has_permission(model) -> bool:
|
31
30
|
'''Check if the authenticated account has permission to do an action(get,insert,update,delete) on the another admin'''
|
32
|
-
if
|
33
|
-
return
|
34
|
-
|
31
|
+
if g.account.uuid == AdminUser.get_super_admin_uuid():
|
32
|
+
return True
|
33
|
+
if isinstance(model, AdminUser) and model.parent_admin_id == g.account.id:
|
34
|
+
return True
|
35
|
+
elif isinstance(model, User) and model.added_by == g.account.id:
|
36
|
+
return True
|
37
|
+
|
38
|
+
return False
|
@@ -3,7 +3,7 @@ from flask import g
|
|
3
3
|
from flask.views import MethodView
|
4
4
|
from apiflask import abort
|
5
5
|
from hiddifypanel.auth import login_required
|
6
|
-
from hiddifypanel.models.admin import AdminUser
|
6
|
+
from hiddifypanel.models.admin import AdminMode, 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
|
@@ -15,7 +15,6 @@ class AdminInfoApi(MethodView):
|
|
15
15
|
|
16
16
|
@app.output(AdminSchema) # type: ignore
|
17
17
|
def get(self):
|
18
|
-
# admin = AdminUser.by_uuid(g.account.uuid) or abort(404, "user not found")
|
19
18
|
admin = g.account or abort(404, "user not found")
|
20
19
|
|
21
20
|
dto = AdminSchema()
|
@@ -24,7 +23,9 @@ class AdminInfoApi(MethodView):
|
|
24
23
|
dto.uuid = admin.uuid # type: ignore
|
25
24
|
dto.mode = admin.mode # type: ignore
|
26
25
|
dto.can_add_admin = admin.can_add_admin # type: ignore
|
27
|
-
|
26
|
+
if g.account.mode == AdminMode.super_admin:
|
27
|
+
if parent := AdminUser.by_id(admin.parent_admin_id):
|
28
|
+
dto.parent_admin_uuid = parent.uuid
|
28
29
|
dto.telegram_id = admin.telegram_id or 0 # type: ignore
|
29
30
|
dto.lang = Lang(hconfig(ConfigEnum.admin_lang)) # type: ignore
|
30
31
|
return dto
|
@@ -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, PatchAdminSchema, SuccessfulSchema
|
9
|
+
from .schema import AdminSchema, PutAdminSchema, PatchAdminSchema, SuccessfulSchema
|
10
10
|
|
11
11
|
|
12
12
|
class AdminUserApi(MethodView):
|
@@ -14,28 +14,46 @@ class AdminUserApi(MethodView):
|
|
14
14
|
|
15
15
|
@app.output(AdminSchema) # type: ignore
|
16
16
|
def get(self, uuid):
|
17
|
-
admin = AdminUser.by_uuid(uuid) or abort(404, "
|
17
|
+
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
18
18
|
if not has_permission(admin):
|
19
|
-
abort(403, "
|
20
|
-
return admin.
|
19
|
+
abort(403, "you don't have permission to access this admin")
|
20
|
+
return admin.to_schema() # type: ignore
|
21
|
+
|
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'}
|
21
34
|
|
22
35
|
@app.input(PatchAdminSchema, arg_name='data') # type: ignore
|
23
36
|
@app.output(SuccessfulSchema) # type: ignore
|
24
37
|
def patch(self, uuid, data):
|
25
|
-
admin = AdminUser.by_uuid(uuid) or abort(404, "
|
38
|
+
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
26
39
|
if not has_permission(admin):
|
27
40
|
abort(403, "You don't have permission to access this admin")
|
28
41
|
|
29
|
-
|
30
|
-
|
31
|
-
|
42
|
+
for field in AdminUser.__table__.columns.keys(): # type: ignore
|
43
|
+
if field in ['id', 'parent_admin_id']:
|
44
|
+
continue
|
45
|
+
if field not in data:
|
46
|
+
data[field] = getattr(admin, field)
|
32
47
|
|
33
|
-
AdminUser.add_or_update(**data)
|
48
|
+
_ = AdminUser.add_or_update(True, **data) or abort(502, "Unknown issue: Admin is not patched")
|
49
|
+
# 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()
|
34
52
|
return {'status': 200, 'msg': 'ok'}
|
35
53
|
|
36
54
|
@app.output(SuccessfulSchema) # type: ignore
|
37
55
|
def delete(self, uuid):
|
38
|
-
admin = AdminUser.by_uuid(uuid) or abort(404, "
|
56
|
+
admin = AdminUser.by_uuid(uuid) or abort(404, "Admin not found")
|
39
57
|
if not has_permission(admin):
|
40
58
|
abort(403, "You don't have permission to access this admin")
|
41
59
|
admin.remove() # type: ignore
|
@@ -4,7 +4,7 @@ from apiflask import abort
|
|
4
4
|
from flask.views import MethodView
|
5
5
|
from hiddifypanel.auth import login_required
|
6
6
|
from hiddifypanel.models.role import Role
|
7
|
-
from .admin_user_api import AdminSchema
|
7
|
+
from .admin_user_api import AdminSchema
|
8
8
|
from hiddifypanel.models import AdminUser
|
9
9
|
|
10
10
|
|
@@ -14,21 +14,4 @@ class AdminUsersApi(MethodView):
|
|
14
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
|
-
return [admin.
|
18
|
-
|
19
|
-
@app.input(AdminSchema, arg_name='data') # type: ignore
|
20
|
-
@app.output(AdminSchema) # type: ignore
|
21
|
-
def put(self, data):
|
22
|
-
uuid = data.get('uuid') or abort(422, "Parameter issue: 'uuid'")
|
23
|
-
admin = AdminUser.by_uuid(uuid) # type: ignore
|
24
|
-
|
25
|
-
if not data.get('added_by_uuid'):
|
26
|
-
data['added_by_uuid'] = g.account.uuid
|
27
|
-
|
28
|
-
# update check permission
|
29
|
-
if admin:
|
30
|
-
if not has_permission(admin):
|
31
|
-
abort(403, "You don't have permission to access this admin")
|
32
|
-
|
33
|
-
dbadmin = AdminUser.add_or_update(**data) or abort(502, "Unknown issue: Admin is not added")
|
34
|
-
return dbadmin.to_dict() # type: ignore
|
17
|
+
return [admin.to_schema() for admin in admins] # type: ignore
|
@@ -27,7 +27,7 @@ class FriendlyUUID(fields.Field):
|
|
27
27
|
if value is None:
|
28
28
|
return None
|
29
29
|
try:
|
30
|
-
return uuid.UUID(value)
|
30
|
+
return str(uuid.UUID(value))
|
31
31
|
except ValueError:
|
32
32
|
self.fail('Invalid uuid')
|
33
33
|
|
@@ -71,6 +71,11 @@ class UserSchema(Schema):
|
|
71
71
|
description="The last time the user's data usage was reset, in a JSON-friendly format",
|
72
72
|
allow_none=True
|
73
73
|
)
|
74
|
+
# expiry_time = Date(
|
75
|
+
# format='%Y-%m-%d',
|
76
|
+
# description="The expiry time of the user's package, in a JSON-friendly format",
|
77
|
+
# allow_none=True
|
78
|
+
# )
|
74
79
|
comment = String(
|
75
80
|
missing=None,
|
76
81
|
allow_none=True,
|
@@ -115,13 +120,21 @@ class UserSchema(Schema):
|
|
115
120
|
)
|
116
121
|
|
117
122
|
lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
|
123
|
+
enable = Boolean(required=False, description="Whether the user is enabled or not")
|
124
|
+
|
125
|
+
|
126
|
+
class PutUserSchema(UserSchema):
|
127
|
+
def __init__(self, *args, **kwargs):
|
128
|
+
super().__init__(*args, **kwargs)
|
129
|
+
# the uuid is sent in the url path
|
130
|
+
self.fields['uuid'].required = False
|
118
131
|
|
119
132
|
|
120
133
|
class PatchUserSchema(UserSchema):
|
121
134
|
def __init__(self, *args, **kwargs):
|
122
135
|
super().__init__(*args, **kwargs)
|
136
|
+
self.fields['uuid'].required = False
|
123
137
|
self.fields['name'].required = False
|
124
|
-
pass
|
125
138
|
|
126
139
|
# endregion
|
127
140
|
|
@@ -134,21 +147,31 @@ class AdminSchema(Schema):
|
|
134
147
|
uuid = FriendlyUUID(required=True, description='The unique identifier for the admin')
|
135
148
|
mode = Enum(AdminMode, required=True, description='The mode for the admin')
|
136
149
|
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,
|
150
|
+
parent_admin_uuid = FriendlyUUID(required=False, description='The unique identifier for the parent admin', allow_none=True,
|
138
151
|
# validate=OneOf([p.uuid for p in AdminUser.query.all()])
|
139
152
|
)
|
140
153
|
telegram_id = Integer(required=False, description='The Telegram ID associated with the admin', allow_none=True)
|
141
154
|
lang = Enum(Lang, required=True)
|
155
|
+
max_users = Integer(required=False, description='The maximum number of users allowed', allow_none=True)
|
156
|
+
max_active_users = Integer(required=False, description='The maximum number of active users allowed', allow_none=True)
|
157
|
+
|
158
|
+
|
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
|
142
164
|
|
143
165
|
|
144
166
|
class PatchAdminSchema(AdminSchema):
|
145
167
|
def __init__(self, *args, **kwargs):
|
146
168
|
super().__init__(*args, **kwargs)
|
169
|
+
self.fields['uuid'].required = False
|
147
170
|
self.fields['name'].required = False
|
148
171
|
self.fields['mode'].required = False
|
149
172
|
self.fields['lang'].required = False
|
150
173
|
self.fields['can_add_admin'].required = False
|
151
|
-
|
174
|
+
|
152
175
|
# endregion
|
153
176
|
|
154
177
|
|
@@ -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, PatchUserSchema, SuccessfulSchema
|
12
|
+
from .schema import UserSchema, PutUserSchema, PatchUserSchema, SuccessfulSchema
|
13
13
|
|
14
14
|
|
15
15
|
class UserApi(MethodView):
|
@@ -17,11 +17,26 @@ class UserApi(MethodView):
|
|
17
17
|
|
18
18
|
@app.output(UserSchema) # type: ignore
|
19
19
|
def get(self, uuid):
|
20
|
-
user = User.by_uuid(uuid) or abort(404, "
|
20
|
+
user = User.by_uuid(uuid) or abort(404, "User not found")
|
21
21
|
if not has_permission(user):
|
22
22
|
abort(403, "You don't have permission to access this user")
|
23
23
|
|
24
|
-
return user.
|
24
|
+
return user.to_schema() # type: ignore
|
25
|
+
|
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'}
|
25
40
|
|
26
41
|
@app.input(PatchUserSchema, arg_name="data") # type: ignore
|
27
42
|
@app.output(SuccessfulSchema) # type: ignore
|
@@ -30,13 +45,17 @@ class UserApi(MethodView):
|
|
30
45
|
if not has_permission(user):
|
31
46
|
abort(403, "You don't have permission to access this user")
|
32
47
|
|
33
|
-
|
34
|
-
|
35
|
-
|
48
|
+
for field in User.__table__.columns.keys(): # type: ignore
|
49
|
+
if field in ['id', 'expiry_time']:
|
50
|
+
continue
|
51
|
+
if field not in data:
|
52
|
+
data[field] = getattr(user, field)
|
36
53
|
|
37
|
-
User.add_or_update(**data)
|
38
|
-
|
39
|
-
|
54
|
+
dbuser = User.add_or_update(**data) or abort(502, "Unknown issue! User is not patched")
|
55
|
+
user_driver.add_client(dbuser)
|
56
|
+
# 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()
|
40
59
|
hiddify.quick_apply_users()
|
41
60
|
return {'status': 200, 'msg': 'ok'}
|
42
61
|
|
@@ -16,24 +16,4 @@ class UsersApi(MethodView):
|
|
16
16
|
@app.output(UserSchema(many=True)) # type: ignore
|
17
17
|
def get(self):
|
18
18
|
users = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no user")
|
19
|
-
return [user.
|
20
|
-
|
21
|
-
@app.input(UserSchema, arg_name="data") # type: ignore
|
22
|
-
@app.output(UserSchema) # type: ignore
|
23
|
-
def put(self, data):
|
24
|
-
uuid = data.get('uuid') or abort(422, "Parameter issue: 'uuid'")
|
25
|
-
user = User.by_uuid(uuid) # type: ignore
|
26
|
-
|
27
|
-
if not data.get('added_by_uuid'):
|
28
|
-
data['added_by_uuid'] = g.account.uuid
|
29
|
-
|
30
|
-
# update check permission
|
31
|
-
if user:
|
32
|
-
if not has_permission(user):
|
33
|
-
abort(403, "You don't have permission to access this user")
|
34
|
-
User.add_or_update(**data) # type: ignore
|
35
|
-
|
36
|
-
dbuser = User.by_uuid(data['uuid']) or abort(502, "Unknown issue: User is not added")
|
37
|
-
user_driver.add_client(dbuser)
|
38
|
-
hiddify.quick_apply_users()
|
39
|
-
return dbuser.to_dict(False) # type: ignore
|
19
|
+
return [user.to_schema() for user in users] # type: ignore
|
@@ -41,7 +41,7 @@ class RegisterApi(MethodView):
|
|
41
41
|
logger.info("Adding users...")
|
42
42
|
User.bulk_register(data['panel_data']['users'], commit=False)
|
43
43
|
logger.info("Adding domains...")
|
44
|
-
|
44
|
+
Domain.bulk_register(data['panel_data']['domains'], commit=False, force_child_unique_id=child.unique_id)
|
45
45
|
logger.info("Adding hconfigs...")
|
46
46
|
bulk_register_configs(data['panel_data']['hconfigs'], commit=False, froce_child_unique_id=child.unique_id)
|
47
47
|
logger.info("Adding proxies...")
|
@@ -64,12 +64,16 @@ class UsageInputOutputSchema(Schema):
|
|
64
64
|
|
65
65
|
# region sync
|
66
66
|
class SyncInputSchema(Schema):
|
67
|
+
domains = fields.List(fields.Nested(DomainSchema), required=False, description="The list of domains")
|
68
|
+
proxies = fields.List(fields.Nested(ProxySchema), required=False, description="The list of proxies")
|
69
|
+
hconfigs = fields.List(fields.Nested(HConfigSchema), required=False, description="The list of configs")
|
67
70
|
# users = fields.List(fields.Nested(UserSchema),required=True,description="The list of users")
|
68
|
-
domains = fields.List(fields.Nested(DomainSchema), required=True, description="The list of domains")
|
69
|
-
proxies = fields.List(fields.Nested(ProxySchema), required=True, description="The list of proxies")
|
70
|
-
# parent_domains = fields.List(fields.Nested(ParentDomainSchema),required=True,description="The list of parent domains")
|
71
71
|
# admin_users = fields.List(fields.Nested(AdminSchema),required=True,description="The list of admin users")
|
72
|
-
|
72
|
+
|
73
|
+
def validate(self, data, **kwargs):
|
74
|
+
if not (data.get("domains") or data.get("proxies") or data.get("hconfigs")):
|
75
|
+
raise ValidationError("At least one field must exist (domains, proxies, or hconfigs)")
|
76
|
+
return data
|
73
77
|
|
74
78
|
|
75
79
|
class SyncOutputSchema(Schema):
|
@@ -34,11 +34,27 @@ class SyncApi(MethodView):
|
|
34
34
|
|
35
35
|
try:
|
36
36
|
logger.info("Syncing domains...")
|
37
|
-
|
37
|
+
if data.get('domains'):
|
38
|
+
logger.info("Inserting domains into database")
|
39
|
+
Domain.bulk_register(data['domains'], commit=False, force_child_unique_id=child.unique_id)
|
40
|
+
else:
|
41
|
+
logger.info("Domains field is empty")
|
42
|
+
|
38
43
|
logger.info("Syncing hconfigs...")
|
39
|
-
|
44
|
+
if data.get('hconfigs'):
|
45
|
+
logger.info("Inserting hconfigs into database")
|
46
|
+
bulk_register_configs(data['hconfigs'], commit=False, froce_child_unique_id=child.unique_id)
|
47
|
+
else:
|
48
|
+
logger.info("Hconfigs field is empty")
|
49
|
+
|
40
50
|
logger.info("Syncing proxies...")
|
41
|
-
|
51
|
+
if data.get('proxies'):
|
52
|
+
logger.info("Inserting proxies into database")
|
53
|
+
Proxy.bulk_register(data['proxies'], commit=False, force_child_unique_id=child.unique_id)
|
54
|
+
else:
|
55
|
+
logger.info("Proxies field is empty")
|
56
|
+
|
57
|
+
logger.info("Commit changes to database")
|
42
58
|
db.session.commit() # type: ignore
|
43
59
|
except Exception as err:
|
44
60
|
with logger.contextualize(error=err):
|