hiddifypanel 10.30.8.dev1__py3-none-any.whl → 10.30.9.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/cache.py +13 -3
  4. hiddifypanel/hutils/network/net.py +41 -10
  5. hiddifypanel/hutils/proxy/shared.py +1 -1
  6. hiddifypanel/hutils/proxy/xrayjson.py +6 -6
  7. hiddifypanel/models/config_enum.py +2 -2
  8. hiddifypanel/models/domain.py +1 -1
  9. hiddifypanel/models/user.py +3 -2
  10. hiddifypanel/panel/admin/Actions.py +2 -2
  11. hiddifypanel/panel/admin/AdminstratorAdmin.py +1 -1
  12. hiddifypanel/panel/admin/Dashboard.py +3 -4
  13. hiddifypanel/panel/admin/DomainAdmin.py +48 -35
  14. hiddifypanel/panel/admin/ProxyAdmin.py +1 -1
  15. hiddifypanel/panel/admin/QuickSetup.py +144 -52
  16. hiddifypanel/panel/admin/SettingAdmin.py +23 -18
  17. hiddifypanel/panel/admin/UserAdmin.py +7 -4
  18. hiddifypanel/panel/admin/templates/model/domain_list.html +13 -12
  19. hiddifypanel/panel/admin/templates/model/user_list.html +8 -5
  20. hiddifypanel/panel/admin/templates/quick_setup.html +20 -14
  21. hiddifypanel/panel/admin/templates/result.html +3 -2
  22. hiddifypanel/panel/commercial/ParentDomainAdmin.py +1 -1
  23. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +3 -0
  24. hiddifypanel/panel/commercial/telegrambot/Usage.py +1 -4
  25. hiddifypanel/panel/commercial/telegrambot/admin.py +3 -3
  26. hiddifypanel/panel/commercial/telegrambot/information.py +1 -3
  27. hiddifypanel/panel/common_bp/login.py +2 -2
  28. hiddifypanel/panel/init_db.py +6 -6
  29. hiddifypanel/panel/user/templates/home/home.html +30 -14
  30. hiddifypanel/panel/user/templates/home/index_old.html +2 -2
  31. hiddifypanel/panel/user/templates/home/multi.html +2 -2
  32. hiddifypanel/panel/user/templates/redirect_to_new_format.html +1 -1
  33. hiddifypanel/templates/500.html +1 -1
  34. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  35. hiddifypanel/translations/en/LC_MESSAGES/messages.po +128 -133
  36. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  37. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +130 -136
  38. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  39. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +115 -129
  40. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  41. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +120 -134
  42. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  43. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +104 -116
  44. hiddifypanel/translations.i18n/en.json +52 -73
  45. hiddifypanel/translations.i18n/fa.json +133 -75
  46. hiddifypanel/translations.i18n/pt.json +337 -70
  47. hiddifypanel/translations.i18n/ru.json +190 -75
  48. hiddifypanel/translations.i18n/zh.json +318 -70
  49. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/METADATA +1 -1
  50. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/RECORD +54 -54
  51. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/LICENSE.md +0 -0
  52. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/WHEEL +0 -0
  53. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/entry_points.txt +0 -0
  54. {hiddifypanel-10.30.8.dev1.dist-info → hiddifypanel-10.30.9.dev1.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import uuid
4
4
  # from flask_babelex import lazy_gettext as _
5
5
  from flask import render_template, g, request
6
6
  from flask_babel import gettext as _
7
+ from markupsafe import Markup
7
8
  import wtforms as wtf
8
9
  from flask_wtf import FlaskForm
9
10
  from flask_bootstrap import SwitchField
@@ -22,67 +23,46 @@ from hiddifypanel.models import *
22
23
  class QuickSetup(FlaskView):
23
24
  decorators = [login_required({Role.super_admin})]
24
25
 
26
+ def current_form(self, step=None, empty=False, next=False):
27
+ step = int(step or request.form.get("step") or request.args.get('step', "1"))
28
+ if next:
29
+ step = step + 1
30
+ form = {1: get_lang_form,
31
+ 2: get_quick_setup_form,
32
+ 3: get_proxy_form}
33
+
34
+ return form[step](empty=empty or next)
35
+
25
36
  def index(self):
26
37
  return render_template(
27
- 'quick_setup.html', lang_form=get_lang_form(),
28
- form=get_quick_setup_form(),
29
- ipv4=hutils.network.get_ip_str(4),
30
- ipv6=hutils.network.get_ip_str(6),
38
+ 'quick_setup.html',
39
+ form=self.current_form(),
40
+ # ipv4=hutils.network.get_ip_str(4),
41
+ # ipv6=hutils.network.get_ip_str(6),
31
42
  admin_link=admin_link(),
32
43
  show_domain_info=True)
33
44
 
34
45
  def post(self):
35
46
  if request.args.get('changepw') == "true":
36
47
  AdminUser.current_admin_or_owner().uuid = str(uuid.uuid4())
37
- set_hconfig(ConfigEnum.first_setup, False)
38
- quick_form = get_quick_setup_form()
39
- lang_form = get_lang_form()
40
- if lang_form.lang_submit.data:
41
- if lang_form.validate_on_submit():
42
- set_hconfig(ConfigEnum.lang, lang_form.admin_lang.data)
43
- set_hconfig(ConfigEnum.admin_lang, lang_form.admin_lang.data)
44
- set_hconfig(ConfigEnum.country, lang_form.country.data)
45
-
46
- flask_babel.refresh()
47
- hutils.flask.flash((_('quicksetup.setlang.success')), 'success')
48
- else:
49
- hutils.flask.flash((_('quicksetup.setlang.error')), 'danger')
48
+ db.session.commit()
50
49
 
50
+ set_hconfig(ConfigEnum.first_setup, False)
51
+ form = self.current_form()
52
+ if not form.validate_on_submit() or form.step.data not in ["1", "2", "3"]:
53
+ hutils.flask.flash(_('config.validation-error'), 'danger')
51
54
  return render_template(
52
- 'quick_setup.html', form=get_quick_setup_form(True),
53
- lang_form=get_lang_form(),
55
+ 'quick_setup.html', form=form,
54
56
  admin_link=admin_link(),
55
57
  ipv4=hutils.network.get_ip_str(4),
56
58
  ipv6=hutils.network.get_ip_str(6),
57
59
  show_domain_info=False)
58
-
59
- if quick_form.validate_on_submit():
60
- Domain.query.filter(Domain.domain == f'{hutils.network.get_ip_str(4)}.sslip.io').delete()
61
- db.session.add(Domain(domain=quick_form.domain.data.lower(), mode=DomainType.direct))
62
- set_hconfig(ConfigEnum.block_iran_sites, quick_form.block_iran_sites.data)
63
- set_hconfig(ConfigEnum.decoy_domain, quick_form.decoy_domain.data)
64
- # hiddify.bulk_register_configs([
65
- # # {"key": ConfigEnum.telegram_enable, "value": quick_form.enable_telegram.data == True},
66
- # # {"key": ConfigEnum.vmess_enable, "value": quick_form.enable_vmess.data == True},
67
- # # {"key": ConfigEnum.firewall, "value": quick_form.enable_firewall.data == True},
68
- # {"key": ConfigEnum.block_iran_sites, "value": quick_form.block_iran_sites.data == True},
69
- # # {"key":ConfigEnum.decoy_domain,"value":quick_form.decoy_domain.data}
70
- # ])
71
-
72
- from .Actions import Actions
73
- return Actions().reinstall(domain_changed=True)
74
- else:
75
- hutils.flask.flash(_('config.validation-error'), 'danger')
76
- return render_template(
77
- 'quick_setup.html', form=quick_form, lang_form=get_lang_form(True),
78
- ipv4=hutils.network.get_ip_str(4),
79
- ipv6=hutils.network.get_ip_str(6),
80
- admin_link=admin_link(),
81
- show_domain_info=False)
60
+ return form.post(self)
82
61
 
83
62
 
84
63
  def get_lang_form(empty=False):
85
64
  class LangForm(FlaskForm):
65
+ step = wtf.HiddenField(default="1")
86
66
  admin_lang = wtf.SelectField(
87
67
  _("config.admin_lang.label"), choices=[("en", _("lang.en")), ("fa", _("lang.fa")), ("pt", _("lang.pt")), ("zh", _("lang.zh")), ("ru", _("lang.ru"))],
88
68
  description=_("config.admin_lang.description"),
@@ -94,7 +74,61 @@ def get_lang_form(empty=False):
94
74
  default=hconfig(ConfigEnum.country))
95
75
  lang_submit = wtf.SubmitField(_('Submit'))
96
76
 
97
- return LangForm(None)if empty else LangForm()
77
+ def post(self, view):
78
+ set_hconfig(ConfigEnum.lang, self.admin_lang.data)
79
+ set_hconfig(ConfigEnum.admin_lang, self.admin_lang.data)
80
+ set_hconfig(ConfigEnum.country, self.country.data)
81
+
82
+ flask_babel.refresh()
83
+ hutils.flask.flash((_('quicksetup.setlang.success')), 'success')
84
+
85
+ return render_template(
86
+ 'quick_setup.html', form=view.current_form(next=True),
87
+ admin_link=admin_link(),
88
+ ipv4=hutils.network.get_ip_str(4),
89
+ ipv6=hutils.network.get_ip_str(6),
90
+ show_domain_info=False)
91
+
92
+ form = LangForm(None)if empty else LangForm()
93
+ form.step.data = "1"
94
+ return form
95
+
96
+
97
+ def get_proxy_form(empty=False):
98
+ class ProxyForm(FlaskForm):
99
+ step = wtf.HiddenField(default="3")
100
+ description_for_fieldset = wtf.TextAreaField("", description=_(f'quicksetup.proxy_cat.description'), render_kw={"class": "d-none"})
101
+
102
+ def post(self, view):
103
+
104
+ for k, vs in self.data.items():
105
+ ek = ConfigEnum[k]
106
+ if ek != ConfigEnum.not_found:
107
+ set_hconfig(ek, vs, commit=False)
108
+
109
+ db.session.commit()
110
+ # print(cat,vs)
111
+ hutils.proxy.get_proxies.invalidate_all()
112
+ if hutils.node.is_child():
113
+ hutils.node.run_node_op_in_bg(hutils.node.child.sync_with_parent, *[hutils.node.child.SyncFields.hconfigs])
114
+
115
+ from .Actions import Actions
116
+ return Actions().reinstall(domain_changed=True)
117
+ boolconfigs = BoolConfig.query.filter(BoolConfig.child_id == Child.current().id).all()
118
+
119
+ for cf in boolconfigs:
120
+ if cf.key.category == 'hidden':
121
+ continue
122
+ if cf.key.startswith("sub_") or cf.key.startswith("mux_"):
123
+ continue
124
+ if not cf.key.endswith("_enable") or cf.key in [ConfigEnum.hysteria_obfs_enable, ConfigEnum.tls_padding_enable]:
125
+ continue
126
+ field = SwitchField(_(f'config.{cf.key}.label'), default=cf.value, description=_(f'config.{cf.key}.description'))
127
+ setattr(ProxyForm, f'{cf.key}', field)
128
+ setattr(ProxyForm, "submit_global", wtf.fields.SubmitField(_('Submit')))
129
+ form = ProxyForm(None) if empty else ProxyForm()
130
+ form.step.data = "3"
131
+ return form
98
132
 
99
133
 
100
134
  def get_quick_setup_form(empty=False):
@@ -108,14 +142,22 @@ def get_quick_setup_form(empty=False):
108
142
  domains.append(d.domain)
109
143
  return domains
110
144
 
111
- class QuickSetupForm(FlaskForm):
145
+ class BasicConfigs(FlaskForm):
146
+ step = wtf.HiddenField(default="2")
147
+ description_for_fieldset = wtf.TextAreaField("", description=_(f'quicksetup.proxy_cat.description'), render_kw={"class": "d-none"})
112
148
  domain_regex = "^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})$"
113
149
 
114
150
  domain_validators = [
115
- wtf.validators.Regexp(domain_regex, re.IGNORECASE, _("config.Invalid domain")),
151
+ wtf.validators.Regexp(domain_regex, re.IGNORECASE, _("config.Invalid_domain")),
116
152
  validate_domain,
117
- wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain already used")),
118
- wtf.validators.NoneOf([c.value.lower() for c in StrConfig.query.all() if "fakedomain" in c.key and c.key != ConfigEnum.decoy_domain], _("config.Domain already used"))]
153
+ wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain_already_used")),
154
+ wtf.validators.NoneOf([c.value.lower() for c in StrConfig.query.all() if "fakedomain" in c.key and c.key != ConfigEnum.decoy_domain], _("config.Domain_already_used"))]
155
+
156
+ cdn_domain_validators = [
157
+ wtf.validators.Regexp(f'({domain_regex})|(^$)', re.IGNORECASE, _("config.Invalid_domain")),
158
+ validate_domain_cdn,
159
+ wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain_already_used")),
160
+ wtf.validators.NoneOf([c.value.lower() for c in StrConfig.query.all() if "fakedomain" in c.key and c.key != ConfigEnum.decoy_domain], _("config.Domain_already_used"))]
119
161
  domain = wtf.StringField(
120
162
  _("domain.domain"),
121
163
  domain_validators,
@@ -126,16 +168,50 @@ def get_quick_setup_form(empty=False):
126
168
  "title": domain_validators[0].message,
127
169
  "required": "",
128
170
  "placeholder": "sub.domain.com"})
171
+
172
+ cdn_domain = wtf.StringField(
173
+ _("quicksetup.cdn_domain.label"),
174
+ cdn_domain_validators,
175
+ description=_("quicksetup.cdn_domain.description"),
176
+ render_kw={
177
+ "class": "ltr",
178
+ "pattern": domain_validators[0].regex.pattern,
179
+ "title": domain_validators[0].message,
180
+ "placeholder": "sub.domain.com"})
129
181
  # enable_telegram = SwitchField(_("config.telegram_enable.label"), description=_("config.telegram_enable.description"), default=hconfig(ConfigEnum.telegram_enable))
130
182
  # enable_firewall = SwitchField(_("config.firewall.label"), description=_("config.firewall.description"), default=hconfig(ConfigEnum.firewall))
131
183
  block_iran_sites = SwitchField(_("config.block_iran_sites.label"), description=_(
132
184
  "config.block_iran_sites.description"), default=hconfig(ConfigEnum.block_iran_sites))
133
185
  # enable_vmess = SwitchField(_("config.vmess_enable.label"), description=_("config.vmess_enable.description"), default=hconfig(ConfigEnum.vmess_enable))
134
186
  decoy_domain = wtf.StringField(_("config.decoy_domain.label"), description=_("config.decoy_domain.description"), default=hconfig(
135
- ConfigEnum.decoy_domain), validators=[wtf.validators.Regexp(domain_regex, re.IGNORECASE, _("config.Invalid domain")), hutils.flask.validate_domain_exist])
187
+ ConfigEnum.decoy_domain), validators=[wtf.validators.Regexp(domain_regex, re.IGNORECASE, _("config.Invalid_domain")), hutils.flask.validate_domain_exist])
136
188
  submit = wtf.SubmitField(_('Submit'))
137
189
 
138
- return QuickSetupForm(None) if empty else QuickSetupForm()
190
+ def post(self, view):
191
+ Domain.query.filter(Domain.domain == f'{hutils.network.get_ip_str(4)}.sslip.io').delete()
192
+ db.session.add(Domain(domain=self.domain.data.lower(), mode=DomainType.direct))
193
+ if self.cdn_domain.data:
194
+ db.session.add(Domain(domain=self.cdn_domain.data.lower(), mode=DomainType.cdn))
195
+ set_hconfig(ConfigEnum.block_iran_sites, self.block_iran_sites.data)
196
+ set_hconfig(ConfigEnum.decoy_domain, self.decoy_domain.data)
197
+ # hiddify.bulk_register_configs([
198
+ # # {"key": ConfigEnum.telegram_enable, "value": quick_form.enable_telegram.data == True},
199
+ # # {"key": ConfigEnum.vmess_enable, "value": quick_form.enable_vmess.data == True},
200
+ # # {"key": ConfigEnum.firewall, "value": quick_form.enable_firewall.data == True},
201
+ # {"key": ConfigEnum.block_iran_sites, "value": quick_form.block_iran_sites.data == True},
202
+ # # {"key":ConfigEnum.decoy_domain,"value":quick_form.decoy_domain.data}
203
+ # ])
204
+
205
+ return render_template(
206
+ 'quick_setup.html', form=view.current_form(next=True),
207
+ # ipv4=hutils.network.get_ip_str(4),
208
+ # ipv6=hutils.network.get_ip_str(6),
209
+ admin_link=admin_link(),
210
+ show_domain_info=False)
211
+
212
+ form = BasicConfigs(None) if empty else BasicConfigs()
213
+ form.step.data = "2"
214
+ return form
139
215
 
140
216
 
141
217
  def validate_domain(form, field):
@@ -145,8 +221,24 @@ def validate_domain(form, field):
145
221
  raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
146
222
 
147
223
  myip = hutils.network.get_ip(4)
148
- if dip and myip != dip:
149
- raise ValidationError(_("Domain (%(domain)s)-> IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode", server_ip=myip, domain_ip=dip, domain=domain))
224
+ myip6 = hutils.network.get_ip(4)
225
+ if dip and myip != dip and (not myip6 or myip6 != dip):
226
+ raise ValidationError(_("Domain (%(domain)s)-> IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode",
227
+ server_ip=myip, domain_ip=dip, domain=domain))
228
+
229
+
230
+ def validate_domain_cdn(form, field):
231
+ domain = field.data
232
+ if not domain:
233
+ return
234
+ dip = hutils.network.get_domain_ip(domain)
235
+ if dip is None:
236
+ raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
237
+
238
+ myip = hutils.network.get_ip(4)
239
+ if myip == dip:
240
+ raise ValidationError(_("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
241
+ server_ip=myip, domain_ip=dip, domain=domain))
150
242
 
151
243
 
152
244
  def admin_link():
@@ -24,6 +24,7 @@ from hiddifypanel.models import *
24
24
  from hiddifypanel.database import db
25
25
  from hiddifypanel.panel import hiddify, custom_widgets
26
26
  from hiddifypanel import __version__
27
+ from hiddifypanel.cache import cache
27
28
 
28
29
 
29
30
  class SettingAdmin(FlaskView):
@@ -111,6 +112,8 @@ class SettingAdmin(FlaskView):
111
112
  if p_mode != PanelMode.standalone:
112
113
  set_hconfig(ConfigEnum.panel_mode, PanelMode.standalone)
113
114
 
115
+ cache.invalidate_all_cached_functions()
116
+ # hutils.proxy.get_proxies.invalidate_all()
114
117
  from hiddifypanel.panel.commercial.telegrambot import register_bot
115
118
  register_bot(set_hook=True)
116
119
 
@@ -194,13 +197,15 @@ def get_config_form():
194
197
  if c.key in bool_types:
195
198
  field = SwitchField(_(f'config.{c.key}.label'), default=c.value, description=_(f'config.{c.key}.description'))
196
199
  elif c.key == ConfigEnum.core_type:
197
- field = wtf.SelectField(_(f"config.{c.key}.label"), choices=[("xray", _("Xray")), ("singbox", _(
198
- "SingBox"))], description=_(f"config.{c.key}.description"), default=hconfig(c.key))
200
+ field = wtf.SelectField(_(f"config.{c.key}.label"),
201
+ choices=[("xray", _("Xray")), ("singbox", _("SingBox"))],
202
+ description=_(f"config.{c.key}.description"),
203
+ default=hconfig(c.key))
199
204
  elif c.key == ConfigEnum.warp_mode:
200
- field = wtf.SelectField(
201
- _(f"config.{c.key}.label"), choices=[("disable", _("Disable")), ("all", _("All")), ("custom", _("Only Blocked and Local websites"))],
202
- description=_(f"config.{c.key}.description"),
203
- default=hconfig(c.key))
205
+ field = wtf.SelectField(_(f"config.{c.key}.label"),
206
+ choices=[("disable", _("Disable")), ("all", _("All")), ("custom", _("Only Blocked and Local websites"))],
207
+ description=_(f"config.{c.key}.description"),
208
+ default=hconfig(c.key))
204
209
 
205
210
  elif c.key == ConfigEnum.lang or c.key == ConfigEnum.admin_lang:
206
211
  field = wtf.SelectField(
@@ -267,16 +272,16 @@ def get_config_form():
267
272
  render_kw = {'class': "ltr"}
268
273
  validators = []
269
274
  if c.key == ConfigEnum.domain_fronting_domain:
270
- validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})|$", re.IGNORECASE, _("config.Invalid domain")))
275
+ validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})|$", re.IGNORECASE, _("config.Invalid_domain")))
271
276
  validators.append(hutils.flask.validate_domain_exist)
272
277
  elif '_domain' in c.key or "_fakedomain" in c.key:
273
- validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})$", re.IGNORECASE, _("config.Invalid domain")))
278
+ validators.append(wtf.validators.Regexp("^([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})$", re.IGNORECASE, _("config.Invalid_domain")))
274
279
  validators.append(hutils.flask.validate_domain_exist)
275
280
 
276
281
  if c.key != ConfigEnum.decoy_domain:
277
- validators.append(wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain already used")))
282
+ validators.append(wtf.validators.NoneOf([d.domain.lower() for d in Domain.query.all()], _("config.Domain_already_used")))
278
283
  validators.append(wtf.validators.NoneOf(
279
- [cc.value.lower() for cc in StrConfig.query.filter(StrConfig.child_id == Child.current().id).all() if cc.key != c.key and "fakedomain" in cc.key and cc.key != ConfigEnum.decoy_domain], _("config.Domain already used")))
284
+ [cc.value.lower() for cc in StrConfig.query.filter(StrConfig.child_id == Child.current().id).all() if cc.key != c.key and "fakedomain" in cc.key and cc.key != ConfigEnum.decoy_domain], _("config.Domain_already_used")))
280
285
 
281
286
  render_kw['required'] = ""
282
287
  if len(c.value) < 3:
@@ -292,36 +297,36 @@ def get_config_form():
292
297
  if c.key == ConfigEnum.parent_panel:
293
298
  validators.append(wtf.validators.Regexp("()|(http(s|)://([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})/.*)", re.IGNORECASE, _("Invalid admin link")))
294
299
  if c.key == ConfigEnum.telegram_bot_token:
295
- validators.append(wtf.validators.Regexp("()|^([0-9]{8,12}:[a-zA-Z0-9_-]{30,40})|$", re.IGNORECASE, _("config.Invalid telegram bot token")))
300
+ validators.append(wtf.validators.Regexp("()|^([0-9]{8,12}:[a-zA-Z0-9_-]{30,40})|$", re.IGNORECASE, _("config.Invalid_telegram_bot_token")))
296
301
  if c.key == ConfigEnum.branding_site:
297
302
  validators.append(wtf.validators.Regexp(
298
- "()|(http(s|)://([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})/?.*)", re.IGNORECASE, _("config.Invalid brand link")))
303
+ "()|(http(s|)://([A-Za-z0-9\\-\\.]+\\.[a-zA-Z]{2,})/?.*)", re.IGNORECASE, _("config.Invalid_brand_link")))
299
304
  # render_kw['required']=""
300
305
 
301
306
  if 'secret' in c.key:
302
307
  validators.append(wtf.validators.Regexp(
303
- "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", re.IGNORECASE, _('config.invalid uuid')))
308
+ "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", re.IGNORECASE, _('config.invalid_uuid')))
304
309
  render_kw['required'] = ""
305
310
 
306
311
  if c.key == ConfigEnum.proxy_path:
307
- validators.append(wtf.validators.Regexp("^[a-zA-Z0-9]*$", re.IGNORECASE, _("config.Invalid proxy path")))
312
+ validators.append(wtf.validators.Regexp("^[a-zA-Z0-9]*$", re.IGNORECASE, _("config.Invalid_proxy_path")))
308
313
  render_kw['required'] = ""
309
314
 
310
315
  if 'port' in c.key:
311
316
  if c.key in [ConfigEnum.http_ports, ConfigEnum.tls_ports]:
312
- validators.append(wtf.validators.Regexp("^(\\d+)(,\\d+)*$", re.IGNORECASE, _("config.Invalid port")))
317
+ validators.append(wtf.validators.Regexp("^(\\d+)(,\\d+)*$", re.IGNORECASE, _("config.Invalid_port")))
313
318
  render_kw['required'] = ""
314
319
  else:
315
- validators.append(wtf.validators.Regexp("^(\\d+)(,\\d+)*$|^$", re.IGNORECASE, _("config.Invalid port")))
320
+ validators.append(wtf.validators.Regexp("^(\\d+)(,\\d+)*$|^$", re.IGNORECASE, _("config.Invalid_port")))
316
321
  # validators.append(wtf.validators.Regexp("^(\d+)(,\d+)*$",re.IGNORECASE,_("config.port is required")))
317
322
 
318
323
  # tls tricks validations
319
324
  if c.key in [ConfigEnum.tls_fragment_size, ConfigEnum.tls_fragment_sleep, ConfigEnum.tls_padding_length, ConfigEnum.wireguard_noise_trick]:
320
- validators.append(wtf.validators.Regexp("^\\d+-\\d+$", re.IGNORECASE, _("config.Invalid! The pattern is number-number") + f' {c.key}'))
325
+ validators.append(wtf.validators.Regexp("^\\d+-\\d+$", re.IGNORECASE, _("config.Invalid_The_pattern_is_number-number") + f' {c.key}'))
321
326
  # mux and hysteria validations
322
327
  if c.key in [ConfigEnum.hysteria_up_mbps, ConfigEnum.hysteria_down_mbps, ConfigEnum.mux_max_connections, ConfigEnum.mux_min_streams, ConfigEnum.mux_max_streams,
323
328
  ConfigEnum.mux_brutal_down_mbps, ConfigEnum.mux_brutal_up_mbps]:
324
- validators.append(wtf.validators.Regexp("^\\d+$", re.IGNORECASE, _("config.Invalid! it should be a number only") + f' {c.key}'))
329
+ validators.append(wtf.validators.Regexp("^\\d+$", re.IGNORECASE, _("config.Invalid_it_should_be_a_number_only") + f' {c.key}'))
325
330
 
326
331
  for val in validators:
327
332
  if hasattr(val, "regex"):
@@ -10,7 +10,7 @@ from wtforms.validators import NumberRange
10
10
  from flask_babel import lazy_gettext as _
11
11
  from flask import g, request # type: ignore
12
12
  from markupsafe import Markup
13
- from sqlalchemy import desc
13
+ from sqlalchemy import desc, func
14
14
 
15
15
  from hiddifypanel.hutils.flask import hurl_for
16
16
  from wtforms.validators import Regexp, ValidationError
@@ -25,6 +25,7 @@ from hiddifypanel import hutils
25
25
 
26
26
 
27
27
  class UserAdmin(AdminLTEModelView):
28
+ column_default_sort = ('id', False) # Sort by username in ascending order
28
29
 
29
30
  column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days", "comment", 'last_online', "uuid", 'remaining_days']
30
31
  column_searchable_list = ["uuid", "name"]
@@ -112,7 +113,7 @@ class UserAdmin(AdminLTEModelView):
112
113
  # usage_limit_GB="in GB",
113
114
  # current_usage_GB="in GB"
114
115
  comment=_("Add some text that is only visible to you."),
115
- mode=_("Define the user mode. Should the usage reset every month?"),
116
+ mode=_("user.define_mode"),
116
117
  last_reset_time=_("If monthly is enabled, the usage will be reset after 30 days from this date."),
117
118
  start_date=_("From when the user package will be started? Empty for start from first connection"),
118
119
  package_days=_("How many days this package should be available?")
@@ -376,15 +377,17 @@ class UserAdmin(AdminLTEModelView):
376
377
  abort(403)
377
378
 
378
379
  query = query.filter(User.added_by.in_(admin.recursive_sub_admins_ids()))
379
- query = query.order_by(desc(User.id))
380
+
380
381
  return query
381
382
 
382
383
  # Override get_count_query() to include the filter condition in the count query
383
384
  def get_count_query(self):
384
385
  # Get the base count query
385
386
 
386
- # query = query.session.query(func.count(User.id))
387
+ # query = self.session.query(func.count(User.id)).
388
+
387
389
  query = super().get_count_query()
390
+
388
391
  admin_id = int(request.args.get("admin_id") or g.account.id)
389
392
  if admin_id not in g.account.recursive_sub_admins_ids():
390
393
  abort(403)
@@ -1,19 +1,20 @@
1
1
  {% extends 'hiddify-flask-admin/list.html' %}
2
2
 
3
3
  {% block body %}
4
- {{super()}}
4
+ {{super()}}
5
5
 
6
- <div class="callout callout-info">
7
- {{_('In this section, you can add your domain. You need to add at least one domain in direct mode.')}}
8
- </div>
6
+ <div class="callout callout-info">
7
+ {{_('domain.intro')}}
8
+ </div>
9
9
 
10
10
 
11
- <style>
12
- .table td{
13
- vertical-align: middle;
14
- }
15
- td.col-domain{
16
- /* direction:ltr; */
17
- }
18
- </style>
11
+ <style>
12
+ .table td {
13
+ vertical-align: middle;
14
+ }
15
+
16
+ td.col-domain {
17
+ /* direction:ltr; */
18
+ }
19
+ </style>
19
20
  {% endblock %}
@@ -11,7 +11,8 @@
11
11
  <button onclick="show_send_message('all')" type="button" class="btn hbtn bg-h-green">
12
12
  {{_("All Users")}}
13
13
  </button>
14
- <button onclick="show_send_message('active')" type="button" class="btn hbtn bg-h-green" style="margin-left: 5px;margin-right: 5px;">
14
+ <button onclick="show_send_message('active')" type="button" class="btn hbtn bg-h-green"
15
+ style="margin-left: 5px;margin-right: 5px;">
15
16
  {{_("Active Users")}}
16
17
  </button>
17
18
  <button onclick="show_send_message('selected')" type="button" class="btn hbtn bg-h-green">
@@ -22,9 +23,11 @@
22
23
  <div style="margin-top: 10px;">
23
24
  <button onclick="show_send_message('offline 1h')" type="button" class="btn hbtn bg-h-red">
24
25
  {{_("Offline more than 1 hour")}}</button>
25
- <button onclick="show_send_message('offline 1d')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;">
26
+ <button onclick="show_send_message('offline 1d')" type="button" class="btn hbtn bg-h-red"
27
+ style="margin-left: 5px;">
26
28
  {{_("Offline more than 1 day")}}</button>
27
- <button onclick="show_send_message('offline 1w')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;margin-right: 5px;">
29
+ <button onclick="show_send_message('offline 1w')" type="button" class="btn hbtn bg-h-red"
30
+ style="margin-left: 5px;margin-right: 5px;">
28
31
  {{_("Offline more than 1 week")}}
29
32
  </button>
30
33
  <button onclick="show_send_message('expired')" type="button" class="btn hbtn bg-h-red">
@@ -37,7 +40,7 @@
37
40
  {{super()}}
38
41
 
39
42
  <div class="callout callout-success">
40
- {{_('User usage will be updated every 6 minutes. To update it now click <a href="%(link)s" class="btn btn-info">here</a>',link=hurl_for("admin.Actions:update_usage"))}}
43
+ {{_('adminuser.force_update_usage',link=hurl_for("admin.Actions:update_usage"))}}
41
44
 
42
45
  </div>
43
46
  <style>
@@ -155,4 +158,4 @@
155
158
  }
156
159
 
157
160
  </script>
158
- {% endblock %}
161
+ {% endblock %}
@@ -5,7 +5,7 @@
5
5
  {% block tail %}
6
6
  {{super()}}
7
7
 
8
- {{modal('quicksetup',_('Save Link'),_('Please note that your panel can be accessed only via <a href="%(adminlink)s" class="btn btn-primary copy-link">%(adminlink)s</a>. Please save this link.',adminlink=admin_link),show=Fales)}}
8
+ {{modal('quicksetup',_('Save Link'),_('admin.save_panel_link',adminlink=admin_link),show=Fales)}}
9
9
 
10
10
  <script>
11
11
  disable_notif = false
@@ -43,26 +43,17 @@
43
43
 
44
44
 
45
45
  <div class="row">
46
- <div class="card col-12">
47
- <div class="card-header">{{_("config.lang.label")}}</div>
48
- <div class="card-body">
49
46
 
50
- <div class="row">
51
- <div class="col-12">
52
47
 
53
- {{render_form(lang_form)}}
54
- </div>
55
- </div>
56
- </div>
57
- </div>
58
-
59
- <div class="card">
48
+ <div class="card col-12">
60
49
  <div class="card-header">{{_("admin.quicksetup.title")}}</div>
61
50
  <div class="card-body">
62
51
 
63
52
  <div class="row">
64
53
  <div class="card-columns">
54
+ {% if ipv4 or ipv6 %}
65
55
  {{_('admin.quicksetup_intro',ipv4=ip_btn(ipv4),ipv6=ip_btn(ipv6))}}
56
+ {%endif%}
66
57
  {{render_form(form,form_type="",extra_classes="card-columns1")}}
67
58
  </div>
68
59
  </div>
@@ -71,9 +62,24 @@
71
62
  </div>
72
63
 
73
64
  <style>
65
+ .form-group {
66
+ display: inline-block;
67
+ width: 100%;
68
+ }
69
+
70
+ fieldset {
71
+ margin: 20px 0;
72
+ }
73
+
74
+ legend {
75
+ background: transparent;
76
+ margin-bottom: 20px;
77
+
78
+ }
79
+
74
80
  @media (min-width: 576px) {
75
81
  .card-columns {
76
- column-count: 1;
82
+ column-count: 2;
77
83
  }
78
84
  }
79
85
 
@@ -40,7 +40,8 @@ page."),temp_link(),show=False)}}
40
40
  <label class="col-6" id="process-title"></label> <label class="col-6" id="process-details"></label>
41
41
  </div>
42
42
  <div class="progress">
43
- <div id="progress" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
43
+ <div id="progress" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0"
44
+ aria-valuemax="100"></div>
44
45
  </div>
45
46
  </div>
46
47
  {% endif %}
@@ -153,7 +154,7 @@ page."),temp_link(),show=False)}}
153
154
  $("#process-title").html("")
154
155
  bootbox.alert({
155
156
  title: '{{_("Success")}}',
156
- message: '{{_("The action done successfully. You can now leave this page.")}}',
157
+ message: '{{_("admin.action_done_sucess")}}',
157
158
  locale: '{{get_locale()}}',
158
159
  });
159
160
  }
@@ -25,7 +25,7 @@
25
25
  # column_descriptions = dict(
26
26
  # domain=_("domain.description"),
27
27
  # alias=_('The name shown in the configs for this domain.'),
28
- # show_domains=_('You can select the configs with which domains show be shown in the user area. If you select all, automatically, all the new domains will be added for each users.')
28
+ # show_domains=_('domain.show_domains_description')
29
29
  # # current_usage_GB="in GB"
30
30
  # )
31
31
 
@@ -129,6 +129,7 @@ class UserSchema(Schema):
129
129
  lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
130
130
  enable = Boolean(required=False, description="Whether the user is enabled or not")
131
131
  is_active = Boolean(required=False, description="Whether the user is active for using hiddify")
132
+ id = Integer(required=False, description="never use it, only for better presentation")
132
133
 
133
134
 
134
135
  class PostUserSchema(UserSchema):
@@ -137,6 +138,7 @@ class PostUserSchema(UserSchema):
137
138
  # the uuid is sent in the url path
138
139
  self.fields['uuid'].required = False
139
140
  self.fields['uuid'].allow_none = True
141
+ del self.fields['id']
140
142
 
141
143
 
142
144
  class PatchUserSchema(UserSchema):
@@ -146,6 +148,7 @@ class PatchUserSchema(UserSchema):
146
148
  self.fields['uuid'].allow_none = True,
147
149
  self.fields['name'].required = False
148
150
  self.fields['name'].allow_none = True,
151
+ del self.fields['id']
149
152
 
150
153
 
151
154
  # endregion
@@ -30,10 +30,7 @@ def send_welcome(message):
30
30
  with force_locale(user.lang or hconfig(ConfigEnum.lang)):
31
31
  bot.reply_to(message, get_usage_msg(user.uuid), reply_markup=user_keyboard(user.uuid))
32
32
  else:
33
- bot.reply_to(message,
34
- _("Hooray \U0001F389 \U0001F389 \U0001F389 \n"
35
- "Welcome to hiddifybot.\n"
36
- "Start by clicking the link on the panel or entering your UUID."))
33
+ bot.reply_to(message, _("bot.welcome"))
37
34
 
38
35
 
39
36
  def user_keyboard(uuid):
@@ -32,7 +32,7 @@ def send_welcome(message):
32
32
 
33
33
  def start_admin(message):
34
34
 
35
- bot.reply_to(message, _("Welcome to admin bot. Choose your action"), reply_markup=admin_keyboard_main())
35
+ bot.reply_to(message, _("bot.admin_welcome"), reply_markup=admin_keyboard_main())
36
36
 
37
37
 
38
38
  def get_admin_by_tgid(message):
@@ -136,14 +136,14 @@ def create_package(call): # <- passes a CallbackQuery type object to your funct
136
136
  days = int(splt[2])
137
137
  count = int(splt[3])
138
138
  domain = int(splt[4])
139
- new_text = _("Please Wait...")
139
+ new_text = _("Please Wait")
140
140
  bot.edit_message_text(new_text, call.message.chat.id, call.message.message_id, reply_markup=None)
141
141
  domain = Domain.query.filter(Domain.id == domain).first()
142
142
  from . import Usage
143
143
  admin_id = admin.id
144
144
  admin_name = admin.name
145
145
  for i in range(1, count + 1):
146
- new_text = _("Please Wait...") + f' {i}/{count}'
146
+ new_text = _("Please Wait") + f' {i}/{count}'
147
147
  bot.edit_message_text(new_text, call.message.chat.id, call.message.message_id, reply_markup=None)
148
148
  user = User(package_days=days, usage_limit_GB=gig, name=f"{admin_name} auto {i} {datetime.date.today()}", added_by=admin_id)
149
149
  db.session.add(user)