Compare commits

...

279 Commits
1.5.5 ... 1.7.2

Author SHA1 Message Date
Alireza Ahmadi
47efac270d v1.7.2 2024-02-21 10:08:14 +01:00
Alireza Ahmadi
368eb89cd9 revert main page style 2024-02-21 02:24:28 +01:00
Alireza Ahmadi
83e360ea1b simplify log and text modals 2024-02-21 00:36:55 +01:00
Alireza Ahmadi
6cdeb7ebeb [dark] change message by theme 2024-02-20 22:12:51 +01:00
Alireza Ahmadi
035a1a7b5e [docker] use go 1.22 2024-02-20 21:20:05 +01:00
Alireza Ahmadi
857f0cb64c [wg] fix subnet in peer 2024-02-20 21:11:12 +01:00
Alireza Ahmadi
849a1249ac Merge pull request #990 from MHSanaei/main
x86 Arch Support
2024-02-20 18:36:20 +01:00
MHSanaei
d608961af8 x86 Arch Support 2024-02-20 20:49:01 +03:30
Alireza Ahmadi
343e7a9f15 [ui] fix loading function 2024-02-20 18:07:27 +01:00
Alireza Ahmadi
6ad558bd36 [wg] new peer with one IP 2024-02-20 18:06:24 +01:00
Alireza Ahmadi
d6bf64f760 update packages 2024-02-20 17:46:00 +01:00
Alireza Ahmadi
f8d20c8303 [xray] fakedns support 2024-02-20 14:05:53 +01:00
Alireza Ahmadi
08403bc8f9 [sub] json + fragment 2024-02-20 11:29:37 +01:00
Alireza Ahmadi
cdb90da138 security alert translations 2024-02-20 10:44:47 +01:00
Alireza Ahmadi
c3d498f9ee [tls] min-max version view 2024-02-20 10:24:03 +01:00
Alireza Ahmadi
0621eb0670 settings security alert 2024-02-19 23:04:20 +01:00
Alireza Ahmadi
577c534e4c remove favicon 2024-02-19 17:56:32 +01:00
Alireza Ahmadi
17dae4a563 open sniffing for all protocols #969 2024-02-19 15:13:51 +01:00
Alireza Ahmadi
9f1f841666 [xray] add balancer
Co-Authored-By: Ho3ein <33454419+mhsanaei@users.noreply.github.com>
2024-02-19 14:27:21 +01:00
X-Oracle
6b66c250b5 fix : add msiing fi (#980) 2024-02-19 13:37:28 +01:00
Alireza Ahmadi
0829116fc1 adapting family protection in dns 2024-02-19 10:32:43 +01:00
Alireza Ahmadi
aae0011a4f Update README.md
Co-Authored-By: Ho3ein <33454419+mhsanaei@users.noreply.github.com>
2024-02-19 10:21:07 +01:00
Alireza Ahmadi
f4c565b208 [ui] small changes 2024-02-18 23:00:37 +01:00
Alireza Ahmadi
2520994b13 [xray] dns 2024-02-18 22:57:54 +01:00
X-Oracle
d3fd56fc97 Safe x-ui update And a function renaming (#961) 2024-02-18 22:37:02 +01:00
Rapunzel
d4df614a9f Improvement for ZH-CN translation (#970) 2024-02-18 22:36:52 +01:00
Alireza Ahmadi
640eb538a4 fix log_writer xray label 2024-02-18 16:38:51 +01:00
Alireza Ahmadi
08153d45d1 fix empty logs error 2024-02-18 00:26:17 +01:00
Alireza Ahmadi
843bb5f3ce hide sub options if sub is not enable 2024-02-18 00:19:23 +01:00
Alireza Ahmadi
7995310aa7 v1.7.1 2024-02-16 13:48:36 +01:00
Shahin
c277fd29fd minor fix in outbound (#946)
* Update outbound.html

* Update outbound.js

* Update outbound.html

* Update outbound.html

* Update outbound.html
2024-02-16 13:25:52 +01:00
Alireza Ahmadi
6249528a58 Merge pull request #950 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.61.1
Bump google.golang.org/grpc from 1.61.0 to 1.61.1
2024-02-16 13:21:20 +01:00
Alireza Ahmadi
83c26ad81d v1.7.1 2024-02-16 13:20:51 +01:00
Alireza Ahmadi
9b5379c0e7 [reality] enhanced number of shortIDs 2024-02-16 12:10:26 +01:00
Alireza Ahmadi
8041950c29 [sub] random reality params 2024-02-16 11:57:17 +01:00
Alireza Ahmadi
541170c3c2 [xray] add meta option to warp 2024-02-15 22:55:44 +01:00
dependabot[bot]
47ec105d1f Bump google.golang.org/grpc from 1.61.0 to 1.61.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.0 to 1.61.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 21:11:51 +00:00
Alireza Ahmadi
525327c4ac [xray] option logs #947 2024-02-14 13:27:25 +01:00
Alireza Ahmadi
0267a1b32a [wg] auto multi-peer and qrcode #896 2024-02-14 11:17:54 +01:00
Alireza Ahmadi
83df4ae7cf [outbound] set master outbound #861 2024-02-13 22:12:17 +01:00
Alireza Ahmadi
24465aeb43 [feature] export subs #880 2024-02-13 21:58:43 +01:00
Alireza Ahmadi
ee2bbffc8f [logs] new bug-free log_writer 2024-02-13 19:35:06 +01:00
Shahin
294a3f46a0 Overall text enhancement (#892)
* Update translate.zh_Hans.toml

* Update translate.vi_VN.toml

* Update translate.ru_RU.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update client_bulk_modal.html

* Update client.html

* Update inbound.html

* Update index.html

* Update warp_modal.html

* Update common_sider.html

* Update inbounds.html

* Update client_bulk_modal.html

* Update inbound.html

* Update client.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.vi_VN.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.zh_Hans.toml

* Update translate.vi_VN.toml

* Update translate.ru_RU.toml

* Update index.html

* Update index.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update inbounds.html

* Update settings.html

* Update xray.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update index.html

* Update index.html

* Update index.html

* Update index.html

* Update server.go

---------

Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2024-02-13 18:38:06 +01:00
Shahin
44a1104bff Update login.html (#931) 2024-02-13 18:37:10 +01:00
Jalal Saberi
fbc4ba1ba0 Update Uninstall Option (#942)
after uninstall, script will delete itself and show Install & Upgrade command for installing again if user need that.
2024-02-13 18:36:35 +01:00
Ho3ein
b829ebca2b minor changes (#922)
* ipv6 for family Protection

* warp - ForceIP

* fix bug in edit SOCKS and HTTP outbound

Co-Authored-By: Saeid <43953720+surbiks@users.noreply.github.com>

* lang - export inbound

* reset button - publicKey & psk

* fix - Ensure logs are not null in show method

---------

Co-authored-by: Saeid <43953720+surbiks@users.noreply.github.com>
2024-02-13 18:36:25 +01:00
Alireza Ahmadi
288c0b982a Merge branch 'main' of https://github.com/alireza0/x-ui 2024-02-13 17:58:03 +01:00
Alireza Ahmadi
8919099594 small fixes 2024-02-13 17:57:57 +01:00
Alireza Ahmadi
3ee7761ab0 Merge pull request #929 from alireza0/dependabot/go_modules/gorm.io/driver/sqlite-1.5.5
Bump gorm.io/driver/sqlite from 1.5.4 to 1.5.5
2024-02-06 23:22:11 +01:00
dependabot[bot]
346a82b203 Bump gorm.io/driver/sqlite from 1.5.4 to 1.5.5
Bumps [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite) from 1.5.4 to 1.5.5.
- [Commits](https://github.com/go-gorm/sqlite/compare/v1.5.4...v1.5.5)

---
updated-dependencies:
- dependency-name: gorm.io/driver/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 22:22:01 +00:00
Alireza Ahmadi
45e2d66f4b Merge pull request #930 from alireza0/dependabot/go_modules/gorm.io/gorm-1.25.7
Bump gorm.io/gorm from 1.25.6 to 1.25.7
2024-02-06 23:21:02 +01:00
dependabot[bot]
36ce3e0130 Bump gorm.io/gorm from 1.25.6 to 1.25.7
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.6 to 1.25.7.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.6...v1.25.7)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 21:38:58 +00:00
Alireza Ahmadi
a04f2306bc Merge pull request #915 from alireza0/dependabot/go_modules/github.com/nicksnyder/go-i18n/v2-2.4.0
Bump github.com/nicksnyder/go-i18n/v2 from 2.3.0 to 2.4.0
2024-02-02 17:55:08 +01:00
Alireza Ahmadi
fdc3b15efe Merge pull request #916 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.24.1
Bump github.com/shirou/gopsutil/v3 from 3.23.12 to 3.24.1
2024-02-02 17:54:58 +01:00
Alireza Ahmadi
2fb5168918 Merge branch 'main' of https://github.com/alireza0/x-ui 2024-02-02 17:53:49 +01:00
Alireza Ahmadi
1fcf3d68f1 bug fix log_writer #872 2024-02-02 17:53:40 +01:00
dependabot[bot]
de654fdbc4 Bump github.com/shirou/gopsutil/v3 from 3.23.12 to 3.24.1
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.12 to 3.24.1.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.12...v3.24.1)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 21:22:52 +00:00
dependabot[bot]
e32c723d68 Bump github.com/nicksnyder/go-i18n/v2 from 2.3.0 to 2.4.0
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 21:26:24 +00:00
Alireza Ahmadi
41089df567 Merge pull request #894 from alireza0/dependabot/go_modules/gorm.io/gorm-1.25.6
Bump gorm.io/gorm from 1.25.5 to 1.25.6
2024-01-28 16:36:34 +01:00
dependabot[bot]
a703f70302 Bump gorm.io/gorm from 1.25.5 to 1.25.6
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.5 to 1.25.6.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.5...v1.25.6)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-26 21:57:05 +00:00
Alireza Ahmadi
3c922d8673 Merge pull request #890 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.61.0
Bump google.golang.org/grpc from 1.60.1 to 1.61.0
2024-01-26 13:18:31 +01:00
Alireza Ahmadi
78c3912002 fix downloaded log format 2024-01-26 13:09:13 +01:00
dependabot[bot]
0b8f5f7f67 Bump google.golang.org/grpc from 1.60.1 to 1.61.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.1 to 1.61.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.60.1...v1.61.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-24 21:48:59 +00:00
Alireza Ahmadi
ca3d45ddb4 fix some styles #887
Co-Authored-By: Ho3ein <33454419+mhsanaei@users.noreply.github.com>
2024-01-23 23:24:08 +01:00
Alireza Ahmadi
fff74d7ea7 [bug] avoid empty inbound #884 2024-01-23 22:01:57 +01:00
Pavel Volkov
b3393e402b fix: SS2022 password generator (#858)
* fix: SS2022 password generator
2024-01-19 14:48:10 +01:00
Alireza Ahmadi
30d7376463 [bug] fix switch enable inbound 2024-01-12 01:10:30 +01:00
Alireza Ahmadi
07f507dc1f Merge pull request #851 from MHSanaei/main
update translations
2024-01-11 17:59:39 +01:00
MHSanaei
49aafa5657 update translations 2024-01-11 18:39:08 +03:30
Alireza Ahmadi
a078335d4c v1.7.0 2024-01-11 14:12:23 +01:00
Alireza Ahmadi
025545f0a4 small enhancements 2024-01-11 14:11:58 +01:00
Alireza Ahmadi
85c2d2ecd7 wireguard info page 2024-01-11 14:11:35 +01:00
Alireza Ahmadi
2026bd7546 update readme and pics 2024-01-11 01:51:39 +01:00
Alireza Ahmadi
f286080edd small fixes 2024-01-11 01:51:12 +01:00
Alireza Ahmadi
d36118fd95 [ui] user settings 2024-01-11 01:00:32 +01:00
shahin-io
1ee6e39bc3 Update EN & FA translation for WARP (#847)
* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml
2024-01-11 00:54:25 +01:00
shahin-io
d8e4f85e03 Small adjustment (#846)
* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update stream_ws.html

* Update stream_tcp.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update translate.zh_Hans.toml

* Update translate.zh_Hans.toml

* Update translate.vi_VN.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml
2024-01-11 00:02:53 +01:00
Alireza Ahmadi
dd2ce332a6 [feature] WARP via wireguard 2024-01-11 00:01:27 +01:00
Alireza Ahmadi
41b0fe4af4 [ui] separate outbound and reverse 2024-01-11 00:00:35 +01:00
Ho3ein
c841c1f148 Minor changes (#844)
* set alpn h2 and https1 for tls

* VLESS as default

* if there is no security set to none

* better view for Allowed IPs wireguard outbound

* cpuCoreFormat

* matrix release + arm arch

s390x removed

* fix typo

* remove migrate_v2_ui

* bash - custom version

* fix translation

* update xray to 1.8.7

* auto gen + button for kcp & quic

* fix typo

* Centralized Xray URLs
2024-01-10 23:56:42 +01:00
Alireza Ahmadi
3b3d70aeaa [wg] auto generate keys 2024-01-10 02:16:43 +01:00
Alireza Ahmadi
2a311287bf fix translation 2024-01-10 02:16:17 +01:00
Alireza Ahmadi
dbb17f5829 [feature] wireguard inbound 2024-01-09 23:23:37 +01:00
Alireza Ahmadi
93412f8a21 fix shadowsocks inbound 2024-01-09 23:22:59 +01:00
Alireza Ahmadi
b63f305737 fix log writer crash 2024-01-09 23:22:44 +01:00
Alireza Ahmadi
2cd6305fe8 wireguard translations 2024-01-09 23:22:25 +01:00
Alireza Ahmadi
37b7c89d5e Merge pull request #842 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.7
Bump github.com/xtls/xray-core from 1.8.6 to 1.8.7
2024-01-09 01:05:23 +01:00
shahin-io
df84b2fa46 Update README.md (#841)
* Update README.md

* Update README.md

* Update README.md
2024-01-09 01:05:05 +01:00
dependabot[bot]
d0a99a469b Bump github.com/xtls/xray-core from 1.8.6 to 1.8.7
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.6...v1.8.7)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 22:00:53 +00:00
shahin-io
47792cdbd3 Small adjustment (#840)
* Update translate.en_US.toml

* Update outbound.html
2024-01-08 21:32:10 +01:00
Alireza Ahmadi
4df056f6a2 [outbound] update wireguard 2024-01-08 20:23:34 +01:00
shahin-io
94ac29ba7f Overall enhancement (#785)
* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update outbound.html

* Update tls_settings.html

* Update stream_tcp.html

* Update stream_ws.html

* Update vless.html

* Update trojan.html

* Update stream_sockopt.html

* Update stream_tcp.html

* Update stream_tcp.html

* Update stream_sockopt.html

* Update index.html

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update outbound.html

* Update translate.en_US.toml

* Update translate.vi_VN.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.vi_VN.toml

* Update translate.ru_RU.toml

* Update translate.zh_Hans.toml

* Update client.html

* Update inbound.html

* Update translate.zh_Hans.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update client_bulk_modal.html

* Update client.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update client_bulk_modal.html

* Update client.html

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update tls_settings.html

* Update outbound.html

* Update outbound.html

* Update trojan.html

* Update vless.html

* Update tls_settings.html

* Update index.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update outbound.html

* Update shadowsocks.html

* Update tls_settings.html

* Update tls_settings.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update index.html

* Update index.html

* Update translate.vi_VN.toml

* Update translate.vi_VN.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update tls_settings.html

* Update translate.en_US.toml

* Update client_modal.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update inbound_info_modal.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update index.html

* Update index.html

* Update index.html

* Update index.html

* Update client_modal.html

* Update client_modal.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update client_bulk_modal.html

* Update client.html

* Update inbound.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update client_bulk_modal.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update outbound.html

* Update stream_kcp.html

* Update stream_quic.html

* Update tls_settings.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update stream_sockopt.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update vless.html

* Update trojan.html

* Update client_bulk_modal.html

* Update client.html

* Update client_bulk_modal.html

* Update client.html

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml
2024-01-08 18:49:47 +01:00
Sudo Space
3a174144c4 Add wireguard to xray outbound (#763)
* Add wireguard to xray outbound

* [Fix little mistake] Add wireguard to xray outbound

* [Requested changes] Add wireguard to xray outbound

* Remove DNS field and add keepAlive field
2024-01-08 17:12:05 +01:00
Alireza Ahmadi
8b64976eef [bug] fix tcp http header version 2024-01-05 19:41:43 +01:00
Alireza Ahmadi
56336cbcef Merge pull request #825 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.23.12
Bump github.com/shirou/gopsutil/v3 from 3.23.11 to 3.23.12
2024-01-05 02:42:14 +01:00
Alireza Ahmadi
43bc53a10e Merge pull request #826 from Enkidu-6/patch-1
Update README.md
2024-01-05 02:42:02 +01:00
Alireza Ahmadi
babe3e8788 fix switchEnable in filter mode #827 2024-01-05 02:40:43 +01:00
Enkidu
c247cd60b8 Update README.md 2024-01-02 01:58:53 -05:00
dependabot[bot]
06507f0e1f Bump github.com/shirou/gopsutil/v3 from 3.23.11 to 3.23.12
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.11 to 3.23.12.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.11...v3.23.12)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 21:13:55 +00:00
shahin-io
6a39ee037d Update README.md (#803)
I've updated it to reflect a more comprehensive set of features for the panel.
2023-12-31 17:05:13 +01:00
Alireza Ahmadi
96f9c73f5f Merge pull request #793 from shahin-io/patch-1
Update xray.html
2023-12-22 09:07:37 +01:00
Alireza Ahmadi
15f84de363 Merge pull request #798 from nonenty/main
fix: Unable to add routing rule that blocks bittorrent
2023-12-22 09:06:55 +01:00
Alireza Ahmadi
2d07b64839 fix protocol in routing rules modal #797 2023-12-22 09:05:07 +01:00
BizarraPan [Z+8]
1ef299951e Update xray_rule_modal.html
enable multiple select for rule.protocol
2023-12-22 14:42:27 +08:00
shahin-io
83fed835e1 Update xray.html
hello dear @alireza0 
 
As you may have noticed, the geo repository that manages Iran IPs has recently made changes to their traffic management rules. I’ve edited the file to reflect new changes.
2023-12-20 16:24:02 +00:00
Alireza Ahmadi
6e29022998 Merge pull request #790 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.60.1
Bump google.golang.org/grpc from 1.60.0 to 1.60.1
2023-12-20 00:21:09 +01:00
dependabot[bot]
5e77dcb198 Bump google.golang.org/grpc from 1.60.0 to 1.60.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.0 to 1.60.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.60.0...v1.60.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 21:22:08 +00:00
Alireza Ahmadi
2d7607165b fix outbound socks/http #783 2023-12-19 14:59:49 +01:00
Alireza Ahmadi
c9e4918494 v1.6.4 2023-12-19 02:00:40 +01:00
Alireza Ahmadi
63b166ce40 fix filter view in inbounds 2023-12-19 01:06:29 +01:00
shahin-io
2ab1726131 unifying texts (#765)
* Update install.sh

* Update x-ui.sh

* Update install.sh

* Update stream_tcp.html

* Update stream_ws.html

* Update tls_settings.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.zh_Hans.toml

* Update translate.vi_VN.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update dokodemo.html

* Update shadowsocks.html

* Update socks.html

* Update xray_rule_modal.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update dokodemo.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update index.html

* Update vless.html

* Update trojan.html

* Update outbound.html

* Update tls_settings.html

* Update dokodemo.html

* Update outbound.html

* Update stream_kcp.html

* Update outbound.html

* Update xray.html

* Update xray.html

* Update xray.html

* Update xray.html

* Update xray.html
2023-12-19 00:47:49 +01:00
Alireza Ahmadi
7dd5449cf4 [xray] add user field 2023-12-18 18:17:54 +01:00
Alireza Ahmadi
ded780b11a fix reverse edit/delete #766 2023-12-15 21:07:27 +01:00
shahin-io
2e44501573 fix small typo here and there (#761)
* Update install.sh

* Update x-ui.sh

* Update install.sh

* Update stream_tcp.html

* Update stream_ws.html

* Update tls_settings.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.zh_Hans.toml

* Update translate.vi_VN.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update dokodemo.html

* Update shadowsocks.html

* Update socks.html

* Update xray_rule_modal.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update dokodemo.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update index.html

* Update translate.en_US.toml

* Update translate.en_US.toml

* Update translate.en_US.toml
2023-12-14 22:36:32 +01:00
Alireza Ahmadi
3e7770ad97 Merge pull request #762 from MHSanaei/main
bug fix - lang
2023-12-14 22:32:53 +01:00
MHSanaei
0c380aebdd bug fix - lang 2023-12-14 10:23:47 +03:30
Alireza Ahmadi
93f6ba897c v1.6.3 2023-12-13 18:18:10 +01:00
Alireza Ahmadi
b93adb7435 v1.6.3 2023-12-13 17:50:06 +01:00
Alireza Ahmadi
538fdb9860 change fallback form to new design 2023-12-13 15:21:54 +01:00
Alireza Ahmadi
c4ed595a1c better information view 2023-12-13 15:17:11 +01:00
Alireza Ahmadi
5acfdf584e password autogenerate for trojan client #751 2023-12-13 15:00:36 +01:00
Alireza Ahmadi
62ceaeba8f [gui] remove login animations 2023-12-13 14:55:24 +01:00
Alireza Ahmadi
3c688d85e7 Merge pull request #755 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.60.0
Bump google.golang.org/grpc from 1.59.0 to 1.60.0
2023-12-13 13:38:25 +01:00
dependabot[bot]
99a4cf4179 Bump google.golang.org/grpc from 1.59.0 to 1.60.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.59.0 to 1.60.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.59.0...v1.60.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-13 12:37:50 +00:00
Alireza Ahmadi
7aa242b64e Merge pull request #749 from alireza0/dependabot/go_modules/github.com/pelletier/go-toml/v2-2.1.1
Bump github.com/pelletier/go-toml/v2 from 2.1.0 to 2.1.1
2023-12-13 13:37:02 +01:00
shahin-io
cbb3c009cc Small typo edit (#753)
* Update install.sh

* Update x-ui.sh

* Update install.sh
2023-12-13 13:36:16 +01:00
Alireza Ahmadi
d8e8195271 [bug] fix errors in link converter 2023-12-13 13:34:56 +01:00
Alireza Ahmadi
e31ec2cade [gui] redesign forms 2023-12-13 12:22:08 +01:00
dependabot[bot]
af42723213 Bump github.com/pelletier/go-toml/v2 from 2.1.0 to 2.1.1
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 21:26:45 +00:00
Alireza Ahmadi
94117f94e8 v1.6.2 2023-12-11 15:17:57 +01:00
Alireza Ahmadi
b946f7a1d4 new geofile strategy 2023-12-10 16:01:13 +01:00
Alireza Ahmadi
aaa0f66a37 small fixes 2023-12-10 15:56:07 +01:00
Alireza Ahmadi
ebbe2c8e5b Merge pull request #739 from TaraRostami/main
Update xq.css
2023-12-10 15:41:54 +01:00
Enkidu
0fdd4dcfb9 Update x-ui.sh (#729)
* Update x-ui.sh

Change log:

- Made the code less bulky. More code = more potential for bugs and vulnerabilities

- The code is now showing the progress of downloads. Having the progres report allows us to see the errors and rectify them.

- Added a Success / Failure prompt for each download

- Solved the inherent problem with wget -O
  
  In the existing script, if a download fails, (i.e. if geofile.dat is not downloaded for any reason, wget -O creates 
  an empty file by the same name and Xray will crash and won't recover and there's no way to know why.

- Current script gives the user an option to restart or not. It shouldn't be optional. That is fixed.

- Function restart() has already been defined in the script. There's no reason to use several lines for each
  restart. We're now using the existing function

- Unrelated: I took the liberty to brighten up the colours for the menu. If that's not what you like, please
  feel free to revert it back.

* Update x-ui.sh

- Removed Iran.dat as it no longer will be supported in the new version.
- Removed the notice about additional 2 files.
- changed restart() to confirm_restart()
- Reverted back to original colours.
2023-12-10 15:39:27 +01:00
Tara Rostami
78fb3844fe Update xq.css 2023-12-10 18:09:04 +03:30
Alireza Ahmadi
9452f4fc15 [log] modal prettifier 2023-12-10 14:02:57 +01:00
Alireza Ahmadi
5996f92fe5 [log] combine with xray logs 2023-12-10 13:08:11 +01:00
Alireza Ahmadi
4cabe80462 [xray] show xray errors #730 2023-12-10 12:57:06 +01:00
Tara Rostami
1cf0d52433 Update custom.css (#732)
* Update login.html

* Update custom.css
2023-12-09 12:46:23 +01:00
Alireza Ahmadi
3cef5142d9 Merge pull request #731 from shahin-io/main
Update readme.md for better reflection of features
2023-12-09 12:20:34 +01:00
shahin-io
ac8af25cef Add files via upload 2023-12-09 05:58:31 +00:00
shahin-io
aee2107f30 Delete README.md 2023-12-09 05:57:49 +00:00
Alireza Ahmadi
7971082beb better tcp/udp display 2023-12-08 22:51:46 +01:00
Alireza Ahmadi
d60290d0a1 Merge pull request #726 from TaraRostami/main
Update custom.css
2023-12-08 12:44:26 +01:00
Tara Rostami
0b1bc1908b Update custom.css 2023-12-08 15:09:29 +03:30
Alireza Ahmadi
a342ef9e90 remove duplicate version_change
Plus fix remove submit button in user/pass change page
2023-12-08 12:26:36 +01:00
Alireza Ahmadi
317e4f1fdd add translates 2023-12-08 11:42:33 +01:00
Alireza Ahmadi
fd82e701b6 small color fixes 2023-12-08 11:19:22 +01:00
Tara Rostami
046be5dd0f Optimized CSS (#722)
* Optimized custom.css

* Optimized <Style>

* Update custom.css

* Update custom.css
2023-12-08 11:18:20 +01:00
Alireza Ahmadi
fa4c954444 show id in confirm modal title 2023-12-08 01:36:43 +01:00
Alireza Ahmadi
03e1434051 Update DB WAL before backup 2023-12-08 01:26:51 +01:00
Alireza Ahmadi
5725420197 [feature] customizable remark #692 2023-12-08 00:44:36 +01:00
Alireza Ahmadi
fd64ae5c85 [feature] import-export inbound #699 2023-12-07 12:58:19 +01:00
Alireza Ahmadi
ff59bb60ce fix user-pass setting style 2023-12-07 12:56:06 +01:00
Alireza Ahmadi
7ec646fd0d Bump actions/setup-go from 4 to 5 #715 2023-12-07 12:55:09 +01:00
Alireza Ahmadi
902368fc03 css correction 2023-12-07 12:54:45 +01:00
Tara Rostami
57827225b4 Minor UI fixes (#713)
* Update custom.css

* Update login.html

* Update custom.css

* Update custom.css

* Update custom.css
2023-12-06 21:59:40 +01:00
Alireza Ahmadi
d406d2925a [xray] fix adding empty inbound 2023-12-06 16:08:33 +01:00
Alireza Ahmadi
2f2876ec90 fix scroll-x display 2023-12-06 15:20:23 +01:00
Alireza Ahmadi
20d00d31a1 fix small design problem 2023-12-06 14:36:37 +01:00
Alireza Ahmadi
48b327ccb5 Merge branch 'main' of https://github.com/alireza0/x-ui 2023-12-06 14:35:25 +01:00
Alireza Ahmadi
9cc893a396 [bug] fix routing dns strategy #668 2023-12-06 14:35:17 +01:00
shahin-io
91e9c2d6b2 Update README.md (#711)
* Update README.md

* Update README.md
2023-12-06 12:08:21 +01:00
Alireza Ahmadi
6bfaad48fd Merge pull request #707 from alireza0/dependabot/go_modules/github.com/nicksnyder/go-i18n/v2-2.3.0
Bump github.com/nicksnyder/go-i18n/v2 from 2.2.2 to 2.3.0
2023-12-06 11:28:07 +01:00
shahin-io
10779201f5 minor edit (#706)
* v1.6.1

* v1.6.1

* Update x-ui.sh

* Update x-ui.sh

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update x-ui.sh

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

---------

Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2023-12-06 11:27:51 +01:00
Tara Rostami
5b20505515 UI improvements (#708)
* Improved login page

* Improved login page

* Update inbound_client_table.html

* Update external_proxy.html

* Update custom.css
2023-12-06 11:27:20 +01:00
Alireza Ahmadi
ed5db9390d Merge pull request #701 from Shellgate/main
Advanced Geo updater x-ui.sh
2023-12-06 11:26:31 +01:00
Shellgate
5ea3333367 Fix loop x-ui.sh
Fix Options and loop
2023-12-06 04:48:09 +03:30
Shellgate
9a95530742 Merge branch 'alireza0:main' into main 2023-12-06 04:05:53 +03:30
Alireza Ahmadi
b5eb368b89 [sub] fix typo 2023-12-05 23:09:24 +01:00
Alireza Ahmadi
1f026d0039 outbound tag velidation 2023-12-05 23:03:53 +01:00
Alireza Ahmadi
056de927da [theme] small fixes 2023-12-05 22:57:24 +01:00
dependabot[bot]
4596a981d6 Bump github.com/nicksnyder/go-i18n/v2 from 2.2.2 to 2.3.0
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.2.2 to 2.3.0.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.2.2...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 21:56:18 +00:00
Alireza Ahmadi
5cebf8faef fix fallback link for nonTLS 2023-12-05 22:45:54 +01:00
Alireza Ahmadi
2cf2da8ae2 fix small typos 2023-12-05 19:23:31 +01:00
Alireza Ahmadi
da82d14c18 [bug] fix error in deepSearch #698 2023-12-05 19:23:02 +01:00
Alireza Ahmadi
8da966a7f7 Merge pull request #703 from vuong2023/main
add : add Vietnamese option for Vietnamese users!!
2023-12-05 18:38:19 +01:00
Alireza Ahmadi
81b96507d0 Merge pull request #700 from sly-fresh/patch-1
Fixed a few typos
2023-12-05 18:26:15 +01:00
vuong2023
0986791fb3 Update langs.js
add : add Vietnamese option for Vietnamese users!!
2023-12-05 21:04:17 +07:00
vuong2023
d4317a0a62 Create translate.vi_VN.toml
add : add Vietnamese option for Vietnamese users
2023-12-05 21:01:15 +07:00
Shellgate
5fc7f132a1 Fix values x-ui.sh 2023-12-05 06:08:29 +03:30
Shellgate
bdb371367e Add Restart request x-ui.sh
Add Restart request
2023-12-05 05:23:07 +03:30
Shellgate
407bebad05 Advanced Geo updater x-ui.sh 2023-12-05 04:42:13 +03:30
sly-fresh
10d85f9855 Fixed a few typos 2023-12-04 23:46:17 +01:00
Alireza Ahmadi
98c11dd83e Merge branch 'main' of https://github.com/alireza0/x-ui 2023-12-04 22:28:38 +01:00
Alireza Ahmadi
86f6050697 [bug] fix outbound link errors #683 2023-12-04 22:28:31 +01:00
shahin-io
e488dc18b1 x-ui menu edit typo (#680)
* Update README.md
* Update x-ui.sh

---------

Co-authored-by: Shahin-IO <115543613+shahin-io@users.noreply.github.com>
2023-12-04 20:29:36 +01:00
Tara Rostami
7ed106a0f0 Minor UI bug fixes (#689)
* minor css modification
* JSON Editor color correction
* Added Line-Hover class
* Update web/assets/css/custom.css
* Update web/assets/css/custom.css

Co-authored-by: Shellgate <128194280+Shellgate@users.noreply.github.com>
2023-12-04 20:18:55 +01:00
Alireza Ahmadi
1f0c9acd89 v1.6.1 2023-12-03 13:49:59 +01:00
Alireza Ahmadi
99f9788175 multi path in tcp request header 2023-12-02 00:38:49 +01:00
Alireza Ahmadi
f64e9dff8a Merge branch 'main' of https://github.com/alireza0/x-ui 2023-12-02 00:37:59 +01:00
Alireza Ahmadi
1e0dabb434 tiny changes #656 2023-12-02 00:37:45 +01:00
Alireza Ahmadi
30bb4efcc4 Merge pull request #655 from shahin-io/main
Update x-ui.sh
2023-12-02 00:33:52 +01:00
Alireza Ahmadi
5028cf8e08 Merge pull request #664 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.23.11
Bump github.com/shirou/gopsutil/v3 from 3.23.10 to 3.23.11
2023-12-01 23:04:30 +01:00
dependabot[bot]
2736779353 Bump github.com/shirou/gopsutil/v3 from 3.23.10 to 3.23.11
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.10 to 3.23.11.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.10...v3.23.11)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 21:46:05 +00:00
Alireza Ahmadi
a45760a9a3 fix settings meta description css 2023-12-01 21:50:59 +01:00
Alireza Ahmadi
d2f6938177 [sub] fix port in externalProxy #656 2023-12-01 21:50:09 +01:00
Alireza Ahmadi
cbc4c144f2 Restart xray in xray page #656 2023-12-01 21:21:33 +01:00
Alireza Ahmadi
3ec5ca3e8f fix reverse delete function #656 2023-12-01 20:44:44 +01:00
Alireza Ahmadi
5c0e55c708 fix colors in logs and placeholders #656 2023-11-30 01:03:58 +01:00
Alireza Ahmadi
d4942a7d83 add placeholder for URI #656 2023-11-30 01:03:20 +01:00
Alireza Ahmadi
3a913ca81a [externalProxy] seprated port field #656 2023-11-30 00:18:37 +01:00
Alireza Ahmadi
3dd8dbdecf add tls to shadowrocket #654 2023-11-29 23:56:53 +01:00
Alireza Ahmadi
02053fea83 fix default address of inbound in link #656 2023-11-29 23:55:24 +01:00
Alireza Ahmadi
b69f0cfe82 use externalProxy of Fallback #656 2023-11-29 23:53:41 +01:00
Alireza Ahmadi
e2912448d6 remove domainName in tls and reality #656 2023-11-29 23:50:35 +01:00
Alireza Ahmadi
211d31390d [outbound] cmoplete importing links #656 2023-11-29 23:40:29 +01:00
shahin-io
b10759d462 Update x-ui.sh 2023-11-29 05:06:53 +00:00
Alireza Ahmadi
e372575f58 migrate old MultiDomain to externalProxy #637 2023-11-29 03:03:41 +01:00
Alireza Ahmadi
59d62afab5 [sub] adjustment for externalProxy #637 2023-11-29 03:02:39 +01:00
Alireza Ahmadi
5546883799 expand multiDomain to externalProxy #637
- Including non-tls inbounds
- Support forcedTLS/none/same security
- Change port is possible
- Move it to standard streamSettings
- Optimizing database
2023-11-29 03:01:59 +01:00
Alireza Ahmadi
3eae6300e7 del x-ui settings in final tls/reality 2023-11-26 23:42:17 +01:00
Alireza Ahmadi
a8878bc1dc [sub] url support for reverse proxy #607 2023-11-26 18:37:45 +01:00
Alireza Ahmadi
b41aef37e5 [tgbot] support telegram ID #633 2023-11-26 15:37:08 +01:00
Alireza Ahmadi
d68decd17b [fallback] open for nonTLS #626 2023-11-26 13:48:32 +01:00
Alireza Ahmadi
5f9ae30b71 [outbound] add json mode and link convertor 2023-11-26 13:32:02 +01:00
Alireza Ahmadi
1f4ff4b985 update README.md 2023-11-25 17:03:03 +01:00
Alireza Ahmadi
1daa8b8d7a [api] online users #639 2023-11-25 16:44:49 +01:00
Alireza Ahmadi
ed8ef18460 Merge pull request #634 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.6
Bump github.com/xtls/xray-core from 1.8.5 to 1.8.6
2023-11-25 16:18:12 +01:00
Alireza Ahmadi
f4cda8a4c0 add default freedom tag 2023-11-25 16:12:38 +01:00
Alireza Ahmadi
144d1e79c8 [feature] outbound and reverse gui 2023-11-25 16:12:26 +01:00
Alireza Ahmadi
7410b80e7a optimize security in front codes 2023-11-25 16:11:26 +01:00
Alireza Ahmadi
54197993db [xray] easy routing rules 2023-11-20 22:49:25 +01:00
Alireza Ahmadi
878e5507e6 [dark] fix color bug 2023-11-20 22:48:42 +01:00
dependabot[bot]
94387d48e3 Bump github.com/xtls/xray-core from 1.8.5 to 1.8.6
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.5 to 1.8.6.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.5...v1.8.6)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 21:07:08 +00:00
Alireza Ahmadi
cfc11151d2 Optimizing loading of script files 2023-11-19 04:00:41 +01:00
Alireza Ahmadi
0dbb67d0bb [theme] fix some bugs in css 2023-11-19 03:59:35 +01:00
Alireza Ahmadi
a4b447e2d7 [xray] add codemirror #605 2023-11-19 03:59:05 +01:00
Alireza Ahmadi
ca155bd871 gzip compression 2023-11-15 00:36:01 +01:00
Alireza Ahmadi
fb8f27ad49 small typo 2023-11-15 00:34:10 +01:00
Alireza Ahmadi
a88044500e Upgrade to antdv 1.7.8 2023-11-14 23:16:23 +01:00
Alireza Ahmadi
3d0663d793 back to top #605 2023-11-14 23:15:39 +01:00
Alireza Ahmadi
1735faa193 [bug] fix remained traffic display #622 2023-11-14 15:24:37 +01:00
Alireza Ahmadi
d1980581d9 Merge pull request #621 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.5
Bump github.com/xtls/xray-core from 1.8.4 to 1.8.5
2023-11-14 15:17:24 +01:00
Alireza Ahmadi
052ceb5a2d [tgbot] online/offline icon for reports 2023-11-13 23:10:44 +01:00
dependabot[bot]
bd075ed81d Bump github.com/xtls/xray-core from 1.8.4 to 1.8.5
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.4 to 1.8.5.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.4...v1.8.5)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 21:42:56 +00:00
Alireza Ahmadi
04b52ae637 Merge pull request #609 from alireza0/dependabot/go_modules/golang.org/x/text-0.14.0
Bump golang.org/x/text from 0.13.0 to 0.14.0
2023-11-13 21:43:45 +01:00
Alireza Ahmadi
739b4a1782 [bug] fix status colors in pages 2023-11-13 21:41:12 +01:00
Alireza Ahmadi
2ca3a05cca [bug] fix login page errors 2023-11-13 21:40:11 +01:00
Alireza Ahmadi
5e07e32388 [bug] fix chinese translation #604 2023-11-13 21:39:21 +01:00
dependabot[bot]
871d46be72 Bump golang.org/x/text from 0.13.0 to 0.14.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-10 21:18:16 +00:00
Alireza Ahmadi
0fb9c2e858 v1.6.0 2023-11-09 23:38:38 +01:00
Alireza Ahmadi
cef94ed4bc Merge pull request #595 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.23.10
Bump github.com/shirou/gopsutil/v3 from 3.23.8 to 3.23.10
2023-11-09 23:34:54 +01:00
Alireza Ahmadi
a0a19a4d2e Merge pull request #593 from alireza0/dependabot/go_modules/github.com/nicksnyder/go-i18n/v2-2.2.2
Bump github.com/nicksnyder/go-i18n/v2 from 2.2.1 to 2.2.2
2023-11-09 23:34:44 +01:00
Alireza Ahmadi
886a300c64 Merge pull request #586 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.59.0
Bump google.golang.org/grpc from 1.58.1 to 1.59.0
2023-11-09 23:34:32 +01:00
Alireza Ahmadi
e78e7c99cd Merge pull request #580 from alireza0/dependabot/go_modules/gorm.io/gorm-1.25.5
Bump gorm.io/gorm from 1.25.4 to 1.25.5
2023-11-09 23:34:12 +01:00
Alireza Ahmadi
a93461e2c2 Merge branch 'main' into dependabot/go_modules/gorm.io/gorm-1.25.5 2023-11-09 23:33:59 +01:00
dependabot[bot]
5cfe617841 Bump google.golang.org/grpc from 1.58.1 to 1.59.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.1 to 1.59.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.58.1...v1.59.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-09 22:33:49 +00:00
Alireza Ahmadi
cc5542b138 Merge pull request #579 from alireza0/dependabot/go_modules/gorm.io/driver/sqlite-1.5.4
Bump gorm.io/driver/sqlite from 1.5.3 to 1.5.4
2023-11-09 23:32:36 +01:00
Alireza Ahmadi
7c74c534f0 DESIGN REFACTOR (#600)
### New features
- New face + dark mode
  - [Change font to vazirmatn](057f3190de)
  - [use customized andtv](f956009fd2)
  - [popConfirm for del and reset client](66c98e8392)
  - [Separate page for xray config](9e1cd6315f)
  - Separate face for mobile view
- [Show online users](bf892e9965) [#559](https://github.com/alireza0/x-ui/issues/559)
- [Auto renew](96408967ae)

### Bug fixes
- [[tgbot] Retry loop on start](211c05ec29)
- [fix docker-compose version](1dcec91ce4)
- [fix redirect after restart](81d25a032c)
2023-11-09 23:31:17 +01:00
dependabot[bot]
46dcff2fe7 Bump github.com/shirou/gopsutil/v3 from 3.23.8 to 3.23.10
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.8 to 3.23.10.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.8...v3.23.10)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 21:20:07 +00:00
dependabot[bot]
127430f227 Bump github.com/nicksnyder/go-i18n/v2 from 2.2.1 to 2.2.2
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 21:52:20 +00:00
dependabot[bot]
c0802c8c71 Bump gorm.io/gorm from 1.25.4 to 1.25.5
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.4 to 1.25.5.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.4...v1.25.5)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-10 21:30:13 +00:00
dependabot[bot]
b845635bb5 Bump gorm.io/driver/sqlite from 1.5.3 to 1.5.4
Bumps [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite) from 1.5.3 to 1.5.4.
- [Commits](https://github.com/go-gorm/sqlite/compare/v1.5.3...v1.5.4)

---
updated-dependencies:
- dependency-name: gorm.io/driver/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 21:07:41 +00:00
Alireza Ahmadi
d4a23f8a23 better trasmission method naming 2023-09-23 13:17:44 +02:00
Alireza Ahmadi
f424d1bbc0 Merge pull request #553 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.58.1
Bump google.golang.org/grpc from 1.58.0 to 1.58.1
2023-09-15 17:28:30 +02:00
dependabot[bot]
935ff96eeb Bump google.golang.org/grpc from 1.58.0 to 1.58.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.0 to 1.58.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.58.0...v1.58.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-14 21:17:16 +00:00
Alireza Ahmadi
95318f51c5 [feature] optional pagination 2023-09-14 19:17:16 +02:00
shahin-io
0d77b52f39 Update index.html (#550)
* Update index.html

* Update index.html

* Update index.html

* Update index.html
2023-09-14 15:42:02 +02:00
Alireza Ahmadi
6b12c314be Merge pull request #549 from alireza0/dependabot/go_modules/github.com/Workiva/go-datastructures-1.1.1
Bump github.com/Workiva/go-datastructures from 1.1.0 to 1.1.1
2023-09-14 00:50:59 +02:00
Alireza Ahmadi
9c35468d3d Merge pull request #544 from alireza0/dependabot/github_actions/docker/setup-qemu-action-3
Bump docker/setup-qemu-action from 2 to 3
2023-09-14 00:50:50 +02:00
Alireza Ahmadi
0411281d8e Merge pull request #543 from alireza0/dependabot/github_actions/docker/login-action-3
Bump docker/login-action from 2 to 3
2023-09-14 00:50:37 +02:00
Alireza Ahmadi
6cb0012622 Merge pull request #542 from alireza0/dependabot/github_actions/docker/setup-buildx-action-3
Bump docker/setup-buildx-action from 2 to 3
2023-09-14 00:50:26 +02:00
Alireza Ahmadi
25e4bcedb1 Merge pull request #541 from alireza0/dependabot/github_actions/docker/build-push-action-5
Bump docker/build-push-action from 4 to 5
2023-09-14 00:50:13 +02:00
Alireza Ahmadi
aea474c7ef Merge pull request #540 from alireza0/dependabot/github_actions/docker/metadata-action-5
Bump docker/metadata-action from 4 to 5
2023-09-14 00:50:02 +02:00
Alireza Ahmadi
6afddc7bee [bug] fix qrcode and info for searched clients 2023-09-14 00:13:08 +02:00
dependabot[bot]
ce54535df5 Bump github.com/Workiva/go-datastructures from 1.1.0 to 1.1.1
Bumps [github.com/Workiva/go-datastructures](https://github.com/Workiva/go-datastructures) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/Workiva/go-datastructures/releases)
- [Commits](https://github.com/Workiva/go-datastructures/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: github.com/Workiva/go-datastructures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-13 21:18:25 +00:00
dependabot[bot]
31155ecff9 Bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 21:21:25 +00:00
dependabot[bot]
ffb05f596b Bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 21:21:18 +00:00
dependabot[bot]
8917c7291d Bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 21:21:11 +00:00
dependabot[bot]
a88d8eadee Bump docker/build-push-action from 4 to 5
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 21:21:05 +00:00
dependabot[bot]
80bb9f7953 Bump docker/metadata-action from 4 to 5
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 21:20:58 +00:00
Alireza Ahmadi
16113ce7aa [bot] replace boolian with yes/no #537 2023-09-09 14:48:19 +02:00
dependabot[bot]
de4affb913 Bump golang.org/x/text from 0.12.0 to 0.13.0 (#529)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-09 13:46:50 +02:00
dependabot[bot]
3230a81a7c Bump actions/checkout from 3 to 4 (#528)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-09 13:46:32 +02:00
dependabot[bot]
df86f7bc29 Bump google.golang.org/grpc from 1.57.0 to 1.58.0 (#533)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.57.0 to 1.58.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.57.0...v1.58.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-09 13:45:16 +02:00
Alireza Ahmadi
ac5981352e remove unused structure 2023-09-09 13:45:00 +02:00
Alireza Ahmadi
53719ecf6a change in logger 2023-09-09 13:44:41 +02:00
dependabot[bot]
9f3502d912 Bump github.com/shirou/gopsutil/v3 from 3.23.7 to 3.23.8 (#524)
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.7 to 3.23.8.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.7...v3.23.8)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-02 10:28:36 +02:00
131 changed files with 56748 additions and 5331 deletions

View File

@@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: true
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
alireza7/x-ui
@@ -27,29 +27,29 @@ jobs:
type=pep440,pattern={{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64/v8
platforms: linux/amd64,linux/arm64/v8, linux/arm/v7, linux/386
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,4 +1,5 @@
name: Release X-ui
name: Release X-UI
on:
push:
tags:
@@ -6,124 +7,96 @@ on:
workflow_dispatch:
jobs:
linuxamd64build:
name: build x-ui amd64 version
build:
strategy:
matrix:
platform:
- amd64
- arm64
- armv7
- 386
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
- name: Checkout repository
uses: actions/checkout@v4.1.1
- name: Setup Go
uses: actions/setup-go@v5.0.0
with:
go-version: '1.21'
- name: build linux amd64 version
run: |
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
mkdir x-ui
cp xui-release x-ui/xui-release
cp x-ui.service x-ui/x-ui.service
cp x-ui.sh x-ui/x-ui.sh
cd x-ui
mv xui-release x-ui
mkdir bin
cd bin
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-64.zip
unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
mv xray xray-linux-amd64
cd ..
cd ..
- name: package
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
- name: upload
uses: svenstaro/upload-release-action@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-amd64.tar.gz
asset_name: x-ui-linux-amd64.tar.gz
prerelease: true
linuxarm64build:
name: build x-ui arm64 version
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: build linux arm64 version
go-version: '1.22'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt install gcc-aarch64-linux-gnu
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o xui-release -v main.go
mkdir x-ui
cp xui-release x-ui/xui-release
cp x-ui.service x-ui/x-ui.service
cp x-ui.sh x-ui/x-ui.sh
cd x-ui
mv xui-release x-ui
mkdir bin
cd bin
wget https://github.com/xtls/xray-core/releases/download/v1.8.4/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
mv xray xray-linux-arm64
cd ..
cd ..
- name: package
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
- name: upload
uses: svenstaro/upload-release-action@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-arm64.tar.gz
asset_name: x-ui-linux-arm64.tar.gz
prerelease: true
linuxs390xbuild:
name: build x-ui s390x version
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: build linux s390x version
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
fi
- name: Build x-ui
run: |
sudo apt-get update
sudo apt install gcc-s390x-linux-gnu -y
CGO_ENABLED=1 GOOS=linux GOARCH=s390x CC=s390x-linux-gnu-gcc go build -o xui-release -v main.go
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
fi
go build -o xui-release -v main.go
mkdir x-ui
cp xui-release x-ui/xui-release
cp x-ui.service x-ui/x-ui.service
cp x-ui.sh x-ui/x-ui.sh
cd x-ui
mv xui-release x-ui
mkdir bin
cd bin
wget https://github.com/xtls/xray-core/releases/download/v1.8.4/Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
cp xui-release x-ui/
cp x-ui.service x-ui/
cp x-ui.sh x-ui/
mv x-ui/xui-release x-ui/x-ui
mkdir x-ui/bin
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "386" ]; then
wget ${Xray_URL}Xray-linux-32.zip
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
fi
rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat
mv xray xray-linux-s390x
cd ..
cd ..
- name: package
run: tar -zcvf x-ui-linux-s390x.tar.gz x-ui
- name: upload
uses: svenstaro/upload-release-action@2.7.0
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }}
cd ../..
- name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- name: Upload files to GH release
uses: MHSanaei/upload-release-action@2.8.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-s390x.tar.gz
asset_name: x-ui-linux-s390x.tar.gz
file: x-ui-linux-${{ matrix.platform }}.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
prerelease: true

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ dist/
release/
/release.sh
/x-ui
.DS_Store

View File

@@ -1,21 +1,34 @@
#!/bin/sh
if [ $1 == "amd64" ]; then
ARCH="64";
FNAME="amd64";
elif [ $1 == "arm64" ]; then
ARCH="arm64-v8a"
FNAME="arm64";
else
ARCH="64";
FNAME="amd64";
fi
case $1 in
amd64)
ARCH="64"
FNAME="amd64"
;;
i386)
ARCH="32"
FNAME="i386"
;;
armv8 | arm64 | aarch64)
ARCH="arm64-v8a"
FNAME="arm64"
;;
armv7 | arm | arm32)
ARCH="arm32-v7a"
FNAME="arm32"
;;
*)
ARCH="64"
FNAME="amd64"
;;
esac
mkdir -p build/bin
cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-${ARCH}.zip"
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
mv xray "xray-linux-${FNAME}"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
wget "https://github.com/Masterkia/iran-hosted-domains/releases/latest/download/iran.dat"
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
cd ../../

View File

@@ -1,9 +1,11 @@
FROM golang:1.21-alpine AS builder
FROM golang:1.22-alpine AS builder
WORKDIR /app
ARG TARGETARCH
RUN apk --no-cache --update add build-base gcc wget unzip
COPY . .
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go
RUN ./DockerInitFiles.sh "$TARGETARCH"
FROM alpine

394
README.md
View File

@@ -1,4 +1,5 @@
# x-ui
# X-UI
**An Advanced Web Panel • Built on Xray Core**
![](https://img.shields.io/github/v/release/alireza0/x-ui.svg)
![](https://img.shields.io/docker/pulls/alireza7/x-ui.svg)
@@ -6,54 +7,81 @@
[![Downloads](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
**If you think this project is helpful to you, you may wish to give a**:star2:
| Features | Enable? |
| ------------------------------------ | :----------------: |
| Multi-lang | :heavy_check_mark: |
| Dark/Light Theme | :heavy_check_mark: |
| Search in deep | :heavy_check_mark: |
| Inbound Multi User | :heavy_check_mark: |
| Multi User Traffic & Expiration time | :heavy_check_mark: |
| REST API | :heavy_check_mark: |
| Telegram BOT (admin + clients) | :heavy_check_mark: |
| Backup database using Telegram BOT | :heavy_check_mark: |
| Subscription link + userInfo | :heavy_check_mark: |
| Calculate expire date on first usage | :heavy_check_mark: |
<img width="125" alt="image"
src="https://github.com/alireza0/x-ui/assets/115543613/dd4f10dd-8bb0-40cf-846f-1fe1de7a6275">
**If you think this project is helpful to you, you may wish to give a** :star2:
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
- Tezos (XTZ):
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
**Buy Me a Coffee :**
- Tron USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
- Tezos (XTZ): tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts
## Quick Overview
| Features | Enable? |
| -------------------------------------- | :----------------: |
| Multi-Protocol | :heavy_check_mark: |
| Multi-Language | :heavy_check_mark: |
| Multi-Client/Inbound | :heavy_check_mark: |
| Advanced Traffic Routing Interface | :heavy_check_mark: |
| Client & Traffic & System Status | :heavy_check_mark: |
| Date & Traffic Cap Based on First Use | :heavy_check_mark: |
| REST API | :heavy_check_mark: |
| TG Bot (DB backup + admin + client) | :heavy_check_mark: |
| Subscription Service (link + info) | :heavy_check_mark: |
| Search in Deep | :heavy_check_mark: |
| Dark/Light Theme | :heavy_check_mark: |
# Install & Upgrade to latest version
## Install & Upgrade to Latest Version
```sh
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
```
## Install custom version
## Install Custom Version
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `1.7.1`:
```sh
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 1.7.1
```
## Manual install & upgrade
## Manual Install & Upgrade
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
2. Then upload the compressed package to the server's `/root/` directory and `root` rootlog in to the server with user
<details>
<summary>Click for details</summary>
### Usage
> If your server cpu architecture is not `amd64` replace another architecture
1. To download the latest version of the compressed package directly to your server, run the following command:
```sh
ARCH=$(uname -m)
[[ "${ARCH}" == "s390x" ]] && XUI_ARCH="s390x" || [[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/alireza0/x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
@@ -66,15 +94,35 @@ systemctl enable x-ui
systemctl restart x-ui
```
## Install using docker
</details>
1. install docker
## Install using Docker
<details>
<summary>Click for details</summary>
### Usage
**Step 1:** Install Docker
```shell
curl -fsSL https://get.docker.com | sh
```
2. install x-ui
**Step 2:** Clone the Project Repository:
```sh
git clone https://github.com/alireza0/x-ui.git
cd x-ui
```
**Step 3:** Start the Service
```sh
docker compose up -d
```
OR
```shell
mkdir x-ui && cd x-ui
@@ -87,65 +135,116 @@ docker run -itd \
alireza7/x-ui:latest
```
update to latest version
```sh
cd x-ui
docker compose down
docker compose pull x-ui
docker compose up -d
```
remove x-ui from docker
```sh
docker stop x-ui
docker rm x-ui
cd --
rm -r x-ui
```
> Build your own image
```shell
docker build -t x-ui .
```
# Features
</details>
- System Status Monitoring
- Search within all inbounds and clients
- Support Dark/Light theme UI
- Support multi-user multi-protocol, web page visualization operation
- Support multi-domain configuration and multi-certificate inbounds
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
- Support for configuring more transport configurations
- Traffic statistics, limit traffic, limit expiration time
- Customizable xray configuration templates
- Support subscription ( multi ) link
- Detect users which are expiring or exceed traffic limit soon
- Support https access panel (self-provided domain name + ssl certificate)
- Support one-click SSL certificate application and automatic renewal
- For more advanced configuration items, please refer to the panel
- Support export/import database from panel
## Languages
## suggestion system
- English
- Chinese
- Farsi
- Russian
- Vietnamese
## Features
- Supports protocols including VLESS, VMess, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP, Wireguard
- Supports XTLS protocols, including Vision and REALITY
- An advanced interface for routing traffic, incorporating PROXY Protocol, Reverse, External, and Transparent Proxy, along with Multi-Domain, SSL Certificate, and Port
- Support auto generate Cloudflare WARP using Wireguard outbound
- An interactive JSON interface for Xray template configuration
- An advanced interface for inbound and outbound configuration
- Clients traffic cap and expiration date based on first use
- Displays online clients, traffic statistics, and system status monitoring
- Deep database search
- Displays depleted clients with expired dates or exceeded traffic cap
- Subscription service with (multi)link
- Importing and exporting databases
- One-Click SSL certificate application and automatic renewal
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
- Dark/Light theme
## Recommended OS
- CentOS 8+
- Ubuntu 20+
- Debian 10+
- Fedora 36+
## API routes
## Preview
![inbounds](./media/inbounds.png)
![Dark inbounds](./media/inbounds-dark.png)
![outbounds](./media/outbounds.png)
![rules](./media/rules.png)
![warp](./media/warp.png)
## API Routes
<details>
<summary>Click for details</summary>
### Usage
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
- `/xui/API/inbounds` base for following actions:
| Method | Path | Action |
| :----: | ------------------------------- | ----------------------------------------- |
| `GET` | `"/"` | Get all inbounds |
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
| `POST` | `"/add"` | Add inbound |
| `POST` | `"/del/:id"` | Delete Inbound |
| `POST` | `"/update/:id"` | Update Inbound |
| `POST` | `"/addClient/"` | Add Client to inbound |
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic |
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
| Method | Path | Action |
| :----: | --------------------------------- | ----------------------------------------- |
| `GET` | `"/"` | Get all inbounds |
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
| `POST` | `"/add"` | Add inbound |
| `POST` | `"/del/:id"` | Delete inbound |
| `POST` | `"/update/:id"` | Update inbound |
| `POST` | `"/addClient/"` | Add client to inbound |
| `POST` | `"/:id/delClient/:clientId"` | Delete client by clientId\* |
| `POST` | `"/updateClient/:clientId"` | Update client by clientId\* |
| `GET` | `"/getClientTraffics/:email"` | Get client's traffic |
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset client's traffic |
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
\*- The field `clientId` should be filled by:
- `client.id` for VMESS and VLESS
- `client.password` for TROJAN
- `client.id` for VMess and VLESS
- `client.password` for Trojan
- `client.email` for Shadowsocks
# Environment Variables
</details>
## Environment Variables
<details>
<summary>Click for details</summary>
### Usage
| Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ |
@@ -154,16 +253,24 @@ docker build -t x-ui .
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
# Screenshot from Inbouds page
</details>
![inbounds](./media/inbounds.png)
![Dark inbounds](./media/inbounds-dark.png)
## SSL certificate application
## SSL Certificate
<details>
<summary>Click for details</summary>
### Cloudflare
The admin management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name has been resolved to the current server through cloudflare
**Step 1:** Run the`x-ui`command on the server's terminal and then choose `17`. Then enter the information as requested.
### Certbot
```bash
@@ -176,102 +283,119 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
</details>
## Tg robot use
## Telegram Bot
<details>
<summary>Click for details</summary>
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
Set the robot-related parameters in the panel background, including:
### Usage
- Tg robot Token
- Tg robot ChatId
- Tg robot cycle runtime, in crontab syntax
- Tg robot Expiration threshold
- Tg robot Traffic threshold
- Tg robot Enable send backup in cycle runtime
- Tg robot Enable CPU usage alarm threshold
The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including:
- Telegram Token
- Admin Chat ID(s)
- Notification Time (in cron syntax)
- Database Backup
- CPU Load Threshold Notification
**Crontab Time Format**
Reference syntax:
- 30 \* \* \* \* \* //Notify at the 30s of each point
- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
- @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning)
- @every 8h // notify every 8 hours
- `*/30 * * * *` - Notify every 30 minutes, every hour
- `30 * * * * *` - Notify at the 30th second of each minute
- `0 */10 * * * *` - Notify at the start of every 10 minutes
- `@hourly` - Hourly notification
- `@daily` - Daily notification (00:00 AM)
- `@every 8h` - Notify every 8 hours
### Telegram Bot Features
For more info about [Crontab](https://acquia.my.site.com/s/article/360004224494-Cron-time-string-format)
- Report periodic
- Login notification
- CPU threshold notification
- Threshold for Expiration time and Traffic to report in advance
- Support client report menu if client's telegram username added to the user's configurations
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
- Menu based bot
- Search client by email ( only admin )
- Check all inbounds
- Check server status
- Check depleted users
- Receive backup by request and in periodic reports
- Multi language bot
### Features
- Periodic reporting
- Login notifications
- CPU load threshold notifications
- Advance notifications for expiration time and traffic
- Client reporting menu with Telegram ID or username in configurations
- Anonymous traffic reports, search by UUID (VLESS/VMess) or Password (Trojan/Shadowsocks)
- Menu-based bot
- Client search by email (admin only)
- Inbound checks
- System status check
- Depleted client checks
- Backup on request and in periodic reports
- Multilingual support
</details>
# T-Shoots:
## Troubleshoots
**If you upgrade from an old version or other forks, for enable traffic for users you should do :**
<details>
<summary>Click for details</summary>
find this in config :
### Enable Traffic Usage
```json
"policy": {
"system": {
```
If you are upgrading from an older version or other forks and find that data traffic usage for clients may not work by default, follow the steps below to enable it:
**and add this just after ` "policy": {` :**
**Step 1: Locate the Configuration Section**
```json
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
```
**the final output is like :**
Find the following section in the config file:
```json
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
// Other policy configurations
}
},
"routing": {
```
**Step 2: Add the Required Configuration**
restart panel
Add the following section just after `"policy": {`:
```json
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
```
**Step 3: Final Configuration**
Your final config should look like this:
```json
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
// Other routing configurations
},
```
**Step 4: Save and Restart**
Save your changes and restart the Xray Service
</details>
# a special thanks to
## A Special Thanks to
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
- [MHSanaei](https://github.com/MHSanaei)
# Acknowledgment
## Acknowledgment
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
- [Loyalsoldier](https://github.com/Loyalsoldier/v2ray-rules-dat) (License: **GPL-3.0**): _The enhanced version of V2Ray routing rule._
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
## Stargazers over time
## Stargazers over Time
[![Stargazers over time](https://starchart.cc/alireza0/x-ui.svg)](https://starchart.cc/alireza0/x-ui)

View File

@@ -1 +1 @@
1.5.5
1.7.2

View File

@@ -110,3 +110,12 @@ func IsSQLiteDB(file io.Reader) (bool, error) {
}
return bytes.Equal(buf, signature), nil
}
func Checkpoint() error {
// Update WAL
err := db.Exec("PRAGMA wal_checkpoint;").Error
if err != nil {
return err
}
return nil
}

View File

@@ -76,4 +76,5 @@ type Client struct {
Enable bool `json:"enable" form:"enable"`
TgID string `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"`
Reset int `json:"reset" form:"reset"`
}

View File

@@ -1,5 +1,5 @@
---
version: "3.9"
version: "3"
services:
xui:

116
go.mod
View File

@@ -1,92 +1,94 @@
module x-ui
go 1.21
toolchain go1.21.0
go 1.22.0
require (
github.com/Workiva/go-datastructures v1.1.0
github.com/gin-contrib/sessions v0.0.4
github.com/Calidity/gin-sessions v1.3.1
github.com/gin-gonic/gin v1.9.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/goccy/go-json v0.10.2
github.com/nicksnyder/go-i18n/v2 v2.2.1
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.1.0
github.com/pelletier/go-toml/v2 v2.1.1
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.7
github.com/xtls/xray-core v1.8.4
github.com/shirou/gopsutil/v3 v3.24.1
github.com/xtls/xray-core v1.8.7
go.uber.org/atomic v1.11.0
golang.org/x/text v0.12.0
google.golang.org/grpc v1.57.0
gorm.io/driver/sqlite v1.5.3
gorm.io/gorm v1.25.4
golang.org/x/text v0.14.0
google.golang.org/grpc v1.61.1
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.7
)
require github.com/chenzhuoyu/iasm v0.9.1 // indirect
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-playground/validator/v10 v10.18.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
github.com/refraction-networking/utls v1.4.3 // indirect
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a // indirect
github.com/quic-go/quic-go v0.41.0 // indirect
github.com/refraction-networking/utls v1.6.2 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sagernet/sing v0.2.9 // indirect
github.com/sagernet/sing-shadowsocks v0.2.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
github.com/sagernet/sing v0.3.0 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/protobuf v1.31.0 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

359
go.sum
View File

@@ -8,29 +8,32 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo=
github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -42,43 +45,42 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -90,39 +92,38 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@@ -131,101 +132,100 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a h1:XCUtNgBnZfUBhdfCX2QK+fslr9vevSsUg3W3peZwlak=
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM=
github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo=
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
github.com/refraction-networking/utls v1.6.2 h1:iTeeGY0o6nMNcGyirxkD5bFIsVctP5InGZ3E0HrzS7k=
github.com/refraction-networking/utls v1.6.2/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo=
github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -259,70 +259,68 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 h1:T+YCYGfFdzyaKTDCdZn/hEiKvsw6yUfd+e4hze0rCUw=
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
github.com/xtls/xray-core v1.8.4 h1:YEoY3iLx/5zoNbt5HORG5LtPyzwICInFfoS+oPkYDIw=
github.com/xtls/xray-core v1.8.4/go.mod h1:GGD9elFSHa4IqOArW8gzMsEksPIqK/jdNLo8RcSMfnI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -331,14 +329,9 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -348,63 +341,52 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -417,44 +399,49 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 h1:tE44CyJgxEGzoPtHs9GI7ddKdgEGCREQBP54AmaVM+I=
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
gvisor.dev/gvisor v0.0.0-20240216214558-53d2b511e78e h1:BAmOamSGzOqvWRcyoAZvud9SuInvUn2qYIK495rjF+4=
gvisor.dev/gvisor v0.0.0-20240216214558-53d2b511e78e/go.mod h1:YcCCAniKhCIGGvWxOobcre6euvNQON7nZCtMcVYO9rA=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -23,25 +23,16 @@ else
fi
echo "The OS release is: $release"
arch=$(arch)
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
arch="amd64"
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
arch="arm64"
elif [[ $arch == "s390x" ]]; then
arch="s390x"
else
arch="amd64"
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
fi
echo "arch: ${arch}"
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
exit -1
fi
arch() {
case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;;
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac
}
echo "arch: $(arch)"
os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@@ -50,7 +41,7 @@ if [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "ubuntu" ]]; then
elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
fi
@@ -68,13 +59,18 @@ else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
fi
install_base() {
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
yum install wget curl tar -y
else
apt install wget curl tar -y
fi
install_dependencies() {
case "${release}" in
centos)
yum -y update && yum install -y -q wget curl tar tzdata
;;
fedora)
dnf -y update && dnf install -y -q wget curl tar tzdata
;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
;;
esac
}
#This function will be called when user installed x-ui out of sercurity
@@ -113,6 +109,21 @@ config_after_install() {
}
install_x-ui() {
# checks if the installation backup dir exist. if existed then ask user if they want to restore it else continue installation.
if [[ -e /usr/local/x-ui-backup/ ]]; then
read -p "Failed installation detected. Do you want to restore previously installed version? [y/n]? ": restore_confirm
if [[ "${restore_confirm}" == "y" || "${restore_confirm}" == "Y" ]]; then
systemctl stop x-ui
mv /usr/local/x-ui-backup/x-ui.db /etc/x-ui/ -f
mv /usr/local/x-ui-backup/ /usr/local/x-ui/ -f
systemctl start x-ui
echo -e "${green}previous installed x-ui restored successfully${plain}, it is up and running now..."
exit 0
else
echo -e "Continuing installing x-ui ..."
fi
fi
cd /usr/local/
if [ $# == 0 ]; then
@@ -122,36 +133,45 @@ install_x-ui() {
exit 1
fi
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then
echo -e "${red}Dowanloading x-ui failed, please be sure that your server can access Github ${plain}"
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
exit 1
fi
else
last_version=$1
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
echo -e "Begining to install x-ui v$1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui v$1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then
echo -e "${red}dowanload x-ui v$1 failed,please check the verison exists${plain}"
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}"
exit 1
fi
fi
if [[ -e /usr/local/x-ui/ ]]; then
systemctl stop x-ui
rm /usr/local/x-ui/ -rf
mv /usr/local/x-ui/ /usr/local/x-ui-backup/ -f
cp /etc/x-ui/x-ui.db /usr/local/x-ui-backup/ -f
fi
tar zxvf x-ui-linux-${arch}.tar.gz
rm x-ui-linux-${arch}.tar.gz -f
tar zxvf x-ui-linux-$(arch).tar.gz
rm x-ui-linux-$(arch).tar.gz -f
cd x-ui
chmod +x x-ui bin/xray-linux-${arch}
chmod +x x-ui
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/alireza0/x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui
config_after_install
rm /usr/local/x-ui-backup/ -rf
#echo -e "If it is a new installation, the default web port is ${green}54321${plain}, The username and password are ${green}admin${plain} by default"
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 54321 has been released${plain}"
# echo -e "If you want to modify the 54321 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
@@ -163,22 +183,24 @@ install_x-ui() {
systemctl start x-ui
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
echo -e ""
echo -e "x-ui control menu usages: "
echo -e "----------------------------------------------"
echo -e "x-ui - Enter Admin menu"
echo -e "x-ui start - Start x-ui"
echo -e "x-ui stop - Stop x-ui"
echo -e "x-ui restart - Restart x-ui"
echo -e "x-ui status - Show x-ui status"
echo -e "x-ui enable - Enable x-ui on system startup"
echo -e "x-ui disable - Disable x-ui on system startup"
echo -e "x-ui log - Check x-ui logs"
echo -e "x-ui update - Update x-ui"
echo -e "x-ui install - Install x-ui"
echo -e "x-ui uninstall - Uninstall x-ui"
echo -e "----------------------------------------------"
echo "X-UI Control Menu Usage"
echo "------------------------------------------"
echo "SUBCOMMANDS:"
echo "x-ui - Admin Management Script"
echo "x-ui start - Start"
echo "x-ui stop - Stop"
echo "x-ui restart - Restart"
echo "x-ui status - Current Status"
echo "x-ui enable - Enable Autostart on OS Startup"
echo "x-ui disable - Disable Autostart on OS Startup"
echo "x-ui log - Check Logs"
echo "x-ui update - Update"
echo "x-ui install - Install"
echo "x-ui uninstall - Uninstall"
echo "x-ui help - Control Menu Usage"
echo "------------------------------------------"
}
echo -e "${green}Excuting...${plain}"
install_base
echo -e "${green}Running...${plain}"
install_dependencies
install_x-ui $1

View File

@@ -26,17 +26,15 @@ func InitLogger(level logging.Level) {
var format logging.Formatter
ppid := os.Getppid()
if ppid == 1 {
backend, err = logging.NewSyslogBackend("")
format = logging.MustStringFormatter(
`%{level} - %{message}`,
)
}
if err != nil || ppid != 1 {
backend, err = logging.NewSyslogBackend("")
if err != nil {
println(err)
backend = logging.NewLogBackend(os.Stderr, "", 0)
format = logging.MustStringFormatter(
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
)
}
if ppid > 0 && err != nil {
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
} else {
format = logging.MustStringFormatter(`%{level} - %{message}`)
}
backendFormatter := logging.NewBackendFormatter(backend, format)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

After

Width:  |  Height:  |  Size: 114 KiB

BIN
media/outbounds.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
media/rules.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
media/warp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

105
sub/default.json Normal file
View File

@@ -0,0 +1,105 @@
{
"dns": {
"tag": "dns_out",
"queryStrategy": "UseIP",
"servers": [
{
"address": "8.8.8.8",
"skipFallback": false
}
]
},
"inbounds": [
{
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"userLevel": 8
},
"sniffing": {
"destOverride": [
"http",
"tls",
"fakedns"
],
"enabled": true
},
"tag": "socks"
},
{
"port": 10809,
"protocol": "http",
"settings": {
"userLevel": 8
},
"tag": "http"
}
],
"log": {
"loglevel": "warning"
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "http"
}
}
}
],
"policy": {
"levels": {
"8": {
"connIdle": 300,
"downlinkOnly": 1,
"handshake": 4,
"uplinkOnly": 1
}
},
"system": {
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"network": "tcp,udp",
"balancerTag": "all"
}
],
"balancers": [
{
"tag": "all",
"selector": [
"proxy"
],
"strategy": {
"type": "leastPing"
}
}
]
},
"observatory": {
"probeInterval": "5m",
"probeURL": "https://api.github.com/_private/browser/stats",
"subjectSelector": [
"proxy"
],
"EnableConcurrency": true
},
"stats": {}
}

View File

@@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default()
subPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
subDomain, err := s.settingService.GetSubDomain()
if err != nil {
return nil, err
@@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
}
g := engine.Group(subPath)
LinksPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
s.sub = NewSUBController(g)
JsonPath, err := s.settingService.GetSubJsonPath()
if err != nil {
return nil, err
}
Encrypt, err := s.settingService.GetSubEncrypt()
if err != nil {
return nil, err
}
ShowInfo, err := s.settingService.GetSubShowInfo()
if err != nil {
return nil, err
}
RemarkModel, err := s.settingService.GetRemarkModel()
if err != nil {
RemarkModel = "-ieo"
}
SubUpdates, err := s.settingService.GetSubUpdates()
if err != nil {
SubUpdates = "10"
}
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
if err != nil {
SubJsonFragment = ""
}
g := engine.Group("/")
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
return engine, nil
}

View File

@@ -3,34 +3,57 @@ package sub
import (
"encoding/base64"
"strings"
"x-ui/web/service"
"github.com/gin-gonic/gin"
)
type SUBController struct {
subService SubService
settingService service.SettingService
subPath string
subJsonPath string
subEncrypt bool
updateInterval string
subService *SubService
subJsonService *SubJsonService
}
func NewSUBController(g *gin.RouterGroup) *SUBController {
a := &SUBController{}
func NewSUBController(
g *gin.RouterGroup,
subPath string,
jsonPath string,
encrypt bool,
showInfo bool,
rModel string,
update string,
jsonFragment string) *SUBController {
a := &SUBController{
subPath: subPath,
subJsonPath: jsonPath,
subEncrypt: encrypt,
updateInterval: update,
subService: NewSubService(showInfo, rModel),
subJsonService: NewSubJsonService(jsonFragment),
}
a.initRouter(g)
return a
}
func (a *SUBController) initRouter(g *gin.RouterGroup) {
g = g.Group("/")
gLink := g.Group(a.subPath)
gJson := g.Group(a.subJsonPath)
g.GET("/:subid", a.subs)
gLink.GET(":subid", a.subs)
gJson.GET(":subid", a.subJsons)
}
func (a *SUBController) subs(c *gin.Context) {
subEncrypt, _ := a.settingService.GetSubEncrypt()
subShowInfo, _ := a.settingService.GetSubShowInfo()
println(c.Request.Header["User-Agent"][0])
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 {
c.String(400, "Error!")
} else {
@@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) {
}
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
c.Writer.Header().Set("Profile-Title", headers[2])
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
if subEncrypt {
if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else {
c.String(200, result)
}
}
}
func (a *SUBController) subJsons(c *gin.Context) {
println(c.Request.Header["User-Agent"][0])
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
c.String(200, jsonSub)
}
}

359
sub/subJsonService.go Normal file
View File

@@ -0,0 +1,359 @@
package sub
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/json_util"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
)
//go:embed default.json
var defaultJson string
type SubJsonService struct {
fragmanet string
inboundService service.InboundService
SubService
}
func NewSubJsonService(fragment string) *SubJsonService {
return &SubJsonService{
fragmanet: fragment,
}
}
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
inbounds, err := s.SubService.getInboundsBySubId(subId)
if err != nil || len(inbounds) == 0 {
return "", "", err
}
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
var configJson map[string]interface{}
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
}
outbounds := []json_util.RawMessage{}
startIndex := 0
// Prepare Inbounds
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
}
if clients == nil {
continue
}
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil {
inbound.Listen = listen
inbound.Port = port
inbound.StreamSettings = streamSettings
}
}
var subClients []model.Client
for _, client := range clients {
if client.Enable && client.SubID == subId {
subClients = append(subClients, client)
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
}
}
outbound := s.getOutbound(inbound, subClients, host, startIndex)
if outbound != nil {
outbounds = append(outbounds, outbound...)
startIndex += len(outbound)
}
}
if len(outbounds) == 0 {
return "", "", nil
}
// Prepare statistics
for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
traffic.Down = clientTraffic.Down
traffic.Total = clientTraffic.Total
if clientTraffic.ExpiryTime > 0 {
traffic.ExpiryTime = clientTraffic.ExpiryTime
}
} else {
traffic.Up += clientTraffic.Up
traffic.Down += clientTraffic.Down
if traffic.Total == 0 || clientTraffic.Total == 0 {
traffic.Total = 0
} else {
traffic.Total += clientTraffic.Total
}
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
traffic.ExpiryTime = 0
}
}
}
if s.fragmanet != "" {
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
}
// Combile outbounds
outbounds = append(outbounds, defaultOutbounds...)
var outboundStrings []json_util.RawMessage
for _, outbound := range outbounds {
outboundStrings = append(outboundStrings, outbound)
}
configJson["outbounds"] = outboundStrings
finalJson, _ := json.MarshalIndent(configJson, "", " ")
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return string(finalJson), header, nil
}
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
var newOutbounds []json_util.RawMessage
stream := s.streamData(inbound.StreamSettings)
externalProxies, ok := stream["externalProxy"].([]interface{})
if !ok || len(externalProxies) == 0 {
externalProxies = []interface{}{
map[string]interface{}{
"forceTls": "same",
"dest": host,
"port": float64(inbound.Port),
},
}
}
delete(stream, "externalProxy")
config_index := startIndex
for _, ep := range externalProxies {
extPrxy := ep.(map[string]interface{})
inbound.Listen = extPrxy["dest"].(string)
inbound.Port = int(extPrxy["port"].(float64))
newStream := stream
switch extPrxy["forceTls"].(string) {
case "tls":
if newStream["security"] != "tls" {
newStream["security"] = "tls"
newStream["tslSettings"] = map[string]interface{}{}
}
case "none":
if newStream["security"] != "none" {
newStream["security"] = "none"
delete(newStream, "tslSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
inbound.StreamSettings = string(streamSettings)
for _, client := range clients {
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
switch inbound.Protocol {
case "vmess", "vless":
newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, client))
}
config_index += 1
}
}
return newOutbounds
}
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
var streamSettings map[string]interface{}
json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string)
if security == "tls" {
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
} else if security == "reality" {
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
}
delete(streamSettings, "sockopt")
if s.fragmanet != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "TcpNoDelay": true}`)
}
// remove proxy protocol
network, _ := streamSettings["network"].(string)
switch network {
case "tcp":
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
case "ws":
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
}
return streamSettings
}
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
netSettings, ok := setting.(map[string]interface{})
if ok {
delete(netSettings, "acceptProxyProtocol")
}
return netSettings
}
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
tlsData := make(map[string]interface{}, 1)
tlsClientSettings := tData["settings"].(map[string]interface{})
tlsData["serverName"] = tData["serverName"]
tlsData["alpn"] = tData["alpn"]
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok {
tlsData["allowInsecure"] = allowInsecure
}
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
tlsData["fingerprint"] = fingerprint
}
return tlsData
}
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
rltyData := make(map[string]interface{}, 1)
rltyClientSettings := rData["settings"].(map[string]interface{})
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)
shortIds, ok := rData["shortIds"].([]interface{})
if ok && len(shortIds) > 0 {
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
} else {
rltyData["shortId"] = ""
}
serverNames, ok := rData["serverNames"].([]interface{})
if ok && len(serverNames) > 0 {
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
} else {
rltyData["serverName"] = ""
}
return rltyData
}
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
outbound := Outbound{}
usersData := make([]UserVnext, 1)
usersData[0].ID = client.ID
usersData[0].Level = 8
if inbound.Protocol == model.VLESS {
usersData[0].Flow = client.Flow
usersData[0].Encryption = "none"
}
vnextData := make([]VnextSetting, 1)
vnextData[0] = VnextSetting{
Address: inbound.Listen,
Port: inbound.Port,
Users: usersData,
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
outbound.Settings = OutboundSettings{
Vnext: vnextData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
outbound := Outbound{}
serverData := make([]ServerSetting, 1)
serverData[0] = ServerSetting{
Address: inbound.Listen,
Port: inbound.Port,
Level: 8,
Password: client.Password,
}
if inbound.Protocol == model.Shadowsocks {
var inboundSettings map[string]interface{}
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
method, _ := inboundSettings["method"].(string)
serverData[0].Method = method
// server password in multi-user 2022 protocols
if strings.HasPrefix(method, "2022") {
if serverPassword, ok := inboundSettings["password"].(string); ok {
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
}
}
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
outbound.Settings = OutboundSettings{
Servers: serverData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux map[string]interface{} `json:"mux,omitempty"`
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
Settings OutboundSettings `json:"settings,omitempty"`
}
type OutboundSettings struct {
Vnext []VnextSetting `json:"vnext,omitempty"`
Servers []ServerSetting `json:"servers,omitempty"`
}
type VnextSetting struct {
Address string `json:"address"`
Port int `json:"port"`
Users []UserVnext `json:"users"`
}
type UserVnext struct {
Encryption string `json:"encryption,omitempty"`
Flow string `json:"flow,omitempty"`
ID string `json:"id"`
Level int `json:"level"`
}
type ServerSetting struct {
Password string `json:"password"`
Level int `json:"level"`
Address string `json:"address"`
Port int `json:"port"`
Flow string `json:"flow,omitempty"`
Method string `json:"method,omitempty"`
}

View File

@@ -10,6 +10,7 @@ import (
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
@@ -17,44 +18,46 @@ import (
)
type SubService struct {
address string
showInfo bool
address string
showInfo bool
remarkModel string
inboundService service.InboundService
settingServics service.SettingService
}
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
func NewSubService(showInfo bool, remarkModel string) *SubService {
return &SubService{
showInfo: showInfo,
remarkModel: remarkModel,
}
}
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host
s.showInfo = showInfo
var result []string
var headers []string
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId)
if err != nil {
return nil, nil, err
return nil, "", err
}
// Prepare Inbounds
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
logger.Error("SubService - GetSub: Unable to get clients from inbound")
logger.Error("SubService - GetClients: Unable to get clients from inbound")
}
if clients == nil {
continue
}
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil {
inbound.Listen = fallbackMaster.Listen
inbound.Port = fallbackMaster.Port
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
inbound.StreamSettings = string(modifiedStream)
inbound.Listen = listen
inbound.Port = port
inbound.StreamSettings = streamSettings
}
}
for _, client := range clients {
@@ -65,6 +68,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
}
}
}
// Prepare statistics
for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
@@ -86,17 +91,21 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
}
}
}
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
updateInterval, _ := s.settingServics.GetSubUpdates()
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
return result, headers, nil
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return result, header, nil
}
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
SELECT DISTINCT inbounds.id
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
WHERE
protocol in ('vmess','vless','trojan','shadowsocks')
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
)`, subId, true).Find(&inbounds).Error
if err != nil {
return nil, err
}
@@ -112,7 +121,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
return xray.ClientTraffic{}
}
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
db := database.GetDB()
var inbound *model.Inbound
err := db.Model(model.Inbound{}).
@@ -120,9 +129,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
Find(&inbound).Error
if err != nil {
return nil, err
return "", 0, "", err
}
return inbound, nil
var stream map[string]interface{}
json.Unmarshal([]byte(streamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
return inbound.Listen, inbound.Port, string(modifiedStream), nil
}
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
@@ -196,7 +215,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}
security, _ := stream["security"].(string)
var domains []interface{}
obj["tls"] = security
if security == "tls" {
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@@ -208,24 +226,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}
obj["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
obj["sni"], _ = sniValue.(string)
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
obj["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
obj["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
obj["allowInsecure"], _ = insecure.(bool)
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
obj["add"] = serverName
}
}
@@ -239,16 +251,30 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}
obj["id"] = clients[clientIndex].ID
if len(domains) > 0 {
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
obj["add"] = domain["domain"].(string)
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
newObj := map[string]interface{}{}
for key, value := range obj {
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
newObj[key] = value
}
}
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
newObj["add"] = ep["dest"].(string)
newObj["port"] = int(ep["port"].(float64))
if newSecurity != "same" {
newObj["tls"] = newSecurity
}
if index > 0 {
links += "\n"
}
jsonStr, _ := json.MarshalIndent(obj, "", " ")
jsonStr, _ := json.MarshalIndent(newObj, "", " ")
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
}
return links
@@ -323,7 +349,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@@ -335,11 +360,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
@@ -348,19 +374,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["allowInsecure"] = "1"
}
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
if security == "reality" {
@@ -370,30 +388,21 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string)
params["sni"] = sNames[random.Num(len(sNames))].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string)
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
}
params["spx"] = "/" + random.Seq(15)
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@@ -401,6 +410,48 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
}
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
url, _ := url.Parse(link)
q := url.Query()
@@ -412,20 +463,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
// Set the new query values on the URL
url.RawQuery = q.Encode()
if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
url.Fragment = s.genRemark(inbound, email, "")
return url.String()
}
@@ -493,7 +530,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
}
security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@@ -505,11 +541,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
@@ -518,14 +555,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["allowInsecure"] = "1"
}
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
}
@@ -536,33 +565,66 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string)
params["sni"] = sNames[random.Num(len(sNames))].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string)
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
}
params["spx"] = "/" + random.Seq(15)
}
}
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
url, _ := url.Parse(link)
@@ -575,20 +637,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
// Set the new query values on the URL
url.RawQuery = q.Encode()
if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
url.Fragment = s.genRemark(inbound, email, "")
return url.String()
}
@@ -658,10 +706,78 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
}
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
}
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
if method[0] == '2' {
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
}
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
url, _ := url.Parse(link)
q := url.Query()
@@ -672,22 +788,36 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, "")
return url.String()
}
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
var remark []string
separationChar := string(s.remarkModel[0])
orderChars := s.remarkModel[1:]
orders := map[byte]string{
'i': "",
'e': "",
'o': "",
}
if len(email) > 0 {
if len(inbound.Remark) > 0 {
remark = append(remark, inbound.Remark)
orders['e'] = email
}
if len(inbound.Remark) > 0 {
orders['i'] = inbound.Remark
}
if len(extra) > 0 {
orders['o'] = extra
}
var remark []string
for i := 0; i < len(orderChars); i++ {
char := orderChars[i]
order, exists := orders[char]
if exists && order != "" {
remark = append(remark, order)
}
remark = append(remark, email)
if len(extra) > 0 {
remark = append(remark, extra)
}
} else {
return inbound.Remark
}
if s.showInfo {
@@ -704,7 +834,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
// Get remained days
if statsExist {
if !stats.Enable {
return fmt.Sprintf("⛔N/A-%s", strings.Join(remark, "-"))
return fmt.Sprintf("⛔N/A%s%s", separationChar, strings.Join(remark, separationChar))
}
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
@@ -718,7 +848,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
}
}
}
return strings.Join(remark, "-")
return strings.Join(remark, separationChar)
}
func searchKey(data interface{}, key string) (interface{}, bool) {

View File

@@ -41,3 +41,7 @@ func Seq(n int) string {
}
return string(runes)
}
func Num(n int) int {
return rand.Intn(n)
}

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
@import "../lib/style/index.less";
@import "../lib/style/components.less";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
@import "../lib/style/index.less";
@import "../lib/style/components.less";
@blue-6: #0E49B5;
@border-radius-base: 1rem;
@progress-remaining-color: #EDEDED;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,344 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor .CodeMirror-line::selection,
.cm-fat-cursor .CodeMirror-line > span::selection,
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
.cm-fat-cursor .CodeMirror-line::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
.cm-fat-cursor { caret-color: transparent; }
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
z-index: 0;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
outline: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function bracketFolding(pairs) {
return function(cm, start) {
var line = start.line, lineText = cm.getLine(line);
function findOpening(pair) {
var tokenType;
for (var at = start.ch, pass = 0;;) {
var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1);
if (found == -1) {
if (pass == 1) break;
pass = 1;
at = lineText.length;
continue;
}
if (pass == 1 && found < start.ch) break;
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair};
at = found - 1;
}
}
function findRange(found) {
var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh
outer: for (var i = line; i <= lastLine; ++i) {
var text = cm.getLine(i), pos = i == line ? startCh : 0;
for (;;) {
var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) {
if (pos == nextOpen) ++count;
else if (!--count) { end = i; endCh = pos; break outer; }
}
++pos;
}
}
if (end == null || line == end) return null
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
}
var found = []
for (var i = 0; i < pairs.length; i++) {
var open = findOpening(pairs[i])
if (open) found.push(open)
}
found.sort(function(a, b) { return a.ch - b.ch })
for (var i = 0; i < found.length; i++) {
var range = findRange(found[i])
if (range) return range
}
return null
}
}
CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]]));
CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]]));
CodeMirror.registerHelper("fold", "import", function(cm, start) {
function hasImport(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type != "keyword" || start.string != "import") return null;
// Now find closing semicolon, return its position
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
var text = cm.getLine(i), semi = text.indexOf(";");
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
}
}
var startLine = start.line, has = hasImport(startLine), prev;
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
function hasInclude(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
var startLine = start.line, has = hasInclude(startLine);
if (has == null || hasInclude(startLine - 1) != null) return null;
for (var end = startLine;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});
});

View File

@@ -0,0 +1,159 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function doFold(cm, pos, options, force) {
if (options && options.call) {
var finder = options;
options = null;
} else {
var finder = getOption(cm, options, "rangeFinder");
}
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
var minSize = getOption(cm, options, "minFoldSize");
function getRange(allowFolded) {
var range = finder(cm, pos);
if (!range || range.to.line - range.from.line < minSize) return null;
if (force === "fold") return range;
var marks = cm.findMarksAt(range.from);
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold) {
if (!allowFolded) return null;
range.cleared = true;
marks[i].clear();
}
}
return range;
}
var range = getRange(true);
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
pos = CodeMirror.Pos(pos.line - 1, 0);
range = getRange(false);
}
if (!range || range.cleared || force === "unfold") return;
var myWidget = makeWidget(cm, options, range);
CodeMirror.on(myWidget, "mousedown", function(e) {
myRange.clear();
CodeMirror.e_preventDefault(e);
});
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
clearOnEnter: getOption(cm, options, "clearOnEnter"),
__isFold: true
});
myRange.on("clear", function(from, to) {
CodeMirror.signal(cm, "unfold", cm, from, to);
});
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
function makeWidget(cm, options, range) {
var widget = getOption(cm, options, "widget");
if (typeof widget == "function") {
widget = widget(range.from, range.to);
}
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");
widget.appendChild(text);
widget.className = "CodeMirror-foldmarker";
} else if (widget) {
widget = widget.cloneNode(true)
}
return widget;
}
// Clumsy backwards-compatible interface
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
};
// New-style interface
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
doFold(this, pos, options, force);
});
CodeMirror.defineExtension("isFolded", function(pos) {
var marks = this.findMarksAt(pos);
for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold) return true;
});
CodeMirror.commands.toggleFold = function(cm) {
cm.foldCode(cm.getCursor());
};
CodeMirror.commands.fold = function(cm) {
cm.foldCode(cm.getCursor(), null, "fold");
};
CodeMirror.commands.unfold = function(cm) {
cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold");
};
CodeMirror.commands.foldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold");
});
};
CodeMirror.commands.unfoldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold");
});
};
CodeMirror.registerHelper("fold", "combine", function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
for (var i = 0; i < funcs.length; ++i) {
var found = funcs[i](cm, start);
if (found) return found;
}
};
});
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
var helpers = cm.getHelpers(start, "fold");
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, start);
if (cur) return cur;
}
});
var defaultOptions = {
rangeFinder: CodeMirror.fold.auto,
widget: "\u2194",
minFoldSize: 0,
scanUp: false,
clearOnEnter: true
};
CodeMirror.defineOption("foldOptions", null);
function getOption(cm, options, name) {
if (options && options[name] !== undefined)
return options[name];
var editorOptions = cm.options.foldOptions;
if (editorOptions && editorOptions[name] !== undefined)
return editorOptions[name];
return defaultOptions[name];
}
CodeMirror.defineExtension("foldOption", function(options, name) {
return getOption(this, options, name);
});
});

View File

@@ -0,0 +1,20 @@
.CodeMirror-foldmarker {
color: blue;
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
font-family: arial;
line-height: .3;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: .7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open:after {
content: "\25BE";
}
.CodeMirror-foldgutter-folded:after {
content: "\25B8";
}

View File

@@ -0,0 +1,169 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./foldcode"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./foldcode"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
cm.off("changes", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
cm.off("swapDoc", onChange);
cm.off("optionChange", optionChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
cm.on("changes", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
cm.on("swapDoc", onChange);
cm.on("optionChange", optionChange);
}
});
var Pos = CodeMirror.Pos;
function State(options) {
this.options = options;
this.from = this.to = 0;
}
function parseOptions(opts) {
if (opts === true) opts = {};
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
return opts;
}
function isFolded(cm, line) {
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold) {
var fromPos = marks[i].find(-1);
if (fromPos && fromPos.line === line)
return marks[i];
}
}
}
function marker(spec) {
if (typeof spec == "string") {
var elt = document.createElement("div");
elt.className = spec + " CodeMirror-guttermarker-subtle";
return elt;
} else {
return spec.cloneNode(true);
}
}
function updateFoldInfo(cm, from, to) {
var opts = cm.state.foldGutter.options, cur = from - 1;
var minSize = cm.foldOption(opts, "minFoldSize");
var func = cm.foldOption(opts, "rangeFinder");
// we can reuse the built-in indicator element if its className matches the new state
var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
cm.eachLine(from, to, function(line) {
++cur;
var mark = null;
var old = line.gutterMarkers;
if (old) old = old[opts.gutter];
if (isFolded(cm, cur)) {
if (clsFolded && old && clsFolded.test(old.className)) return;
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0);
var range = func && func(cm, pos);
if (range && range.to.line - range.from.line >= minSize) {
if (clsOpen && old && clsOpen.test(old.className)) return;
mark = marker(opts.indicatorOpen);
}
}
if (!mark && !old) return;
cm.setGutterMarker(line, opts.gutter, mark);
});
}
// copied from CodeMirror/src/util/dom.js
function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
function updateInViewport(cm) {
var vp = cm.getViewport(), state = cm.state.foldGutter;
if (!state) return;
cm.operation(function() {
updateFoldInfo(cm, vp.from, vp.to);
});
state.from = vp.from; state.to = vp.to;
}
function onGutterClick(cm, line, gutter) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
if (gutter != opts.gutter) return;
var folded = isFolded(cm, line);
if (folded) folded.clear();
else cm.foldCode(Pos(line, 0), opts);
}
function optionChange(cm, option) {
if (option == "mode") onChange(cm)
}
function onChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
state.from = state.to = 0;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
}
function onViewportChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() {
var vp = cm.getViewport();
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
updateInViewport(cm);
} else {
cm.operation(function() {
if (vp.from < state.from) {
updateFoldInfo(cm, vp.from, state.from);
state.from = vp.from;
}
if (vp.to > state.to) {
updateFoldInfo(cm, state.to, vp.to);
state.to = vp.to;
}
});
}
}, opts.updateViewportTimeSpan || 400);
}
function onFold(cm, from) {
var state = cm.state.foldGutter;
if (!state) return;
var line = from.line;
if (line >= state.from && line < state.to)
updateFoldInfo(cm, line, line + 1);
}
});

View File

@@ -0,0 +1,162 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var Pos = CodeMirror.Pos;
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
function arrayContains(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
if (arr[i] === item) {
return true;
}
}
return false;
}
return arr.indexOf(item) != -1;
}
function scriptHint(editor, keywords, getToken, options) {
// Find the token at the cursor
var cur = editor.getCursor(), token = getToken(editor, cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
if (innerMode.mode.helperType === "json") return;
token.state = innerMode.state;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
type: token.string == "." ? "property" : null};
} else if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
var tprop = token;
// If it is a property, find out what it is a property of.
while (tprop.type == "property") {
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string != ".") return;
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (!context) var context = [];
context.push(tprop);
}
return {list: getCompletions(token, context, keywords, options),
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)};
}
function javascriptHint(editor, options) {
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {
// This getToken, it is for coffeescript, imitates the behavior of
// getTokenAt method in javascript.js, that is, returning "property"
// type and treat "." as independent token.
var token = editor.getTokenAt(cur);
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
token.end = token.start;
token.string = '.';
token.type = "property";
}
else if (/^\.[\w$_]*$/.test(token.string)) {
token.type = "property";
token.start++;
token.string = token.string.replace(/\./, '');
}
return token;
}
function coffeescriptHint(editor, options) {
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
}
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
"toUpperCase toLowerCase split concat match replace search").split(" ");
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
var funcProps = "prototype apply call bind".split(" ");
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function forAllProps(obj, callback) {
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
for (var name in obj) callback(name)
} else {
for (var o = obj; o; o = Object.getPrototypeOf(o))
Object.getOwnPropertyNames(o).forEach(callback)
}
}
function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
forAllProps(obj, maybeAdd)
}
if (context && context.length) {
// If this is a property, see if it belongs to some object we can
// find in the current environment.
var obj = context.pop(), base;
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
if (!options || options.useGlobalScope !== false)
base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
(typeof global.jQuery == 'function'))
base = global.jQuery();
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
base = global._();
}
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
// If not, just look in the global object, any local scope, and optional additional-context
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var c = token.state.context; c; c = c.prev)
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
if (options && options.additionalContext != null)
for (var key in options.additionalContext)
maybeAdd(key);
if (!options || options.useGlobalScope !== false)
gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
}
});

View File

@@ -0,0 +1,960 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var trackScope = parserConfig.trackScope !== false
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
};
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eat("=");
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#" && stream.peek() == "!") {
stream.skipToEnd();
return ret("meta", "meta");
} else if (ch == "#" && stream.eatWhile(wordRE)) {
return ret("variable", "property")
} else if (ch == "<" && stream.match("!--") ||
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-|&?]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
if (ch == "?" && stream.eat(".")) return ret(".")
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/`]/.test(ch)) {
for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
if (!trackScope) return false
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function inList(name, list) {
for (var v = list; v; v = v.next) if (v.name == name) return true
return false;
}
function register(varname) {
var state = cx.state;
cx.marked = "def";
if (!trackScope) return
if (state.context) {
if (state.lexical.info == "var" && state.context && state.context.block) {
// FIXME function decls are also not block scoped
var newContext = registerVarScoped(varname, state.context)
if (newContext != null) {
state.context = newContext
return
}
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
}
// Combinators
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars
}
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
pushcontext.lex = pushblockcontext.lex = true
function popcontext() {
cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev
}
popcontext.lex = true
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function maybeCatchBinding(type) {
if (type == "(") return cont(funarg, expect(")"))
}
function expression(type, value) {
return expressionInner(type, value, false);
}
function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(maybeexpression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(maybeexpression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
if (type == "quasi") { return pass(quasiType, afterType); }
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprops(type) {
if (type.match(/[\}\)\]]/)) return cont()
if (type == "," || type == ";") return cont(typeprops)
return pass(typeprop, typeprops)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
} else if (!type.match(/[;\}\)\],]/)) {
return cont()
}
}
function quasiType(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasiType);
return cont(typeexpr, continueQuasiType);
}
function continueQuasiType(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasiType);
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, forspec2);
if (type == "variable") return cont(forspec2);
return pass(forspec2)
}
function forspec2(type, value) {
if (type == ")") return cont()
if (type == ";") return cont(forspec2)
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
return pass(expression, forspec2)
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "async" ||
(type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword";
return cont(classBody);
}
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
return cont(classfield, classBody);
}
if (type == "number" || type == "string") return cont(classfield, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), classfield, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "!") return cont(classfield)
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
if (type == "(") return pass(expression);
if (type == ".") return pass(maybeoperatorComma);
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function enumdef() {
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
}
function enummember() {
return pass(pattern, maybeAssign);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse && c != popcontext) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
// Depends on jshint.js from https://github.com/jshint/jshint
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
// declare global: JSHINT
function validator(text, options) {
if (!window.JSHINT) {
if (window.console) {
window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.");
}
return [];
}
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
JSHINT(text, options, options.globals);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);
return result;
}
CodeMirror.registerHelper("lint", "javascript", validator);
function parseErrors(errors, output) {
for ( var i = 0; i < errors.length; i++) {
var error = errors[i];
if (error) {
if (error.line <= 0) {
if (window.console) {
window.console.warn("Cannot display JSHint error (invalid line " + error.line + ")", error);
}
continue;
}
var start = error.character - 1, end = start + 1;
if (error.evidence) {
var index = error.evidence.substring(start).search(/.\b/);
if (index > -1) {
end += index;
}
}
// Convert to format expected by validation service
var hint = {
message: error.reason,
severity: error.code ? (error.code.startsWith('W') ? "warning" : "error") : "error",
from: CodeMirror.Pos(error.line - 1, start),
to: CodeMirror.Pos(error.line - 1, end)
};
output.push(hint);
}
}
}
});

View File

@@ -0,0 +1,79 @@
/* The lint marker gutter */
.CodeMirror-lint-markers {
width: 16px;
}
.CodeMirror-lint-tooltip {
background-color: #ffd;
border: 1px solid black;
border-radius: 4px 4px 4px 4px;
color: black;
font-family: monospace;
font-size: 10pt;
overflow: hidden;
padding: 2px 5px;
position: fixed;
white-space: pre;
white-space: pre-wrap;
z-index: 100;
max-width: 600px;
opacity: 0;
transition: opacity .4s;
-moz-transition: opacity .4s;
-webkit-transition: opacity .4s;
-o-transition: opacity .4s;
-ms-transition: opacity .4s;
}
.CodeMirror-lint-mark {
background-position: left bottom;
background-repeat: repeat-x;
}
.CodeMirror-lint-mark-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
}
.CodeMirror-lint-mark-error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
}
.CodeMirror-lint-marker {
background-position: center center;
background-repeat: no-repeat;
cursor: pointer;
display: inline-block;
height: 16px;
width: 16px;
vertical-align: middle;
position: relative;
}
.CodeMirror-lint-message {
padding-left: 18px;
background-position: top left;
background-repeat: no-repeat;
}
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
}
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
}
.CodeMirror-lint-marker-multiple {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right bottom;
width: 100%; height: 100%;
}
.CodeMirror-lint-line-error {
background-color: rgba(183, 76, 81, 0.08);
}
.CodeMirror-lint-line-warning {
background-color: rgba(255, 211, 0, 0.1);
}

View File

@@ -0,0 +1,288 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var GUTTER_ID = "CodeMirror-lint-markers";
var LINT_LINE_ID = "CodeMirror-lint-line-";
function showTooltip(cm, e, content) {
var tt = document.createElement("div");
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
tt.appendChild(content.cloneNode(true));
if (cm.state.lint.options.selfContain)
cm.getWrapperElement().appendChild(tt);
else
document.body.appendChild(tt);
function position(e) {
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
var top = Math.max(0, e.clientY - tt.offsetHeight - 5);
var left = Math.max(0, Math.min(e.clientX + 5, tt.ownerDocument.defaultView.innerWidth - tt.offsetWidth));
tt.style.top = top + "px"
tt.style.left = left + "px";
}
CodeMirror.on(document, "mousemove", position);
position(e);
if (tt.style.opacity != null) tt.style.opacity = 1;
return tt;
}
function rm(elt) {
if (elt.parentNode) elt.parentNode.removeChild(elt);
}
function hideTooltip(tt) {
if (!tt.parentNode) return;
if (tt.style.opacity == null) rm(tt);
tt.style.opacity = 0;
setTimeout(function() { rm(tt); }, 600);
}
function showTooltipFor(cm, e, content, node) {
var tooltip = showTooltip(cm, e, content);
function hide() {
CodeMirror.off(node, "mouseout", hide);
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
}
var poll = setInterval(function() {
if (tooltip) for (var n = node;; n = n.parentNode) {
if (n && n.nodeType == 11) n = n.host;
if (n == document.body) return;
if (!n) { hide(); break; }
}
if (!tooltip) return clearInterval(poll);
}, 400);
CodeMirror.on(node, "mouseout", hide);
}
function LintState(cm, conf, hasGutter) {
this.marked = [];
if (conf instanceof Function) conf = {getAnnotations: conf};
if (!conf || conf === true) conf = {};
this.options = {};
this.linterOptions = conf.options || {};
for (var prop in defaults) this.options[prop] = defaults[prop];
for (var prop in conf) {
if (defaults.hasOwnProperty(prop)) {
if (conf[prop] != null) this.options[prop] = conf[prop];
} else if (!conf.options) {
this.linterOptions[prop] = conf[prop];
}
}
this.timeout = null;
this.hasGutter = hasGutter;
this.onMouseOver = function(e) { onMouseOver(cm, e); };
this.waitingFor = 0
}
var defaults = {
highlightLines: false,
tooltips: true,
delay: 500,
lintOnChange: true,
getAnnotations: null,
async: false,
selfContain: null,
formatAnnotation: null,
onUpdateLinting: null
}
function clearMarks(cm) {
var state = cm.state.lint;
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
if (state.options.highlightLines) clearErrorLines(cm);
for (var i = 0; i < state.marked.length; ++i)
state.marked[i].clear();
state.marked.length = 0;
}
function clearErrorLines(cm) {
cm.eachLine(function(line) {
var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
if (has) cm.removeLineClass(line, "wrap", has[0]);
})
}
function makeMarker(cm, labels, severity, multiple, tooltips) {
var marker = document.createElement("div"), inner = marker;
marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
if (multiple) {
inner = marker.appendChild(document.createElement("div"));
inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
}
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
showTooltipFor(cm, e, labels, inner);
});
return marker;
}
function getMaxSeverity(a, b) {
if (a == "error") return a;
else return b;
}
function groupByLine(annotations) {
var lines = [];
for (var i = 0; i < annotations.length; ++i) {
var ann = annotations[i], line = ann.from.line;
(lines[line] || (lines[line] = [])).push(ann);
}
return lines;
}
function annotationTooltip(ann) {
var severity = ann.severity;
if (!severity) severity = "error";
var tip = document.createElement("div");
tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
if (typeof ann.messageHTML != 'undefined') {
tip.innerHTML = ann.messageHTML;
} else {
tip.appendChild(document.createTextNode(ann.message));
}
return tip;
}
function lintAsync(cm, getAnnotations) {
var state = cm.state.lint
var id = ++state.waitingFor
function abort() {
id = -1
cm.off("change", abort)
}
cm.on("change", abort)
getAnnotations(cm.getValue(), function(annotations, arg2) {
cm.off("change", abort)
if (state.waitingFor != id) return
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
cm.operation(function() {updateLinting(cm, annotations)})
}, state.linterOptions, cm);
}
function startLinting(cm) {
var state = cm.state.lint;
if (!state) return;
var options = state.options;
/*
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
*/
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
if (!getAnnotations) return;
if (options.async || getAnnotations.async) {
lintAsync(cm, getAnnotations)
} else {
var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
if (!annotations) return;
if (annotations.then) annotations.then(function(issues) {
cm.operation(function() {updateLinting(cm, issues)})
});
else cm.operation(function() {updateLinting(cm, annotations)})
}
}
function updateLinting(cm, annotationsNotSorted) {
var state = cm.state.lint;
if (!state) return;
var options = state.options;
clearMarks(cm);
var annotations = groupByLine(annotationsNotSorted);
for (var line = 0; line < annotations.length; ++line) {
var anns = annotations[line];
if (!anns) continue;
var maxSeverity = null;
var tipLabel = state.hasGutter && document.createDocumentFragment();
for (var i = 0; i < anns.length; ++i) {
var ann = anns[i];
var severity = ann.severity;
if (!severity) severity = "error";
maxSeverity = getMaxSeverity(maxSeverity, severity);
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
__annotation: ann
}));
}
if (state.hasGutter)
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
options.tooltips));
if (options.highlightLines)
cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
}
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
}
function onChange(cm) {
var state = cm.state.lint;
if (!state) return;
clearTimeout(state.timeout);
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
}
function popupTooltips(cm, annotations, e) {
var target = e.target || e.srcElement;
var tooltip = document.createDocumentFragment();
for (var i = 0; i < annotations.length; i++) {
var ann = annotations[i];
tooltip.appendChild(annotationTooltip(ann));
}
showTooltipFor(cm, e, tooltip, target);
}
function onMouseOver(cm, e) {
var target = e.target || e.srcElement;
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
var annotations = [];
for (var i = 0; i < spans.length; ++i) {
var ann = spans[i].__annotation;
if (ann) annotations.push(ann);
}
if (annotations.length) popupTooltips(cm, annotations, e);
}
CodeMirror.defineOption("lint", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
clearMarks(cm);
if (cm.state.lint.options.lintOnChange !== false)
cm.off("change", onChange);
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
clearTimeout(cm.state.lint.timeout);
delete cm.state.lint;
}
if (val) {
var gutters = cm.getOption("gutters"), hasLintGutter = false;
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
if (state.options.lintOnChange)
cm.on("change", onChange);
if (state.options.tooltips != false && state.options.tooltips != "gutter")
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
startLinting(cm);
}
});
CodeMirror.defineExtension("performLint", function() {
startLinting(this);
});
});

View File

@@ -0,0 +1,86 @@
/*
Copyright (C) 2011 by MarkLogic Corporation
Author: Mike Brevoort <mike@brevoort.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
.cm-s-xq.CodeMirror { border-radius: 1.5rem; border: 1px solid #d9d9d9; height: auto; }
.cm-s-xq.CodeMirror:hover { background-color: #edf4fa; border-color: #2f67c2; transition: all .3s; }
.cm-s-xq .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: rgb(221 221 221 / 20%); white-space: nowrap; }
.cm-s-xq span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
.cm-s-xq span.cm-atom { color: #7A316F; font-weight:bold; }
.cm-s-xq span.cm-number { color: #389E0D; }
.cm-s-xq span.cm-def { text-decoration:underline; }
.cm-s-xq span.cm-variable { color: black; }
.cm-s-xq span.cm-variable-2 { color:black; }
.cm-s-xq span.cm-variable-3, .cm-s-xq span.cm-type { color: black; }
.cm-s-xq span.cm-property { color: #0e49b5; }
.cm-s-xq span.cm-operator {}
.cm-s-xq span.cm-comment { color: #bbbbbb; font-style: italic; }
.cm-s-xq span.cm-string {}
.cm-s-xq span.cm-meta { color: yellow; }
.cm-s-xq span.cm-qualifier { color: grey; }
.cm-s-xq span.cm-builtin { color: #7EA656; }
.cm-s-xq span.cm-bracket { color: #cc7; }
.cm-s-xq span.cm-tag { color: #3F7F7F; }
.cm-s-xq span.cm-attribute { color: #7F007F; }
.cm-s-xq span.cm-error { color: #e04141; }
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); }
.dark .cm-s-xq.CodeMirror:hover { background-color: #0e2040; border-color: #0e49b5; transition: all .3s; }
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
.dark .cm-s-xq .CodeMirror-cursor { border-left: 1px solid white; }
.dark .cm-s-xq span.cm-keyword { color: #FFBD40; }
.dark .cm-s-xq span.cm-atom { color: #c099ff; }
.dark .cm-s-xq span.cm-number { color: #9ccfd8; }
.dark .cm-s-xq span.cm-def { color: #FFF; text-decoration:underline; }
.dark .cm-s-xq span.cm-variable { color: #FFF; }
.dark .cm-s-xq span.cm-variable-2 { color: #EEE; }
.dark .cm-s-xq span.cm-variable-3, .dark .cm-s-xq span.cm-type { color: #DDD; }
.dark .cm-s-xq span.cm-property { color: #f6c177 }
.dark .cm-s-xq span.cm-operator {}
.dark .cm-s-xq span.cm-comment { color: gray; }
.dark .cm-s-xq span.cm-string {}
.dark .cm-s-xq span.cm-meta { color: yellow; }
.dark .cm-s-xq span.cm-qualifier { color: #FFF700; }
.dark .cm-s-xq span.cm-builtin { color: #30a; }
.dark .cm-s-xq span.cm-bracket { color: #cc7; }
.dark .cm-s-xq span.cm-tag { color: #FFBD40; }
.dark .cm-s-xq span.cm-attribute { color: #FFF700; }
.dark .cm-s-xq span.cm-error { color: #e04141; }
.dark .cm-s-xq .CodeMirror-activeline-background { background: #27282E; }
.dark .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
.Line-Hover{transition: all .2s;}
.Line-Hover:hover{ background-color: rgb(4 48 143 / 5%) !important; }
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !important; }
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -19,6 +19,11 @@ const supportLangs = [
value: 'ru-RU',
icon: '🇷🇺',
},
{
name: 'Tiếng Việt',
value: 'vi-VN',
icon: '🇻🇳',
},
];
function getLang() {
@@ -60,4 +65,4 @@ function isSupportLang(lang) {
}
return false;
}
}

View File

@@ -1,30 +1,3 @@
class User {
constructor() {
this.username = "";
this.password = "";
}
}
class Msg {
constructor(success, msg, obj) {
this.success = false;
this.msg = "";
this.obj = null;
if (success != null) {
this.success = success;
}
if (msg != null) {
this.msg = msg;
}
if (obj != null) {
this.obj = obj;
}
}
}
class DBInbound {
constructor(data) {
@@ -83,6 +56,10 @@ class DBInbound {
return this.protocol === Protocols.HTTP;
}
get isWireguard() {
return this.protocol === Protocols.WIREGUARD;
}
get address() {
let address = location.hostname;
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
@@ -139,6 +116,19 @@ class DBInbound {
return Inbound.fromJson(config);
}
isMultiUser() {
switch (this.protocol) {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
return true;
case Protocols.SHADOWSOCKS:
return this.toInbound().isSSMultiUser;
default:
return false;
}
}
hasLink() {
switch (this.protocol) {
case Protocols.VMESS:
@@ -150,59 +140,9 @@ class DBInbound {
return false;
}
}
genLink(address=this.address, remark=this.remark, clientIndex=0) {
const inbound = this.toInbound();
return inbound.genLink(address, remark, clientIndex);
}
get genInboundLinks() {
genInboundLinks(remarkModel) {
const inbound = this.toInbound();
return inbound.genInboundLinks(this.address, this.remark);
}
}
class AllSetting {
constructor(data) {
this.webListen = "";
this.webDomain = "";
this.webPort = 54321;
this.webCertFile = "";
this.webKeyFile = "";
this.webBasePath = "/";
this.sessionMaxAge = "";
this.expireDiff = "";
this.trafficDiff = "";
this.tgBotEnable = false;
this.tgBotToken = "";
this.tgBotChatId = "";
this.tgRunTime = "@daily";
this.tgBotBackup = false;
this.tgBotLoginNotify = false;
this.tgCpu = "";
this.tgLang = "";
this.xrayTemplateConfig = "";
this.subEnable = false;
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
this.subUpdates = 0;
this.subEncrypt = true;
this.subShowInfo = false;
this.timeLocation = "Asia/Tehran";
if (data == null) {
return
}
ObjectUtil.cloneProps(this, data);
}
equals(other) {
return ObjectUtil.equals(this, other);
return inbound.genInboundLinks(this.remark,remarkModel);
}
}

View File

@@ -0,0 +1,993 @@
const Protocols = {
Freedom: "freedom",
Blackhole: "blackhole",
DNS: "dns",
VMess: "vmess",
VLESS: "vless",
Trojan: "trojan",
Shadowsocks: "shadowsocks",
Socks: "socks",
HTTP: "http",
Wireguard: "wireguard"
};
const SSMethods = {
AES_256_GCM: 'aes-256-gcm',
AES_128_GCM: 'aes-128-gcm',
CHACHA20_POLY1305: 'chacha20-poly1305',
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
XCHACHA20_POLY1305: 'xchacha20-poly1305',
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
};
const TLS_FLOW_CONTROL = {
VISION: "xtls-rprx-vision",
VISION_UDP443: "xtls-rprx-vision-udp443",
};
const UTLS_FINGERPRINT = {
UTLS_CHROME: "chrome",
UTLS_FIREFOX: "firefox",
UTLS_SAFARI: "safari",
UTLS_IOS: "ios",
UTLS_android: "android",
UTLS_EDGE: "edge",
UTLS_360: "360",
UTLS_QQ: "qq",
UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized",
};
const ALPN_OPTION = {
H3: "h3",
H2: "h2",
HTTP1: "http/1.1",
};
const OutboundDomainStrategies = [
"AsIs",
"UseIP",
"UseIPv4",
"UseIPv6"
];
const WireguardDomainStrategy = [
"ForceIP",
"ForceIPv4",
"ForceIPv4v6",
"ForceIPv6",
"ForceIPv6v4"
];
Object.freeze(Protocols);
Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(ALPN_OPTION);
Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
class CommonClass {
static toJsonArray(arr) {
return arr.map(obj => obj.toJson());
}
static fromJson() {
return new CommonClass();
}
toJson() {
return this;
}
toString(format=true) {
return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson());
}
}
class TcpStreamSettings extends CommonClass {
constructor(type='none', host, path) {
super();
this.type = type;
this.host = host;
this.path = path;
}
static fromJson(json={}) {
let header = json.header;
if (!header) return new TcpStreamSettings();
if(header.type == 'http' && header.request){
return new TcpStreamSettings(
header.type,
header.request.headers.Host.join(','),
header.request.path.join(','),
);
}
return new TcpStreamSettings(header.type,'','');
}
toJson() {
return {
header: {
type: this.type,
request: this.type === 'http' ? {
headers: {
Host: ObjectUtil.isEmpty(this.host) ? [] : this.host.split(',')
},
path: ObjectUtil.isEmpty(this.path) ? ["/"] : this.path.split(',')
} : undefined,
}
};
}
}
class KcpStreamSettings extends CommonClass {
constructor(mtu=1350, tti=20,
uplinkCapacity=5,
downlinkCapacity=20,
congestion=false,
readBufferSize=2,
writeBufferSize=2,
type='none',
seed='',
) {
super();
this.mtu = mtu;
this.tti = tti;
this.upCap = uplinkCapacity;
this.downCap = downlinkCapacity;
this.congestion = congestion;
this.readBuffer = readBufferSize;
this.writeBuffer = writeBufferSize;
this.type = type;
this.seed = seed;
}
static fromJson(json={}) {
return new KcpStreamSettings(
json.mtu,
json.tti,
json.uplinkCapacity,
json.downlinkCapacity,
json.congestion,
json.readBufferSize,
json.writeBufferSize,
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
json.seed,
);
}
toJson() {
return {
mtu: this.mtu,
tti: this.tti,
uplinkCapacity: this.upCap,
downlinkCapacity: this.downCap,
congestion: this.congestion,
readBufferSize: this.readBuffer,
writeBufferSize: this.writeBuffer,
header: {
type: this.type,
},
seed: this.seed,
};
}
}
class WsStreamSettings extends CommonClass {
constructor(path='/', host='') {
super();
this.path = path;
this.host = host;
}
static fromJson(json={}) {
return new WsStreamSettings(
json.path,
json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
);
}
toJson() {
return {
path: this.path,
headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
};
}
}
class HttpStreamSettings extends CommonClass {
constructor(path='/', host='') {
super();
this.path = path;
this.host = host;
}
static fromJson(json={}) {
return new HttpStreamSettings(
json.path,
json.host ? json.host.join(',') : '',
);
}
toJson() {
return {
path: this.path,
host: ObjectUtil.isEmpty(this.host) ? [''] : this.host.split(','),
}
}
}
class QuicStreamSettings extends CommonClass {
constructor(security='none',
key='', type='none') {
super();
this.security = security;
this.key = key;
this.type = type;
}
static fromJson(json={}) {
return new QuicStreamSettings(
json.security,
json.key,
json.header ? json.header.type : 'none',
);
}
toJson() {
return {
security: this.security,
key: this.key,
header: {
type: this.type,
}
}
}
}
class GrpcStreamSettings extends CommonClass {
constructor(serviceName="", multiMode=false) {
super();
this.serviceName = serviceName;
this.multiMode = multiMode;
}
static fromJson(json={}) {
return new GrpcStreamSettings(json.serviceName, json.multiMode);
}
toJson() {
return {
serviceName: this.serviceName,
multiMode: this.multiMode,
}
}
}
class TlsStreamSettings extends CommonClass {
constructor(serverName='',
alpn=[],
fingerprint = '',
allowInsecure = false) {
super();
this.serverName = serverName;
this.alpn = alpn;
this.fingerprint = fingerprint;
this.allowInsecure = allowInsecure;
}
static fromJson(json={}) {
return new TlsStreamSettings(
json.serverName,
json.alpn,
json.fingerprint,
json.allowInsecure,
);
}
toJson() {
return {
serverName: this.serverName,
alpn: this.alpn,
fingerprint: this.fingerprint,
allowInsecure: this.allowInsecure,
};
}
}
class RealityStreamSettings extends CommonClass {
constructor(publicKey = '', fingerprint = '', serverName = '', shortId = '', spiderX = '/') {
super();
this.publicKey = publicKey;
this.fingerprint = fingerprint;
this.serverName = serverName;
this.shortId = shortId
this.spiderX = spiderX;
}
static fromJson(json = {}) {
return new RealityStreamSettings(
json.publicKey,
json.fingerprint,
json.serverName,
json.shortId,
json.spiderX,
);
}
toJson() {
return {
publicKey: this.publicKey,
fingerprint: this.fingerprint,
serverName: this.serverName,
shortId: this.shortId,
spiderX: this.spiderX,
};
}
};
class StreamSettings extends CommonClass {
constructor(network='tcp',
security='none',
tlsSettings=new TlsStreamSettings(),
realitySettings = new RealityStreamSettings(),
tcpSettings=new TcpStreamSettings(),
kcpSettings=new KcpStreamSettings(),
wsSettings=new WsStreamSettings(),
httpSettings=new HttpStreamSettings(),
quicSettings=new QuicStreamSettings(),
grpcSettings=new GrpcStreamSettings(),
) {
super();
this.network = network;
this.security = security;
this.tls = tlsSettings;
this.reality = realitySettings;
this.tcp = tcpSettings;
this.kcp = kcpSettings;
this.ws = wsSettings;
this.http = httpSettings;
this.quic = quicSettings;
this.grpc = grpcSettings;
}
get isTls() {
return this.security === 'tls';
}
get isReality() {
return this.security === "reality";
}
static fromJson(json={}) {
return new StreamSettings(
json.network,
json.security,
TlsStreamSettings.fromJson(json.tlsSettings),
RealityStreamSettings.fromJson(json.realitySettings),
TcpStreamSettings.fromJson(json.tcpSettings),
KcpStreamSettings.fromJson(json.kcpSettings),
WsStreamSettings.fromJson(json.wsSettings),
HttpStreamSettings.fromJson(json.httpSettings),
QuicStreamSettings.fromJson(json.quicSettings),
GrpcStreamSettings.fromJson(json.grpcSettings),
);
}
toJson() {
const network = this.network;
return {
network: network,
security: this.security,
tlsSettings: this.security == 'tls' ? this.tls.toJson() : undefined,
realitySettings: this.security == 'reality' ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
httpSettings: network === 'http' ? this.http.toJson() : undefined,
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
};
}
}
class Outbound extends CommonClass {
constructor(
tag='',
protocol=Protocols.VMess,
settings=null,
streamSettings = new StreamSettings(),
) {
super();
this.tag = tag;
this._protocol = protocol;
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
this.stream = streamSettings;
}
get protocol() {
return this._protocol;
}
set protocol(protocol) {
this._protocol = protocol;
this.settings = Outbound.Settings.getSettings(protocol);
this.stream = new StreamSettings();
}
canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
}
//this is used for xtls-rprx-vision
canEnableTlsFlow() {
if ((this.stream.security != 'none') && (this.stream.network === "tcp")) {
return this.protocol === Protocols.VLESS;
}
return false;
}
canEnableReality() {
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
return ["tcp", "http", "grpc"].includes(this.stream.network);
}
canEnableStream() {
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
}
hasVnext() {
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
}
hasServers() {
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
}
hasAddressPort() {
return [
Protocols.DNS,
Protocols.VMess,
Protocols.VLESS,
Protocols.Trojan,
Protocols.Shadowsocks,
Protocols.Socks,
Protocols.HTTP
].includes(this.protocol);
}
hasUsername() {
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
}
static fromJson(json={}) {
return new Outbound(
json.tag,
json.protocol,
Outbound.Settings.fromJson(json.protocol, json.settings),
StreamSettings.fromJson(json.streamSettings),
)
}
toJson() {
return {
tag: this.tag == '' ? undefined : this.tag,
protocol: this.protocol,
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
};
}
static fromLink(link) {
data = link.split('://');
if(data.length !=2) return null;
switch(data[0].toLowerCase()){
case Protocols.VMess:
return this.fromVmessLink(JSON.parse(Base64.decode(data[1])));
case Protocols.VLESS:
case Protocols.Trojan:
case 'ss':
return this.fromParamLink(link);
default:
return null;
}
}
static fromVmessLink(json={}){
let stream = new StreamSettings(json.net, json.tls);
let network = json.net;
if (network === 'tcp') {
stream.tcp = new TcpStreamSettings(
json.type,
json.host ?? '',
json.path ?? '');
} else if (network === 'kcp') {
stream.kcp = new KcpStreamSettings();
stream.type = json.type;
stream.seed = json.path;
} else if (network === 'ws') {
stream.ws = new WsStreamSettings(json.path,json.host);
} else if (network === 'http' || network == 'h2') {
stream.network = 'http'
stream.http = new HttpStreamSettings(
json.path,
json.host);
} else if (network === 'quic') {
stream.quic = new QuicStreamSettings(
json.host ? json.host : 'none',
json.path,
json.type ? json.type : 'none');
} else if (network === 'grpc') {
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
}
if(json.tls && json.tls == 'tls'){
stream.tls = new TlsStreamSettings(
json.sni,
json.alpn ? json.alpn.split(',') : [],
json.fp,
json.allowInsecure);
}
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
}
static fromParamLink(link){
const url = new URL(link);
let type = url.searchParams.get('type');
let security = url.searchParams.get('security') ?? 'none';
let stream = new StreamSettings(type, security);
let headerType = url.searchParams.get('headerType');
let host = url.searchParams.get('host');
let path = url.searchParams.get('path');
if (type === 'tcp') {
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
} else if (type === 'kcp') {
stream.kcp = new KcpStreamSettings();
stream.kcp.type = headerType ?? 'none';
stream.kcp.seed = path;
} else if (type === 'ws') {
stream.ws = new WsStreamSettings(path,host);
} else if (type === 'http' || type == 'h2') {
stream.http = new HttpStreamSettings(path,host);
} else if (type === 'quic') {
stream.quic = new QuicStreamSettings(
url.searchParams.get('quicSecurity') ?? 'none',
url.searchParams.get('key') ?? '',
headerType ?? 'none');
} else if (type === 'grpc') {
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
}
if(security == 'tls'){
let fp=url.searchParams.get('fp') ?? 'none';
let alpn=url.searchParams.get('alpn');
let allowInsecure=url.searchParams.get('allowInsecure');
let sni=url.searchParams.get('sni') ?? '';
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
}
if(security == 'reality'){
let pbk=url.searchParams.get('pbk');
let fp=url.searchParams.get('fp');
let sni=url.searchParams.get('sni') ?? '';
let sid=url.searchParams.get('sid') ?? '';
let spx=url.searchParams.get('spx') ?? '';
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
}
let data = link.split('?');
if(data.length != 2) return null;
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
const match = link.match(regex);
if (!match) return null;
let [, protocol, userData, address, port, ] = match;
port *= 1;
if(protocol == 'ss') {
protocol = 'shadowsocks';
userData = atob(userData).split(':');
}
var settings;
switch(protocol){
case Protocols.VLESS:
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
break;
case Protocols.Trojan:
settings = new Outbound.TrojanSettings(address, port, userData);
break;
case Protocols.Shadowsocks:
let method = userData.splice(0,1)[0];
settings = new Outbound.ShadowsocksSettings(address, port, userData.join(":"), method, true);
break;
default:
return null;
}
let remark = decodeURIComponent(url.hash);
// Remove '#' from url.hash
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
return new Outbound(remark, protocol, settings, stream);
}
}
Outbound.Settings = class extends CommonClass {
constructor(protocol) {
super();
this.protocol = protocol;
}
static getSettings(protocol) {
switch (protocol) {
case Protocols.Freedom: return new Outbound.FreedomSettings();
case Protocols.Blackhole: return new Outbound.BlackholeSettings();
case Protocols.DNS: return new Outbound.DNSSettings();
case Protocols.VMess: return new Outbound.VmessSettings();
case Protocols.VLESS: return new Outbound.VLESSSettings();
case Protocols.Trojan: return new Outbound.TrojanSettings();
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
case Protocols.Socks: return new Outbound.SocksSettings();
case Protocols.HTTP: return new Outbound.HttpSettings();
case Protocols.Wireguard: return new Outbound.WireguardSettings();
default: return null;
}
}
static fromJson(protocol, json) {
switch (protocol) {
case Protocols.Freedom: return Outbound.FreedomSettings.fromJson(json);
case Protocols.Blackhole: return Outbound.BlackholeSettings.fromJson(json);
case Protocols.DNS: return Outbound.DNSSettings.fromJson(json);
case Protocols.VMess: return Outbound.VmessSettings.fromJson(json);
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
default: return null;
}
}
toJson() {
return {};
}
};
Outbound.FreedomSettings = class extends CommonClass {
constructor(domainStrategy='', fragment={}) {
super();
this.domainStrategy = domainStrategy;
this.fragment = fragment;
}
static fromJson(json={}) {
return new Outbound.FreedomSettings(
json.domainStrategy,
json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : undefined,
);
}
toJson() {
return {
domainStrategy: ObjectUtil.isEmpty(this.domainStrategy) ? undefined : this.domainStrategy,
fragment: Object.keys(this.fragment).length === 0 ? undefined : this.fragment,
};
}
};
Outbound.FreedomSettings.Fragment = class extends CommonClass {
constructor(packets='1-3',length='',interval=''){
super();
this.packets = packets;
this.length = length;
this.interval = interval;
}
static fromJson(json={}) {
return new Outbound.FreedomSettings.Fragment(
json.packets,
json.length,
json.interval,
);
}
};
Outbound.BlackholeSettings = class extends CommonClass {
constructor(type) {
super();
this.type;
}
static fromJson(json={}) {
return new Outbound.BlackholeSettings(
json.response ? json.response.type : undefined,
);
}
toJson() {
return {
response: ObjectUtil.isEmpty(this.type) ? undefined : {type: this.type},
};
}
};
Outbound.DNSSettings = class extends CommonClass {
constructor(network='udp', address='1.1.1.1', port=53) {
super();
this.network = network;
this.address = address;
this.port = port;
}
static fromJson(json={}){
return new Outbound.DNSSettings(
json.network,
json.address,
json.port,
);
}
};
Outbound.VmessSettings = class extends CommonClass {
constructor(address, port, id) {
super();
this.address = address;
this.port = port;
this.id = id;
}
static fromJson(json={}) {
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VmessSettings();
return new Outbound.VmessSettings(
json.vnext[0].address,
json.vnext[0].port,
json.vnext[0].users[0].id,
);
}
toJson() {
return {
vnext: [{
address: this.address,
port: this.port,
users: [{id: this.id}],
}],
};
}
};
Outbound.VLESSSettings = class extends CommonClass {
constructor(address, port, id, flow, encryption='none') {
super();
this.address = address;
this.port = port;
this.id = id;
this.flow = flow;
this.encryption = encryption
}
static fromJson(json={}) {
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VLESSSettings();
return new Outbound.VLESSSettings(
json.vnext[0].address,
json.vnext[0].port,
json.vnext[0].users[0].id,
json.vnext[0].users[0].flow,
json.vnext[0].users[0].encryption,
);
}
toJson() {
return {
vnext: [{
address: this.address,
port: this.port,
users: [{id: this.id, flow: this.flow, encryption: 'none',}],
}],
};
}
};
Outbound.TrojanSettings = class extends CommonClass {
constructor(address, port, password) {
super();
this.address = address;
this.port = port;
this.password = password;
}
static fromJson(json={}) {
if(ObjectUtil.isArrEmpty(json.servers)) return new Outbound.TrojanSettings();
return new Outbound.TrojanSettings(
json.servers[0].address,
json.servers[0].port,
json.servers[0].password,
);
}
toJson() {
return {
servers: [{
address: this.address,
port: this.port,
password: this.password,
}],
};
}
};
Outbound.ShadowsocksSettings = class extends CommonClass {
constructor(address, port, password, method, uot) {
super();
this.address = address;
this.port = port;
this.password = password;
this.method = method;
this.uot = uot;
}
static fromJson(json={}) {
let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{}];
return new Outbound.ShadowsocksSettings(
servers[0].address,
servers[0].port,
servers[0].password,
servers[0].method,
servers[0].uot,
);
}
toJson() {
return {
servers: [{
address: this.address,
port: this.port,
password: this.password,
method: this.method,
uot: this.uot,
}],
};
}
};
Outbound.SocksSettings = class extends CommonClass {
constructor(address, port, user, pass) {
super();
this.address = address;
this.port = port;
this.user = user;
this.pass = pass;
}
static fromJson(json={}) {
let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.SocksSettings(
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
);
}
toJson() {
return {
servers: [{
address: this.address,
port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
}],
};
}
};
Outbound.HttpSettings = class extends CommonClass {
constructor(address, port, user, pass) {
super();
this.address = address;
this.port = port;
this.user = user;
this.pass = pass;
}
static fromJson(json={}) {
let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.HttpSettings(
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
);
}
toJson() {
return {
servers: [{
address: this.address,
port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
}],
};
}
};
Outbound.WireguardSettings = class extends CommonClass {
constructor(
mtu=1420, secretKey='',
address='', workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.address = address instanceof Array ? address.join(',') : address;
this.workers = workers;
this.domainStrategy = domainStrategy;
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Outbound.WireguardSettings.Peer());
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Outbound.WireguardSettings(
json.mtu,
json.secretKey,
json.address,
json.workers,
json.domainStrategy,
json.reserved,
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
address: this.address ? this.address.split(",") : [],
workers: this.workers?? undefined,
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
reserved: this.reserved ? this.reserved.split(",") : undefined,
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Outbound.WireguardSettings.Peer = class extends CommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
this.allowedIPs = allowedIPs;
this.endpoint = endpoint;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Outbound.WireguardSettings.Peer(
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.endpoint,
json.keepAlive
);
}
toJson() {
return {
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs ? this.allowedIPs : undefined,
endpoint: this.endpoint,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -0,0 +1,49 @@
class AllSetting {
constructor(data) {
this.webListen = "";
this.webDomain = "";
this.webPort = 54321;
this.webCertFile = "";
this.webKeyFile = "";
this.webBasePath = "/";
this.sessionMaxAge = "";
this.pageSize = 0;
this.expireDiff = "";
this.trafficDiff = "";
this.remarkModel = "-ieo";
this.tgBotEnable = false;
this.tgBotToken = "";
this.tgBotChatId = "";
this.tgRunTime = "@daily";
this.tgBotBackup = false;
this.tgBotLoginNotify = false;
this.tgCpu = "";
this.tgLang = "";
this.subEnable = false;
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
this.subUpdates = 0;
this.subEncrypt = true;
this.subShowInfo = false;
this.subURI = '';
this.subJsonURI = '';
this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran";
if (data == null) {
return
}
ObjectUtil.cloneProps(this, data);
}
equals(other) {
return ObjectUtil.equals(this, other);
}
}

View File

@@ -6,13 +6,7 @@ const Protocols = {
DOKODEMO: 'dokodemo-door',
SOCKS: 'socks',
HTTP: 'http',
};
const VmessMethods = {
AES_128_GCM: 'aes-128-gcm',
CHACHA20_POLY1305: 'chacha20-poly1305',
AUTO: 'auto',
NONE: 'none',
WIREGUARD: 'wireguard',
};
const SSMethods = {
@@ -86,7 +80,6 @@ const SNIFFING_OPTION = {
};
Object.freeze(Protocols);
Object.freeze(VmessMethods);
Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION);
@@ -237,6 +230,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
toJson() {
return {
version: this.version,
method: this.method,
path: ObjectUtil.clone(this.path),
headers: XrayCommonClass.toV2Headers(this.headers),
@@ -413,8 +407,8 @@ class HttpStreamSettings extends XrayCommonClass {
}
class QuicStreamSettings extends XrayCommonClass {
constructor(security=VmessMethods.NONE,
key='', type='none') {
constructor(security='none',
key=RandomUtil.randomSeq(10), type='none') {
super();
this.security = security;
this.key = key;
@@ -466,10 +460,10 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '',
rejectUnknownSni = false,
certificates=[new TlsStreamSettings.Cert()],
alpn=[],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=new TlsStreamSettings.Settings()) {
super();
this.server = serverName;
this.sni = serverName;
this.minVersion = minVersion;
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
@@ -511,7 +505,7 @@ class TlsStreamSettings extends XrayCommonClass {
toJson() {
return {
serverName: this.server,
serverName: this.sni,
minVersion: this.minVersion,
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
@@ -570,27 +564,21 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
};
TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
constructor(allowInsecure = false, fingerprint = '') {
super();
this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint;
this.serverName = serverName;
this.domains = domains;
}
static fromJson(json = {}) {
return new TlsStreamSettings.Settings(
json.allowInsecure,
json.fingerprint,
json.serverName,
json.domains,
);
}
toJson() {
return {
allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint,
serverName: this.serverName,
domains: this.domains,
};
}
};
@@ -708,6 +696,7 @@ class SockoptStreamSettings extends XrayCommonClass {
class StreamSettings extends XrayCommonClass {
constructor(network='tcp',
security='none',
externalProxy = [],
tlsSettings=new TlsStreamSettings(),
realitySettings = new RealityStreamSettings(),
tcpSettings=new TcpStreamSettings(),
@@ -721,6 +710,7 @@ class StreamSettings extends XrayCommonClass {
super();
this.network = network;
this.security = security;
this.externalProxy = externalProxy;
this.tls = tlsSettings;
this.reality = realitySettings;
this.tcp = tcpSettings;
@@ -765,10 +755,10 @@ class StreamSettings extends XrayCommonClass {
}
static fromJson(json={}) {
return new StreamSettings(
json.network,
json.security,
json.externalProxy,
TlsStreamSettings.fromJson(json.tlsSettings),
RealityStreamSettings.fromJson(json.realitySettings),
TcpStreamSettings.fromJson(json.tcpSettings),
@@ -786,6 +776,7 @@ class StreamSettings extends XrayCommonClass {
return {
network: network,
security: this.security,
externalProxy: this.externalProxy,
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
realitySettings: this.isReality ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
@@ -823,7 +814,7 @@ class Sniffing extends XrayCommonClass {
class Inbound extends XrayCommonClass {
constructor(port=RandomUtil.randomIntRange(10000, 60000),
listen='',
protocol=Protocols.VMESS,
protocol=Protocols.VLESS,
settings=null,
streamSettings=new StreamSettings(),
tag='',
@@ -844,6 +835,16 @@ class Inbound extends XrayCommonClass {
return this.clientStats;
}
get clients() {
switch (this.protocol) {
case Protocols.VMESS: return this.settings.vmesses;
case Protocols.VLESS: return this.settings.vlesses;
case Protocols.TROJAN: return this.settings.trojans;
case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null;
default: return null;
}
}
get protocol() {
return this._protocol;
}
@@ -852,31 +853,7 @@ class Inbound extends XrayCommonClass {
this._protocol = protocol;
this.settings = Inbound.Settings.getSettings(protocol);
if (protocol === Protocols.TROJAN) {
this.tls = true;
}
}
get tls() {
return this.stream.security === 'tls';
}
set tls(isTls) {
if (isTls) {
this.stream.security = 'tls';
} else {
this.stream.security = 'none';
}
}
get reality() {
return this.stream.security === 'reality';
}
set reality(isReality) {
if (isReality) {
this.stream.security = 'reality';
} else {
this.stream.security = 'none';
this.stream.isTls = true;
}
}
@@ -929,9 +906,8 @@ class Inbound extends XrayCommonClass {
}
get serverName() {
if (this.stream.isTls || this.stream.isReality) {
return this.stream.tls.server;
}
if (this.stream.isTls) return this.stream.tls.sni;
if (this.stream.isReality) return this.stream.reality.serverNames;
return "";
}
@@ -982,108 +958,30 @@ class Inbound extends XrayCommonClass {
}
isExpiry(index) {
switch (this.protocol) {
case Protocols.VMESS:
if(this.settings.vmesses[index].expiryTime > 0)
return this.settings.vmesses[index].expiryTime < new Date().getTime();
return false
case Protocols.VLESS:
if(this.settings.vlesses[index].expiryTime > 0)
return this.settings.vlesses[index].expiryTime < new Date().getTime();
return false
case Protocols.TROJAN:
if(this.settings.trojans[index].expiryTime > 0)
return this.settings.trojans[index].expiryTime < new Date().getTime();
return false
case Protocols.SHADOWSOCKS:
if(this.settings.shadowsockses.length > 0 && this.settings.shadowsockses[index].expiryTime > 0)
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
return false
default:
return false;
}
let exp = this.clients[index].expiryTime;
return exp > 0 ? exp < new Date().getTime() : false;
}
canEnableTls() {
switch (this.protocol) {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
break;
default:
return false;
}
switch (this.network) {
case "tcp":
case "ws":
case "http":
case "quic":
case "grpc":
return true;
default:
return false;
}
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
}
//this is used for xtls-rprx-vision
canEnableTlsFlow() {
if ((this.stream.security != 'none') && (this.network === "tcp")) {
switch (this.protocol) {
case Protocols.VLESS:
return true;
default:
return false;
}
return this.protocol === Protocols.VLESS;
}
return false;
}
canSetTls() {
return this.canEnableTls();
}
canEnableReality() {
switch (this.protocol) {
case Protocols.VLESS:
case Protocols.TROJAN:
break;
default:
return false;
}
switch (this.network) {
case "tcp":
case "http":
case "grpc":
return true;
default:
return false;
}
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return ["tcp", "http", "grpc"].includes(this.network);
}
canEnableStream() {
switch (this.protocol) {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
case Protocols.SHADOWSOCKS:
return true;
default:
return false;
}
}
canSniffing() {
switch (this.protocol) {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
case Protocols.SHADOWSOCKS:
return true;
default:
return false;
}
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
}
reset() {
@@ -1096,19 +994,20 @@ class Inbound extends XrayCommonClass {
this.sniffing = new Sniffing();
}
genVmessLink(address='', remark='', clientIndex=0) {
genVmessLink(address='', port=this.port, forceTls, remark='', clientId) {
if (this.protocol !== Protocols.VMESS) {
return '';
}
const security = forceTls == 'same' ? this.stream.security : forceTls;
let obj = {
v: '2',
ps: remark,
add: address,
port: this.port,
id: this.settings.vmesses[clientIndex].id,
port: port,
id: clientId,
net: this.stream.network,
type: 'none',
tls: this.stream.security,
tls: security,
};
let network = this.stream.network;
if (network === 'tcp') {
@@ -1148,12 +1047,9 @@ class Inbound extends XrayCommonClass {
}
}
if (this.stream.security === 'tls') {
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
obj.add = this.stream.tls.server;
}
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
obj.sni = this.stream.tls.settings.serverName;
if (security === 'tls') {
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
obj.sni = this.stream.tls.sni;
}
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
obj.fp = this.stream.tls.settings.fingerprint;
@@ -1169,11 +1065,10 @@ class Inbound extends XrayCommonClass {
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
}
genVLESSLink(address = '', remark='', clientIndex=0) {
const settings = this.settings;
const uuid = settings.vlesses[clientIndex].id;
const port = this.port;
genVLESSLink(address = '', port=this.port, forceTls, remark='', clientId, flow) {
const uuid = clientId;
const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const params = new Map();
params.set("type", this.stream.network);
switch (type) {
@@ -1224,25 +1119,24 @@ class Inbound extends XrayCommonClass {
break;
}
if (this.tls) {
if (security === 'tls') {
params.set("security", "tls");
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server;
}
if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings.serverName);
}
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
params.set("flow", this.settings.vlesses[clientIndex].flow);
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
}
}
}
if (this.reality) {
else if (security === 'reality') {
params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint);
@@ -1252,17 +1146,18 @@ class Inbound extends XrayCommonClass {
if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
address = this.stream.reality.settings.serverName;
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX);
}
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
params.set("flow", this.settings.vlesses[clientIndex].flow);
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
}
}
else {
params.set("security", "none");
}
const link = `vless://${uuid}@${address}:${port}`;
const url = new URL(link);
for (const [key, value] of params) {
@@ -1272,10 +1167,10 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genSSLink(address='', remark='', clientIndex = 0) {
genSSLink(address='', port=this.port, forceTls, remark='', clientPassword) {
let settings = this.settings;
const port = this.port;
const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const params = new Map();
params.set("type", this.stream.network);
switch (type) {
@@ -1326,11 +1221,26 @@ class Inbound extends XrayCommonClass {
break;
}
if (security === 'tls') {
params.set("security", "tls");
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
}
}
let password = new Array();
if (this.isSS2022) password.push(settings.password);
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
if (this.isSSMultiUser) password.push(clientPassword);
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`;
const url = new URL(link);
for (const [key, value] of params) {
url.searchParams.set(key, value)
@@ -1339,9 +1249,8 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genTrojanLink(address = '', remark = '', clientIndex = 0) {
let settings = this.settings;
const port = this.port;
genTrojanLink(address = '', port=this.port, forceTls, remark = '', clientPassword) {
const security = forceTls == 'same' ? this.stream.security : forceTls;
const type = this.stream.network;
const params = new Map();
params.set("type", this.stream.network);
@@ -1393,22 +1302,21 @@ class Inbound extends XrayCommonClass {
break;
}
if (this.tls) {
if (security === 'tls') {
params.set("security", "tls");
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server;
}
if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings.serverName);
}
}
if (this.reality) {
else if (security === 'reality') {
params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint);
@@ -1418,15 +1326,15 @@ class Inbound extends XrayCommonClass {
if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
address = this.stream.reality.settings.serverName;
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX);
}
}
else {
params.set("security", "none");
}
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
const link = `trojan://${clientPassword}@${address}:${port}`;
const url = new URL(link);
for (const [key, value] of params) {
url.searchParams.set(key, value)
@@ -1435,38 +1343,93 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
genLink(address='', remark='', clientIndex=0) {
getWireguardLink(address, port, remark, peerId) {
let txt = `[Interface]\n`
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
txt += `DNS = 1.1.1.1, 9.9.9.9\n`
if (this.settings.mtu) {
txt += `MTU = ${this.settings.mtu}\n`
}
txt += `\n# ${remark}\n`
txt += `[Peer]\n`
txt += `PublicKey = ${this.settings.pubKey}\n`
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
txt += `Endpoint = ${address}:${port}`
if (this.settings.peers[peerId].psk) {
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
}
if (this.settings.peers[peerId].keepAlive) {
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
}
return txt;
}
genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) {
case Protocols.VMESS:
return this.genVmessLink(address, remark, clientIndex);
return this.genVmessLink(address, port, forceTls, remark, client.id);
case Protocols.VLESS:
return this.genVLESSLink(address, remark, clientIndex);
return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow);
case Protocols.SHADOWSOCKS:
return this.genSSLink(address, remark, clientIndex);
return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
case Protocols.TROJAN:
return this.genTrojanLink(address, remark, clientIndex);
return this.genTrojanLink(address, port, forceTls, remark, client.password);
default: return '';
}
}
genInboundLinks(address = '', remark = '') {
let link = '';
switch (this.protocol) {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
case Protocols.SHADOWSOCKS:
JSON.parse(this.settings).clients.forEach((client,index) => {
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
this.stream.tls.settings.domains.forEach((domain) => {
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
});
} else {
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
}
genAllLinks(remark='', remarkModel = '-ieo', client){
let result = [];
let email = client ? client.email : '';
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
let port = this.port;
const separationChar = remarkModel.charAt(0);
const orderChars = remarkModel.slice(1);
let orders = {
'i': remark,
'e': email,
'o': '',
};
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
result.push({
remark: r,
link: this.genLink(addr, port, 'same', r, client)
});
} else {
this.stream.externalProxy.forEach((ep) => {
orders['o'] = ep.remark;
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
result.push({
remark: r,
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
});
return link;
default: return '';
});
}
return result;
}
genInboundLinks(remark = '', remarkModel = '-ieo') {
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
if(this.clients){
let links = [];
this.clients.forEach((client) => {
this.genAllLinks(remark,remarkModel,client).forEach(l => {
links.push(l.link);
})
});
return links.join('\r\n');
} else {
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
if(this.protocol == Protocols.WIREGUARD) {
let links = [];
this.settings.peers.forEach((p,index) => {
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
});
return links.join('\r\n');
}
return '';
}
}
@@ -1485,7 +1448,7 @@ class Inbound extends XrayCommonClass {
toJson() {
let streamSettings;
if (this.canEnableStream() || this.protocol === Protocols.TROJAN) {
if (this.canEnableStream()) {
streamSettings = this.stream.toJson();
}
return {
@@ -1516,6 +1479,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
default: return null;
}
}
@@ -1529,6 +1493,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
default: return null;
}
}
@@ -1577,7 +1542,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
}
};
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.id = id;
this.email = email;
@@ -1586,6 +1551,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
static fromJson(json={}) {
@@ -1597,6 +1563,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
get _expiryTime() {
@@ -1633,7 +1600,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
fallbacks=[],) {
super(protocol);
this.vlesses = vlesses;
this.decryption = 'none'; // Using decryption is not implemented here
this.decryption = decryption;
this.fallbacks = fallbacks;
}
@@ -1665,7 +1632,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
};
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.id = id;
this.flow = flow;
@@ -1675,6 +1642,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
static fromJson(json={}) {
@@ -1687,6 +1655,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
@@ -1763,11 +1732,11 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
this.fallbacks = fallbacks;
}
addTrojanFallback() {
addFallback() {
this.fallbacks.push(new Inbound.TrojanSettings.Fallback());
}
delTrojanFallback(index) {
delFallback(index) {
this.fallbacks.splice(index, 1);
}
@@ -1786,7 +1755,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
}
};
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.password = password;
this.email = email;
@@ -1795,6 +1764,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
toJson() {
@@ -1806,6 +1776,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
reset: this.reset,
};
}
@@ -1818,6 +1789,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
@@ -1922,7 +1894,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
};
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.method = method;
this.password = password;
@@ -1932,6 +1904,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
toJson() {
@@ -1944,6 +1917,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
reset: this.reset,
};
}
@@ -1957,6 +1931,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
@@ -2109,3 +2084,81 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
return new Inbound.HttpSettings.HttpAccount(json.user, json.pass);
}
};
Inbound.WireguardSettings = class extends XrayCommonClass {
constructor(protocol, mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, peers=[new Inbound.WireguardSettings.Peer()], kernelMode=false) {
super(protocol);
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Inbound.WireguardSettings(
Protocols.WIREGUARD,
json.mtu,
json.secretKey,
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
})
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
json.privateKey,
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.keepAlive
);
}
toJson() {
this.allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
});
return {
privateKey: this.privateKey,
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -20,6 +20,14 @@ function sizeFormat(size) {
}
}
function cpuCoreFormat(cores) {
if (cores === 1) {
return "1 Core";
} else {
return cores + " Cores";
}
}
function base64(str) {
return Base64.encode(str);
}
@@ -33,13 +41,15 @@ function safeBase64(str) {
function formatSecond(second) {
if (second < 60) {
return second.toFixed(0) + ' s';
return second.toFixed(0) + 's';
} else if (second < 3600) {
return (second / 60).toFixed(0) + ' m';
return (second / 60).toFixed(0) + 'm';
} else if (second < 3600 * 24) {
return (second / 3600).toFixed(0) + ' h';
return (second / 3600).toFixed(0) + 'h';
} else {
return (second / 3600 / 24).toFixed(0) + ' d';
day = Math.floor(second / 3600 / 24);
remain = ((second/3600) - (day*24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
@@ -53,7 +63,7 @@ function addZero(num) {
function toFixed(num, n) {
n = Math.pow(10, n);
return Math.round(num * n) / n;
return Math.floor(num * n) / n;
}
function debounce(fn, delay) {
@@ -94,11 +104,13 @@ function setCookie(cname, cvalue, exdays) {
function usageColor(data, threshold, total) {
switch (true) {
case data === null:
return "green";
case total < 0:
return "blue";
case total <= 0:
return "blue";
case total == 0:
return "purple";
case data < total - threshold:
return "cyan";
return "blue";
case data < total:
return "orange";
default:
@@ -106,6 +118,41 @@ function usageColor(data, threshold, total) {
}
}
function clientUsageColor(clientStats, trafficDiff) {
switch (true) {
case !clientStats || clientStats.total == 0:
return "#7a316f"; // Purple
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
return "#0e49b5"; // Blue
case clientStats.up + clientStats.down < clientStats.total:
return "#f37b24"; // Orange
default:
return "#e04141"; // red
}
}
function userExpiryColor(threshold, client, isDark = false) {
if (!client.enable) {
return isDark ? '#2c3950' : '#bcbcbc'; // Gray
}
now = new Date().getTime(),
expiry = client.expiryTime;
switch (true) {
case expiry === null:
return "#7a316f"; // Purple
case expiry < 0:
return "#0e49b5"; // Blue
case expiry == 0:
return "#7a316f"; // Purple
case now < expiry - threshold:
return "#0e49b5"; // Blue
case now < expiry:
return "#f37b24"; // Orange
default:
return "#e04141"; // red
}
}
function doAllItemsExist(array1, array2) {
for (let i = 0; i < array1.length; i++) {
if (!array2.includes(array1[i])) {

View File

@@ -1,3 +1,21 @@
class Msg {
constructor(success, msg, obj) {
this.success = false;
this.msg = "";
this.obj = null;
if (success != null) {
this.success = success;
}
if (msg != null) {
this.msg = msg;
}
if (obj != null) {
this.obj = obj;
}
}
}
class HttpUtil {
static _handleMsg(msg) {
if (!(msg instanceof Msg)) {
@@ -118,9 +136,9 @@ class RandomUtil {
}
static randomShortId() {
let shortIds = ['','','',''];
for (var ii = 0; ii < 4; ii++) {
for (var jj = 0; jj < this.randomInt(8); jj++){
let shortIds = new Array(24).fill('');
for (var ii = 0; ii < 24; ii++) {
for (var jj = 0; jj < this.randomInt(16); jj++){
let randomNum = this.randomInt(256);
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
}
@@ -159,7 +177,7 @@ class ObjectUtil {
}
}
} else {
return obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
return this.isEmpty(obj) ? false : obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
}
return false;
}
@@ -278,3 +296,190 @@ class ObjectUtil {
return true;
}
}
class Wireguard {
static gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
static pack(o, n) {
var b, m = this.gf(), t = this.gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
this.carry(t);
this.carry(t);
this.carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
this.cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
static cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
static subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
static multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
this.carry(o);
this.carry(o);
}
static invert(o, i) {
var c = this.gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
this.multmod(c, c, c);
if (a !== 2 && a !== 4)
this.multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
static clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
static generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = this.gf([1]),
b = this.gf([9]),
c = this.gf(),
d = this.gf([1]),
e = this.gf(),
f = this.gf(),
_121665 = this.gf([0xdb41, 1]),
_9 = this.gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
this.clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
this.cswap(a, b, r);
this.cswap(c, d, r);
this.add(e, a, c);
this.subtract(a, a, c);
this.add(c, b, d);
this.subtract(b, b, d);
this.multmod(d, e, e);
this.multmod(f, a, a);
this.multmod(a, c, a);
this.multmod(c, b, e);
this.add(e, a, c);
this.subtract(a, a, c);
this.multmod(b, a, a);
this.subtract(c, d, f);
this.multmod(a, c, _121665);
this.add(a, a, d);
this.multmod(c, c, a);
this.multmod(a, d, f);
this.multmod(d, b, _9);
this.multmod(b, e, e);
this.cswap(a, b, r);
this.cswap(c, d, r);
}
this.invert(c, c);
this.multmod(a, a, c);
this.pack(z, a);
return z;
}
static generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
static generatePrivateKey() {
var privateKey = this.generatePresharedKey();
this.clamp(privateKey);
return privateKey;
}
static encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
static keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
static keyFromBase64(encoded) {
const binaryStr = atob(encoded);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}
static generateKeypair(secretKey='') {
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
var publicKey = this.generatePublicKey(privateKey);
return {
publicKey: this.keyToBase64(publicKey),
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
};
}
}

View File

@@ -36,6 +36,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
g.GET("/createbackup", a.createBackup)
g.POST("/onlines", a.onlines)
a.inboundController = NewInboundController(g)
}
@@ -95,3 +96,7 @@ func (a *APIController) delDepletedClients(c *gin.Context) {
func (a *APIController) createBackup(c *gin.Context) {
a.Tgbot.SendBackupToAdmins()
}
func (a *APIController) onlines(c *gin.Context) {
a.inboundController.onlines(c)
}

View File

@@ -1,6 +1,7 @@
package controller
import (
"encoding/json"
"fmt"
"strconv"
"x-ui/database/model"
@@ -35,7 +36,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
g.POST("/import", a.importInbound)
g.POST("/onlines", a.onlines)
}
func (a *InboundController) getInbounds(c *gin.Context) {
@@ -253,3 +255,32 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
}
jsonMsg(c, "All delpeted clients are deleted", nil)
}
func (a *InboundController) importInbound(c *gin.Context) {
inbound := &model.Inbound{}
err := json.Unmarshal([]byte(c.PostForm("data")), inbound)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}
user := session.GetLoginUser(c)
inbound.Id = 0
inbound.UserId = user.Id
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
for index := range inbound.ClientStats {
inbound.ClientStats[index].Id = 0
inbound.ClientStats[index].Enable = true
}
needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
}
}
func (a *InboundController) onlines(c *gin.Context) {
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
}

View File

@@ -50,44 +50,11 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
}
func (a *SettingController) getDefaultSettings(c *gin.Context) {
type settingFunc func() (interface{}, error)
settings := map[string]settingFunc{
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
result, err := a.settingService.GetDefaultSettings(c.Request.Host)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
result := make(map[string]interface{})
for key, fn := range settings {
value, err := fn()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
result[key] = value
}
subTLS := false
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
subTLS = true
}
result["subTLS"] = subTLS
delete(result, "subKeyFile")
delete(result, "subCertFile")
jsonObj(c, result, nil)
}

View File

@@ -0,0 +1,85 @@
package controller
import (
"x-ui/web/service"
"github.com/gin-gonic/gin"
)
type XraySettingController struct {
XraySettingService service.XraySettingService
SettingService service.SettingService
InboundService service.InboundService
XrayService service.XrayService
}
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
a := &XraySettingController{}
a.initRouter(g)
return a
}
func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g = g.Group("/xray")
g.POST("/", a.getXraySetting)
g.POST("/update", a.updateSetting)
g.GET("/getXrayResult", a.getXrayResult)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
}
func (a *XraySettingController) getXraySetting(c *gin.Context) {
xraySetting, err := a.SettingService.GetXrayConfigTemplate()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
inboundTags, err := a.InboundService.GetInboundTags()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
xrayResponse := "{ \"xraySetting\": " + xraySetting + ", \"inboundTags\": " + inboundTags + " }"
jsonObj(c, xrayResponse, nil)
}
func (a *XraySettingController) updateSetting(c *gin.Context) {
xraySetting := c.PostForm("xraySetting")
err := a.XraySettingService.SaveXraySetting(xraySetting)
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
}
func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
jsonObj(c, defaultJsonConfig, nil)
}
func (a *XraySettingController) getXrayResult(c *gin.Context) {
jsonObj(c, a.XrayService.GetXrayResult(), nil)
}
func (a *XraySettingController) warp(c *gin.Context) {
action := c.Param("action")
var resp string
var err error
switch action {
case "data":
resp, err = a.XraySettingService.GetWarp()
case "config":
resp, err = a.XraySettingService.GetWarpConfig()
case "reg":
skey := c.PostForm("privateKey")
pkey := c.PostForm("publicKey")
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
}
jsonObj(c, resp, err)
}

View File

@@ -7,8 +7,9 @@ import (
type XUIController struct {
BaseController
inboundController *InboundController
settingController *SettingController
inboundController *InboundController
settingController *SettingController
xraySettingController *XraySettingController
}
func NewXUIController(g *gin.RouterGroup) *XUIController {
@@ -24,9 +25,11 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
g.GET("/", a.index)
g.GET("/inbounds", a.inbounds)
g.GET("/settings", a.settings)
g.GET("/xray", a.xraySettings)
a.inboundController = NewInboundController(g)
a.settingController = NewSettingController(g)
a.xraySettingController = NewXraySettingController(g)
}
func (a *XUIController) index(c *gin.Context) {
@@ -40,3 +43,7 @@ func (a *XUIController) inbounds(c *gin.Context) {
func (a *XUIController) settings(c *gin.Context) {
html(c, "settings.html", "pages.settings.title", nil)
}
func (a *XUIController) xraySettings(c *gin.Context) {
html(c, "xray.html", "pages.xray.title", nil)
}

View File

@@ -2,12 +2,10 @@ package entity
import (
"crypto/tls"
"encoding/json"
"net"
"strings"
"time"
"x-ui/util/common"
"x-ui/xray"
)
type Msg struct {
@@ -16,46 +14,41 @@ type Msg struct {
Obj interface{} `json:"obj"`
}
type Pager struct {
Current int `json:"current"`
PageSize int `json:"page_size"`
Total int `json:"total"`
OrderBy string `json:"order_by"`
Desc bool `json:"desc"`
Key string `json:"key"`
List interface{} `json:"list"`
}
type AllSetting struct {
WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"`
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"`
SubDomain string `json:"subDomain" form:"subDomain"`
SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubUpdates int `json:"subUpdates" form:"subUpdates"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
PageSize int `json:"pageSize" form:"pageSize"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"`
SubDomain string `json:"subDomain" form:"subDomain"`
SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubUpdates int `json:"subUpdates" form:"subUpdates"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
}
func (s *AllSetting) CheckValid() error {
@@ -106,13 +99,21 @@ func (s *AllSetting) CheckValid() error {
s.WebBasePath += "/"
}
xrayConfig := &xray.Config{}
err := json.Unmarshal([]byte(s.XrayTemplateConfig), xrayConfig)
if err != nil {
return common.NewError("xray template config invalid:", err)
if !strings.HasPrefix(s.SubPath, "/") {
s.SubPath = "/" + s.SubPath
}
if !strings.HasSuffix(s.SubPath, "/") {
s.SubPath += "/"
}
_, err = time.LoadLocation(s.TimeLocation)
if !strings.HasPrefix(s.SubJsonPath, "/") {
s.SubJsonPath = "/" + s.SubJsonPath
}
if !strings.HasSuffix(s.SubJsonPath, "/") {
s.SubJsonPath += "/"
}
_, err := time.LoadLocation(s.TimeLocation)
if err != nil {
return common.NewError("time location not exist:", s.TimeLocation)
}

View File

@@ -4,16 +4,29 @@
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
<style>
[v-cloak] {
display: none;
}
/* vazirmatn-regular - arabic_latin_latin-ext */
@font-face {
font-display: swap;
font-family: 'Vazirmatn';
font-style: normal;
font-weight: 400;
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol';
}
</style>
<title>{{ .host }}-{{ i18n .title}}</title>
</head>
<div id="message"></div>
{{end}}

View File

@@ -1,19 +1,13 @@
{{define "js"}}
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
<script src="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.js"></script>
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
<script src="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.js"></script>
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/langs.js"></script>
<script>
const basePath = '{{ .base_path }}';

View File

@@ -1,8 +1,7 @@
{{define "promptModal"}}
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
:closable="true" @ok="promptModal.ok" :mask-closable="false"
:class="themeSwitcher.darkCardClass"
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
<a-input id="prompt-modal-input" :type="promptModal.type"
v-model="promptModal.value"
:autosize="{minRows: 10, maxRows: 20}"

View File

@@ -1,18 +1,27 @@
{{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
:closable="true"
:class="themeSwitcher.darkCardClass"
:footer="null"
width="300px">
width="300px" :class="themeSwitcher.currentTheme">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
<template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>Subscription</a-divider>
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
id="qrCode-sub"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
id="qrCode-subJson"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
:id="'qrCode-'+index"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
</template>
</a-modal>
@@ -20,37 +29,32 @@
const qrModal = {
title: '',
clientIndex: 0,
inbound: new Inbound(),
dbInbound: new DBInbound(),
client: null,
qrcodes: [],
clipboard: null,
visible: false,
subId: '',
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
show: function (title = '', dbInbound, client) {
this.title = title;
this.clientIndex = clientIndex;
this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound();
settings = JSON.parse(this.inbound.settings);
this.client = settings.clients[clientIndex];
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
address = this.dbInbound.address;
this.client = client;
this.subId = '';
this.qrcodes = [];
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
this.inbound.stream.tls.settings.domains.forEach((domain) => {
remarkText = [remark, domain.remark].filter(Boolean).join('-');
if (this.inbound.protocol == Protocols.WIREGUARD){
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
this.qrcodes.push({
remark: remarkText,
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
remark: "Peer " + (index+1),
link: l
});
});
} else {
this.qrcodes.push({
remark: remark,
link: this.inbound.genLink(address, remark, clientIndex)
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true;
@@ -84,14 +88,17 @@
});
},
genSubLink(subID) {
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
return buildURL({ host, port, isTLS, base, path: subID+'?name='+subID });
return app.subSettings.subURI+subID+'?name='+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
}
},
updated() {
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);

View File

@@ -1,15 +1,16 @@
{{define "textModal"}}
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.darkCardClass"
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName">
{{ i18n "download" }} [[ txtModal.fileName ]]
</a-button>
:closable="true"
:class="themeSwitcher.currentTheme">
<template slot="footer">
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName">[[ txtModal.fileName ]]
</a-button>
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
</template>
<a-input type="textarea" v-model="txtModal.content"
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
</a-modal>
<script>
@@ -28,10 +29,13 @@
this.visible = true;
textModalApp.$nextTick(() => {
if (this.clipboard === null) {
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
this.clipboard = new ClipboardJS('#copy-btn', {
text: () => this.content,
});
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
this.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.close();
});
}
});
},

View File

@@ -2,64 +2,112 @@
<html lang="en">
{{template "head" .}}
<style>
#app {
padding-top: 100px;
}
h1 {
text-align: center;
margin: 20px 0 50px 0;
}
.ant-btn, .ant-input {
height: 50px;
border-radius: 30px;
}
.ant-input-group-addon {
border-radius: 0 30px 30px 0;
width: 50px;
font-size: 18px;
}
.ant-input-affix-wrapper .ant-input-prefix {
left: 23px;
}
.ant-input-affix-wrapper .ant-input:not(:first-child) {
padding-left: 50px;
}
.centered {
display: flex;
text-align: center;
align-items: center;
justify-content: center;
}
.title {
font-size: 32px;
font-weight: bold;
}
h1 {
text-align: center;
margin: 20px 0 50px 0;
}
.ant-btn,
.ant-input {
height: 50px;
border-radius: 30px;
}
.ant-input-group-addon {
border-radius: 0 30px 30px 0;
width: 50px;
font-size: 18px;
}
.ant-input-affix-wrapper .ant-input-prefix {
left: 23px;
}
.ant-input-affix-wrapper .ant-input:not(:first-child) {
padding-left: 50px;
}
.centered {
display: flex;
text-align: center;
align-items: center;
justify-content: center;
width: 100%;
}
.title {
font-size: 32px;
font-weight: bold;
}
#app {
overflow: hidden;
}
#login {
background-color: #fff;
border-radius: 2rem;
padding: 3rem;
transition: all 0.3s;
}
#login:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.wave {
opacity: 0.6;
position: absolute;
bottom: 40%;
left: 50%;
width: 6000px;
height: 6000px;
background-color: rgba(0, 135, 113, 0.08);
margin-left: -3000px;
transform-origin: 50% 48%;
border-radius: 46%;
pointer-events: none;
rotate: 125deg;
}
.wave2 {
opacity: 0.4;
rotate: 70deg;
}
.wave3 {
opacity: 0.2;
rotate: 90deg;
}
.under {
background-color: #dce9f5;
}
.dark .wave {
background: rgba(14, 73, 181, 0.2);
}
.dark .under {
background-color: #101828;
}
.dark #login {
background-color: #151f31;
}
.dark h1 {
color: rgba(255, 255, 255, 0.85);
}
.ant-form-item {
margin-bottom: 16px;
}
</style>
<body>
<a-layout id="app" v-cloak :class="themeSwitcher.darkCardClass">
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
<transition name="list" appear>
<a-layout-content>
<a-layout-content class="under" style="min-height: 0;">
<div class='wave'></div>
<div class='wave wave2'></div>
<div class='wave wave3'></div>
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<a-row type="flex" justify="center">
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
<h1 class="title" :style="themeSwitcher.textStyle">{{ i18n "pages.login.title" }}</h1>
<a-col>
<h1 class="title" style="margin-bottom: 5px; font-size: 24px;">X-UI</h1>
<h2 class="title" style="text-align: center;">{{ i18n "pages.login.title" }}</h2>
</a-col>
</a-row>
<a-row type="flex" justify="center">
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
<a-col span="24">
<a-form>
<a-form-item>
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
@keydown.enter.native="login" autofocus>
<a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle"/>
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
</a-input>
</a-form-item>
<a-form-item>
@@ -70,15 +118,15 @@
<a-form-item>
<a-row justify="center" class="centered">
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
:style="loading ? { width: '50px' } : { display: 'block', width: '100%' }">
[[ loading ? '' : '{{ i18n "login" }}' ]]
:style="{ fontWeight: 'bold', width: loading ? '50px' : '100%', display: 'inline-block' }">
[[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>
</a-row>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<a-col :span="12">
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-col :span="24">
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
<span role="img" aria-label="l.name" v-text="l.icon"></span>
&nbsp;&nbsp;<span v-text="l.name"></span>
@@ -89,12 +137,19 @@
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<theme-switch />
<a-col>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>&nbsp;
</a-col>
<a-col>
<theme-switch />
</a-col>
</a-row>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-col>
</a-row>
</a-layout-content>
</transition>
</a-layout>
@@ -102,6 +157,13 @@
{{template "component/themeSwitcher" .}}
{{template "component/password" .}}
<script>
class User {
constructor() {
this.username = "";
this.password = "";
}
}
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
@@ -112,7 +174,6 @@
lang: ""
},
created() {
this.updateBackground();
this.lang = getLang();
},
methods: {
@@ -123,17 +184,9 @@
if (msg.success) {
location.href = basePath + 'xui/';
}
},
updateBackground() {
document.querySelector('#app').style.background = colors[this.themeSwitcher.currentTheme].bg;
},
},
watch: {
'themeSwitcher.isDarkTheme'(newVal, oldVal) {
this.updateBackground();
},
}
},
});
</script>
</body>
</html>
</html>

View File

@@ -1,143 +1,106 @@
{{define "clientsBulkModal"}}
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
:class="themeSwitcher.darkCardClass"
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.client.method" }}</td>
<td>
<a-form-item>
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
:dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option :value="0">Random</a-select-option>
<a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>1">
<td>{{ i18n "pages.client.first" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>1">
<td>{{ i18n "pages.client.last" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>0">
<td>{{ i18n "pages.client.prefix" }}</td>
<td>
<a-form-item>
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>2">
<td>{{ i18n "pages.client.postfix" }}</td>
<td>
<a-form-item>
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod < 2">
<td>{{ i18n "pages.client.clientCount" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="app.subSettings.enable">
<td>Subscription</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.client.method" }}'>
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="0">Random</a-select-option>
<a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="app.subSettings.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
Subscription
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
</a-form-item>
<a-form-item v-if="app.tgBotEnable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
Telegram ID
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
<a-form-item v-else>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime"></a-date-picker>
</a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label">
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
</a-form-item>
</a-form>
</a-modal>
<script>
@@ -162,6 +125,7 @@
tgId: "",
flow: "",
delayedStart: false,
reset: 0,
ok() {
clients = [];
method=clientsBulkModal.emailMethod;
@@ -186,11 +150,12 @@
if(clientsBulkModal.inbound.canEnableTlsFlow()){
newClient.flow = clientsBulkModal.flow;
}
newClient.reset = clientsBulkModal.reset;
clients.push(newClient);
}
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
},
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
show({ title='', okText='{{ i18n "confirm" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
this.visible = true;
this.title = title;
this.okText = okText;
@@ -209,6 +174,7 @@
this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound();
this.delayedStart = false;
this.reset = 0;
},
newClient(protocol) {
switch (protocol) {
@@ -223,7 +189,7 @@
clientsBulkModal.visible = false;
clientsBulkModal.loading(false);
},
loading(loading) {
loading(loading=true) {
clientsBulkModal.confirmLoading = loading;
},
};
@@ -245,4 +211,4 @@
},
});
</script>
{{end}}
{{end}}

View File

@@ -1,8 +1,10 @@
{{define "clientsModal"}}
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
:class="themeSwitcher.darkCardClass"
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<template v-if="isEdit">
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
</template>
{{template "form/client"}}
</a-modal>
<script>
@@ -27,14 +29,14 @@
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
}
},
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
show({ title='', okText='{{ i18n "confirm" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
this.visible = true;
this.title = title;
this.okText = okText;
this.isEdit = isEdit;
this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound();
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
this.clients = this.inbound.clients;
this.index = index === null ? this.clients.length : index;
this.delayedStart = false;
if (isEdit){
@@ -48,15 +50,6 @@
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
this.confirm = confirm;
},
getClients(protocol, clientSettings) {
switch(protocol){
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
getClientId(protocol, client) {
switch(protocol){
case Protocols.TROJAN: return client.password;
@@ -77,7 +70,7 @@
clientModal.visible = false;
clientModal.loading(false);
},
loading(loading) {
loading(loading=true) {
clientModal.confirmLoading = loading;
},
};
@@ -108,8 +101,11 @@
get isExpiry() {
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
},
get statsColor() {
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
get delayedStart() {
return this.clientModal.delayedStart;
},
set delayedStart(value) {
this.clientModal.delayedStart = value;
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
@@ -123,7 +119,7 @@
this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
class: themeSwitcher.darkCardClass,
class: themeSwitcher.currentTheme,
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {

View File

@@ -1,19 +1,23 @@
{{define "menuItems"}}
<a-menu-item key="{{ .base_path }}xui/">
<a-icon type="dashboard"></a-icon>
<span>{{ i18n "menu.dashboard"}}</span>
<span><strong>{{ i18n "menu.dashboard"}}</strong></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}xui/inbounds">
<a-icon type="user"></a-icon>
<span>{{ i18n "menu.inbounds"}}</span>
<span><strong>{{ i18n "menu.inbounds"}}</strong></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}xui/settings">
<a-icon type="setting"></a-icon>
<span>{{ i18n "menu.settings"}}</span>
<span><strong>{{ i18n "menu.settings"}}</strong></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}xui/xray">
<a-icon type="tool"></a-icon>
<span><strong>{{ i18n "menu.xray"}}</strong></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}logout">
<a-icon type="logout"></a-icon>
<span>{{ i18n "menu.logout"}}</span>
<span><strong>{{ i18n "menu.logout"}}</strong></span>
</a-menu-item>
{{end}}
@@ -22,7 +26,7 @@
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-menu-item mode="inline">
<a-icon type="bg-colors"></a-icon>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<theme-switch />
</a-menu-item>
</a-menu>
@@ -31,17 +35,16 @@
{{template "menuItems" .}}
</a-menu>
</a-layout-sider>
<a-drawer id="sider-drawer" placement="left" :closable="false"
<a-drawer id="sider-drawer" placement="left" :closable="false" :class="themeSwitcher.currentTheme"
@close="siderDrawer.close()"
:visible="siderDrawer.visible"
:wrap-class-name="themeSwitcher.darkDrawerClass"
:wrap-style="{ padding: 0 }">
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</div>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-menu-item mode="inline">
<a-icon type="bg-colors"></a-icon>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<theme-switch />
</a-menu-item>
</a-menu>

View File

@@ -4,12 +4,12 @@
:placeholder="placeholder"
@input="$emit('input', $event.target.value)">
<template v-if="icon" #prefix>
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
<a-icon :type="icon" style="font-size: 16px;" />
</template>
<template #addonAfter>
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
@click="toggleShowPassword"
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
style="font-size: 16px;" />
</template>
</a-input>
</template>

View File

@@ -12,10 +12,10 @@
</a-col>
<a-col :lg="24" :xl="12">
<template v-if="type === 'text'">
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
</template>
<template v-else-if="type === 'number'">
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :step="step" style="width: 100%;"></a-input-number>
</template>
<template v-else-if="type === 'switch'">
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
@@ -28,7 +28,7 @@
{{define "component/setting"}}
<script>
Vue.component('setting-list-item', {
props: ["type", "title", "desc", "value", "min"],
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
template: `{{template "component/settingListItem"}}`,
});
</script>

View File

@@ -1,8 +1,6 @@
{{define "component/themeSwitchTemplate"}}
<template>
<a-switch :default-checked="themeSwitcher.isDarkTheme"
checked-children="☀"
un-checked-children="🌙"
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
@change="themeSwitcher.toggleTheme()">
</a-switch>
</template>
@@ -10,39 +8,18 @@
{{define "component/themeSwitcher"}}
<script>
const colors = {
dark: {
bg: "#242c3a",
text: "hsla(0,0%,100%,.65)"
},
light: {
bg: '#f0f2f5',
text: "rgba(0, 0, 0, 0.7)",
}
}
function createThemeSwitcher() {
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
const theme = isDarkTheme ? 'dark' : 'light';
return {
isDarkTheme,
bgStyle: `background: ${colors[theme].bg};`,
textStyle: `color: ${colors[theme].text};`,
darkClass: isDarkTheme ? 'ant-dark' : '',
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
get currentTheme() {
return this.isDarkTheme ? 'dark' : 'light';
},
toggleTheme() {
this.isDarkTheme = !this.isDarkTheme;
this.theme = this.isDarkTheme ? 'dark' : 'light';
localStorage.setItem('dark-mode', this.isDarkTheme);
this.bgStyle = `background: ${colors[this.theme].bg};`;
this.textStyle = `color: ${colors[this.theme].text};`;
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
document.getElementById('message').className = themeSwitcher.currentTheme;
},
};
}
@@ -53,6 +30,10 @@
props: [],
template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({ themeSwitcher }),
mounted() {
this.$message.config({getContainer: () => document.getElementById('message')});
document.getElementById('message').className = themeSwitcher.currentTheme;
}
});
</script>
{{end}}

View File

@@ -0,0 +1,86 @@
{{define "dnsModal"}}
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
<a-select
v-model="dnsModal.dnsServer.queryStrategy"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
[[ l ]]
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<script>
const dnsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
dnsServer: {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
},
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
if (typeof dnsServer == 'object'){
this.dnsServer = dnsServer;
} else {
this.dnsServer.address = dnsServer?? '';
}
} else {
this.dnsServer = {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
}
}
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function () { return dnsModal.dnsServer.domains.length>0 }
}
}
});
</script>
{{end}}

View File

@@ -0,0 +1,57 @@
{{define "fakednsModal"}}
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
</a-form-item>
</a-form>
</a-modal>
<script>
const fakednsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
fakeDns: {
ipPool: "198.18.0.0/16",
poolSize: 65535,
},
ok() {
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
},
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
this.fakeDns = fakeDns;
} else {
this.fakeDns = {
ipPool: "198.18.0.0/16",
poolSize: 65535,
}
}
this.isEdit = isEdit;
},
close() {
fakednsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#fakedns-modal',
data: {
fakednsModal: fakednsModal,
}
});
</script>
{{end}}

View File

@@ -1,143 +1,125 @@
{{define "form/client"}}
<a-form layout="inline" v-if="client">
<template v-if="isEdit">
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
</template>
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.enable" }}</td>
<td>
<a-form-item>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<td>password
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.id" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.canEnableTlsFlow()">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="isEdit && clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag :color="statsColor">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
</a-form-item>
</td>
</tr>
</table>
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
{{ i18n "pages.inbounds.email" }}
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.email"></a-input>
</a-form-item>
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.password"></a-input>
</a-form-item>
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.id"></a-input>
</a-form-item>
<a-form-item v-if="client.email && app.subSettings.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
Subscription
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.subId"></a-input>
</a-form-item>
<a-form-item v-if="client.email && app.tgBotEnable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
Telegram ID
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.tgId"></a-input>
</a-form-item>
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
</a-tooltip>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
<a-form-item v-else>
<template slot="label">
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="client._expiryTime"></a-date-picker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item>
<a-form-item v-if="client.expiryTime != 0">
<template slot="label">
<a-tooltip>
<template slot="title">{{ i18n "pages.client.renewDesc" }}</template>
{{ i18n "pages.client.renew" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,91 +1,63 @@
{{define "form/inbound"}}
<!-- base -->
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "enable" }}</td>
<td>
<a-form-item>
<a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "remark" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "monitor" }}
<a-tooltip>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "enable" }}'>
<a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "remark" }}'>
<a-input v-model.trim="dbInbound.remark"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.port"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
{{ i18n "monitor" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.listen"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="inbound.port"></a-input-number>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker>
</a-form-item>
</a-form>
<!-- vmess settings -->
@@ -123,9 +95,15 @@
{{template "form/http"}}
</template>
<!-- wireguard -->
<template v-if="inbound.protocol === Protocols.WIREGUARD">
{{template "form/wireguard"}}
</template>
<!-- stream settings -->
<template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}}
{{template "form/externalProxy" }}
</template>
<!-- tls settings -->
@@ -134,7 +112,7 @@
</template>
<!-- sniffing -->
<template v-if="inbound.canSniffing()">
<template>
{{template "form/sniffing"}}
</template>
{{end}}
{{end}}

View File

@@ -0,0 +1,404 @@
{{define "form/outbound"}}
<!-- base -->
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
</a-form-item>
<!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'>
<a-select
v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Fragment'>
<a-switch
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'>
<a-select
v-model="outbound.settings.fragment.packets"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Length'>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item>
<a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item>
</template>
</template>
<!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'>
<a-select
v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select
v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</template>
<!-- Address + Port -->
<template v-if="outbound.hasAddressPort()">
<a-form-item label='{{ i18n "pages.inbounds.address" }}'>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item>
</template>
<!-- Vnext (vless/vmess) settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</template>
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()">
<!-- http / socks -->
<template v-if="outbound.hasUsername()">
<a-form-item label='{{ i18n "username" }}'>
<a-input v-model.trim="outbound.settings.user"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item>
</template>
<!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</template>
</template>
<!-- stream settings -->
<template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP/2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
</a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch
:checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item>
</template>
</template>
<!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model="outbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</template>
<!-- ws -->
<template v-if="outbound.stream.network === 'ws'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.ws.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item>
</template>
<!-- http -->
<template v-if="outbound.stream.network === 'http'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.http.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.http.path"></a-input>
</a-form-item>
</template>
<!-- quic -->
<template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'">
<a-form-item label='Service Name'>
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
</a-form-item>
<a-form-item label='Multi Mode'>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</template>
</template>
<!-- tls settings -->
<template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group>
</a-form-item>
<template v-if="outbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select
mode="multiple"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
</template>
<!-- reality settings -->
<template v-if="outbound.stream.isReality">
<a-form-item label="SNI">
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Short ID">
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="SpiderX">
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="Public Key">
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item>
</template>
</template>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true">
<a-form-item style="margin: 10px 0">
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
</a-form-item>
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
</a-tab-pane>
</a-tabs>
{{end}}

View File

@@ -1,42 +1,20 @@
{{define "form/dokodemo"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.targetAddress"}}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.address"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.destinationPort"}}</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network"}}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>FollowRedirect</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
<a-input v-model.trim="inbound.settings.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Follow Redirect'>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,19 +1,21 @@
{{define "form/http"}}
<a-form layout="inline">
<a-form-item>
<a-row>
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
</a-row>
<a-input-group v-for="(account, index) in inbound.settings.accounts">
<a-input style="width: 45%" v-model.trim="account.user"
addon-before='{{ i18n "username" }}'></a-input>
<a-input style="width: 55%" v-model.trim="account.pass"
addon-before='{{ i18n "password" }}'>
<a-form>
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,102 +1,8 @@
{{define "form/shadowsocks"}}
<a-form layout="inline">
<template v-if="inbound.isSSMultiUser">
<template v-if="inbound.isSSMultiUser">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<table width="100%" class="ant-table-tbody">
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>password
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
@@ -113,40 +19,32 @@
</table>
</a-collapse-panel>
</a-collapse>
</template>
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</template>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="inbound.isSS2022">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.password"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
</a-form>
{{end}}
<a-divider style="margin:0;"></a-divider>
{{end}}

View File

@@ -1,50 +1,33 @@
{{define "form/socks"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.settings.auth === 'password'">
<td colspan="2">
<a-form-item>
<a-row>
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
</a-row>
<a-input-group v-for="(account, index) in inbound.settings.accounts">
<a-input style="width: 45%" v-model.trim="account.user"
addon-before='{{ i18n "username" }}'></a-input>
<a-input style="width: 55%" v-model.trim="account.pass"
addon-before='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.settings.udp">
<td>IP</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
<a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
<a-form-item label="IP" v-if="inbound.settings.udp">
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
<template v-if="inbound.settings.auth === 'password'">
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -1,99 +1,7 @@
{{define "form/trojan"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<table width="100%" class="ant-table-tbody">
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Password</td>
<td>
<a-form-item>
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
@@ -110,12 +18,12 @@
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && inbound.tls">
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form layout="inline">
<a-form-item label="Fallbacks">
<a-row>
<a-button type="primary" size="small"
@click="inbound.settings.addTrojanFallback()">
@click="inbound.settings.addFallback()">
+
</a-button>
</a-row>
@@ -123,28 +31,28 @@
</a-form>
<!-- trojan fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-divider>
fallback[[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label="name">
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label="alpn">
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label="path">
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label="dest">
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label="xver">
<a-input-number v-model.number="fallback.xver"></a-input-number>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form>
<a-divider style="margin:0;"></a-divider>
</template>
{{end}}
{{end}}

View File

@@ -1,110 +1,7 @@
{{define "form/vless"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<table width="100%" class="ant-table-tbody">
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.canEnableTlsFlow()">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
@@ -123,7 +20,7 @@
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && inbound.tls">
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form layout="inline">
<a-form-item label="Fallbacks">
<a-row>
@@ -136,28 +33,28 @@
</a-form>
<!-- vless fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-divider>
fallback[[ index + 1 ]]
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label="name">
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label="alpn">
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label="path">
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label="dest">
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label="xver">
<a-input-number v-model.number="fallback.xver"></a-input-number>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form>
<a-divider style="margin:0;"></a-divider>
</template>
{{end}}

View File

@@ -1,99 +1,7 @@
{{define "form/vmess"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<table width="100%" class="ant-table-tbody">
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>

View File

@@ -0,0 +1,80 @@
{{define "form/wireguard"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="inbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</a-form>
{{end}}

View File

@@ -1,19 +1,19 @@
{{define "form/sniffing"}}
<a-divider style="margin:0;"></a-divider>
<a-form layout="inline">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<span slot="label">
sniffing
Sniffing
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
</a-form-item>
<a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-checkbox-group>

View File

@@ -0,0 +1,26 @@
{{define "form/externalProxy"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"></a-divider>
<a-form-item label="External Proxy">
<a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
</a-form-item>
<a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template>
<a-tooltip title="Force TLS">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
<a-select-option value="tls">TLS</a-select-option>
</a-select>
</a-tooltip>
</template>
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
</a-form>
{{end}}

View File

@@ -1,22 +1,10 @@
{{define "form/streamGRPC"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>serviceName</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>MultiMode</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Service Name">
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
<a-form-item label="Multi Mode">
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,24 +1,19 @@
{{define "form/streamHTTP"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>host</td>
<td>
<a-form-item>
<a-row v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.http.path"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">{{ i18n "host" }}
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
</template>
<template v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]">
<a-button size="small" slot="addonAfter"
@click="inbound.stream.http.removeHost(index)"
v-if="inbound.stream.http.host.length>1">-</a-button>
</a-input>
</template>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,85 +1,47 @@
{{define "form/streamKCP"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>mtu</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>tti (ms)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uplink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,41 +1,33 @@
{{define "form/streamQUIC"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="none">none</a-select-option>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,16 +1,15 @@
{{define "form/streamSettings"}}
<!-- select stream network -->
<a-divider style="margin:0;"></a-divider>
<a-form layout="inline">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="{{ i18n "transmission" }}">
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="kcp">kcp</a-select-option>
<a-select-option value="ws">ws</a-select-option>
<a-select-option value="http">http</a-select-option>
<a-select-option value="quic">quic</a-select-option>
<a-select-option value="grpc">grpc</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP/2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
</a-select>
</a-form-item>
</a-form>
@@ -48,4 +47,4 @@
<template>
{{template "form/streamSockopt"}}
</template>
{{end}}
{{end}}

View File

@@ -1,47 +1,26 @@
{{define "form/streamSockopt"}}
<a-divider style="margin:0;"></a-divider>
<a-form layout="inline">
<a-form-item label="Transparent Proxy">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="TPROXY">
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
</a-form-item>
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
<tr>
<td>Accept Proxy Protocol</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>TCP FastOpen</td>
<td>
<a-form-item>
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Route Mark</td>
<td>
<a-form-item>
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>T-Proxy</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
:dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="off">OFF</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">T-Proxy</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
<template v-if="inbound.stream.sockoptSwitch">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TPROXY">
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">Off</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">TPROXY</a-select-option>
</a-select>
</a-form-item>
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -1,121 +1,82 @@
{{define "form/streamTCP"}}
<!-- tcp type -->
<a-form layout="inline">
<a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol" v-if="inbound.canEnableTls()">
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label="http {{ i18n "camouflage" }}">
<a-switch
:checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
</a-form>
<!-- tcp request -->
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
<td>
<a-form-item>
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
<a-row>
<a-button size="small"
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
+
</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<!-- tcp response -->
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-row>
<a-button size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
+
</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.tcp.response.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<!-- tcp request -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
</template>
<template v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<!-- tcp response -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-button size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,47 +1,23 @@
{{define "form/streamWS"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr v-if="inbound.canEnableTls()">
<td>Accept Proxy Protocol</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
<a-row>
<a-button size="small"
@click="inbound.stream.ws.addHeader('Host', '')">
+
</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.ws.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,290 +1,135 @@
{{define "form/tlsSettings"}}
<!-- tls enable -->
<a-divider style="margin:0;"></a-divider>
<a-form v-if="inbound.canSetTls()" layout="inline">
<a-form-item label="TLS">
<a-switch v-model="inbound.tls">
</a-switch>
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"></a-divider>
<a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="inbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="Reality" v-if="inbound.canEnableReality()">
<a-switch v-model="inbound.reality"></a-switch>
</a-form-item>
</a-form>
<!-- tls settings -->
<a-form v-if="inbound.tls" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>SNI</td>
<td>
<a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>CipherSuites</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value="">auto</a-select-option>
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>MinVersion</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>MaxVersion</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.settings.fingerprint"
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Multi Domain</td>
<td>
<a-switch v-model="multiDomain"></a-switch>
<a-button v-if="multiDomain" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
</td>
</tr>
<tr v-if="multiDomain">
<td colspan="2">
<a-form-item>
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
<template slot="addonAfter">
<a-button size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>ALPN</td>
<td>
<a-form-item>
<a-select
mode="multiple"
style="width: 250px"
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Reject Unknown SNI</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
</td>
</tr>
<template v-for="cert,index in inbound.stream.tls.certs">
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
</td>
</tr>
<template v-if="cert.useFile">
<tr>
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</td>
</tr>
</template>
<template v-else>
<tr>
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</td>
</tr>
</template>
<tr>
<td>ocspStapling</td>
<td>
<a-form-item>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item>
<a-form-item label="Cipher Suites">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">Auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Min/Max Version">
<a-input-group compact>
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select
mode="multiple"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="Reject Unknown SNI">
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
</table>
</a-form>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</template>
</template>
<!-- reality settings -->
<a-form v-if="inbound.reality" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Show</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Xver</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.reality.settings.fingerprint"
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Server Names</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Short Ids
<template v-if="inbound.stream.isReality">
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
Short IDs
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>SpiderX</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Private Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
</td>
</tr>
</table>
</a-icon>
</template>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='Private Key'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='Public Key'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
</a-form-item>
</template>
</a-form>
{{end}}
{{end}}

Some files were not shown because too many files have changed in this diff Show More