mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-01 19:37:48 +08:00
feat: 面板设置增加监听地址 (#2663)
This commit is contained in:
parent
b045261f16
commit
d638796798
@ -150,6 +150,49 @@ func (b *BaseApi) DownloadSSL(c *gin.Context) {
|
||||
c.File(pathItem)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system address
|
||||
// @Description 获取系统地址信息
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/interface [get]
|
||||
func (b *BaseApi) LoadInterfaceAddr(c *gin.Context) {
|
||||
data, err := settingService.LoadInterfaceAddr()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system bind info
|
||||
// @Description 更新系统监听信息
|
||||
// @Accept json
|
||||
// @Param request body dto.BindInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/bind/update [post]
|
||||
// @x-panel-log {"bodyKeys":["ipv6", "bindAddress"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]","formatEN":"update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"}
|
||||
func (b *BaseApi) UpdateBindInfo(c *gin.Context) {
|
||||
var req dto.BindInfo
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdateBindInfo(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system port
|
||||
// @Description 更新系统端口
|
||||
|
@ -14,6 +14,8 @@ type SettingInfo struct {
|
||||
NtpSite string `json:"ntpSite"`
|
||||
|
||||
Port string `json:"port"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
BindAddress string `json:"bindAddress"`
|
||||
PanelName string `json:"panelName"`
|
||||
Theme string `json:"theme"`
|
||||
Language string `json:"language"`
|
||||
@ -136,6 +138,11 @@ type SyncTime struct {
|
||||
NtpSite string `json:"ntpSite"`
|
||||
}
|
||||
|
||||
type BindInfo struct {
|
||||
Ipv6 string `json:"ipv6" validate:"required,oneof=enable disable"`
|
||||
BindAddress string `json:"bindAddress" validate:"required"`
|
||||
}
|
||||
|
||||
type Upgrade struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@ -30,10 +31,12 @@ type SettingService struct{}
|
||||
|
||||
type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
LoadInterfaceAddr() ([]string, error)
|
||||
LoadTimeZone() ([]string, error)
|
||||
Update(key, value string) error
|
||||
UpdatePassword(c *gin.Context, old, new string) error
|
||||
UpdatePort(port uint) error
|
||||
UpdateBindInfo(req dto.BindInfo) error
|
||||
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
|
||||
LoadFromCert() (*dto.SSLInfo, error)
|
||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||
@ -160,6 +163,41 @@ func (u *SettingService) SyncTime(req dto.SyncTime) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) LoadInterfaceAddr() ([]string, error) {
|
||||
addrMap := make(map[string]struct{})
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if ok && ipNet.IP.To16() != nil {
|
||||
addrMap[ipNet.IP.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
var data []string
|
||||
for key := range addrMap {
|
||||
data = append(data, key)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
|
||||
if err := settingRepo.Update("Ipv6", req.Ipv6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("BindAddress", req.BindAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdatePort(port uint) error {
|
||||
if common.ScanPort(int(port)) {
|
||||
return buserr.WithDetail(constant.ErrPortInUsed, port, nil)
|
||||
|
@ -2,6 +2,8 @@ package configs
|
||||
|
||||
type System struct {
|
||||
Port string `mapstructure:"port"`
|
||||
Ipv6 string `mapstructure:"ipv6"`
|
||||
BindAddress string `mapstructure:"bindAddress"`
|
||||
SSL string `mapstructure:"ssl"`
|
||||
DbFile string `mapstructure:"db_file"`
|
||||
DbPath string `mapstructure:"db_path"`
|
||||
|
@ -18,6 +18,16 @@ func Init() {
|
||||
global.LOG.Errorf("load service port from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.Port = portSetting.Value
|
||||
ipv6Setting, err := settingRepo.Get(settingRepo.WithByKey("Ipv6"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load ipv6 status from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.Ipv6 = ipv6Setting.Value
|
||||
bindAddressSetting, err := settingRepo.Get(settingRepo.WithByKey("BindAddress"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load bind address from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.BindAddress = bindAddressSetting.Value
|
||||
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
|
||||
|
@ -47,7 +47,9 @@ func Init() {
|
||||
migrations.AddDefaultNetwork,
|
||||
migrations.UpdateRuntime,
|
||||
migrations.UpdateTag,
|
||||
|
||||
migrations.AddFavorite,
|
||||
migrations.AddBindAddress,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -15,3 +15,16 @@ var AddFavorite = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddBindAddress = &gormigrate.Migration{
|
||||
ID: "20231024-add-bind-address",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "BindAddress", Value: "0.0.0.0"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "Ipv6", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
router.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||
settingRouter.GET("/interface", baseApi.LoadInterfaceAddr)
|
||||
settingRouter.POST("/bind/update", baseApi.UpdateBindInfo)
|
||||
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
||||
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
|
||||
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/init/app"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/business"
|
||||
@ -26,7 +26,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/init/validator"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||
|
||||
"github.com/fvbock/endless"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -46,19 +45,17 @@ func Start() {
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
address := fmt.Sprintf(":%s", global.CONF.System.Port)
|
||||
s := endless.NewServer(address, rootRouter)
|
||||
s.ReadHeaderTimeout = 20 * time.Second
|
||||
s.WriteTimeout = 60 * time.Second
|
||||
s.MaxHeaderBytes = 1 << 20
|
||||
|
||||
if global.CONF.System.SSL == "disable" {
|
||||
global.LOG.Infof("server run success on %s with http", global.CONF.System.Port)
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
tcpItem := "tcp4"
|
||||
if global.CONF.System.Ipv6 == "enable" {
|
||||
tcpItem = "tcp"
|
||||
global.CONF.System.BindAddress = fmt.Sprintf("[%s]", global.CONF.System.BindAddress)
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: global.CONF.System.BindAddress + ":" + global.CONF.System.Port,
|
||||
Handler: rootRouter,
|
||||
}
|
||||
if global.CONF.System.SSL == "enable" {
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -71,18 +68,19 @@ func Start() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: address,
|
||||
Handler: rootRouter,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
},
|
||||
}
|
||||
|
||||
global.LOG.Infof("server run success on %s with https", global.CONF.System.Port)
|
||||
if err := s.ListenAndServeTLS("", ""); err != nil {
|
||||
global.LOG.Error(err)
|
||||
panic(err)
|
||||
server.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("listen at %s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)
|
||||
ln, err := net.Listen(tcpItem, global.CONF.System.BindAddress+":"+global.CONF.System.Port)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@ -8931,6 +8931,49 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/bind/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "更新系统监听信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Update system bind info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BindInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"ipv6",
|
||||
"bindAddress"
|
||||
],
|
||||
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/clean": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -9014,6 +9057,28 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/interface": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取系统地址信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Load system address",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/mfa": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -12501,6 +12566,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.BindInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bindAddress",
|
||||
"ipv6"
|
||||
],
|
||||
"properties": {
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv6": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"disable"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.CaptchaResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -15379,6 +15463,9 @@ const docTemplate = `{
|
||||
"appStoreVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindDomain": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15403,6 +15490,9 @@ const docTemplate = `{
|
||||
"expirationTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv6": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -8924,6 +8924,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/bind/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "更新系统监听信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Update system bind info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BindInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"ipv6",
|
||||
"bindAddress"
|
||||
],
|
||||
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/clean": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -9007,6 +9050,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/interface": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取系统地址信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"System Setting"
|
||||
],
|
||||
"summary": "Load system address",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/mfa": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -12494,6 +12559,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.BindInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bindAddress",
|
||||
"ipv6"
|
||||
],
|
||||
"properties": {
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv6": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"disable"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.CaptchaResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -15372,6 +15456,9 @@
|
||||
"appStoreVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"bindDomain": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15396,6 +15483,9 @@
|
||||
"expirationTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipv6": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -122,6 +122,19 @@ definitions:
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
dto.BindInfo:
|
||||
properties:
|
||||
bindAddress:
|
||||
type: string
|
||||
ipv6:
|
||||
enum:
|
||||
- enable
|
||||
- disable
|
||||
type: string
|
||||
required:
|
||||
- bindAddress
|
||||
- ipv6
|
||||
type: object
|
||||
dto.CaptchaResponse:
|
||||
properties:
|
||||
captchaID:
|
||||
@ -2064,6 +2077,8 @@ definitions:
|
||||
type: string
|
||||
appStoreVersion:
|
||||
type: string
|
||||
bindAddress:
|
||||
type: string
|
||||
bindDomain:
|
||||
type: string
|
||||
complexityVerification:
|
||||
@ -2080,6 +2095,8 @@ definitions:
|
||||
type: string
|
||||
expirationTime:
|
||||
type: string
|
||||
ipv6:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
lastCleanData:
|
||||
@ -9879,6 +9896,34 @@ paths:
|
||||
summary: Load local backup dir
|
||||
tags:
|
||||
- System Setting
|
||||
/settings/bind/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 更新系统监听信息
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.BindInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Update system bind info
|
||||
tags:
|
||||
- System Setting
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- ipv6
|
||||
- bindAddress
|
||||
formatEN: 'update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]'
|
||||
formatZH: '修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]'
|
||||
paramKeys: []
|
||||
/settings/clean:
|
||||
post:
|
||||
consumes:
|
||||
@ -9933,6 +9978,19 @@ paths:
|
||||
formatEN: reset an expired Password
|
||||
formatZH: 重置过期密码
|
||||
paramKeys: []
|
||||
/settings/interface:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取系统地址信息
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load system address
|
||||
tags:
|
||||
- System Setting
|
||||
/settings/mfa:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -22,6 +22,8 @@ export namespace Setting {
|
||||
lastCleanData: string;
|
||||
|
||||
serverPort: number;
|
||||
ipv6: string;
|
||||
bindAddress: string;
|
||||
ssl: string;
|
||||
sslType: string;
|
||||
allowIPs: string;
|
||||
|
@ -21,6 +21,14 @@ export const updatePassword = (param: Setting.PasswordUpdate) => {
|
||||
return http.post(`/settings/password/update`, param);
|
||||
};
|
||||
|
||||
export const loadInterfaceAddr = () => {
|
||||
return http.get(`/settings/interface`);
|
||||
};
|
||||
|
||||
export const updateBindInfo = (ipv6: string, bindAddress: string) => {
|
||||
return http.post(`/settings/bind/update`, { ipv6: ipv6, bindAddress: bindAddress });
|
||||
};
|
||||
|
||||
export const updatePort = (param: Setting.PortUpdate) => {
|
||||
return http.post(`/settings/port/update`, param);
|
||||
};
|
||||
|
@ -22,6 +22,33 @@ const checkIp = (rule: any, value: any, callback: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const checkIpV6 = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
|
||||
} else {
|
||||
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
||||
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
||||
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
||||
const IPv6AddressRegExp = new RegExp(
|
||||
'^(' +
|
||||
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
|
||||
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
|
||||
')(%[0-9a-zA-Z-.:]{1,})?$',
|
||||
);
|
||||
if (!IPv6AddressRegExp.test(value) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkIpV4V6OrDomain = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
|
||||
@ -453,6 +480,7 @@ interface CommonRule {
|
||||
integerNumberWith0: FormItemRule;
|
||||
floatNumber: FormItemRule;
|
||||
ip: FormItemRule;
|
||||
ipV6: FormItemRule;
|
||||
ipV4V6OrDomain: FormItemRule;
|
||||
host: FormItemRule;
|
||||
illegal: FormItemRule;
|
||||
@ -576,6 +604,11 @@ export const Rules: CommonRule = {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
ipV6: {
|
||||
validator: checkIpV6,
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
ipV4V6OrDomain: {
|
||||
validator: checkIpV4V6OrDomain,
|
||||
required: true,
|
||||
|
@ -1125,6 +1125,12 @@ const message = {
|
||||
path: 'Path',
|
||||
|
||||
safe: 'Security',
|
||||
bindInfo: 'BindInfo',
|
||||
bindAll: 'Listen All',
|
||||
bindInfoHelper:
|
||||
'Changing the service listening address or protocol may result in service unavailability. Do you want to continue?',
|
||||
ipv6: 'Listen IPv6',
|
||||
bindAddress: 'Listen Address',
|
||||
entrance: 'Entrance',
|
||||
showEntrance: 'Enable Home Page Notification Not Enabled',
|
||||
entranceHelper: 'Enabling secure entry will only allow logging in to the panel through specified secure entry.',
|
||||
|
@ -1119,6 +1119,11 @@ const message = {
|
||||
hasNewVersion: '有新版本',
|
||||
|
||||
safe: '安全',
|
||||
bindInfo: '監聽地址',
|
||||
bindAll: '監聽所有',
|
||||
bindInfoHelper: '修改服務監聽地址或協議可能導致服務不可用,是否繼續?',
|
||||
ipv6: '監聽 IPv6',
|
||||
bindAddress: '監聽地址',
|
||||
entrance: '安全入口',
|
||||
showEntrance: '啟用概覽頁未開啟提醒',
|
||||
entranceHelper: '開啟安全入口後只能通過指定安全入口登錄面板',
|
||||
|
@ -1120,6 +1120,11 @@ const message = {
|
||||
hasNewVersion: '有新版本',
|
||||
|
||||
safe: '安全',
|
||||
bindInfo: '监听地址',
|
||||
bindAll: '监听所有',
|
||||
bindInfoHelper: '修改服务监听地址或协议可能导致服务不可用,是否继续?',
|
||||
ipv6: '监听 IPv6',
|
||||
bindAddress: '监听地址',
|
||||
entrance: '安全入口',
|
||||
showEntrance: '启用概览页未开启提醒',
|
||||
entranceHelper: '开启安全入口后只能通过指定安全入口登录面板',
|
||||
|
136
frontend/src/views/setting/safe/bind/index.vue
Normal file
136
frontend/src/views/setting/safe/bind/index.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.bindInfo')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('setting.ipv6')" prop="ipv6" :rules="Rules.requiredSelect">
|
||||
<el-radio-group style="width: 100%" v-model="form.ipv6" @change="onChangeMode()">
|
||||
<el-radio label="enable">{{ $t('commons.button.enable') }}</el-radio>
|
||||
<el-radio label="disable">{{ $t('commons.button.disable') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.ipv6 === 'disable'"
|
||||
:label="$t('setting.bindAddress')"
|
||||
prop="bindAddress"
|
||||
:rules="Rules.ip"
|
||||
>
|
||||
<el-checkbox v-model="form.input" :label="$t('container.input')" />
|
||||
<el-select v-if="!form.input" clearable v-model="form.bindAddress">
|
||||
<el-option value="0.0.0.0" :label="$t('setting.bindAll') + ' (0.0.0.0)'"></el-option>
|
||||
<div v-for="item in interfaceOptions" :key="item">
|
||||
<el-option v-if="item.indexOf(':') === -1" :value="item" :label="item" />
|
||||
</div>
|
||||
</el-select>
|
||||
<el-input v-else clearable v-model="form.bindAddress"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-else :label="$t('setting.bindAddress')" prop="bindAddress" :rules="Rules.ipV6">
|
||||
<el-checkbox v-model="form.input" :label="$t('container.input')" />
|
||||
<el-select v-if="!form.input" clearable v-model="form.bindAddress">
|
||||
<el-option value="::" :label="$t('setting.bindAll') + ' (::)'"></el-option>
|
||||
<div v-for="item in interfaceOptions" :key="item">
|
||||
<el-option v-if="item.indexOf(':') !== -1" :value="item" :label="item" />
|
||||
</div>
|
||||
</el-select>
|
||||
<el-input v-else clearable v-model="form.bindAddress"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSavePort(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { updateBindInfo, loadInterfaceAddr } from '@/api/modules/setting';
|
||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { GlobalStore } from '@/store';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
interface DialogProps {
|
||||
ipv6: string;
|
||||
bindAddress: string;
|
||||
}
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
const interfaceOptions = ref();
|
||||
|
||||
const form = reactive({
|
||||
ipv6: '',
|
||||
bindAddress: '',
|
||||
input: false,
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.ipv6 = params.ipv6;
|
||||
form.bindAddress = params.bindAddress;
|
||||
loadInterface();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const loadInterface = async () => {
|
||||
const res = await loadInterfaceAddr();
|
||||
interfaceOptions.value = res.data || [];
|
||||
};
|
||||
|
||||
const onChangeMode = () => {
|
||||
form.bindAddress = form.ipv6 === 'enable' ? '::' : '0.0.0.0';
|
||||
};
|
||||
|
||||
const onSavePort = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
ElMessageBox.confirm(i18n.global.t('setting.bindInfoHelper'), i18n.global.t('setting.bindInfo'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await updateBindInfo(form.ipv6, form.bindAddress)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
globalStore.isLogin = false;
|
||||
let href = window.location.href;
|
||||
let address = '';
|
||||
if (globalStore.entrance) {
|
||||
address = href.replaceAll('settings/safe', globalStore.entrance);
|
||||
} else {
|
||||
address = href.replaceAll('settings/safe', 'login');
|
||||
}
|
||||
window.open(address, '_self');
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -15,7 +15,15 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('setting.bindInfo')" prop="bindAddress">
|
||||
<el-input disabled v-model="form.bindAddressItem">
|
||||
<template #append>
|
||||
<el-button @click="onChangeBind" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.entrance')">
|
||||
<el-input
|
||||
type="password"
|
||||
@ -148,10 +156,11 @@
|
||||
</LayoutContent>
|
||||
|
||||
<PortSetting ref="portRef" />
|
||||
<BindSetting ref="bindRef" />
|
||||
<MfaSetting ref="mfaRef" @search="search" />
|
||||
<SSLSetting ref="sslRef" @search="search" />
|
||||
<EntranceSetting ref="entranceRef" @search="search" />
|
||||
<TimeoutSetting ref="timeoutref" @search="search" />
|
||||
<TimeoutSetting ref="timeoutRef" @search="search" />
|
||||
<DomainSetting ref="domainRef" @search="search" />
|
||||
<AllowIPsSetting ref="allowIPsRef" @search="search" />
|
||||
</div>
|
||||
@ -161,6 +170,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElForm, ElMessageBox } from 'element-plus';
|
||||
import PortSetting from '@/views/setting/safe/port/index.vue';
|
||||
import BindSetting from '@/views/setting/safe/bind/index.vue';
|
||||
import SSLSetting from '@/views/setting/safe/ssl/index.vue';
|
||||
import MfaSetting from '@/views/setting/safe/mfa/index.vue';
|
||||
import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
|
||||
@ -177,7 +187,8 @@ const globalStore = GlobalStore();
|
||||
const loading = ref(false);
|
||||
const entranceRef = ref();
|
||||
const portRef = ref();
|
||||
const timeoutref = ref();
|
||||
const bindRef = ref();
|
||||
const timeoutRef = ref();
|
||||
const mfaRef = ref();
|
||||
|
||||
const sslRef = ref();
|
||||
@ -187,6 +198,9 @@ const allowIPsRef = ref();
|
||||
|
||||
const form = reactive({
|
||||
serverPort: 9999,
|
||||
ipv6: 'disable',
|
||||
bindAddress: '',
|
||||
bindAddressItem: '',
|
||||
ssl: 'disable',
|
||||
sslType: 'self',
|
||||
securityEntrance: '',
|
||||
@ -204,6 +218,10 @@ const unset = ref(i18n.global.t('setting.unSetting'));
|
||||
const search = async () => {
|
||||
const res = await getSettingInfo();
|
||||
form.serverPort = Number(res.data.serverPort);
|
||||
form.ipv6 = res.data.ipv6;
|
||||
form.bindAddress = res.data.bindAddress;
|
||||
let proto = form.ipv6 === 'enable' ? 'ipv6' : 'ipv4';
|
||||
form.bindAddressItem = ' [' + proto + '] ' + res.data.bindAddress;
|
||||
form.ssl = res.data.ssl;
|
||||
form.sslType = res.data.sslType;
|
||||
if (form.ssl === 'enable') {
|
||||
@ -259,6 +277,9 @@ const onChangeEntrance = () => {
|
||||
const onChangePort = () => {
|
||||
portRef.value.acceptParams({ serverPort: form.serverPort });
|
||||
};
|
||||
const onChangeBind = () => {
|
||||
bindRef.value.acceptParams({ ipv6: form.ipv6, bindAddress: form.bindAddress });
|
||||
};
|
||||
const onChangeBindDomain = () => {
|
||||
domainRef.value.acceptParams({ bindDomain: form.bindDomain });
|
||||
};
|
||||
@ -305,7 +326,7 @@ const loadInfo = async () => {
|
||||
};
|
||||
|
||||
const onChangeExpirationTime = async () => {
|
||||
timeoutref.value.acceptParams({ expirationDays: form.expirationDays });
|
||||
timeoutRef.value.acceptParams({ expirationDays: form.expirationDays });
|
||||
};
|
||||
|
||||
function loadTimeOut() {
|
||||
|
1
go.mod
1
go.mod
@ -12,7 +12,6 @@ require (
|
||||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-contrib/i18n v0.0.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
|
2
go.sum
2
go.sum
@ -228,8 +228,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
|
||||
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
|
||||
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
|
Loading…
Reference in New Issue
Block a user