mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 06:32:59 +08:00
feat: 网站增加负载均衡管理 (#6144)
This commit is contained in:
parent
2c2228192f
commit
bf82fe743c
@ -855,3 +855,65 @@ func (b *BaseApi) UpdateDefaultHtml(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Get website upstreams
|
||||
// @Description 获取网站 upstreams
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCommonReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/lbs [get]
|
||||
func (b *BaseApi) GetLoadBalances(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetLoadBalances(id)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Create website upstream
|
||||
// @Description 创建网站 upstream
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteLBCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/lbs/create [post]
|
||||
func (b *BaseApi) CreateLoadBalance(c *gin.Context) {
|
||||
var req request.WebsiteLBCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.CreateLoadBalance(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Delete website upstream
|
||||
// @Description 删除网站 upstream
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteLBDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/lbs/delete [post]
|
||||
func (b *BaseApi) DeleteLoadBalance(c *gin.Context) {
|
||||
var req request.WebsiteLBDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.DeleteLoadBalance(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
@ -64,3 +64,20 @@ var StaticFileKeyMap = map[NginxKey]struct {
|
||||
CACHE: {},
|
||||
ProxyCache: {},
|
||||
}
|
||||
|
||||
type NginxUpstream struct {
|
||||
Name string `json:"name"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
Servers []NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type NginxUpstreamServer struct {
|
||||
Server string `json:"server"`
|
||||
Weight int `json:"weight"`
|
||||
FailTimeout string `json:"failTimeout"`
|
||||
MaxFails int `json:"maxFails"`
|
||||
MaxConns int `json:"maxConns"`
|
||||
Flag string `json:"flag"`
|
||||
}
|
||||
|
||||
var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}}
|
||||
|
@ -256,3 +256,15 @@ type WebsiteHtmlUpdate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteLBCreate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
Servers []dto.NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type WebsiteLBDelete struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
@ -726,6 +726,22 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
}
|
||||
}
|
||||
appParam.Values = form.Values
|
||||
} else if form.Type == "apps" {
|
||||
if m, ok := form.Child.(map[string]interface{}); ok {
|
||||
result := make(map[string]string)
|
||||
for key, value := range m {
|
||||
if strVal, ok := value.(string); ok {
|
||||
result[key] = strVal
|
||||
}
|
||||
}
|
||||
if envKey, ok := result["envKey"]; ok {
|
||||
serviceName := envs[envKey]
|
||||
if serviceName != nil {
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
|
||||
appParam.ShowValue = appInstall.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params = append(params, appParam)
|
||||
} else {
|
||||
|
@ -108,6 +108,10 @@ type IWebsiteService interface {
|
||||
|
||||
UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error
|
||||
GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error)
|
||||
|
||||
GetLoadBalances(id uint) ([]dto.NginxUpstream, error)
|
||||
CreateLoadBalance(req request.WebsiteLBCreate) error
|
||||
DeleteLoadBalance(req request.WebsiteLBDelete) error
|
||||
}
|
||||
|
||||
func NewIWebsiteService() IWebsiteService {
|
||||
@ -2898,6 +2902,188 @@ func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error {
|
||||
return fileOp.SaveFile(resourcePath, req.Content, 0644)
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetUpStreams() ([]dto.NginxUpstream, error) {
|
||||
return nil, nil
|
||||
func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
return nil, nil
|
||||
}
|
||||
entries, err := os.ReadDir(includeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []dto.NginxUpstream
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasSuffix(name, ".conf") {
|
||||
continue
|
||||
}
|
||||
upstreamName := strings.TrimSuffix(name, ".conf")
|
||||
upstream := dto.NginxUpstream{
|
||||
Name: upstreamName,
|
||||
}
|
||||
upstreamPath := path.Join(includeDir, name)
|
||||
nginxParser, err := parser.NewParser(upstreamPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := nginxParser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreams := config.FindUpstreams()
|
||||
for _, up := range upstreams {
|
||||
if up.UpstreamName == upstreamName {
|
||||
directives := up.GetDirectives()
|
||||
for _, d := range directives {
|
||||
dName := d.GetName()
|
||||
if _, ok := dto.LBAlgorithms[dName]; ok {
|
||||
upstream.Algorithm = dName
|
||||
}
|
||||
}
|
||||
var servers []dto.NginxUpstreamServer
|
||||
for _, ups := range up.UpstreamServers {
|
||||
server := dto.NginxUpstreamServer{
|
||||
Server: ups.Address,
|
||||
}
|
||||
parameters := ups.Parameters
|
||||
if weight, ok := parameters["weight"]; ok {
|
||||
num, err := strconv.Atoi(weight)
|
||||
if err == nil {
|
||||
server.Weight = num
|
||||
}
|
||||
}
|
||||
if maxFails, ok := parameters["max_fails"]; ok {
|
||||
num, err := strconv.Atoi(maxFails)
|
||||
if err == nil {
|
||||
server.MaxFails = num
|
||||
}
|
||||
}
|
||||
if failTimeout, ok := parameters["fail_timeout"]; ok {
|
||||
server.FailTimeout = failTimeout
|
||||
}
|
||||
if maxConns, ok := parameters["max_conns"]; ok {
|
||||
num, err := strconv.Atoi(maxConns)
|
||||
if err == nil {
|
||||
server.MaxConns = num
|
||||
}
|
||||
}
|
||||
for _, flag := range ups.Flags {
|
||||
server.Flag = flag
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
upstream.Servers = servers
|
||||
}
|
||||
}
|
||||
res = append(res, upstream)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0644)
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if fileOp.Stat(filePath) {
|
||||
return buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Block = &components.Block{}
|
||||
config.FilePath = filePath
|
||||
upstream := components.Upstream{
|
||||
UpstreamName: req.Name,
|
||||
}
|
||||
|
||||
servers := make([]*components.UpstreamServer, 0)
|
||||
|
||||
for _, server := range req.Servers {
|
||||
upstreamServer := &components.UpstreamServer{
|
||||
Address: server.Server,
|
||||
}
|
||||
parameters := make(map[string]string)
|
||||
if server.Weight > 0 {
|
||||
parameters["weight"] = strconv.Itoa(server.Weight)
|
||||
}
|
||||
if server.MaxFails > 0 {
|
||||
parameters["max_fails"] = strconv.Itoa(server.MaxFails)
|
||||
}
|
||||
if server.FailTimeout != "" {
|
||||
parameters["fail_timeout"] = server.FailTimeout
|
||||
}
|
||||
if server.MaxConns > 0 {
|
||||
parameters["max_conns"] = strconv.Itoa(server.MaxConns)
|
||||
}
|
||||
if server.Flag != "" {
|
||||
upstreamServer.Flags = []string{server.Flag}
|
||||
}
|
||||
upstreamServer.Parameters = parameters
|
||||
servers = append(servers, upstreamServer)
|
||||
}
|
||||
upstream.UpstreamServers = servers
|
||||
config.Block.Directives = append(config.Block.Directives, &upstream)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteFile(filePath)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias)
|
||||
if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream")
|
||||
fileOp := files.NewFileOp()
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if !fileOp.Stat(filePath) {
|
||||
return nil
|
||||
}
|
||||
if err = fileOp.DeleteFile(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -40,3 +40,6 @@ var StopHTML []byte
|
||||
|
||||
//go:embed path_auth.conf
|
||||
var PathAuth []byte
|
||||
|
||||
//go:embed upstream.conf
|
||||
var Upstream []byte
|
||||
|
3
agent/cmd/server/nginx_conf/upstream.conf
Normal file
3
agent/cmd/server/nginx_conf/upstream.conf
Normal file
@ -0,0 +1,3 @@
|
||||
upstream backend {
|
||||
server backend1.example.com;
|
||||
}
|
@ -68,5 +68,9 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
websiteRouter.GET("/default/html/:type", baseApi.GetDefaultHtml)
|
||||
websiteRouter.POST("/default/html/update", baseApi.UpdateDefaultHtml)
|
||||
|
||||
websiteRouter.GET("/:id/lbs", baseApi.GetLoadBalances)
|
||||
websiteRouter.POST("/lbs/create", baseApi.CreateLoadBalance)
|
||||
websiteRouter.POST("/lbs/del", baseApi.DeleteLoadBalance)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,15 @@ func (c *Config) FindHttp() *Http {
|
||||
return http
|
||||
}
|
||||
|
||||
func (c *Config) FindUpstreams() []*Upstream {
|
||||
var upstreams []*Upstream
|
||||
directives := c.Block.FindDirectives("upstream")
|
||||
for _, directive := range directives {
|
||||
upstreams = append(upstreams, directive.(*Upstream))
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
|
||||
var repeatKeys = map[string]struct {
|
||||
}{
|
||||
"limit_conn": {},
|
||||
|
@ -568,4 +568,31 @@ export namespace Website {
|
||||
type: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface NginxUpstream {
|
||||
name: string;
|
||||
algorithm: string;
|
||||
servers: NginxUpstreamServer[];
|
||||
}
|
||||
|
||||
export interface LoadBalanceReq {
|
||||
websiteID: number;
|
||||
name: string;
|
||||
algorithm: string;
|
||||
servers: NginxUpstreamServer[];
|
||||
}
|
||||
|
||||
interface NginxUpstreamServer {
|
||||
server: string;
|
||||
weight: number;
|
||||
failTimeout: string;
|
||||
maxFails: number;
|
||||
maxConns: number;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
export interface LoadBalanceDel {
|
||||
websiteID: number;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
|
@ -295,3 +295,15 @@ export const DownloadCAFile = (params: Website.SSLDownload) => {
|
||||
timeout: TimeoutEnum.T_40S,
|
||||
});
|
||||
};
|
||||
|
||||
export const GetLoadBalances = (id: number) => {
|
||||
return http.get<Website.NginxUpstream[]>(`/websites/${id}/lbs`);
|
||||
};
|
||||
|
||||
export const CreateLoadBalance = (req: Website.LoadBalanceReq) => {
|
||||
return http.post(`/websites/lbs/create`, req);
|
||||
};
|
||||
|
||||
export const DeleteLoadBalance = (req: Website.LoadBalanceDel) => {
|
||||
return http.post(`/websites/lbs/del`, req);
|
||||
};
|
||||
|
@ -265,3 +265,37 @@ export const Actions = [
|
||||
value: 'five_seconds',
|
||||
},
|
||||
];
|
||||
|
||||
export const Algorithms = [
|
||||
{
|
||||
label: i18n.global.t('commons.table.default'),
|
||||
value: 'default',
|
||||
placeHolder: i18n.global.t('website.defaultHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.ipHash'),
|
||||
value: 'ip_hash',
|
||||
placeHolder: i18n.global.t('website.ipHashHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.leastConn'),
|
||||
value: 'least_conn',
|
||||
placeHolder: i18n.global.t('website.leastConnHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.leastTime'),
|
||||
value: 'least_time',
|
||||
placeHolder: i18n.global.t('website.leastTimeHelper'),
|
||||
},
|
||||
];
|
||||
|
||||
export const StatusStrategy = [
|
||||
{
|
||||
label: i18n.global.t('website.strategyDown'),
|
||||
value: 'down',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.strategyBackup'),
|
||||
value: 'backup',
|
||||
},
|
||||
];
|
||||
|
@ -2139,6 +2139,24 @@ const message = {
|
||||
subsiteHelper: 'A subsite can select an existing PHP or static website directory as the main directory.',
|
||||
parentWbeiste: 'Parent Website',
|
||||
deleteSubsite: 'To delete the current website, you must first delete the subsite(s) {0}',
|
||||
loadBalance: 'Load Balancing',
|
||||
server: 'Server',
|
||||
algorithm: 'Algorithm',
|
||||
ipHash: 'IP Hash',
|
||||
ipHashHelper:
|
||||
'Distributes requests to a specific server based on the client IP address, ensuring that a particular client is always routed to the same server.',
|
||||
leastConn: 'Least Connections',
|
||||
leastConnHelper: 'Sends requests to the server with the fewest active connections.',
|
||||
leastTime: 'Least Time',
|
||||
leastTimeHelper: 'Sends requests to the server with the shortest active connection time.',
|
||||
defaultHelper:
|
||||
'Default method, requests are evenly distributed to each server. If servers have weights configured, requests are distributed based on the specified weights, with higher-weighted servers receiving more requests.',
|
||||
weight: 'Weight',
|
||||
maxFails: 'Max Fails',
|
||||
maxConns: 'Max Connections',
|
||||
strategy: 'Strategy',
|
||||
strategyDown: 'Down',
|
||||
strategyBackup: 'Backup',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Short tag support',
|
||||
|
@ -1988,6 +1988,23 @@ const message = {
|
||||
subsiteHelper: '子網站可以選擇已存在的 PHP 和靜態網站的目錄作為主目錄。',
|
||||
parentWbeiste: '父級網站',
|
||||
deleteSubsite: '刪除當前網站需要先刪除子網站 {0}',
|
||||
loadBalance: '負載均衡',
|
||||
server: '節點',
|
||||
algorithm: '演算法',
|
||||
ipHash: 'IP 雜湊',
|
||||
ipHashHelper: '基於客戶端 IP 位址將請求分配到特定伺服器,可以確保特定客戶端總是被路由到同一伺服器。',
|
||||
leastConn: '最少連接',
|
||||
leastConnHelper: '將請求發送到當前活動連接數最少的伺服器。',
|
||||
leastTime: '最少時間',
|
||||
leastTimeHelper: '將請求發送到當前活動連接時間最短的伺服器。',
|
||||
defaultHelper:
|
||||
'預設方法,請求被均勻分配到每個伺服器。如果伺服器有權重配置,則根據指定的權重分配請求,權重越高的伺服器接收更多請求。',
|
||||
weight: '權重',
|
||||
maxFails: '最大失敗次數',
|
||||
maxConns: '最大連接數',
|
||||
strategy: '策略',
|
||||
strategyDown: '停用',
|
||||
strategyBackup: '備用',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短標簽支持',
|
||||
|
@ -1990,6 +1990,23 @@ const message = {
|
||||
subsiteHelper: '子网站可以选择已存在的 PHP 和静态网站的目录作为主目录',
|
||||
parentWbeiste: '父级网站',
|
||||
deleteSubsite: '删除当前网站需要先删除子网站 {0}',
|
||||
loadBalance: '负载均衡',
|
||||
server: '节点',
|
||||
algorithm: '算法',
|
||||
ipHash: 'IP 哈希',
|
||||
ipHashHelper: '基于客户端 IP 地址将请求分配到特定服务器,可以确保特定客户端总是被路由到同一服务器',
|
||||
leastConn: '最小连接',
|
||||
leastConnHelper: '将请求发送到当前活动连接数最少的服务器',
|
||||
leastTime: '最小时间',
|
||||
leastTimeHelper: '将请求发送到当前活动连接时间最短的服务器',
|
||||
defaultHelper:
|
||||
'默认方法,请求被均匀分配到每个服务器,如果服务器有权重配置,则根据指定的权重分配请求,权重越高的服务器接收更多请求',
|
||||
weight: '权重',
|
||||
maxFails: '最大失败次数',
|
||||
maxConns: '最大连接数',
|
||||
strategy: '策略',
|
||||
strategyDown: '停用',
|
||||
strategyBackup: '备用',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短标签支持',
|
||||
|
@ -203,7 +203,6 @@ const searchPath = async () => {
|
||||
const searchAll = () => {
|
||||
search();
|
||||
searchPath();
|
||||
console.log(11111);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -99,7 +99,7 @@ const deleteDomain = async (row: Website.Domain) => {
|
||||
names: [row.domain],
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('website.domain'),
|
||||
i18n.global.t('commons.msg.delete'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: DeleteDomain,
|
||||
params: { id: row.id },
|
||||
|
@ -15,23 +15,26 @@
|
||||
<el-tab-pane :label="$t('website.proxy')">
|
||||
<Proxy :id="id" v-if="tabIndex == '4'"></Proxy>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.loadBalance')">
|
||||
<LoadBalance :id="id" v-if="tabIndex == '5'"></LoadBalance>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.basicAuth')">
|
||||
<AuthBasic :id="id" v-if="tabIndex == '5'"></AuthBasic>
|
||||
<AuthBasic :id="id" v-if="tabIndex == '6'"></AuthBasic>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'HTTPS'">
|
||||
<HTTPS :id="id" v-if="tabIndex == '6'"></HTTPS>
|
||||
<HTTPS :id="id" v-if="tabIndex == '7'"></HTTPS>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.rewrite')">
|
||||
<Rewrite :id="id" v-if="tabIndex == '7'"></Rewrite>
|
||||
<Rewrite :id="id" v-if="tabIndex == '8'"></Rewrite>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.antiLeech')">
|
||||
<AntiLeech :id="id" v-if="tabIndex == '8'"></AntiLeech>
|
||||
<AntiLeech :id="id" v-if="tabIndex == '9'"></AntiLeech>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.redirect')">
|
||||
<Redirect :id="id" v-if="tabIndex == '9'"></Redirect>
|
||||
<Redirect :id="id" v-if="tabIndex == '10'"></Redirect>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.other')">
|
||||
<Other :id="id" v-if="tabIndex == '10'"></Other>
|
||||
<Other :id="id" v-if="tabIndex == '11'"></Other>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
@ -50,6 +53,7 @@ import Proxy from './proxy/index.vue';
|
||||
import AuthBasic from './auth-basic/index.vue';
|
||||
import AntiLeech from './anti-Leech/index.vue';
|
||||
import Redirect from './redirect/index.vue';
|
||||
import LoadBalance from './load-balance/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
|
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<ComplexTable :data="data" @search="search" v-loading="loading" :heightDiff="420">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" plain @click="create()">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name"></el-table-column>
|
||||
<el-table-column :label="$t('website.algorithm')" prop="algorithm"></el-table-column>
|
||||
<el-table-column :label="$t('website.server')" prop="servers" minWidth="400px">
|
||||
<template #default="{ row }">
|
||||
<table>
|
||||
<tr v-for="(item, index) in row.servers" :key="index">
|
||||
<td>
|
||||
<el-tag>
|
||||
{{ item.server }}
|
||||
</el-tag>
|
||||
</td>
|
||||
<td v-if="item.weight > 0">
|
||||
<el-tag type="success">{{ $t('website.weight') }}: {{ item.weight }}</el-tag>
|
||||
</td>
|
||||
<td v-if="item.failTimeout != ''">
|
||||
<el-tag type="warning">{{ $t('website.failTimeout') }}: {{ item.failTimeout }}</el-tag>
|
||||
</td>
|
||||
<td v-if="item.maxFails > 0">
|
||||
<el-tag type="danger">{{ $t('website.maxFails') }}: {{ item.maxFails }}</el-tag>
|
||||
</td>
|
||||
<td v-if="item.maxConns > 0">
|
||||
<el-tag>{{ $t('website.maxConns') }}: {{ item.maxConns }}</el-tag>
|
||||
</td>
|
||||
<td v-if="item.flag != ''">
|
||||
<el-tag type="info">{{ $t('website.strategy') }}: {{ item.flag }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="260px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</div>
|
||||
<Operate ref="operateRef" @search="search()"></Operate>
|
||||
<OpDialog ref="delRef" @search="search()" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DeleteLoadBalance, GetLoadBalances } from '@/api/modules/website';
|
||||
import { defineProps, onMounted, ref } from 'vue';
|
||||
import Operate from './operate/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Website } from '@/api/interface/website';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const data = ref([]);
|
||||
const loading = ref(false);
|
||||
const operateRef = ref();
|
||||
const delRef = ref();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: any) => {
|
||||
deleteLb(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const search = () => {
|
||||
GetLoadBalances(props.id).then((res) => {
|
||||
data.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteLb = async (row: Website.NginxUpstream) => {
|
||||
delRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.msg.deleteTitle'),
|
||||
names: [row.name],
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('website.loadBalance'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: DeleteLoadBalance,
|
||||
params: { websiteID: props.id, name: row.name },
|
||||
});
|
||||
};
|
||||
|
||||
const create = () => {
|
||||
operateRef.value.acceptParams(props.id);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('website.addDomain')" :back="handleClose" size="large">
|
||||
<el-form ref="lbForm" label-position="top" :model="item" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="item.name" :disabled="item.operate === 'edit'"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.algorithm')" prop="algorithm">
|
||||
<el-select v-model="item.algorithm">
|
||||
<el-option
|
||||
v-for="(algorithm, index) in Algorithms"
|
||||
:label="algorithm.label"
|
||||
:key="index"
|
||||
:value="algorithm.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help">{{ getHelper(item.algorithm) }}</span>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20" v-for="(server, index) of item.servers" :key="index">
|
||||
<el-col :span="7">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('setting.address') : ''"
|
||||
:prop="`servers.${index}.server`"
|
||||
:rules="rules.server"
|
||||
>
|
||||
<el-input
|
||||
type="string"
|
||||
v-model="item.servers[index].server"
|
||||
:placeholder="index > 0 ? $t('setting.address') : ''"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('website.weight') : ''"
|
||||
:prop="`servers.${index}.weight`"
|
||||
:rules="rules.weight"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].weight"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('website.maxFails') : ''"
|
||||
:prop="`servers.${index}.maxFails`"
|
||||
:rules="rules.maxFails"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].maxFails"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('website.maxConns') : ''"
|
||||
:prop="`servers.${index}.maxConns`"
|
||||
:rules="rules.maxConns"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].maxConns"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('website.strategy') : ''"
|
||||
:prop="`servers.${index}.flag`"
|
||||
:rules="rules.flag"
|
||||
>
|
||||
<el-select v-model="item.servers[index].flag">
|
||||
<el-option
|
||||
v-for="flag in StatusStrategy"
|
||||
:label="flag.label"
|
||||
:key="flag.value"
|
||||
:value="flag.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3" v-if="index == 0">
|
||||
<el-form-item :label="$t('commons.button.add') + $t('website.server')">
|
||||
<el-button @click="addServer">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3" v-else>
|
||||
<el-form-item>
|
||||
<el-button @click="removeServer(index)" link type="primary">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(lbForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CreateLoadBalance } from '@/api/modules/website';
|
||||
import i18n from '@/lang';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { Algorithms, StatusStrategy } from '@/global/mimetype';
|
||||
|
||||
const rules = ref<any>({
|
||||
name: [Rules.linuxName],
|
||||
algorithm: [Rules.requiredSelect],
|
||||
server: [Rules.requiredInput],
|
||||
weight: [checkNumberRange(0, 100)],
|
||||
servers: {
|
||||
type: Array,
|
||||
},
|
||||
maxFails: [checkNumberRange(1, 1000)],
|
||||
maxConns: [checkNumberRange(1, 1000)],
|
||||
});
|
||||
|
||||
const lbForm = ref<FormInstance>();
|
||||
|
||||
const initServer = () => ({
|
||||
server: '',
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const item = ref({
|
||||
websiteID: 0,
|
||||
name: '',
|
||||
operate: 'create',
|
||||
servers: [],
|
||||
algorithm: 'default',
|
||||
flag: '',
|
||||
});
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
const handleClose = () => {
|
||||
lbForm.value?.resetFields();
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const helper = ref();
|
||||
|
||||
const getHelper = (key: string) => {
|
||||
Algorithms.forEach((algorithm) => {
|
||||
if (algorithm.value === key) {
|
||||
helper.value = algorithm.placeHolder;
|
||||
}
|
||||
});
|
||||
return helper.value;
|
||||
};
|
||||
|
||||
const addServer = () => {
|
||||
item.value.servers.push(initServer());
|
||||
};
|
||||
|
||||
const removeServer = (index: number) => {
|
||||
item.value.servers.splice(index, 1);
|
||||
};
|
||||
|
||||
const acceptParams = async (websiteId: number) => {
|
||||
item.value.websiteID = Number(websiteId);
|
||||
item.value.servers = [initServer()];
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
CreateLoadBalance(item.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -12,7 +12,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-text type="warning">{{ $t('website.rewriteHelper2') }}</el-text>
|
||||
<CodemirrorPro v-model="content" mode="nginx"></CodemirrorPro>
|
||||
<CodemirrorPro v-model="content" mode="nginx" :heightDiff="500"></CodemirrorPro>
|
||||
<div class="mt-2">
|
||||
<el-form-item>
|
||||
<el-alert :title="$t('website.rewriteHelper')" type="info" :closable="false" />
|
||||
|
@ -50,7 +50,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" v-if="index == 0">
|
||||
<el-form-item :label="$t('commons.button.add') + $t('commons.table.port')">
|
||||
<el-form-item :label="$t('commons.button.add') + $t('website.domain')">
|
||||
<el-button @click="addDomain">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
|
Loading…
Reference in New Issue
Block a user