1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 16:29:17 +08:00

feat: 网站增加负载均衡管理 (#6144)

This commit is contained in:
zhengkunwang 2024-08-15 23:15:29 +08:00 committed by GitHub
parent 2c2228192f
commit bf82fe743c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 751 additions and 12 deletions

View File

@ -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)
}

View File

@ -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": {}}

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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
}

View File

@ -40,3 +40,6 @@ var StopHTML []byte
//go:embed path_auth.conf
var PathAuth []byte
//go:embed upstream.conf
var Upstream []byte

View File

@ -0,0 +1,3 @@
upstream backend {
server backend1.example.com;
}

View File

@ -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)
}
}

View File

@ -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": {},

View File

@ -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;
}
}

View File

@ -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);
};

View File

@ -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',
},
];

View File

@ -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',

View File

@ -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: '短標簽支持',

View File

@ -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: '短标签支持',

View File

@ -203,7 +203,6 @@ const searchPath = async () => {
const searchAll = () => {
search();
searchPath();
console.log(11111);
};
onMounted(() => {

View File

@ -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 },

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>