mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-16 04:25:46 +00:00
Merge branch 'main' into fix/ip-limit
This commit is contained in:
commit
588f0c400a
9 changed files with 114 additions and 96 deletions
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
14
.github/workflows/docker.yml
vendored
14
.github/workflows/docker.yml
vendored
|
|
@ -15,13 +15,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
hsanaeii/3x-ui
|
||||
|
|
@ -32,28 +32,28 @@ jobs:
|
|||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
|
|||
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
|
|
@ -2,11 +2,9 @@ name: Release 3X-UI
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
paths:
|
||||
|
|
@ -20,9 +18,48 @@ on:
|
|||
- 'x-ui.service.debian'
|
||||
- 'x-ui.service.arch'
|
||||
- 'x-ui.service.rhel'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze Go code
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
unformatted=$(gofmt -l .)
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "These files are not gofmt-formatted:"
|
||||
echo "$unformatted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run staticcheck
|
||||
uses: dominikh/staticcheck-action@v1
|
||||
with:
|
||||
version: "latest"
|
||||
install-go: false
|
||||
|
||||
- name: Run tests
|
||||
run: go test -race -shuffle=on ./...
|
||||
|
||||
build:
|
||||
needs: analyze
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
|
|
@ -38,7 +75,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
|
|
@ -133,19 +170,17 @@ jobs:
|
|||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: x-ui-linux-${{ matrix.platform }}
|
||||
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: |
|
||||
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
tag: ${{ github.ref_name }}
|
||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
overwrite: true
|
||||
|
|
@ -156,6 +191,7 @@ jobs:
|
|||
# =================================
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
needs: analyze
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
|
|
@ -165,7 +201,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
|
|
@ -230,19 +266,17 @@ jobs:
|
|||
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: x-ui-windows-amd64
|
||||
path: ./x-ui-windows-amd64.zip
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: |
|
||||
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
tag: ${{ github.ref_name }}
|
||||
file: x-ui-windows-amd64.zip
|
||||
asset_name: x-ui-windows-amd64.zip
|
||||
overwrite: true
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||
|
|
|
|||
|
|
@ -2032,7 +2032,6 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
|||
return nil, err
|
||||
}
|
||||
if t != nil && client != nil {
|
||||
t.Enable = client.Enable
|
||||
t.UUID = client.ID
|
||||
t.SubId = client.SubID
|
||||
return t, nil
|
||||
|
|
|
|||
|
|
@ -1926,6 +1926,8 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
} else {
|
||||
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.successfulOperation"), tu.ReplyKeyboardRemove())
|
||||
t.sendClientIndividualLinks(chatId, client_Email)
|
||||
t.sendClientQRLinks(chatId, client_Email)
|
||||
}
|
||||
case "add_client_submit_enable":
|
||||
client_Enable = true
|
||||
|
|
@ -1936,6 +1938,8 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
} else {
|
||||
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.successfulOperation"), tu.ReplyKeyboardRemove())
|
||||
t.sendClientIndividualLinks(chatId, client_Email)
|
||||
t.sendClientQRLinks(chatId, client_Email)
|
||||
}
|
||||
case "reset_all_traffics_cancel":
|
||||
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
||||
|
|
@ -3302,23 +3306,9 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|||
}
|
||||
}
|
||||
|
||||
// addClient handles the process of adding a new client to an inbound.
|
||||
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
||||
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
|
||||
if err != nil {
|
||||
t.SendMsgToTgbot(chatId, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
protocol := inbound.Protocol
|
||||
|
||||
switch protocol {
|
||||
case model.VMESS, model.VLESS:
|
||||
inlineKeyboard := tu.InlineKeyboard(
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_id")).WithCallbackData("add_client_ch_default_id"),
|
||||
),
|
||||
// getCommonClientButtons returns the shared inline keyboard rows for client configuration
|
||||
func (t *Tgbot) getCommonClientButtons() [][]telego.InlineKeyboardButton {
|
||||
return [][]telego.InlineKeyboardButton{
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
|
||||
|
|
@ -3334,68 +3324,52 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
|||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
|
||||
),
|
||||
)
|
||||
if len(messageID) > 0 {
|
||||
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
|
||||
} else {
|
||||
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
|
||||
}
|
||||
}
|
||||
|
||||
// addClient handles the process of adding a new client to an inbound.
|
||||
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
||||
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
|
||||
if err != nil {
|
||||
t.SendMsgToTgbot(chatId, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
protocol := inbound.Protocol
|
||||
|
||||
var protocolRows [][]telego.InlineKeyboardButton
|
||||
switch protocol {
|
||||
case model.VMESS, model.VLESS:
|
||||
protocolRows = [][]telego.InlineKeyboardButton{
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_id")).WithCallbackData("add_client_ch_default_id"),
|
||||
),
|
||||
}
|
||||
case model.Trojan:
|
||||
inlineKeyboard := tu.InlineKeyboard(
|
||||
protocolRows = [][]telego.InlineKeyboardButton{
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_password")).WithCallbackData("add_client_ch_default_pass_tr"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
|
||||
tu.InlineKeyboardButton("ip limit").WithCallbackData("add_client_ch_default_ip_limit"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitEnable")).WithCallbackData("add_client_submit_enable"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
|
||||
),
|
||||
)
|
||||
if len(messageID) > 0 {
|
||||
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
|
||||
} else {
|
||||
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
|
||||
}
|
||||
case model.Shadowsocks:
|
||||
inlineKeyboard := tu.InlineKeyboard(
|
||||
protocolRows = [][]telego.InlineKeyboardButton{
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_password")).WithCallbackData("add_client_ch_default_pass_sh"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
|
||||
tu.InlineKeyboardButton("ip limit").WithCallbackData("add_client_ch_default_ip_limit"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitEnable")).WithCallbackData("add_client_submit_enable"),
|
||||
),
|
||||
tu.InlineKeyboardRow(
|
||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
commonRows := t.getCommonClientButtons()
|
||||
inlineKeyboard := tu.InlineKeyboard(append(protocolRows, commonRows...)...)
|
||||
|
||||
if len(messageID) > 0 {
|
||||
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
|
||||
} else {
|
||||
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
|
||||
"geofilesUpdateDialogDesc" = "Это обновит все геофайлы."
|
||||
"geofilesUpdateAll" = "Обновить все"
|
||||
"geofileUpdatePopover" = "Геофайл успешно обновлён"
|
||||
"geofileUpdatePopover" = "Геофайлы успешно обновлены"
|
||||
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
||||
"logs" = "Журнал"
|
||||
"config" = "Конфигурация"
|
||||
|
|
|
|||
4
x-ui.sh
4
x-ui.sh
|
|
@ -317,12 +317,12 @@ check_config() {
|
|||
start >/dev/null 2>&1
|
||||
else
|
||||
LOGE "IP certificate setup failed."
|
||||
echo -e "${yellow}You can try again via option 18 (SSL Certificate Management).${plain}"
|
||||
echo -e "${yellow}You can try again via option 19 (SSL Certificate Management).${plain}"
|
||||
start >/dev/null 2>&1
|
||||
fi
|
||||
else
|
||||
echo -e "${yellow}Access URL: http://${server_ip}:${existing_port}${existing_webBasePath}${plain}"
|
||||
echo -e "${yellow}For security, please configure SSL certificate using option 18 (SSL Certificate Management)${plain}"
|
||||
echo -e "${yellow}For security, please configure SSL certificate using option 19 (SSL Certificate Management)${plain}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue