mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-17 03:04:46 +08:00
feat: 增加CC防护
This commit is contained in:
parent
ba77f2853f
commit
047bd907f0
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/request"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -268,3 +269,30 @@ func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) {
|
||||||
|
var req request.WebsiteWafReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := websiteService.GetWafConfig(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
|
||||||
|
var req request.WebsiteWafUpdate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := websiteService.UpdateWafConfig(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
@ -76,6 +76,5 @@ var StaticFileKeyMap = map[NginxKey]struct {
|
|||||||
type NginxParam struct {
|
type NginxParam struct {
|
||||||
UpdateScope string `json:"scope"`
|
UpdateScope string `json:"scope"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
SecondKey string `json:"secondKey"`
|
|
||||||
Params []string `json:"params"`
|
Params []string `json:"params"`
|
||||||
}
|
}
|
||||||
|
@ -121,3 +121,9 @@ type WebsitePreInstallCheck struct {
|
|||||||
type WebsiteInstallCheckReq struct {
|
type WebsiteInstallCheckReq struct {
|
||||||
InstallIds []uint `json:"InstallIds"`
|
InstallIds []uint `json:"InstallIds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebsiteWafConfig struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
FilePath string `json:"filePath"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
13
backend/app/request/website.go
Normal file
13
backend/app/request/website.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
type WebsiteWafReq struct {
|
||||||
|
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||||
|
Key string `json:"key" validate:"required"`
|
||||||
|
Rule string `json:"rule" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteWafUpdate struct {
|
||||||
|
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||||
|
Key string `json:"key" validate:"required"`
|
||||||
|
Enable bool `json:"enable" validate:"required"`
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/request"
|
||||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -33,6 +34,18 @@ type IWebsiteService interface {
|
|||||||
RecoverByUpload(req dto.WebSiteRecoverByFile) error
|
RecoverByUpload(req dto.WebSiteRecoverByFile) error
|
||||||
UpdateWebsite(req dto.WebSiteUpdate) error
|
UpdateWebsite(req dto.WebSiteUpdate) error
|
||||||
DeleteWebSite(req dto.WebSiteDel) error
|
DeleteWebSite(req dto.WebSiteDel) error
|
||||||
|
GetWebsite(id uint) (dto.WebsiteDTO, error)
|
||||||
|
CreateWebsiteDomain(create dto.WebSiteDomainCreate) (model.WebSiteDomain, error)
|
||||||
|
GetWebsiteDomain(websiteId uint) ([]model.WebSiteDomain, error)
|
||||||
|
DeleteWebsiteDomain(domainId uint) error
|
||||||
|
GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.WebsiteNginxConfig, error)
|
||||||
|
UpdateNginxConfigByScope(req dto.NginxConfigReq) error
|
||||||
|
GetWebsiteNginxConfig(websiteId uint) (dto.FileInfo, error)
|
||||||
|
GetWebsiteHTTPS(websiteId uint) (dto.WebsiteHTTPS, error)
|
||||||
|
OpWebsiteHTTPS(req dto.WebsiteHTTPSOp) (dto.WebsiteHTTPS, error)
|
||||||
|
PreInstallCheck(req dto.WebsiteInstallCheckReq) ([]dto.WebsitePreInstallCheck, error)
|
||||||
|
GetWafConfig(req request.WebsiteWafReq) (dto.WebsiteWafConfig, error)
|
||||||
|
UpdateWafConfig(req request.WebsiteWafUpdate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebsiteService() IWebsiteService {
|
func NewWebsiteService() IWebsiteService {
|
||||||
@ -54,7 +67,6 @@ func (w WebsiteService) PageWebSite(req dto.WebSiteReq) (int64, []dto.WebSiteDTO
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
|
func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
|
||||||
|
|
||||||
if exist, _ := websiteRepo.GetBy(websiteRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 {
|
if exist, _ := websiteRepo.GetBy(websiteRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 {
|
||||||
return buserr.New(constant.ErrNameIsExist)
|
return buserr.New(constant.ErrNameIsExist)
|
||||||
}
|
}
|
||||||
@ -229,7 +241,6 @@ func (w WebsiteService) GetWebsite(id uint) (dto.WebsiteDTO, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error {
|
func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error {
|
||||||
|
|
||||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -296,7 +307,6 @@ func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebSiteDomain,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||||
|
|
||||||
webSiteDomain, err := websiteDomainRepo.GetFirst(commonRepo.WithByID(domainId))
|
webSiteDomain, err := websiteDomainRepo.GetFirst(commonRepo.WithByID(domainId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -328,7 +338,6 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.WebsiteNginxConfig, error) {
|
func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.WebsiteNginxConfig, error) {
|
||||||
|
|
||||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||||
if !ok || len(keys) == 0 {
|
if !ok || len(keys) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -350,7 +359,6 @@ func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.Webs
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) UpdateNginxConfigByScope(req dto.NginxConfigReq) error {
|
func (w WebsiteService) UpdateNginxConfigByScope(req dto.NginxConfigReq) error {
|
||||||
|
|
||||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||||
if !ok || len(keys) == 0 {
|
if !ok || len(keys) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -554,3 +562,50 @@ func (w WebsiteService) PreInstallCheck(req dto.WebsiteInstallCheckReq) ([]dto.W
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (dto.WebsiteWafConfig, error) {
|
||||||
|
var res dto.WebsiteWafConfig
|
||||||
|
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"set"}, &website)
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
for _, param := range params {
|
||||||
|
if param.Params[0] == req.Key {
|
||||||
|
res.Enable = len(param.Params) > 1 && param.Params[1] == "on"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nginxFull, err := getNginxFull(&website)
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := path.Join(nginxFull.SiteDir, "sites", website.Alias, "waf", "rules", req.Rule)
|
||||||
|
content, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.FilePath = filePath
|
||||||
|
res.Content = string(content)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w WebsiteService) UpdateWafConfig(req request.WebsiteWafUpdate) error {
|
||||||
|
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updateValue := "on"
|
||||||
|
if !req.Enable {
|
||||||
|
updateValue = "off"
|
||||||
|
}
|
||||||
|
return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{
|
||||||
|
{Name: "set", Params: []string{req.Key, updateValue}},
|
||||||
|
}, &website)
|
||||||
|
}
|
||||||
|
@ -33,5 +33,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
|||||||
groupRouter.POST("/config/update", baseApi.UpdateNginxConfig)
|
groupRouter.POST("/config/update", baseApi.UpdateNginxConfig)
|
||||||
groupRouter.GET("/:id/https", baseApi.GetHTTPSConfig)
|
groupRouter.GET("/:id/https", baseApi.GetHTTPSConfig)
|
||||||
groupRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig)
|
groupRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig)
|
||||||
|
groupRouter.POST("/waf/config", baseApi.GetWebsiteWafConfig)
|
||||||
|
groupRouter.POST("/waf/update", baseApi.UpdateWebsiteWafConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,4 +212,22 @@ export namespace WebSite {
|
|||||||
version: string;
|
version: string;
|
||||||
appName: string;
|
appName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WafReq {
|
||||||
|
websiteId: number;
|
||||||
|
key: string;
|
||||||
|
rule: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WafRes {
|
||||||
|
enable: boolean;
|
||||||
|
filePath: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WafUpdate {
|
||||||
|
enable: boolean;
|
||||||
|
websiteId: number;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,3 +148,11 @@ export const UpdateHTTPSConfig = (req: WebSite.HTTPSReq) => {
|
|||||||
export const PreCheck = (req: WebSite.CheckReq) => {
|
export const PreCheck = (req: WebSite.CheckReq) => {
|
||||||
return http.post<WebSite.CheckRes[]>(`/websites/check`, req);
|
return http.post<WebSite.CheckRes[]>(`/websites/check`, req);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GetWafConfig = (req: WebSite.WafReq) => {
|
||||||
|
return http.post<WebSite.WafRes>(`/websites/waf/config`, req);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UpdateWafEnable = (req: WebSite.WafUpdate) => {
|
||||||
|
return http.post<any>(`/websites/waf/update`, req);
|
||||||
|
};
|
||||||
|
@ -850,6 +850,12 @@ export default {
|
|||||||
default: '默认',
|
default: '默认',
|
||||||
deleteHelper: '相关应用状态不正常,请检查',
|
deleteHelper: '相关应用状态不正常,请检查',
|
||||||
toApp: '去已安装列表',
|
toApp: '去已安装列表',
|
||||||
|
enableCC: '开启CC攻击防护',
|
||||||
|
cycle: '周期',
|
||||||
|
frequency: '频率',
|
||||||
|
ccHelper: '{0} 秒内累计请求同一URL超过 {1} 次,触发CC防御,封锁此IP',
|
||||||
|
seconds: '秒',
|
||||||
|
count: '次',
|
||||||
},
|
},
|
||||||
nginx: {
|
nginx: {
|
||||||
serverNamesHashBucketSizeHelper: '服务器名字的hash表大小',
|
serverNamesHashBucketSizeHelper: '服务器名字的hash表大小',
|
||||||
|
@ -73,10 +73,10 @@ const search = (req: WebSite.NginxConfigReq) => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
GetNginxConfig(req)
|
GetNginxConfig(req)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.data && res.data.length > 0) {
|
if (res.data && res.data.params.length > 0) {
|
||||||
const indexParam = res.data[0];
|
const params = res.data.params[0].params;
|
||||||
let values = '';
|
let values = '';
|
||||||
for (const param of indexParam.params) {
|
for (const param of params) {
|
||||||
values = values + param + '\n';
|
values = values + param + '\n';
|
||||||
}
|
}
|
||||||
defaultModel.value.index = values;
|
defaultModel.value.index = values;
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="10" :offset="2">
|
||||||
|
<el-form
|
||||||
|
ref="wafForm"
|
||||||
|
label-position="left"
|
||||||
|
label-width="auto"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<el-form-item prop="enable" :label="$t('website.enableCC')">
|
||||||
|
<el-switch v-model="form.enable" @change="updateEnable"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="cycle" :label="$t('website.cycle')">
|
||||||
|
<el-input v-model.number="form.cycle" type="number">
|
||||||
|
<template #append>{{ $t('website.seconds') }}</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="frequency" :label="$t('website.frequency')">
|
||||||
|
<el-input v-model.number="form.frequency" type="number">
|
||||||
|
<template #append>{{ $t('website.count') }}</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-alert
|
||||||
|
:title="$t('website.ccHelper', [form.cycle, form.frequency])"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
></el-alert>
|
||||||
|
<el-form-item></el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="submit(wafForm)" :loading="loading">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { WebSite } from '@/api/interface/website';
|
||||||
|
import { SaveFileContent } from '@/api/modules/files';
|
||||||
|
import { GetWafConfig, UpdateWafEnable } from '@/api/modules/website';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElMessage, FormInstance } from 'element-plus';
|
||||||
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const id = computed(() => {
|
||||||
|
return props.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = ref<WebSite.WafRes>();
|
||||||
|
let loading = ref(false);
|
||||||
|
let form = reactive({
|
||||||
|
enable: false,
|
||||||
|
cycle: 60,
|
||||||
|
frequency: 120,
|
||||||
|
});
|
||||||
|
let req = ref<WebSite.WafReq>({
|
||||||
|
websiteId: 0,
|
||||||
|
key: '$CCDeny',
|
||||||
|
rule: 'ccrate',
|
||||||
|
});
|
||||||
|
let enableUpdate = ref<WebSite.WafUpdate>({
|
||||||
|
websiteId: 0,
|
||||||
|
key: '$CCDeny',
|
||||||
|
enable: false,
|
||||||
|
});
|
||||||
|
let fileUpdate = reactive({
|
||||||
|
path: '',
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
let rules = ref({
|
||||||
|
cycle: [Rules.requiredInput],
|
||||||
|
frequency: [Rules.requiredInput],
|
||||||
|
});
|
||||||
|
const wafForm = ref<FormInstance>();
|
||||||
|
|
||||||
|
const get = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await GetWafConfig(req.value);
|
||||||
|
loading.value = false;
|
||||||
|
data.value = res.data;
|
||||||
|
form.enable = data.value.enable;
|
||||||
|
if (data.value.content != '') {
|
||||||
|
const params = data.value.content.split('/');
|
||||||
|
form.frequency = Number(params[0]);
|
||||||
|
form.cycle = Number(params[1]);
|
||||||
|
}
|
||||||
|
fileUpdate.path = data.value.filePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateEnable = async (enable: boolean) => {
|
||||||
|
enableUpdate.value.enable = enable;
|
||||||
|
loading.value = true;
|
||||||
|
await UpdateWafEnable(enableUpdate.value);
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileUpdate.content = String(form.frequency) + '/' + String(form.cycle);
|
||||||
|
loading.value = true;
|
||||||
|
SaveFileContent(fileUpdate)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
req.value.websiteId = id.value;
|
||||||
|
enableUpdate.value.websiteId = id.value;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-tabs tab-position="left" type="border-card" v-model="index">
|
<el-tabs tab-position="left" type="border-card" v-model="index">
|
||||||
<el-tab-pane :label="'CC 防护'" :id="id"></el-tab-pane>
|
<el-tab-pane :label="'CC 防护'" name="cc">
|
||||||
|
<CCDeny :id="id" v-if="index == 'cc'"></CCDeny>
|
||||||
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="'白名单'"></el-tab-pane>
|
<el-tab-pane :label="'白名单'"></el-tab-pane>
|
||||||
<el-tab-pane :label="'黑名单'"></el-tab-pane>
|
<el-tab-pane :label="'黑名单'"></el-tab-pane>
|
||||||
<el-tab-pane :label="'并发限制'"></el-tab-pane>
|
<el-tab-pane :label="'并发限制'"></el-tab-pane>
|
||||||
@ -10,6 +12,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import CCDeny from './ccdeny/index.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
@ -22,5 +25,5 @@ const id = computed(() => {
|
|||||||
return props.id;
|
return props.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
let index = ref('0');
|
let index = ref('cc');
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user