0.2.0
- 优化 ui 界面 - 优化网站加载速度 - 新增从 v2-ui 迁移账号数据的功能
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0.1.0
|
0.2.0
|
||||||
@@ -137,6 +137,7 @@ install_x-ui() {
|
|||||||
echo -e "x-ui enable - 设置 x-ui 开机自启"
|
echo -e "x-ui enable - 设置 x-ui 开机自启"
|
||||||
echo -e "x-ui disable - 取消 x-ui 开机自启"
|
echo -e "x-ui disable - 取消 x-ui 开机自启"
|
||||||
echo -e "x-ui log - 查看 x-ui 日志"
|
echo -e "x-ui log - 查看 x-ui 日志"
|
||||||
|
echo -e "x-ui v2-ui - 迁移本机器的 v2-ui 账号数据至 x-ui"
|
||||||
echo -e "x-ui update - 更新 x-ui 面板"
|
echo -e "x-ui update - 更新 x-ui 面板"
|
||||||
echo -e "x-ui install - 安装 x-ui 面板"
|
echo -e "x-ui install - 安装 x-ui 面板"
|
||||||
echo -e "x-ui uninstall - 卸载 x-ui 面板"
|
echo -e "x-ui uninstall - 卸载 x-ui 面板"
|
||||||
|
|||||||
12
main.go
12
main.go
@@ -50,11 +50,12 @@ func runWebServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigCh, syscall.SIGHUP)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
if sig == syscall.SIGHUP {
|
switch sig {
|
||||||
|
case syscall.SIGHUP:
|
||||||
err := server.Stop()
|
err := server.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop server err:", err)
|
logger.Warning("stop server err:", err)
|
||||||
@@ -66,8 +67,9 @@ func runWebServer() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
continue
|
server.Stop()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +175,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
err = v2ui.MigrateFromV2UI(dbPath)
|
err = v2ui.MigrateFromV2UI(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("migrate from v2-ui failed:", err)
|
fmt.Println("migrate from v2-ui failed:", err)
|
||||||
}
|
}
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
|
|||||||
28
v2ui/db.go
Normal file
28
v2ui/db.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package v2ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var v2db *gorm.DB
|
||||||
|
|
||||||
|
func initDB(dbPath string) error {
|
||||||
|
c := &gorm.Config{
|
||||||
|
Logger: logger.Discard,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getV2Inbounds() ([]*V2Inbound, error) {
|
||||||
|
inbounds := make([]*V2Inbound, 0)
|
||||||
|
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
||||||
|
return inbounds, err
|
||||||
|
}
|
||||||
41
v2ui/models.go
Normal file
41
v2ui/models.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package v2ui
|
||||||
|
|
||||||
|
import "x-ui/database/model"
|
||||||
|
|
||||||
|
type V2Inbound struct {
|
||||||
|
Id int `gorm:"primaryKey;autoIncrement"`
|
||||||
|
Port int `gorm:"unique"`
|
||||||
|
Listen string
|
||||||
|
Protocol string
|
||||||
|
Settings string
|
||||||
|
StreamSettings string
|
||||||
|
Tag string `gorm:"unique"`
|
||||||
|
Sniffing string
|
||||||
|
Remark string
|
||||||
|
Up int64
|
||||||
|
Down int64
|
||||||
|
Enable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *V2Inbound) TableName() string {
|
||||||
|
return "inbound"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
||||||
|
return &model.Inbound{
|
||||||
|
UserId: userId,
|
||||||
|
Up: i.Up,
|
||||||
|
Down: i.Down,
|
||||||
|
Total: 0,
|
||||||
|
Remark: i.Remark,
|
||||||
|
Enable: i.Enable,
|
||||||
|
ExpiryTime: 0,
|
||||||
|
Listen: i.Listen,
|
||||||
|
Port: i.Port,
|
||||||
|
Protocol: model.Protocol(i.Protocol),
|
||||||
|
Settings: i.Settings,
|
||||||
|
StreamSettings: i.StreamSettings,
|
||||||
|
Tag: i.Tag,
|
||||||
|
Sniffing: i.Sniffing,
|
||||||
|
}
|
||||||
|
}
|
||||||
48
v2ui/v2ui.go
48
v2ui/v2ui.go
@@ -1,7 +1,51 @@
|
|||||||
package v2ui
|
package v2ui
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
func MigrateFromV2UI(dbPath string) error {
|
func MigrateFromV2UI(dbPath string) error {
|
||||||
return errors.New("not support right now")
|
err := initDB(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("init v2-ui database failed:", err)
|
||||||
|
}
|
||||||
|
err = database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("init x-ui database failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Inbounds, err := getV2Inbounds()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("get v2-ui inbounds failed:", err)
|
||||||
|
}
|
||||||
|
if len(v2Inbounds) == 0 {
|
||||||
|
fmt.Println("migrate v2-ui inbounds success: 0")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userService := service.UserService{}
|
||||||
|
user, err := userService.GetFirstUser()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("get x-ui user failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds := make([]*model.Inbound, 0)
|
||||||
|
for _, v2inbound := range v2Inbounds {
|
||||||
|
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundService := service.InboundService{}
|
||||||
|
err = inboundService.AddInbounds(inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("add x-ui inbounds failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
1
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
1
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -168,6 +168,15 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
|
|||||||
this.headers.push({ name: name, value: value });
|
this.headers.push({ name: name, value: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHeader(name) {
|
||||||
|
for (const header of this.headers) {
|
||||||
|
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||||
|
return header.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
removeHeader(index) {
|
removeHeader(index) {
|
||||||
this.headers.splice(index, 1);
|
this.headers.splice(index, 1);
|
||||||
}
|
}
|
||||||
@@ -294,6 +303,15 @@ class WsStreamSettings extends XrayCommonClass {
|
|||||||
this.headers.push({ name: name, value: value });
|
this.headers.push({ name: name, value: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHeader(name) {
|
||||||
|
for (const header of this.headers) {
|
||||||
|
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||||
|
return header.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
removeHeader(index) {
|
removeHeader(index) {
|
||||||
this.headers.splice(index, 1);
|
this.headers.splice(index, 1);
|
||||||
}
|
}
|
||||||
@@ -643,6 +661,30 @@ class Inbound extends XrayCommonClass {
|
|||||||
this.stream.network = network;
|
this.stream.network = network;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isTcp() {
|
||||||
|
return this.network === "tcp";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isWs() {
|
||||||
|
return this.network === "ws";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isKcp() {
|
||||||
|
return this.network === "kcp";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isQuic() {
|
||||||
|
return this.network === "quic"
|
||||||
|
}
|
||||||
|
|
||||||
|
get isGrpc() {
|
||||||
|
return this.network === "grpc";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isH2() {
|
||||||
|
return this.network === "http";
|
||||||
|
}
|
||||||
|
|
||||||
// VMess & VLess
|
// VMess & VLess
|
||||||
get uuid() {
|
get uuid() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -718,6 +760,52 @@ class Inbound extends XrayCommonClass {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get host() {
|
||||||
|
if (this.isTcp) {
|
||||||
|
return this.stream.tcp.request.getHeader("Host");
|
||||||
|
} else if (this.isWs) {
|
||||||
|
return this.stream.ws.getHeader("Host");
|
||||||
|
} else if (this.isH2) {
|
||||||
|
return this.stream.http.host[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
if (this.isTcp) {
|
||||||
|
return this.stream.tcp.request.path[0];
|
||||||
|
} else if (this.isWs) {
|
||||||
|
return this.stream.ws.path;
|
||||||
|
} else if (this.isH2) {
|
||||||
|
return this.stream.http.path[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quicSecurity() {
|
||||||
|
return this.stream.quic.security;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quicKey() {
|
||||||
|
return this.stream.quic.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quicType() {
|
||||||
|
return this.stream.quic.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get kcpType() {
|
||||||
|
return this.stream.kcp.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get kcpSeed() {
|
||||||
|
return this.stream.kcp.seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
get serviceName() {
|
||||||
|
return this.stream.grpc.serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
|
|||||||
@@ -1,7 +1,28 @@
|
|||||||
{{define "inboundInfoStream"}}
|
{{define "inboundInfoStream"}}
|
||||||
<p>传输: <a-tag color="green">[[ inbound.network ]]</a-tag></p>
|
<p>传输: <a-tag color="green">[[ inbound.network ]]</a-tag></p>
|
||||||
|
|
||||||
<!-- TODO -->
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
|
<p v-if="inbound.host">host: <a-tag color="green">[[ inbound.host ]]</a-tag></p>
|
||||||
|
<p v-else>host: <a-tag color="orange">无</a-tag></p>
|
||||||
|
|
||||||
|
<p v-if="inbound.path">path: <a-tag color="green">[[ inbound.path ]]</a-tag></p>
|
||||||
|
<p v-else>path: <a-tag color="orange">无</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="inbound.isQuic">
|
||||||
|
<p>quic 加密: <a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></p>
|
||||||
|
<p>quic 密码: <a-tag color="green">[[ inbound.quicKey ]]</a-tag></p>
|
||||||
|
<p>quic 伪装: <a-tag color="green">[[ inbound.quicType ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="inbound.isKcp">
|
||||||
|
<p>kcp 加密: <a-tag color="green">[[ inbound.kcpType ]]</a-tag></p>
|
||||||
|
<p>kcp 密码: <a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="inbound.isGrpc">
|
||||||
|
<p>grpc serviceName: <a-tag color="green">[[ inbound.serviceName ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.tls || inbound.xtls">
|
<template v-if="inbound.tls || inbound.xtls">
|
||||||
<p v-if="inbound.tls">tls: <a-tag color="green">开启</a-tag></p>
|
<p v-if="inbound.tls">tls: <a-tag color="green">开启</a-tag></p>
|
||||||
@@ -11,10 +32,10 @@
|
|||||||
<p>tls: <a-tag color="red">关闭</a-tag></p>
|
<p>tls: <a-tag color="red">关闭</a-tag></p>
|
||||||
</template>
|
</template>
|
||||||
<p v-if="inbound.tls">
|
<p v-if="inbound.tls">
|
||||||
tls域名: <a-tag color="green">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
tls域名: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="inbound.xtls">
|
<p v-if="inbound.xtls">
|
||||||
xtls域名: <a-tag color="green">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
xtls域名: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{{template "component/inboundInfo"}}
|
{{template "component/inboundInfo"}}
|
||||||
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title="详细信息" @ok="infoModal.ok"
|
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title="详细信息" @ok="infoModal.ok"
|
||||||
:closable="true" :mask-closable="true"
|
:closable="true" :mask-closable="true"
|
||||||
ok-text="复制链接" cancel-text='{{ i18n "close" }}'>
|
ok-text="复制链接" cancel-text='{{ i18n "close" }}' :ok-button-props="infoModal.okBtnPros">
|
||||||
<inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
|
<inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -11,20 +11,39 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
ok() {
|
clipboard: null,
|
||||||
|
okBtnPros: {
|
||||||
|
attrs: {
|
||||||
|
id: "inbound-info-modal-ok-btn",
|
||||||
|
style: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
show(dbInbound) {
|
show(dbInbound) {
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
|
||||||
|
if (dbInbound.hasLink()) {
|
||||||
|
this.okBtnPros.attrs.style = "";
|
||||||
|
} else {
|
||||||
|
this.okBtnPros.attrs.style = "display: none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clipboard == null) {
|
||||||
|
infoModalApp.$nextTick(() => {
|
||||||
|
this.clipboard = new ClipboardJS(`#${this.okBtnPros.attrs.id}`, {
|
||||||
|
text: () => this.dbInbound.genLink(),
|
||||||
|
});
|
||||||
|
this.clipboard.on('success', () => app.$message.success('复制成功'));
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
new Vue({
|
const infoModalApp = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#inbound-info-modal',
|
el: '#inbound-info-modal',
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -59,7 +59,10 @@
|
|||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]]</a-tag>
|
||||||
<a-tag color="green">[[ sizeFormat(dbInbound.down) ]]</a-tag>
|
<a-tag color="green">[[ sizeFormat(dbInbound.down) ]]</a-tag>
|
||||||
<a-tag v-if="dbInbound.total > 0" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<template v-if="dbInbound.total > 0">
|
||||||
|
<a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
||||||
|
<a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
||||||
|
</template>
|
||||||
<a-tag v-else color="cyan">无限制</a-tag>
|
<a-tag v-else color="cyan">无限制</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="settings" slot-scope="text, dbInbound">
|
<template slot="settings" slot-scope="text, dbInbound">
|
||||||
@@ -101,6 +104,11 @@
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 60,
|
width: 60,
|
||||||
|
}, {
|
||||||
|
title: "备注",
|
||||||
|
align: 'center',
|
||||||
|
width: 60,
|
||||||
|
scopedSlots: { customRender: 'remark' },
|
||||||
}, {
|
}, {
|
||||||
title: "协议",
|
title: "协议",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,11 +32,64 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
|||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
db = db.Model(model.Inbound{}).Where("port = ?", port)
|
||||||
|
if ignoreId > 0 {
|
||||||
|
db = db.Where("id != ?", ignoreId)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
err := db.Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInbound(inbound *model.Inbound) error {
|
func (s *InboundService) AddInbound(inbound *model.Inbound) error {
|
||||||
|
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
return common.NewError("端口已存在:", inbound.Port)
|
||||||
|
}
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Save(inbound).Error
|
return db.Save(inbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error {
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
return common.NewError("端口已存在:", inbound.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err = tx.Save(inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelInbound(id int) error {
|
func (s *InboundService) DelInbound(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Delete(model.Inbound{}, id).Error
|
return db.Delete(model.Inbound{}, id).Error
|
||||||
@@ -52,6 +106,14 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInbound(inbound *model.Inbound) error {
|
func (s *InboundService) UpdateInbound(inbound *model.Inbound) error {
|
||||||
|
exist, err := s.checkPortExist(inbound.Port, inbound.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
return common.NewError("端口已存在:", inbound.Port)
|
||||||
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(inbound.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -11,6 +11,19 @@ import (
|
|||||||
type UserService struct {
|
type UserService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetFirstUser() (*model.User, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).
|
||||||
|
First(user).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) CheckUser(username string, password string) *model.User {
|
func (s *UserService) CheckUser(username string, password string) *model.User {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
|||||||
40
web/web.go
40
web/web.go
@@ -18,6 +18,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -35,12 +36,42 @@ var htmlFS embed.FS
|
|||||||
//go:embed translation/*
|
//go:embed translation/*
|
||||||
var i18nFS embed.FS
|
var i18nFS embed.FS
|
||||||
|
|
||||||
|
var startTime = time.Now()
|
||||||
|
|
||||||
type wrapAssetsFS struct {
|
type wrapAssetsFS struct {
|
||||||
embed.FS
|
embed.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *wrapAssetsFS) Open(name string) (fs.File, error) {
|
func (f *wrapAssetsFS) Open(name string) (fs.File, error) {
|
||||||
return f.FS.Open("assets/" + name)
|
file, err := f.FS.Open("assets/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &wrapAssetsFile{
|
||||||
|
File: file,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapAssetsFile struct {
|
||||||
|
fs.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *wrapAssetsFile) Stat() (fs.FileInfo, error) {
|
||||||
|
info, err := f.File.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &wrapAssetsFileInfo{
|
||||||
|
FileInfo: info,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapAssetsFileInfo struct {
|
||||||
|
fs.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *wrapAssetsFileInfo) ModTime() time.Time {
|
||||||
|
return startTime
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -131,12 +162,19 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
assetsBasePath := basePath + "assets/"
|
||||||
|
|
||||||
store := cookie.NewStore(secret)
|
store := cookie.NewStore(secret)
|
||||||
engine.Use(sessions.Sessions("session", store))
|
engine.Use(sessions.Sessions("session", store))
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
c.Set("base_path", basePath)
|
c.Set("base_path", basePath)
|
||||||
})
|
})
|
||||||
|
engine.Use(func(c *gin.Context) {
|
||||||
|
uri := c.Request.RequestURI
|
||||||
|
if strings.HasPrefix(uri, assetsBasePath) {
|
||||||
|
c.Header("Cache-Control", "max-age=31536000")
|
||||||
|
}
|
||||||
|
})
|
||||||
err = s.initI18n(engine)
|
err = s.initI18n(engine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
9
x-ui.sh
9
x-ui.sh
@@ -270,6 +270,12 @@ show_log() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrate_v2_ui() {
|
||||||
|
/usr/local/x-ui/x-ui v2-ui
|
||||||
|
|
||||||
|
before_show_menu
|
||||||
|
}
|
||||||
|
|
||||||
install_bbr() {
|
install_bbr() {
|
||||||
bash <(curl -L -s https://raw.githubusercontent.com/sprov065/blog/master/bbr.sh)
|
bash <(curl -L -s https://raw.githubusercontent.com/sprov065/blog/master/bbr.sh)
|
||||||
echo ""
|
echo ""
|
||||||
@@ -393,6 +399,7 @@ show_usage() {
|
|||||||
echo "x-ui enable - 设置 x-ui 开机自启"
|
echo "x-ui enable - 设置 x-ui 开机自启"
|
||||||
echo "x-ui disable - 取消 x-ui 开机自启"
|
echo "x-ui disable - 取消 x-ui 开机自启"
|
||||||
echo "x-ui log - 查看 x-ui 日志"
|
echo "x-ui log - 查看 x-ui 日志"
|
||||||
|
echo "x-ui v2-ui - 迁移本机器的 v2-ui 账号数据至 x-ui"
|
||||||
echo "x-ui update - 更新 x-ui 面板"
|
echo "x-ui update - 更新 x-ui 面板"
|
||||||
echo "x-ui install - 安装 x-ui 面板"
|
echo "x-ui install - 安装 x-ui 面板"
|
||||||
echo "x-ui uninstall - 卸载 x-ui 面板"
|
echo "x-ui uninstall - 卸载 x-ui 面板"
|
||||||
@@ -480,6 +487,8 @@ if [[ $# > 0 ]]; then
|
|||||||
;;
|
;;
|
||||||
"log") check_install 0 && show_log 0
|
"log") check_install 0 && show_log 0
|
||||||
;;
|
;;
|
||||||
|
"log") check_install 0 && migrate_v2_ui 0
|
||||||
|
;;
|
||||||
"update") check_install 0 && update 0
|
"update") check_install 0 && update 0
|
||||||
;;
|
;;
|
||||||
"install") check_uninstall 0 && install 0
|
"install") check_uninstall 0 && install 0
|
||||||
|
|||||||
Reference in New Issue
Block a user