diff --git a/Makefile b/Makefile index 47100ee1fe..81b3e6921e 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,6 @@ pre: # 本地测试前后端编译 all: pre ui server suite - @cd ${PRO_DIR}/cmd && make @echo -e "\033[32;1mBuild All Success!\n\033[0m" # 后端本地测试编译 diff --git a/cmd/auth-server/service/auth/adaptor.go b/cmd/auth-server/service/auth/adaptor.go index bd72a89ec1..7aa0145cb8 100644 --- a/cmd/auth-server/service/auth/adaptor.go +++ b/cmd/auth-server/service/auth/adaptor.go @@ -90,6 +90,17 @@ func AdaptAuthOptions(a *meta.ResourceAttribute) (client.ActionID, []client.Reso return sys.CloudSelectionRecommend, make([]client.Resource, 0), nil case meta.ArgumentTemplate: return genArgumentTemplateResource(a) + case meta.Cert: + return genCertResource(a) + case meta.LoadBalancer: + return genLoadBalancerResource(a) + case meta.Listener: + return genListenerResource(a) + case meta.TargetGroup: + return genTargetGroupResource(a) + case meta.UrlRuleAuditResType: + return genUrlRuleResource(a) + default: return "", nil, errf.Newf(errf.InvalidParameter, "unsupported hcm auth type: %s", a.Basic.Type) } diff --git a/cmd/auth-server/service/auth/gen_id.go b/cmd/auth-server/service/auth/gen_id.go index c1b88f3aca..7111511afe 100644 --- a/cmd/auth-server/service/auth/gen_id.go +++ b/cmd/auth-server/service/auth/gen_id.go @@ -529,3 +529,113 @@ func genCostManageResource(a *meta.ResourceAttribute) (client.ActionID, []client func genArgumentTemplateResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { return genIaaSResourceResource(a) } + +// genCertResource generate cert related iam resource. +func genCertResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { + res := client.Resource{ + System: sys.SystemIDHCM, + Type: sys.Account, + } + + // compatible for authorize any + if len(a.ResourceID) > 0 { + res.ID = a.ResourceID + } + + bizRes := client.Resource{ + System: sys.SystemIDCMDB, + Type: sys.Biz, + ID: strconv.FormatInt(a.BizID, 10), + } + + switch a.Basic.Action { + case meta.Find, meta.Assign: + return genIaaSResourceResource(a) + case meta.Create: + if a.BizID > 0 { + return sys.BizCertResCreate, []client.Resource{bizRes}, nil + } + return sys.CertResCreate, []client.Resource{res}, nil + case meta.Update: + // update resource is related to hcm account resource + if a.BizID > 0 { + return sys.BizIaaSResOperate, []client.Resource{bizRes}, nil + } + return sys.IaaSResOperate, []client.Resource{res}, nil + case meta.Delete: + if a.BizID > 0 { + return sys.BizCertResDelete, []client.Resource{bizRes}, nil + } + return sys.CertResDelete, []client.Resource{res}, nil + default: + return "", nil, errf.Newf(errf.InvalidParameter, "unsupported hcm action: %s", a.Basic.Action) + } +} + +// genLoadBalancerResource generate load balancer related iam resource. +func genLoadBalancerResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { + res := client.Resource{ + System: sys.SystemIDHCM, + Type: sys.Account, + } + + // compatible for authorize any + if len(a.ResourceID) > 0 { + res.ID = a.ResourceID + } + + bizRes := client.Resource{ + System: sys.SystemIDCMDB, + Type: sys.Biz, + ID: strconv.FormatInt(a.BizID, 10), + } + + switch a.Basic.Action { + case meta.Associate, meta.Disassociate: + if a.BizID > 0 { + return sys.BizIaaSResOperate, []client.Resource{bizRes}, nil + } + return sys.IaaSResOperate, []client.Resource{res}, nil + default: + return genIaaSResourceResource(a) + } +} + +// genListenerResource generate clb listener related iam resource. +func genListenerResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { + return genIaaSResourceResource(a) +} + +// genTargetGroupResource generate target group related iam resource. +func genTargetGroupResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { + res := client.Resource{ + System: sys.SystemIDHCM, + Type: sys.Account, + } + + // compatible for authorize any + if len(a.ResourceID) > 0 { + res.ID = a.ResourceID + } + + bizRes := client.Resource{ + System: sys.SystemIDCMDB, + Type: sys.Biz, + ID: strconv.FormatInt(a.BizID, 10), + } + + switch a.Basic.Action { + case meta.Associate, meta.Disassociate: + if a.BizID > 0 { + return sys.BizIaaSResOperate, []client.Resource{bizRes}, nil + } + return sys.IaaSResOperate, []client.Resource{res}, nil + default: + return genIaaSResourceResource(a) + } +} + +// genUrlRuleResource generate clb listener related iam resource. +func genUrlRuleResource(a *meta.ResourceAttribute) (client.ActionID, []client.Resource, error) { + return genIaaSResourceResource(a) +} diff --git a/cmd/cloud-server/logics/cert/assign.go b/cmd/cloud-server/logics/cert/assign.go new file mode 100644 index 0000000000..4063a32d0b --- /dev/null +++ b/cmd/cloud-server/logics/cert/assign.go @@ -0,0 +1,94 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cert + +import ( + "fmt" + + logicaudit "hcm/cmd/cloud-server/logics/audit" + "hcm/pkg/api/core" + corecert "hcm/pkg/api/core/cloud/cert" + protocloud "hcm/pkg/api/data-service/cloud" + dataservice "hcm/pkg/client/data-service" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/runtime/filter" + "hcm/pkg/tools/slice" +) + +// Assign 分配证书到业务下 +func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64) error { + if len(ids) == 0 { + return fmt.Errorf("ids is required") + } + + if err := ValidateBeforeAssign(kt, cli, ids); err != nil { + return err + } + + // create cert assign audit + audit := logicaudit.NewAudit(cli) + if err := audit.ResBizAssignAudit(kt, enumor.SslCertAuditResType, ids, bizID); err != nil { + logs.Errorf("create assign cert audit failed, ids: %v, bizID: %d, err: %v, rid: %s", ids, bizID, err, kt.Rid) + return err + } + + // assign + req := &protocloud.CertBatchUpdateExprReq{ + IDs: ids, + BkBizID: bizID, + } + _, err := cli.Global.BatchUpdateCert(kt, req) + if err != nil { + logs.Errorf("batch update cert db failed, ids: %v, bizID: %d, err: %v, rid: %s", ids, bizID, err, kt.Rid) + return err + } + + return nil +} + +// ValidateBeforeAssign 分配前置校验 +func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string) error { + // 判断是否已经分配 + listReq := &core.ListReq{ + Filter: &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{ + &filter.AtomRule{Field: "id", Op: filter.In.Factory(), Value: ids}, + &filter.AtomRule{Field: "bk_biz_id", Op: filter.NotEqual.Factory(), Value: constant.UnassignedBiz}, + }, + }, + Page: core.NewDefaultBasePage(), + } + listResp, err := cli.Global.ListCert(kt, listReq) + if err != nil { + logs.Errorf("list cert failed, req: %+v, err: %v, rid: %s", listReq, err, kt.Rid) + return err + } + + if len(listResp.Details) != 0 { + return fmt.Errorf("cert(ids=%v) already assigned", slice.Map(listResp.Details, + func(cert corecert.BaseCert) string { return cert.ID })) + } + + return nil +} diff --git a/cmd/cloud-server/logics/cvm/assign.go b/cmd/cloud-server/logics/cvm/assign.go index a3d548939c..9064db5aa0 100644 --- a/cmd/cloud-server/logics/cvm/assign.go +++ b/cmd/cloud-server/logics/cvm/assign.go @@ -57,7 +57,7 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64) err } // 校验主机关联资源信息 - if err := ValidateCvmRelResBeforeAssign(kt, cli, eipIDs, diskIDs, niIDs); err != nil { + if err := ValidateCvmRelResBeforeAssign(kt, cli, bizID, eipIDs, diskIDs, niIDs); err != nil { return err } @@ -158,23 +158,23 @@ func GetCvmRelResIDs(kt *kit.Kit, cli *dataservice.Client, ids []string) ( } // ValidateCvmRelResBeforeAssign 校验主机关联资源在分配前 -func ValidateCvmRelResBeforeAssign(kt *kit.Kit, cli *dataservice.Client, eipIDs []string, +func ValidateCvmRelResBeforeAssign(kt *kit.Kit, cli *dataservice.Client, targetBizId int64, eipIDs []string, diskIDs []string, niIDs []string) error { if len(eipIDs) != 0 { - if err := eip.ValidateBeforeAssign(kt, cli, eipIDs, true); err != nil { + if err := eip.ValidateBeforeAssign(kt, cli, targetBizId, eipIDs, true); err != nil { return err } } if len(diskIDs) != 0 { - if err := disk.ValidateBeforeAssign(kt, cli, diskIDs, true); err != nil { + if err := disk.ValidateBeforeAssign(kt, cli, targetBizId, diskIDs, true); err != nil { return err } } if len(niIDs) != 0 { - if err := logicsni.ValidateBeforeAssign(kt, cli, niIDs, true); err != nil { + if err := logicsni.ValidateBeforeAssign(kt, cli, targetBizId, niIDs, true); err != nil { return err } } diff --git a/cmd/cloud-server/logics/disk/assign.go b/cmd/cloud-server/logics/disk/assign.go index 1d75d03d58..b4563bc34c 100644 --- a/cmd/cloud-server/logics/disk/assign.go +++ b/cmd/cloud-server/logics/disk/assign.go @@ -33,7 +33,6 @@ import ( "hcm/pkg/dal/dao/tools" "hcm/pkg/kit" "hcm/pkg/logs" - "hcm/pkg/runtime/filter" "hcm/pkg/tools/slice" ) @@ -44,7 +43,7 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID uint64, is return fmt.Errorf("ids is required") } - if err := ValidateBeforeAssign(kt, cli, ids, isBind); err != nil { + if err := ValidateBeforeAssign(kt, cli, int64(bizID), ids, isBind); err != nil { return err } @@ -69,16 +68,15 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID uint64, is } // ValidateBeforeAssign 分配前置校验 -func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, isBind bool) error { +func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, + targetBizId int64, diskIds []string, isBind bool) error { + // 判断是否已经分配 listReq := &core.ListReq{ - Filter: &filter.Expression{ - Op: filter.And, - Rules: []filter.RuleFactory{ - &filter.AtomRule{Field: "id", Op: filter.In.Factory(), Value: ids}, - &filter.AtomRule{Field: "bk_biz_id", Op: filter.NotEqual.Factory(), Value: constant.UnassignedBiz}, - }, - }, + Filter: tools.ExpressionAnd( + tools.RuleIn("id", diskIds), + tools.RuleNotIn("bk_biz_id", []int64{constant.UnassignedBiz, targetBizId}), + ), Page: core.NewDefaultBasePage(), } listResp, err := cli.Global.ListDisk(kt, listReq) @@ -94,7 +92,7 @@ func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, is // 判断是否关联资源 listRelReq := &core.ListReq{ - Filter: tools.ContainersExpression("disk_id", ids), + Filter: tools.ContainersExpression("disk_id", diskIds), Page: core.NewDefaultBasePage(), } listRelResp, err := cli.Global.ListDiskCvmRel(kt, listRelReq) @@ -116,8 +114,8 @@ func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, is diskBindMap[one.DiskID] = true } - if len(ids) != len(diskBindMap) { - unBindIDs := slice.Filter(ids, func(id string) bool { + if len(diskIds) != len(diskBindMap) { + unBindIDs := slice.Filter(diskIds, func(id string) bool { return !diskBindMap[id] }) return fmt.Errorf("disk(ids=%v) not bind cvm", unBindIDs) diff --git a/cmd/cloud-server/logics/eip/assign.go b/cmd/cloud-server/logics/eip/assign.go index 96ab18cb5a..ffdf5cbe41 100644 --- a/cmd/cloud-server/logics/eip/assign.go +++ b/cmd/cloud-server/logics/eip/assign.go @@ -32,7 +32,6 @@ import ( "hcm/pkg/dal/dao/tools" "hcm/pkg/kit" "hcm/pkg/logs" - "hcm/pkg/runtime/filter" "hcm/pkg/tools/slice" ) @@ -43,7 +42,7 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID uint64, is return fmt.Errorf("ids is required") } - if err := ValidateBeforeAssign(kt, cli, ids, isBind); err != nil { + if err := ValidateBeforeAssign(kt, cli, int64(bizID), ids, isBind); err != nil { return err } @@ -68,16 +67,14 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID uint64, is } // ValidateBeforeAssign 分配前置校验 -func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, isBind bool) error { +func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, targetBizId int64, eipIds []string, isBind bool) error { // 判断是否已经分配 + // 允许已经在目标业务下 listReq := &core.ListReq{ - Filter: &filter.Expression{ - Op: filter.And, - Rules: []filter.RuleFactory{ - &filter.AtomRule{Field: "id", Op: filter.In.Factory(), Value: ids}, - &filter.AtomRule{Field: "bk_biz_id", Op: filter.NotEqual.Factory(), Value: constant.UnassignedBiz}, - }, - }, + Filter: tools.ExpressionAnd( + tools.RuleIn("id", eipIds), + tools.RuleNotIn("bk_biz_id", []int64{constant.UnassignedBiz, targetBizId}), + ), Page: core.NewDefaultBasePage(), } listResp, err := cli.Global.ListEip(kt, listReq) @@ -93,7 +90,7 @@ func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, is // 判断是否关联资源 listRelReq := &core.ListReq{ - Filter: tools.ContainersExpression("eip_id", ids), + Filter: tools.ContainersExpression("eip_id", eipIds), Page: core.NewDefaultBasePage(), } listRelResp, err := cli.Global.ListEipCvmRel(kt, listRelReq) @@ -115,8 +112,8 @@ func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, is eipBindMap[one.EipID] = true } - if len(ids) != len(eipBindMap) { - unBindIDs := slice.Filter(ids, func(id string) bool { + if len(eipIds) != len(eipBindMap) { + unBindIDs := slice.Filter(eipIds, func(id string) bool { return !eipBindMap[id] }) return fmt.Errorf("eip(ids=%v) not bind cvm", unBindIDs) diff --git a/cmd/cloud-server/logics/load-balancer/assign.go b/cmd/cloud-server/logics/load-balancer/assign.go new file mode 100644 index 0000000000..6e6fd7a006 --- /dev/null +++ b/cmd/cloud-server/logics/load-balancer/assign.go @@ -0,0 +1,170 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package lblogic + +import ( + "fmt" + + logicaudit "hcm/cmd/cloud-server/logics/audit" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + dataservice "hcm/pkg/client/data-service" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +// AssignTCloud 分配负载均衡及关联资源到业务下 +// 目前在负载均衡和监听器下有业务字段,监听器不能独立分配业务,分配时需要将监听器和对应的目标组分配到业务下 +func AssignTCloud(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64) error { + + if len(ids) == 0 { + return fmt.Errorf("ids is required") + } + + // 校验负载均衡信息 + if err := ValidateBeforeAssign(kt, cli, ids, bizID); err != nil { + return err + } + + // 获取负载均衡关联资源 + lblIds, tgIDs, err := GetLoadBalancerRelateResIDs(kt, cli, ids) + if err != nil { + return err + } + + // 校验负载均衡关联资源信息 + if err := ValidateLoadBalancerRelatedBeforeAssign(kt, cli, lblIds, tgIDs); err != nil { + return err + } + + // create assign audit + audit := logicaudit.NewAudit(cli) + if err := audit.ResBizAssignAudit(kt, enumor.LoadBalancerAuditResType, ids, bizID); err != nil { + logs.Errorf("create assign clb audit failed, err: %v, rid: %s", err, kt.Rid) + return err + } + + // 分配负载均衡关联资源 + if err := AssignLoadBalancerRelated(kt, cli, lblIds, tgIDs, bizID); err != nil { + return err + } + + // 分配负载均衡 + update := &dataproto.BizBatchUpdateReq{IDs: ids, BkBizID: bizID} + if err := cli.Global.LoadBalancer.BatchUpdateLbBizInfo(kt, update); err != nil { + logs.Errorf("BatchUpdateLbBizInfo failed, err: %v, req: %+v, rid: %s", err, update, kt.Rid) + return err + } + + return nil +} + +// ValidateBeforeAssign 分配负载均衡前校验 +func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64) error { + listReq := &core.ListReq{ + Fields: []string{"id", "bk_biz_id"}, + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + result, err := cli.Global.LoadBalancer.ListLoadBalancer(kt, listReq) + if err != nil { + logs.Errorf("list clb failed, err: %v, rid: %s", err, kt.Rid) + return err + } + + // 判断是否已经分配到业务下 + assignedIDs := make([]string, 0) + for _, one := range result.Details { + if one.BkBizID != constant.UnassignedBiz && one.BkBizID != bizID { + assignedIDs = append(assignedIDs, one.ID) + } + } + + // 存在已经分配到业务下的clb实例,报错 + if len(assignedIDs) != 0 { + return fmt.Errorf("load balancer(ids=%v) already assigned", assignedIDs) + } + + return nil +} + +// GetLoadBalancerRelateResIDs 获取clb关联资源列表,包括监听器和目标组 +func GetLoadBalancerRelateResIDs(kt *kit.Kit, cli *dataservice.Client, lbIds []string) ( + lblIds []string, tgIDs []string, err error) { + lbReq := &core.ListReq{ + Filter: tools.ContainersExpression("lb_id", lbIds), + Page: core.NewDefaultBasePage(), + } + lblResp, err := cli.Global.LoadBalancer.ListListener(kt, lbReq) + if err != nil { + logs.Errorf("fail to list listener for lb(ids=%v), err: %v, rid: %s", lbIds, err, kt.Rid) + return nil, nil, err + } + for _, lbl := range lblResp.Details { + lblIds = append(lblIds, lbl.ID) + } + + tgRelResp, err := cli.Global.LoadBalancer.ListTargetGroupListenerRel(kt, lbReq) + if err != nil { + logs.Errorf("fail to list load balancer(ids=%v) related target group relation, err: %v, rid: %s", + lbIds, err, kt.Rid) + return nil, nil, err + } + for _, rel := range tgRelResp.Details { + tgIDs = append(tgIDs, rel.TargetGroupID) + } + + return lblIds, tgIDs, nil +} + +// ValidateLoadBalancerRelatedBeforeAssign 在分配前校验lb关联资源信息 +func ValidateLoadBalancerRelatedBeforeAssign(kt *kit.Kit, cli *dataservice.Client, lblIds []string, + tgIds []string) error { + + // 目前都是以负载均衡粒度分配到业务,因此暂不做关联资源分配校验 + return nil +} + +// AssignLoadBalancerRelated 分配负载均衡关联的监听器和规则 +func AssignLoadBalancerRelated(kt *kit.Kit, cli *dataservice.Client, lblIds []string, tgIds []string, + bizID int64) error { + + if len(lblIds) != 0 { + // 分配关联规则、关联目标组 + updateLbl := &dataproto.BizBatchUpdateReq{IDs: lblIds, BkBizID: bizID} + if err := cli.Global.LoadBalancer.BatchUpdateListenerBizInfo(kt, updateLbl); err != nil { + logs.Errorf("batch update listener biz info failed, err: %v, req: %+v, rid: %s", err, updateLbl, kt.Rid) + return err + } + } + if len(tgIds) != 0 { + updateTg := &dataproto.BizBatchUpdateReq{IDs: tgIds, BkBizID: bizID} + if err := cli.Global.LoadBalancer.BatchUpdateTargetGroupBizInfo(kt, updateTg); err != nil { + logs.Errorf("batch update target group biz info failed, err: %v, req: %+v, rid: %s", err, updateTg, kt.Rid) + return err + } + } + + return nil +} diff --git a/cmd/cloud-server/logics/load-balancer/query.go b/cmd/cloud-server/logics/load-balancer/query.go new file mode 100644 index 0000000000..df8e7a22a4 --- /dev/null +++ b/cmd/cloud-server/logics/load-balancer/query.go @@ -0,0 +1,75 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package lblogic + +import ( + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataservice "hcm/pkg/client/data-service" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +// ListLoadBalancerMap 批量获取负载均衡列表信息 +func ListLoadBalancerMap(kt *kit.Kit, cli *dataservice.Client, lbIDs []string) ( + map[string]corelb.BaseLoadBalancer, error) { + if len(lbIDs) == 0 { + return nil, nil + } + + clbReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", lbIDs), + Page: core.NewDefaultBasePage(), + } + lbList, err := cli.Global.LoadBalancer.ListLoadBalancer(kt, clbReq) + if err != nil { + logs.Errorf("list load balancer failed, lbIDs: %v, err: %v, rid: %s", lbIDs, err, kt.Rid) + return nil, err + } + + lbMap := make(map[string]corelb.BaseLoadBalancer, len(lbList.Details)) + for _, lbItem := range lbList.Details { + lbMap[lbItem.ID] = lbItem + } + + return lbMap, nil +} + +// GetListenerByID 根据监听器ID、业务ID获取监听器信息 +func GetListenerByID(kt *kit.Kit, cli *dataservice.Client, lblID string) (corelb.BaseListener, error) { + listenerInfo := corelb.BaseListener{} + lblReq := &core.ListReq{ + Filter: tools.EqualExpression("id", lblID), + Page: core.NewDefaultBasePage(), + } + lblList, err := cli.Global.LoadBalancer.ListListener(kt, lblReq) + if err != nil { + logs.Errorf("list listener by id failed, lblID: %s, err: %v, rid: %s", lblID, err, kt.Rid) + return listenerInfo, err + } + if len(lblList.Details) == 0 { + return listenerInfo, errf.Newf(errf.RecordNotFound, "listener_id: %s not found", lblID) + } + + return lblList.Details[0], nil +} diff --git a/cmd/cloud-server/logics/network-interface/assign.go b/cmd/cloud-server/logics/network-interface/assign.go index 7a614cf434..cc4a6c0c8c 100644 --- a/cmd/cloud-server/logics/network-interface/assign.go +++ b/cmd/cloud-server/logics/network-interface/assign.go @@ -33,7 +33,6 @@ import ( "hcm/pkg/dal/dao/tools" "hcm/pkg/kit" "hcm/pkg/logs" - "hcm/pkg/runtime/filter" "hcm/pkg/tools/slice" ) @@ -44,7 +43,7 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64, isB return fmt.Errorf("ids is required") } - if err := ValidateBeforeAssign(kt, cli, ids, isBind); err != nil { + if err := ValidateBeforeAssign(kt, cli, bizID, ids, isBind); err != nil { return err } @@ -69,16 +68,13 @@ func Assign(kt *kit.Kit, cli *dataservice.Client, ids []string, bizID int64, isB } // ValidateBeforeAssign 分配前置校验 -func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, ids []string, isBind bool) error { +func ValidateBeforeAssign(kt *kit.Kit, cli *dataservice.Client, targetBizId int64, ids []string, isBind bool) error { // 判断是否已经分配 listReq := &core.ListReq{ - Filter: &filter.Expression{ - Op: filter.And, - Rules: []filter.RuleFactory{ - &filter.AtomRule{Field: "id", Op: filter.In.Factory(), Value: ids}, - &filter.AtomRule{Field: "bk_biz_id", Op: filter.NotEqual.Factory(), Value: constant.UnassignedBiz}, - }, - }, + Filter: tools.ExpressionAnd( + tools.RuleIn("id", ids), + tools.RuleNotIn("bk_biz_id", []int64{constant.UnassignedBiz, targetBizId}), + ), Page: core.NewDefaultBasePage(), } listResp, err := cli.Global.NetworkInterface.List(kt, listReq) diff --git a/cmd/cloud-server/service/account/account_type.go b/cmd/cloud-server/service/account/account_type.go new file mode 100644 index 0000000000..bfb6ad35c1 --- /dev/null +++ b/cmd/cloud-server/service/account/account_type.go @@ -0,0 +1,52 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package account + +import ( + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/rest" +) + +func (a *accountSvc) GetTCloudNetworkAccountType(cts *rest.Contexts) (any, error) { + + accountID := cts.PathParameter("account_id").String() + if len(accountID) == 0 { + return nil, errf.New(errf.InvalidParameter, "accountID is required") + } + + // 校验用户有该账号的查看权限 + if err := a.checkPermission(cts, meta.Find, accountID); err != nil { + return nil, err + } + // 查询该账号对应的Vendor + baseInfo, err := a.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.AccountCloudResType, accountID) + if err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + if baseInfo.Vendor != enumor.TCloud { + return nil, errf.New(errf.InvalidParameter, "only TCloud account is support now") + } + return a.client.HCService().TCloud.Account.GetNetworkAccountType(cts.Kit, accountID) +} diff --git a/cmd/cloud-server/service/account/service.go b/cmd/cloud-server/service/account/service.go index a903361379..5b98211afa 100644 --- a/cmd/cloud-server/service/account/service.go +++ b/cmd/cloud-server/service/account/service.go @@ -83,6 +83,9 @@ func InitAccountService(c *capability.Capability) { h.Add("ListTCloudAuthPolicies", http.MethodPost, "/vendors/tcloud/accounts/auth_policies/list", svc.ListTCloudAuthPolicies) + h.Add("GetTCloudNetworkAccountType", http.MethodGet, "/vendors/tcloud/accounts/{account_id}/network_type", + svc.GetTCloudNetworkAccountType) + h.Load(c.WebService) } diff --git a/cmd/cloud-server/service/async-task/async_task.go b/cmd/cloud-server/service/async-task/async_task.go new file mode 100644 index 0000000000..502a968057 --- /dev/null +++ b/cmd/cloud-server/service/async-task/async_task.go @@ -0,0 +1,54 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package asynctask ... +package asynctask + +import ( + "net/http" + + "hcm/cmd/cloud-server/logics/audit" + "hcm/cmd/cloud-server/service/capability" + "hcm/pkg/client" + "hcm/pkg/iam/auth" + "hcm/pkg/rest" +) + +// InitService initialize the async task service. +func InitService(c *capability.Capability) { + svc := &asyncTaskSvc{ + client: c.ApiClient, + authorizer: c.Authorizer, + audit: c.Audit, + } + + h := rest.NewHandler() + + // async task apis in resource + h.Add("GetFlow", http.MethodGet, "/async_task/flows/{id}", svc.GetFlow) + h.Add("ListTask", http.MethodGet, "/async_task/flows/{id}/tasks/list", svc.ListTask) + + h.Load(c.WebService) +} + +type asyncTaskSvc struct { + client *client.ClientSet + authorizer auth.Authorizer + audit audit.Interface +} diff --git a/cmd/cloud-server/service/async-task/query.go b/cmd/cloud-server/service/async-task/query.go new file mode 100644 index 0000000000..fe1b179585 --- /dev/null +++ b/cmd/cloud-server/service/async-task/query.go @@ -0,0 +1,115 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package asynctask + +import ( + "hcm/pkg/api/core" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// GetFlow 根据异步任务FlowID,获取异步任务Flow详情. +func (svc *asyncTaskSvc) GetFlow(cts *rest.Contexts) (any, error) { + return svc.getFlow(cts, handler.ListResourceAuthRes) +} + +func (svc *asyncTaskSvc) getFlow(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + flowInfo, err := svc.client.TaskServer().GetFlow(cts.Kit, id) + if err != nil { + logs.Errorf("fail to call task-server get flow info, err: %v, id: %s, rid: %s", err, id, cts.Kit.Rid) + return nil, err + } + + // 仅支持查询负载均衡的Flow信息 + if err = flowInfo.Name.ValidateLoadBalancer(); err != nil { + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.LoadBalancer, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + return nil, errf.New(errf.PermissionDenied, "permission denied for get load balancer") + } + + flowInfo.Worker = nil + flowInfo.Memo = "" + flowInfo.ShareData = nil + + return flowInfo, nil +} + +// ListTask 根据异步任务FlowID,获取异步任务Task列表. +func (svc *asyncTaskSvc) ListTask(cts *rest.Contexts) (any, error) { + return svc.listTask(cts, handler.ListResourceAuthRes) +} + +func (svc *asyncTaskSvc) listTask(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + flowInfo, err := svc.client.TaskServer().GetFlow(cts.Kit, id) + if err != nil { + logs.Errorf("fail to call task-server get flow info, err: %v, id: %s, rid: %s", err, id, cts.Kit.Rid) + return nil, err + } + + // 仅支持查询负载均衡的Flow信息 + if err = flowInfo.Name.ValidateLoadBalancer(); err != nil { + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.LoadBalancer, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + return nil, errf.New(errf.PermissionDenied, "permission denied for get load balancer") + } + + taskReq := &core.ListReq{ + Filter: tools.EqualExpression("flow_id", id), + Page: core.NewDefaultBasePage(), + } + taskList, err := svc.client.TaskServer().ListTask(cts.Kit, taskReq) + if err != nil { + logs.Errorf("fail to call task-server get task list, err: %v, id: %s, rid: %s", err, id, cts.Kit.Rid) + return nil, err + } + + return taskList, nil +} diff --git a/cmd/cloud-server/service/audit/audit.go b/cmd/cloud-server/service/audit/audit.go index 31c9dbeefc..35d3d02cb0 100644 --- a/cmd/cloud-server/service/audit/audit.go +++ b/cmd/cloud-server/service/audit/audit.go @@ -30,11 +30,13 @@ import ( "hcm/pkg/api/data-service/audit" "hcm/pkg/client" "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" "hcm/pkg/dal/dao/types" "hcm/pkg/iam/auth" "hcm/pkg/iam/meta" "hcm/pkg/logs" "hcm/pkg/rest" + "hcm/pkg/runtime/filter" "hcm/pkg/tools/hooks/handler" ) @@ -49,10 +51,16 @@ func InitService(c *capability.Capability) { h.Add("GetAudit", http.MethodGet, "/audits/{id}", svc.GetAudit) h.Add("ListAudit", http.MethodPost, "/audits/list", svc.ListAudit) + h.Add("ListAuditAsyncFlow", http.MethodPost, "/audits/async_flow/list", svc.ListAuditAsyncFlow) + h.Add("ListAuditAsyncTask", http.MethodPost, "/audits/async_task/list", svc.ListAuditAsyncTask) // biz audit apis h.Add("GetBizAudit", http.MethodGet, "/bizs/{bk_biz_id}/audits/{id}", svc.GetBizAudit) h.Add("ListBizAudit", http.MethodPost, "/bizs/{bk_biz_id}/audits/list", svc.ListBizAudit) + h.Add("ListBizAuditAsyncFlow", http.MethodPost, "/bizs/{bk_biz_id}/audits/async_flow/list", + svc.ListBizAuditAsyncFlow) + h.Add("ListBizAuditAsyncTask", http.MethodPost, "/bizs/{bk_biz_id}/audits/async_task/list", + svc.ListBizAuditAsyncTask) h.Load(c.WebService) } @@ -78,7 +86,7 @@ func (svc svc) getAudit(cts *rest.Contexts, validHandler handler.ValidWithAuthHa return nil, errf.NewFromErr(errf.InvalidParameter, err) } - audit, err := svc.client.DataService().Global.Audit.GetAudit(cts.Kit.Ctx, cts.Kit.Header(), id) + rawAudit, err := svc.client.DataService().Global.Audit.GetAuditRaw(cts.Kit, id) if err != nil { logs.Errorf("get audit failed, err: %v, id: %s, rid: %s", err, id, cts.Kit.Rid) return nil, err @@ -87,12 +95,12 @@ func (svc svc) getAudit(cts *rest.Contexts, validHandler handler.ValidWithAuthHa // validate biz and authorize err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Audit, Action: meta.Find, - BasicInfo: &types.CloudResourceBasicInfo{BkBizID: audit.BkBizID, AccountID: audit.AccountID}}) + BasicInfo: &types.CloudResourceBasicInfo{BkBizID: rawAudit.BkBizID, AccountID: rawAudit.AccountID}}) if err != nil { return nil, err } - return audit, nil + return rawAudit, nil } // ListAudit list audit. @@ -133,3 +141,154 @@ func (svc svc) listAudit(cts *rest.Contexts, authHandler handler.ListAuthResHand } return svc.client.DataService().Global.Audit.ListAudit(cts.Kit.Ctx, cts.Kit.Header(), listReq) } + +// ListAuditAsyncFlow 查询资源下异步任务的操作记录详情. +func (svc svc) ListAuditAsyncFlow(cts *rest.Contexts) (interface{}, error) { + return svc.listAuditAsyncFlow(cts, handler.ListResourceAuthRes) +} + +// ListBizAuditAsyncFlow 查询业务下异步任务的操作记录详情. +func (svc svc) ListBizAuditAsyncFlow(cts *rest.Contexts) (interface{}, error) { + return svc.listAuditAsyncFlow(cts, handler.ListBizAuthRes) +} + +func (svc svc) listAuditAsyncFlow(cts *rest.Contexts, authHandler handler.ListAuthResHandler) ( + interface{}, error) { + + req := new(proto.AuditAsyncFlowListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + idFilter := &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{&filter.AtomRule{Field: "id", Op: filter.Equal.Factory(), Value: req.AuditID}}, + } + // authorize + _, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.Audit, Action: meta.Find, Filter: idFilter}) + if err != nil { + return nil, err + } + + result := &audit.GetAsyncTaskResp{} + if noPermFlag { + return result, nil + } + + // 获取操作记录详情 + auditInfo, err := svc.client.DataService().Global.Audit.GetAudit(cts.Kit.Ctx, cts.Kit.Header(), req.AuditID) + if err != nil { + logs.Errorf("get audit by id failed, err: %v, id: %d, req: %+v, rid: %s", err, req.AuditID, req, cts.Kit.Rid) + return nil, err + } + if auditInfo == nil { + return nil, errf.Newf(errf.RecordNotFound, "audit: %d not found", req.AuditID) + } + + // 获取异步任务-Flow详情 + flow, err := svc.client.TaskServer().GetFlow(cts.Kit, req.FlowID) + if err != nil { + logs.Errorf("get flow by id failed, err: %v, auditID: %d, flowID: %s, rid: %s", + err, req.AuditID, req.FlowID, cts.Kit.Rid) + return nil, err + } + result.Flow = flow + + // 获取异步任务-子任务列表 + taskReq := &core.ListReq{ + Fields: []string{"id", "flow_id", "action_id", "action_name", "state", "reason", "created_at", "updated_at"}, + Filter: tools.EqualExpression("flow_id", req.FlowID), + Page: &core.BasePage{ + Count: false, + Start: 0, + Limit: core.DefaultMaxPageLimit, + Sort: "action_id", + Order: core.Ascending, + }, + } + taskList, err := svc.client.TaskServer().ListTask(cts.Kit, taskReq) + if err != nil { + logs.Errorf("get flow by id failed, flowID: %s, err: %v, rid: %s", req.FlowID, err, cts.Kit.Rid) + return nil, err + } + result.Tasks = taskList.Details + + return result, nil +} + +// ListAuditAsyncTask 查询资源下异步任务的操作记录指定子任务的详情. +func (svc svc) ListAuditAsyncTask(cts *rest.Contexts) (interface{}, error) { + return svc.listAuditAsyncTask(cts, handler.ListResourceAuthRes) +} + +// ListBizAuditAsyncTask 查询业务下异步任务的操作记录指定子任务的详情. +func (svc svc) ListBizAuditAsyncTask(cts *rest.Contexts) (interface{}, error) { + return svc.listAuditAsyncTask(cts, handler.ListBizAuthRes) +} + +func (svc svc) listAuditAsyncTask(cts *rest.Contexts, authHandler handler.ListAuthResHandler) ( + interface{}, error) { + + req := new(proto.AuditAsyncTaskListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + idFilter := &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{&filter.AtomRule{Field: "id", Op: filter.Equal.Factory(), Value: req.AuditID}}, + } + // authorize + _, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.Audit, Action: meta.Find, Filter: idFilter}) + if err != nil { + return nil, err + } + + result := &audit.GetAsyncTaskResp{} + if noPermFlag { + return result, nil + } + + // 获取操作记录详情 + auditInfo, err := svc.client.DataService().Global.Audit.GetAudit(cts.Kit.Ctx, cts.Kit.Header(), req.AuditID) + if err != nil { + logs.Errorf("get audit by id failed, id: %d, err: %v, rid: %s", req.AuditID, err, cts.Kit.Rid) + return nil, err + } + if auditInfo == nil { + return nil, errf.Newf(errf.RecordNotFound, "audit: %d not found", req.AuditID) + } + + // 获取异步任务-Flow详情 + flow, err := svc.client.TaskServer().GetFlow(cts.Kit, req.FlowID) + if err != nil { + logs.Errorf("get flow by id failed, flowID: %s, err: %v, rid: %s", req.FlowID, err, cts.Kit.Rid) + return nil, err + } + result.Flow = flow + + // 获取异步任务-子任务列表 + taskReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("flow_id", req.FlowID), + tools.RuleEqual("action_id", req.ActionID), + ), + Page: core.NewDefaultBasePage(), + } + taskList, err := svc.client.TaskServer().ListTask(cts.Kit, taskReq) + if err != nil { + logs.Errorf("get flow by id failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + result.Tasks = taskList.Details + + return result, nil +} diff --git a/cmd/cloud-server/service/cert/assign.go b/cmd/cloud-server/service/cert/assign.go new file mode 100644 index 0000000000..8950144c12 --- /dev/null +++ b/cmd/cloud-server/service/cert/assign.go @@ -0,0 +1,66 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + "hcm/cmd/cloud-server/logics/cert" + proto "hcm/pkg/api/cloud-server/cert" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// AssignCertToBiz assign cert to biz. +func (svc *certSvc) AssignCertToBiz(cts *rest.Contexts) (interface{}, error) { + req := new(proto.AssignCertToBizReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 权限校验 + basicInfoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.CertCloudResType, + IDs: req.CertIDs, + } + basicInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, basicInfoReq) + if err != nil { + return nil, err + } + + authRes := make([]meta.ResourceAttribute, 0, len(basicInfoMap)) + for _, info := range basicInfoMap { + authRes = append(authRes, meta.ResourceAttribute{Basic: &meta.Basic{Type: meta.Cert, + Action: meta.Assign, ResourceID: info.AccountID}, BizID: req.BkBizID}) + } + err = svc.authorizer.AuthorizeWithPerm(cts.Kit, authRes...) + if err != nil { + logs.Errorf("assign cert to biz auth failed, authRes: %+v, err: %v, rid: %s", authRes, err, cts.Kit.Rid) + return nil, err + } + + return nil, cert.Assign(cts.Kit, svc.client.DataService(), req.CertIDs, req.BkBizID) +} diff --git a/cmd/cloud-server/service/cert/cert.go b/cmd/cloud-server/service/cert/cert.go new file mode 100644 index 0000000000..052300a103 --- /dev/null +++ b/cmd/cloud-server/service/cert/cert.go @@ -0,0 +1,61 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + "net/http" + + "hcm/cmd/cloud-server/logics/audit" + "hcm/cmd/cloud-server/service/capability" + "hcm/pkg/client" + "hcm/pkg/iam/auth" + "hcm/pkg/rest" +) + +// InitCertService initialize the cvm service. +func InitCertService(c *capability.Capability) { + svc := &certSvc{ + client: c.ApiClient, + authorizer: c.Authorizer, + audit: c.Audit, + } + + h := rest.NewHandler() + + // cert apis in biz + h.Add("ListBizCvm", http.MethodPost, "/bizs/{bk_biz_id}/certs/list", svc.ListBizCert) + h.Add("CreateBizCert", http.MethodPost, "/bizs/{bk_biz_id}/certs/create", svc.CreateBizCert) + h.Add("DeleteBizCert", http.MethodDelete, "/bizs/{bk_biz_id}/certs/{id}", svc.DeleteBizCert) + + // cert apis in resource + h.Add("ListCert", http.MethodPost, "/certs/list", svc.ListCert) + h.Add("AssignCertToBiz", http.MethodPost, "/certs/assign/bizs", svc.AssignCertToBiz) + h.Add("CreateCert", http.MethodPost, "/certs/create", svc.CreateCert) + h.Add("DeleteCert", http.MethodDelete, "/certs/{id}", svc.DeleteCert) + + h.Load(c.WebService) +} + +type certSvc struct { + client *client.ClientSet + authorizer auth.Authorizer + audit audit.Interface +} diff --git a/cmd/cloud-server/service/cert/create.go b/cmd/cloud-server/service/cert/create.go new file mode 100644 index 0000000000..618846dcad --- /dev/null +++ b/cmd/cloud-server/service/cert/create.go @@ -0,0 +1,129 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + */ + +// Package cert ... +package cert + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + cloudserver "hcm/pkg/api/cloud-server" + hccert "hcm/pkg/api/hc-service/cert" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// CreateCert create resource cert. +func (svc *certSvc) CreateCert(cts *rest.Contexts) (interface{}, error) { + return svc.createCert(cts, handler.ResOperateAuth, false) +} + +// CreateBizCert create biz cert. +func (svc *certSvc) CreateBizCert(cts *rest.Contexts) (interface{}, error) { + return svc.createCert(cts, handler.BizOperateAuth, true) +} + +func (svc *certSvc) createCert(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler, bizRequired bool) ( + interface{}, error) { + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("create cert request decode failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if len(req.AccountID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "account_id is required") + } + + var bkBizID int64 = constant.UnassignedBiz + var err error + if bizRequired { + bkBizID, err = cts.PathParameter("bk_biz_id").Int64() + if err != nil { + return nil, err + } + } + + // create authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err = authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Cert, + Action: meta.Create, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("create cert auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + info, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, accID: %s, err: %v, rid: %s", req.AccountID, err, cts.Kit.Rid) + return nil, err + } + + switch info.Vendor { + case enumor.TCloud: + return svc.createTCloudCert(cts.Kit, req.Data, bkBizID) + default: + return nil, fmt.Errorf("vendor: %s not support", info.Vendor) + } +} + +func (svc *certSvc) createTCloudCert(kt *kit.Kit, body json.RawMessage, bkBizID int64) (interface{}, error) { + req := new(hccert.TCloudCreateReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + publicKey, err := base64.URLEncoding.DecodeString(req.PublicKey) + if err != nil { + logs.Errorf("create tcloud cert decode publickey failed, pk: %s, err: %v, rid: %s", req.PublicKey, err, kt.Rid) + return nil, err + } + + privateKey, err := base64.URLEncoding.DecodeString(req.PrivateKey) + if err != nil { + logs.Errorf("create tcloud cert decode privatekey failed, ik: %s, err: %v, rid: %s", req.PublicKey, err, kt.Rid) + return nil, err + } + req.PublicKey = string(publicKey) + req.PrivateKey = string(privateKey) + req.BkBizID = bkBizID + + if err = req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.client.HCService().TCloud.Cert.CreateCert(kt, req) + if err != nil { + logs.Errorf("create tcloud cert failed, req: %+v, result: %+v, err: %v, rid: %s", req, result, err, kt.Rid) + return result, err + } + + return result, nil +} diff --git a/cmd/cloud-server/service/cert/delete.go b/cmd/cloud-server/service/cert/delete.go new file mode 100644 index 0000000000..cc5e59b34a --- /dev/null +++ b/cmd/cloud-server/service/cert/delete.go @@ -0,0 +1,90 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + dataproto "hcm/pkg/api/data-service/cloud" + protocert "hcm/pkg/api/hc-service/cert" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// DeleteCert delete resource cert. +func (svc *certSvc) DeleteCert(cts *rest.Contexts) (interface{}, error) { + return svc.deleteCertSvc(cts, handler.ResOperateAuth) +} + +// DeleteBizCert delete biz cert. +func (svc *certSvc) DeleteBizCert(cts *rest.Contexts) (interface{}, error) { + return svc.deleteCertSvc(cts, handler.BizOperateAuth) +} + +func (svc *certSvc) deleteCertSvc(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) (interface{}, error) { + id := cts.PathParameter("id").String() + + basicInfoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.CertCloudResType, + IDs: []string{id}, + Fields: types.CommonBasicInfoFields, + } + basicInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, basicInfoReq) + if err != nil { + logs.Errorf("list cert basic info failed, req: %+v, err: %v, rid: %s", basicInfoReq, err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Cert, + Action: meta.Delete, BasicInfos: basicInfoMap}) + if err != nil { + logs.Errorf("delete cert auth failed, id: %s, err: %v, rid: %s", id, err, cts.Kit.Rid) + return nil, err + } + + if err = svc.audit.ResDeleteAudit(cts.Kit, enumor.SslCertAuditResType, basicInfoReq.IDs); err != nil { + logs.Errorf("create operation audit cert failed, ids: %v, err: %v, rid: %s", basicInfoReq.IDs, err, cts.Kit.Rid) + return nil, err + } + + // delete tcloud cloud cert + certInfo, ok := basicInfoMap[id] + if !ok { + logs.Errorf("cert record is not found, id: %s, rid: %s", id, cts.Kit.Rid) + return nil, errf.Newf(errf.Aborted, "cert %s record is not found", id) + } + + err = svc.client.HCService().TCloud.Cert.DeleteCert(cts.Kit, &protocert.TCloudDeleteReq{ + AccountID: certInfo.AccountID, + ID: id, + }) + if err != nil { + logs.Errorf("[%s] request hcservice to delete cert failed, id: %s, err: %v, rid: %s", + enumor.TCloud, id, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/cloud-server/service/cert/query.go b/cmd/cloud-server/service/cert/query.go new file mode 100644 index 0000000000..bc1dd91938 --- /dev/null +++ b/cmd/cloud-server/service/cert/query.go @@ -0,0 +1,70 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + proto "hcm/pkg/api/cloud-server" + "hcm/pkg/api/core" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// ListCert list resource cert. +func (svc *certSvc) ListCert(cts *rest.Contexts) (interface{}, error) { + return svc.listCert(cts, handler.ListResourceAuthRes) +} + +// ListBizCert list biz cert. +func (svc *certSvc) ListBizCert(cts *rest.Contexts) (interface{}, error) { + return svc.listCert(cts, handler.ListBizAuthRes) +} + +func (svc *certSvc) listCert(cts *rest.Contexts, authHandler handler.ListAuthResHandler) (interface{}, error) { + req := new(proto.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // list authorized instances + expr, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.Cert, Action: meta.Find, Filter: req.Filter}) + if err != nil { + logs.Errorf("list cert auth failed, noPermFlag: %v, err: %v, rid: %s", noPermFlag, err, cts.Kit.Rid) + return nil, err + } + + if noPermFlag { + return &core.ListResult{Count: 0, Details: make([]interface{}, 0)}, nil + } + + listReq := &core.ListReq{ + Filter: expr, + Page: req.Page, + } + return svc.client.DataService().Global.ListCert(cts.Kit, listReq) +} diff --git a/cmd/cloud-server/service/cloud-selection/bkbase.go b/cmd/cloud-server/service/cloud-selection/bkbase.go index 766f930756..23f865bae3 100644 --- a/cmd/cloud-server/service/cloud-selection/bkbase.go +++ b/cmd/cloud-server/service/cloud-selection/bkbase.go @@ -38,7 +38,6 @@ import ( "hcm/pkg/tools/classifier" "hcm/pkg/tools/converter" "hcm/pkg/tools/slice" - "hcm/pkg/tools/times" ) // ListAvailableCountry 列出支持的国家 @@ -86,7 +85,7 @@ func (svc *service) QueryUserDistribution(cts *rest.Contexts) (any, error) { total := float64(0) // list all once instead of one by one, reduce network overhead - groupByCountry, err := svc.listAllCountryUserDistDist(cts.Kit, times.DateAfterNow(-svc.cfg.AvgLatencySampleDays)) + groupByCountry, err := svc.listAllCountryUserDistDist(cts.Kit, bkbase.DateBefore(svc.cfg.AvgLatencySampleDays)) if err != nil { logs.Errorf("fail to query user distribution, err: %v, rid: %s", err, cts.Kit.Rid) return nil, err @@ -198,8 +197,8 @@ func (svc *service) QueryServiceArea(cts *rest.Contexts) (any, error) { return nil, err } - notBefore := times.DateAfterNow(-svc.cfg.AvgLatencySampleDays) - allPingData, err := svc.listAllAvgProvincePingData(cts.Kit, tableName, notBefore, bizID, idcNames) + startDate := bkbase.DateBefore(svc.cfg.AvgLatencySampleDays) + allPingData, err := svc.listAllAvgProvincePingData(cts.Kit, tableName, startDate, bizID, idcNames) if err != nil { logs.Errorf("fail to query %s, err: %v, rid: %s", svc.cfg.TableNames.LatencyPingProvinceIdc, err, cts.Kit.Rid) @@ -359,9 +358,9 @@ func getBizIDAndIDCNames(idcList []coresel.Idc) (bizId int64, idcNames []string, func (svc *service) queryLatency(kt *kit.Kit, areaTopo []coresel.AreaInfo, table string, bizId int64, idcNames []string) ([]cssel.MultiIdcTopo, error) { - notBefore := times.DateAfterNow(-svc.cfg.AvgLatencySampleDays) + startDate := bkbase.DateBefore(svc.cfg.AvgLatencySampleDays) userDist := make([]cssel.MultiIdcTopo, 0, len(areaTopo)) - pingDataMap, err := svc.listAllAvgProvincePingData(kt, table, notBefore, bizId, idcNames) + pingDataMap, err := svc.listAllAvgProvincePingData(kt, table, startDate, bizId, idcNames) if err != nil { logs.Errorf("fail to query %s data, err: %v, rid: %s", table, err, kt.Rid) return nil, err @@ -401,7 +400,7 @@ func (svc *service) queryLatency(kt *kit.Kit, areaTopo []coresel.AreaInfo, table func (svc *service) listAvailableCountry(kt *kit.Kit) ([]string, error) { - sampleDate := times.DateAfterNow(-svc.cfg.DefaultSampleOffset) + sampleDate := bkbase.DateBefore(svc.cfg.DefaultSampleOffset) sql := fmt.Sprintf("SELECT DISTINCT country FROM %s WHERE thedate='%s' ORDER BY country LIMIT %d", svc.cfg.TableNames.UserCountryDistribution, sampleDate, bkbase.DefaultQueryLimit) @@ -414,9 +413,8 @@ func (svc *service) listAvailableCountry(kt *kit.Kit) ([]string, error) { return slice.Map(countries, func(c coresel.CountryInfo) string { return c.Country }), nil } -func (svc *service) listAllCountryUserDistDist(kt *kit.Kit, - notBeforeDate string) (map[string][]coresel.UserDistribution, - error) { +func (svc *service) listAllCountryUserDistDist(kt *kit.Kit, startDate *bkbase.Date) ( + map[string][]coresel.UserDistribution, error) { sql := fmt.Sprintf(` SELECT country,province, avg(cnt) AS count @@ -426,7 +424,7 @@ func (svc *service) listAllCountryUserDistDist(kt *kit.Kit, ORDER BY country,province LIMIT %d `, - svc.cfg.TableNames.UserProvinceDistribution, notBeforeDate, bkbase.DefaultQueryLimit) + svc.cfg.TableNames.UserProvinceDistribution, startDate, bkbase.DefaultQueryLimit) distList, err := bkbase.QuerySql[coresel.UserDistribution](svc.bkBase, kt, sql) if err != nil { logs.Errorf("fail to listAllCountryUserDistDist data, err: %v, rid: %s", err, kt.Rid) @@ -437,14 +435,13 @@ func (svc *service) listAllCountryUserDistDist(kt *kit.Kit, } // list all country data -func (svc *service) listAllAvgProvincePingData(kt *kit.Kit, table string, notBeforeDate string, idcBizId int64, - idcNames []string) (map[string][]coresel.ProvinceToIDCLatency, error) { +func (svc *service) listAllAvgProvincePingData(kt *kit.Kit, table string, startDate *bkbase.Date, idcBizId int64, idcNames []string) (map[string][]coresel.ProvinceToIDCLatency, error) { fullMap := map[string][]coresel.ProvinceToIDCLatency{} page := core.BasePage{Limit: svc.cfg.BkBase.QueryLimit} for page.Limit > 0 { - latencyList, err := svc.listAllAvgProvincePingList(kt, table, notBeforeDate, idcBizId, idcNames, page) + latencyList, err := svc.listAllAvgProvincePingList(kt, table, startDate, idcBizId, idcNames, page) if err != nil { logs.Errorf("fail to query province idc average ping data, err: %v, rid: %s", err, kt.Rid) return nil, err @@ -464,8 +461,8 @@ func (svc *service) listAllAvgProvincePingData(kt *kit.Kit, table string, notBef } // list all country data -func (svc *service) listAllAvgProvincePingList(kt *kit.Kit, table string, notBeforeDate string, idcBizId int64, - idcNames []string, page core.BasePage) ([]coresel.ProvinceToIDCLatency, error) { +func (svc *service) listAllAvgProvincePingList(kt *kit.Kit, table string, startDate *bkbase.Date, + idcBizId int64, idcNames []string, page core.BasePage) ([]coresel.ProvinceToIDCLatency, error) { sql := fmt.Sprintf(`SELECT country, province, idc_name, avg(avg_ping) AS latency FROM %s @@ -473,7 +470,7 @@ func (svc *service) listAllAvgProvincePingList(kt *kit.Kit, table string, notBef GROUP BY country,province,idc_name ORDER BY country,province,idc_name LIMIT %d OFFSET %d `, - table, notBeforeDate, idcBizId, strings.Join(idcNames, "','"), page.Limit, page.Start) + table, startDate, idcBizId, strings.Join(idcNames, "','"), page.Limit, page.Start) return bkbase.QuerySql[coresel.ProvinceToIDCLatency](svc.bkBase, kt, sql) diff --git a/cmd/cloud-server/service/cloud-selection/scheme.go b/cmd/cloud-server/service/cloud-selection/scheme.go index 706ea0e391..ba64f2f791 100644 --- a/cmd/cloud-server/service/cloud-selection/scheme.go +++ b/cmd/cloud-server/service/cloud-selection/scheme.go @@ -26,7 +26,7 @@ import ( "hcm/cmd/cloud-server/plugin/recommend" csselection "hcm/pkg/api/cloud-server/cloud-selection" "hcm/pkg/api/core" - coreselection "hcm/pkg/api/core/cloud-selection" + coresel "hcm/pkg/api/core/cloud-selection" dsselection "hcm/pkg/api/data-service/cloud-selection" "hcm/pkg/criteria/enumor" "hcm/pkg/criteria/errf" @@ -35,9 +35,9 @@ import ( "hcm/pkg/logs" "hcm/pkg/plugin" "hcm/pkg/rest" + "hcm/pkg/thirdparty/api-gateway/bkbase" "hcm/pkg/tools/converter" "hcm/pkg/tools/slice" - "hcm/pkg/tools/times" ) // BatchDeleteScheme .. @@ -261,8 +261,7 @@ func (svc *service) GenerateRecommendScheme(cts *rest.Contexts) (any, error) { res := meta.ResourceAttribute{Basic: &meta.Basic{ Type: meta.CloudSelectionScheme, Action: meta.Create, - }, - } + }} if err := svc.authorizer.AuthorizeWithPerm(cts.Kit, res); err != nil { logs.Errorf("generate scheme auth failed, err: %v, rid: %s", err, cts.Kit.Rid) return nil, err @@ -275,9 +274,7 @@ func (svc *service) GenerateRecommendScheme(cts *rest.Contexts) (any, error) { logs.Errorf("fail to list IDC, err: %v", err) return nil, err } - idcByID := converter.SliceToMap(idcResp.Details, func(idc coreselection.Idc) (string, coreselection.Idc) { - return idc.ID, idc - }) + idcByID := converter.SliceToMap(idcResp.Details, func(idc coresel.Idc) (string, coresel.Idc) { return idc.ID, idc }) // 前端输入转算法输入 algIn, err := svc.buildALgIn(cts, req, idcByID) @@ -314,11 +311,11 @@ func (svc *service) GenerateRecommendScheme(cts *rest.Contexts) (any, error) { } func (svc *service) buildALgIn(cts *rest.Contexts, req *csselection.GenSchemeReq, - idcByID map[string]coreselection.Idc) (*recommend.AlgorithmInput, error) { + idcByID map[string]coresel.Idc) (*recommend.AlgorithmInput, error) { idcPriceMap := make(map[string]float64, len(idcByID)) usedIdcIds := make([]string, 0, len(idcByID)) - idcByName := make(map[string]coreselection.Idc, len(idcByID)) + idcByName := make(map[string]coresel.Idc, len(idcByID)) var idcBizID int64 = -1 for _, idc := range idcByID { price, ok := svc.cfg.DefaultIdcPrice[idc.Vendor] @@ -333,11 +330,11 @@ func (svc *service) buildALgIn(cts *rest.Contexts, req *csselection.GenSchemeReq } // 人口分布和ping数据 - notBefore := times.DateAfterNow(-svc.cfg.AvgLatencySampleDays) + startDate := bkbase.DateBefore(svc.cfg.AvgLatencySampleDays) userDistribution := map[string]float64{} pingInfo := make(map[string]map[string]float64, len(req.UserDistributions)) - allProvinceData, err := svc.listAllAvgProvincePingData(cts.Kit, svc.getRecommendDataSource(), notBefore, idcBizID, + allProvinceData, err := svc.listAllAvgProvincePingData(cts.Kit, svc.getRecommendDataSource(), startDate, idcBizID, converter.MapKeyToStringSlice(idcByName)) if err != nil { logs.Errorf("fail to get avg ping data, err: %v, rid: %s", err, cts.Kit.Rid) @@ -350,7 +347,7 @@ func (svc *service) buildALgIn(cts *rest.Contexts, req *csselection.GenSchemeReq return nil, errf.Newf(errf.InvalidParameter, "wrong country: %s", area.Name) } for _, pd := range pingData { - name := area.Name + "-" + pd.Province + name := getCombinedKey(area.Name, pd.Province) if _, exists := pingInfo[name]; !exists { pingInfo[name] = make(map[string]float64, len(idcByID)) } @@ -358,7 +355,7 @@ func (svc *service) buildALgIn(cts *rest.Contexts, req *csselection.GenSchemeReq } // 人口数据 for _, p := range area.Children { - name := area.Name + "-" + p.Name + name := getCombinedKey(area.Name, p.Name) userDistribution[name] = p.Value } } @@ -378,6 +375,11 @@ func (svc *service) buildALgIn(cts *rest.Contexts, req *csselection.GenSchemeReq return algIn, nil } +// 拼接唯一key main-sub +func getCombinedKey(main, sub string) string { + return main + "-" + sub +} + func (svc *service) getRecommendDataSource() string { tableName := svc.cfg.TableNames.RecommendDataSource if tableName == "" { diff --git a/cmd/cloud-server/service/load-balancer/assign.go b/cmd/cloud-server/service/load-balancer/assign.go new file mode 100644 index 0000000000..ed7b81e32e --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/assign.go @@ -0,0 +1,74 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + lblogic "hcm/cmd/cloud-server/logics/load-balancer" + cslb "hcm/pkg/api/cloud-server/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// AssignLbToBiz 分配到业务下 +func (svc *lbSvc) AssignLbToBiz(cts *rest.Contexts) (any, error) { + // 分配关联资源预检 + req := new(cslb.AssignLbToBizReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 权限校验 + clbInfoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.LoadBalancerCloudResType, + IDs: req.LbIDs, + } + basicInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, clbInfoReq) + if err != nil { + logs.Errorf("list clb info failed, err: %s, clb_ids: %v, rid: %s", err, req.LbIDs, cts.Kit.Rid) + return nil, err + } + + authRes := make([]meta.ResourceAttribute, 0, len(basicInfoMap)) + for _, info := range basicInfoMap { + authRes = append(authRes, meta.ResourceAttribute{ + Basic: &meta.Basic{ + Type: meta.LoadBalancer, + Action: meta.Assign, + ResourceID: info.AccountID, + }, + BizID: req.BkBizID, + }) + } + + err = svc.authorizer.AuthorizeWithPerm(cts.Kit, authRes...) + if err != nil { + return nil, err + } + + return nil, lblogic.AssignTCloud(cts.Kit, svc.client.DataService(), req.LbIDs, req.BkBizID) +} diff --git a/cmd/cloud-server/service/load-balancer/associate_target_group_listener.go b/cmd/cloud-server/service/load-balancer/associate_target_group_listener.go new file mode 100644 index 0000000000..6dad1fbae5 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/associate_target_group_listener.go @@ -0,0 +1,185 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + lblogic "hcm/cmd/cloud-server/logics/load-balancer" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + protoaudit "hcm/pkg/api/data-service/audit" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// AssociateTargetGroupListenerRel associate target group listener rel. +func (svc *lbSvc) AssociateTargetGroupListenerRel(cts *rest.Contexts) (interface{}, error) { + return svc.associateTargetGroupListenerRel(cts, handler.ResOperateAuth) +} + +// AssociateBizTargetGroupListenerRel associate biz target group listener rel. +func (svc *lbSvc) AssociateBizTargetGroupListenerRel(cts *rest.Contexts) (interface{}, error) { + return svc.associateTargetGroupListenerRel(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) associateTargetGroupListenerRel(cts *rest.Contexts, + validHandler handler.ValidWithAuthHandler) (interface{}, error) { + + req := new(cslb.TargetGroupListenerRelAssociateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 鉴权和校验资源分配状态 + basicReq := &dataproto.BatchListResourceBasicInfoReq{ + Items: []dataproto.ListResourceBasicInfoReq{ + {ResourceType: enumor.TargetGroupCloudResType, IDs: []string{req.TargetGroupID}, + Fields: types.CommonBasicInfoFields}, + {ResourceType: enumor.ListenerCloudResType, IDs: []string{req.ListenerID}, + Fields: types.CommonBasicInfoFields}, + }, + } + basicInfos, err := svc.client.DataService().Global.Cloud.BatchListResBasicInfo(cts.Kit, basicReq) + if err != nil { + logs.Errorf("batch list listener or target group resource basic info failed, err: %v, req: %+v, rid: %s", + err, basicReq, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Associate, BasicInfos: basicInfos}) + if err != nil { + logs.Errorf("valid lb resource basic info failed, err: %v, req: %+v, rid: %s", err, basicReq, cts.Kit.Rid) + return nil, err + } + + var vendor enumor.Vendor + for _, info := range basicInfos { + vendor = info.Vendor + break + } + + // create operation audit. + audit := protoaudit.CloudResourceOperationInfo{ + ResType: enumor.TargetGroupAuditResType, + ResID: req.TargetGroupID, + Action: protoaudit.Associate, + AssociatedResType: enumor.ListenerAuditResType, + AssociatedResID: req.ListenerID, + } + if err = svc.audit.ResOperationAudit(cts.Kit, audit); err != nil { + logs.Errorf("create target group listener rel operation audit failed, req: %+v, err: %v, rid: %s", + req, err, cts.Kit.Rid) + return nil, err + } + + switch vendor { + case enumor.TCloud: + return svc.tcloudTargetGroupListenerRel(cts.Kit, req) + default: + return nil, errf.Newf(errf.Unknown, "vendor: %s not support", vendor) + } +} + +func (svc *lbSvc) tcloudTargetGroupListenerRel(kt *kit.Kit, req *cslb.TargetGroupListenerRelAssociateReq) ( + interface{}, error) { + + lblInfo, err := lblogic.GetListenerByID(kt, svc.client.DataService(), req.ListenerID) + if err != nil { + return nil, err + } + + // 查询目标组基本信息 + tg, err := svc.getTargetGroupByID(kt, req.TargetGroupID) + if err != nil { + return nil, err + } + if tg == nil { + return nil, errf.Newf(errf.RecordNotFound, "target_group_id: %s not found", req.TargetGroupID) + } + // 查询目标组下,是否有RS信息 + targetList, err := svc.getTargetByTGIDs(kt, []string{req.TargetGroupID}) + if err != nil { + return nil, err + } + if len(targetList) > 0 { + return nil, errf.Newf(errf.InvalidParameter, "target_group_id: %s has bound rs", req.TargetGroupID) + } + + // 根据ruleID,查询规则详情信息 + ruleReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("id", req.ListenerRuleID), + tools.RuleEqual("lbl_id", req.ListenerID), + ), + Page: core.NewDefaultBasePage(), + } + ruleList, err := svc.listRuleWithCondition(kt, ruleReq) + if err != nil { + return nil, err + } + if len(ruleList.Details) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "listener_rule_id: %s not found or not belong to %s", + req.ListenerRuleID, req.ListenerID) + } + + // 检查监听器ID、目标组ID是否已经关联过了 + tgLblRelReq := &core.ListReq{ + Filter: tools.ExpressionOr( + tools.RuleEqual("target_group_id", req.TargetGroupID), + tools.RuleEqual("lbl_id", req.ListenerID), + ), + Page: core.NewDefaultBasePage(), + } + tgLblRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, tgLblRelReq) + if err != nil { + return nil, err + } + if len(tgLblRelList.Details) > 0 { + return nil, errf.Newf(errf.RecordDuplicated, "target group %s or listener %s rel already exists", + req.TargetGroupID, req.ListenerID) + } + + relReq := &dataproto.TargetGroupListenerRelCreateReq{ + ListenerRuleID: req.ListenerRuleID, + CloudListenerRuleID: ruleList.Details[0].CloudID, + ListenerRuleType: ruleList.Details[0].RuleType, + TargetGroupID: req.TargetGroupID, + CloudTargetGroupID: tg.CloudID, + LbID: lblInfo.LbID, + CloudLbID: lblInfo.CloudLbID, + LblID: req.ListenerID, + CloudLblID: lblInfo.CloudID, + BindingStatus: enumor.SuccessBindingStatus, + } + return svc.client.DataService().Global.LoadBalancer.CreateTargetGroupListenerRel(kt, relReq) +} diff --git a/cmd/cloud-server/service/load-balancer/async.go b/cmd/cloud-server/service/load-balancer/async.go new file mode 100644 index 0000000000..432773cb1f --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/async.go @@ -0,0 +1,419 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "errors" + "time" + + actionflow "hcm/cmd/task-server/logics/flow" + "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + hclb "hcm/pkg/api/hc-service/load-balancer" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/producer" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/json" + "hcm/pkg/tools/slice" + + v20180317 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" +) + +// BizTerminateFlow 终止flow +func (svc *lbSvc) BizTerminateFlow(cts *rest.Contexts) (any, error) { + return svc.terminateFlow(cts, handler.BizOperateAuth) +} + +// BizRetryTask ... +func (svc *lbSvc) BizRetryTask(cts *rest.Contexts) (any, error) { + return svc.retryTask(cts, handler.BizOperateAuth) +} + +// BizCloneFlow .... +func (svc *lbSvc) BizCloneFlow(cts *rest.Contexts) (any, error) { + return svc.cloneFlow(cts, handler.BizOperateAuth) +} + +// BizGetResultAfterTerminate ... +func (svc *lbSvc) BizGetResultAfterTerminate(cts *rest.Contexts) (any, error) { + return svc.getResultAfterTerminate(cts, handler.BizOperateAuth) +} + +// CancelFlow 终止flow +// 1. 检查负载均衡操作权限 +// 2. 检查对应res_flow_rel状态,终止应该是处于 executing +// 3. 调用task server 终止 +func (svc *lbSvc) terminateFlow(cts *rest.Contexts, + operateAuth handler.ValidWithAuthHandler) (any, error) { + + // check lb operation perm first + lbInfo, err := svc.getAndCheckLBPerm(cts, operateAuth) + if err != nil { + return nil, err + } + + req := new(cslb.AsyncFlowTerminateReq) + if err = cts.DecodeInto(req); err != nil { + return nil, err + } + if err = req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + rel, err := svc.getLoadBalancerFlowRel(cts.Kit, lbInfo.ID, req.FlowID) + if err != nil { + return nil, err + } + + // 需要对应flow没有被逻辑终止 + if rel.Status != enumor.ExecutingResFlowStatus { + return nil, errf.Newf(errf.InvalidParameter, "given flow status incorrect: %s", rel.Status) + } + // 从flow 检查到任务结束会自动解锁 + err = svc.client.TaskServer().CancelFlow(cts.Kit, req.FlowID) + if err != nil { + logs.Errorf("fail to call task server to terminate flow(%s), err: %s, rid: %s", req.FlowID, err, cts.Kit.Rid) + return nil, err + } + return nil, nil +} + +// RetryTask 重试子任务 要求有资源操作权限, 且对应的rel为 executing +func (svc *lbSvc) retryTask(cts *rest.Contexts, + operateAuth handler.ValidWithAuthHandler) (any, error) { + + // check lb operate perm + lbInfo, err := svc.getAndCheckLBPerm(cts, operateAuth) + if err != nil { + return nil, err + } + + req := new(cslb.AsyncTaskRetryReq) + if err = cts.DecodeInto(req); err != nil { + return nil, err + } + if err = req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + // 对应的rel表 是否已经取消或失败 + rel, err := svc.getLoadBalancerFlowRel(cts.Kit, lbInfo.ID, req.FlowID) + if err != nil { + return nil, err + } + if rel.Status != enumor.ExecutingResFlowStatus { + return nil, errf.Newf(errf.InvalidParameter, "given flow status incorrect: %s", rel.Status) + } + err = svc.client.TaskServer().RetryTask(cts.Kit, req.FlowID, req.TaskID) + if err != nil { + logs.Errorf("fail to call task server to retry flow(%s),task(%s), err: %s, rid: %s", + req.FlowID, req.TaskID, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// CloneFlow 重新发起 +func (svc *lbSvc) cloneFlow(cts *rest.Contexts, operateAuth handler.ValidWithAuthHandler) (any, error) { + + // check lb operate perm + lbInfo, err := svc.getAndCheckLBPerm(cts, operateAuth) + if err != nil { + return nil, err + } + + req := new(cslb.AsyncFlowCloneReq) + if err = cts.DecodeInto(req); err != nil { + return nil, err + } + if err = req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + rel, err := svc.getLoadBalancerFlowRel(cts.Kit, lbInfo.ID, req.FlowID) + if err != nil { + return nil, err + } + // 只能是终态 才能重新发起 + if rel.Status != enumor.CancelResFlowStatus && + rel.Status != enumor.TimeoutResFlowStatus && + rel.Status != enumor.SuccessResFlowStatus { + return nil, errf.Newf(errf.InvalidParameter, "given flow status incorrect: %s", rel.Status) + } + + cloneReq := &producer.CloneFlowOption{ + Memo: "cloned for " + req.FlowID, + IsInitState: true, + } + flowRet, err := svc.client.TaskServer().CloneFlow(cts.Kit, req.FlowID, cloneReq) + if err != nil { + logs.Errorf("fail to call task server to clone flow(%s), err: %s, rid: %s", req.FlowID, err, cts.Kit.Rid) + return nil, err + } + // 从Flow,负责监听主Flow的状态 + flowWatchReq := &ts.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []ts.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowRet.ID, + ResID: lbInfo.ID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: []string{lbInfo.ID}, + SubResType: enumor.LoadBalancerCloudResType, + TaskType: rel.TaskType, + }, + }}, + } + + _, err = svc.client.TaskServer().CreateTemplateFlow(cts.Kit, flowWatchReq) + if err != nil { + logs.Errorf("call task server to create res flow status watch task failed, err: %v, flowID: %s, rid: %s", + err, req.FlowID, cts.Kit.Rid) + return nil, err + } + + // 锁定资源跟Flow的状态 + err = svc.lockResFlowStatus(cts.Kit, lbInfo.ID, enumor.LoadBalancerCloudResType, req.FlowID, rel.TaskType) + if err != nil { + return nil, err + } + + return flowRet, nil +} + +// GetResultAfterTerminate 获取结束后的result +func (svc *lbSvc) getResultAfterTerminate(cts *rest.Contexts, operateAuth handler.ValidWithAuthHandler) (any, error) { + // check lb operate perm + lbInfo, err := svc.getAndCheckLBPerm(cts, operateAuth) + if err != nil { + return nil, err + } + + req := new(cslb.TerminatedAsyncFlowResultReq) + if err = cts.DecodeInto(req); err != nil { + return nil, err + } + if err = req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + rel, err := svc.getLoadBalancerFlowRel(cts.Kit, lbInfo.ID, req.FlowID) + if err != nil { + return nil, err + } + // 只能是终态 才能查询 + if !rel.Status.IsEnd() { + return nil, errf.Newf(errf.InvalidParameter, "given flow status incorrect: %s", rel.Status) + } + + tgIdList, taskParaMap, err := svc.getTaskParams(cts.Kit, req.FlowID, req.TaskIDs) + if err != nil { + return nil, err + } + // 查询对应关联规则 + relListReq := &core.ListReq{ + Filter: tools.ContainersExpression("target_group_id", tgIdList), + Page: core.NewDefaultBasePage(), + } + relResp, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, relListReq) + if err != nil { + return nil, err + } + if len(relResp.Details) != len(tgIdList) { + logs.Errorf("some target group rel can not be found, tgIDs: %v, rel: %v, rid: %s", + tgIdList, relResp.Details, cts.Kit.Rid) + return nil, errors.New("some target group binding rel can not be found") + } + + lblIds := make([]string, 0) + // 查找对应的监听器id + var lbCloudID string + tgLblRuleIDMap := make(map[string]string, len(relResp.Details)) + for _, rel := range relResp.Details { + lblIds = append(lblIds, rel.CloudLblID) + if lbCloudID == "" { + lbCloudID = rel.CloudLbID + } + tgLblRuleIDMap[rel.TargetGroupID] = rel.CloudListenerRuleID + } + lblRuleTargetsMap, err := svc.getBackend(cts.Kit, lbInfo, lbCloudID, lblIds) + if err != nil { + return nil, err + } + + // convert result + result := make([]cslb.TerminatedAsyncFlowResult, 0, len(taskParaMap)) + for taskId, param := range taskParaMap { + result = append(result, cslb.TerminatedAsyncFlowResult{ + TaskID: taskId, + TargetGroupID: param.TargetGroupID, + Targets: lblRuleTargetsMap[tgLblRuleIDMap[param.TargetGroupID]], + }) + } + + return result, nil +} + +func (svc *lbSvc) getBackend(kt *kit.Kit, lbInfo *types.CloudResourceBasicInfo, lbCloudID string, + lblIds []string) (map[string][]cslb.TCloudResultTarget, error) { + + req := &hclb.QueryTCloudListenerTargets{ + AccountID: lbInfo.AccountID, + Region: lbInfo.Region, + LoadBalancerCloudId: lbCloudID, + ListenerCloudIDs: slice.Unique(lblIds), + } + + targetResp, err := svc.client.HCService().TCloud.Clb.QueryListenerTargetsByCloudIDs(kt, req) + if err != nil { + return nil, err + } + if targetResp == nil { + return nil, errors.New("got nil pointer") + } + lblRuleTargetsMap := make(map[string][]cslb.TCloudResultTarget) + // 将监听器和规则打平 + for _, lbl := range *targetResp { + if len(lbl.Targets) > 0 { + lblRuleTargetsMap[cvt.PtrToVal(lbl.ListenerId)] = convTargets(lbl.Targets) + } + for _, rule := range lbl.Rules { + lblRuleTargetsMap[cvt.PtrToVal(rule.LocationId)] = convTargets(rule.Targets) + } + } + return lblRuleTargetsMap, nil +} + +func convTargets(backends []*v20180317.Backend) []cslb.TCloudResultTarget { + targets := make([]cslb.TCloudResultTarget, len(backends)) + for i, backend := range backends { + targets[i].CloudInstID = cvt.PtrToVal(backend.InstanceId) + targets[i].InstType = enumor.InstType(cvt.PtrToVal(backend.Type)) + targets[i].InstName = cvt.PtrToVal(backend.InstanceName) + targets[i].Port = cvt.PtrToVal(backend.Port) + targets[i].Weight = backend.Weight + } + return targets +} + +// 获对应任务的参数和目标组id +func (svc *lbSvc) getTaskParams(kt *kit.Kit, flowID string, taskIds []string) (tgIds []string, + taskParamMap map[string]*hclb.TCloudBatchOperateTargetReq, err error) { + + // 查询对应任务 + taskListReq := &core.ListReq{ + Filter: tools.EqualExpression("flow_id", flowID), + Page: core.NewDefaultBasePage(), + } + if len(taskIds) != 0 { + taskListReq.Filter.Rules = append(taskListReq.Filter.Rules, tools.RuleIn("id", taskIds)) + } + taskResp, err := svc.client.TaskServer().ListTask(kt, taskListReq) + if err != nil { + return nil, nil, err + } + tgIdList := make([]string, 0) + taskParamMap = make(map[string]*hclb.TCloudBatchOperateTargetReq, len(taskResp.Details)) + for _, detail := range taskResp.Details { + if detail.State == enumor.TaskSuccess || detail.State == enumor.TaskPending { + continue + } + // 由pending 取消转过来的状态,不查询 + if detail.State == enumor.TaskCancel && enumor.TaskState(detail.Reason.PreState) == enumor.TaskPending { + continue + } + taskParam := &hclb.TCloudBatchOperateTargetReq{} + err = json.Unmarshal([]byte(detail.Params), taskParam) + if err != nil { + logs.Errorf("fail to parse task param, err: %v, param json: %s, rid: %s", + err, detail.Params, kt.Rid) + return nil, nil, err + } + taskParamMap[detail.ID] = taskParam + // 收集目标组id + tgIdList = append(tgIdList, taskParam.TargetGroupID) + } + + return slice.Unique(tgIdList), taskParamMap, nil +} + +func (svc *lbSvc) getAndCheckLBPerm(cts *rest.Contexts, + operateAuth handler.ValidWithAuthHandler) (*types.CloudResourceBasicInfo, error) { + + lbID := cts.PathParameter("lb_id").String() + if len(lbID) == 0 { + return nil, errors.New("lb_id is required") + } + + // 获取操作记录详情 + lbInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.LoadBalancerCloudResType, lbID) + if err != nil { + logs.Errorf("get load balancer basic info failed, id: %d, err: %v, rid: %s", lbID, err, cts.Kit.Rid) + return nil, err + } + + err = operateAuth(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Update, + BasicInfo: lbInfo, + }) + if err != nil { + return nil, err + } + return lbInfo, err +} + +// 查询负载均衡和对应flow的在有效期内的关系条目 +func (svc *lbSvc) getLoadBalancerFlowRel(kt *kit.Kit, lbID, flowID string) (*corelb.BaseResFlowRel, error) { + aWeekAgo := time.Now().Add(-time.Hour * 24 * constant.ResFlowLockExpireDays) + relListReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("res_id", lbID), + tools.RuleEqual("res_type", enumor.LoadBalancerCloudResType), + tools.RuleEqual("flow_id", flowID), + tools.RuleGreaterThan("created_at", aWeekAgo.Format(constant.TimeStdFormat)), + ), + Page: core.NewDefaultBasePage(), + } + relResp, err := svc.client.DataService().Global.LoadBalancer.ListResFlowRel(kt, relListReq) + if err != nil { + return nil, err + } + if len(relResp.Details) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "relation of flow(%s) not found", flowID) + } + + return &relResp.Details[0], nil +} diff --git a/cmd/cloud-server/service/load-balancer/async_target_group_add_rs.go b/cmd/cloud-server/service/load-balancer/async_target_group_add_rs.go new file mode 100644 index 0000000000..f7a9016c01 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/async_target_group_add_rs.go @@ -0,0 +1,448 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "fmt" + + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + actionflow "hcm/cmd/task-server/logics/flow" + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corecvm "hcm/pkg/api/core/cloud/cvm" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + hcproto "hcm/pkg/api/hc-service/load-balancer" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/async/backend" + "hcm/pkg/async/producer" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/slice" +) + +// BatchAddBizTargets create add biz targets. +func (svc *lbSvc) BatchAddBizTargets(cts *rest.Contexts) (any, error) { + return svc.batchAddBizTarget(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) batchAddBizTarget(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) (any, error) { + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch add target request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Update, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("batch add target auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + accountInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch accountInfo.Vendor { + case enumor.TCloud: + return svc.buildAddTCloudTarget(cts.Kit, req.Data, accountInfo.AccountID) + default: + return nil, fmt.Errorf("vendor: %s not support", accountInfo.Vendor) + } +} + +func (svc *lbSvc) buildAddTCloudTarget(kt *kit.Kit, body json.RawMessage, accountID string) (interface{}, error) { + req := new(cslb.TCloudTargetBatchCreateReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 检查传入的Target是否已存在 + if err := svc.checkAddTarget(kt, req, accountID); err != nil { + return nil, err + } + + targetIDs := make([]string, 0) + lbIDs := make([]string, 0) + targetGroupRsListMap := make(map[string][]*dataproto.TargetBaseReq, 0) + targetGroupRuleRelMap := make(map[string][]corelb.BaseTargetListenerRuleRel, 0) + for _, item := range req.TargetGroups { + if _, ok := targetGroupRuleRelMap[item.TargetGroupID]; !ok { + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", item.TargetGroupID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgItem: %+v, err: %v, rid: %s", item, err, kt.Rid) + return nil, err + } + targetGroupRuleRelMap[item.TargetGroupID] = ruleRelList.Details + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + ruleRelList := targetGroupRuleRelMap[item.TargetGroupID] + if len(ruleRelList) == 0 { + rsIDs, err := svc.batchCreateTargetDb(kt, item.Targets, item.TargetGroupID, accountID) + if err != nil { + return nil, err + } + targetIDs = append(targetIDs, rsIDs.IDs...) + } else { + lbIDs = slice.Unique(slice.Map(ruleRelList, func(rel corelb.BaseTargetListenerRuleRel) string { + return rel.LbID + })) + targetGroupRsListMap[item.TargetGroupID] = append(targetGroupRsListMap[item.TargetGroupID], item.Targets...) + } + } + + // 都是未绑定监听器的目标组,不需要云端操作 + if len(targetGroupRsListMap) == 0 { + return &corelb.TargetOperateResult{TargetIDs: targetIDs}, nil + } + + // 目标组需要属于同一个负载均衡 + if len(lbIDs) > 1 { + return nil, errf.New(errf.InvalidParameter, "target group need belong to the same load balancer") + } + + return svc.buildAddTCloudTargetTasks(kt, accountID, lbIDs[0], targetGroupRsListMap) +} + +func (svc *lbSvc) checkAddTarget(kt *kit.Kit, req *cslb.TCloudTargetBatchCreateReq, accountID string) error { + for _, tgItem := range req.TargetGroups { + cloudInstIDs := make([]string, 0) + ports := make([]int64, 0) + for _, item := range tgItem.Targets { + cloudInstIDs = append(cloudInstIDs, item.CloudInstID) + ports = append(ports, item.Port) + } + tgReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("target_group_id", tgItem.TargetGroupID), + tools.RuleIn("cloud_inst_id", cloudInstIDs), + tools.RuleIn("port", ports), + ), + Page: core.NewDefaultBasePage(), + } + rsList, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(rsList.Details) > 0 { + tmpCloudInstIds := slice.Unique(slice.Map(rsList.Details, func(target corelb.BaseTarget) string { + return target.CloudInstID + })) + return errf.Newf(errf.RecordDuplicated, "targetGroupID: %s, cloudInstIDs: %v has exist", + tgItem.TargetGroupID, tmpCloudInstIds) + } + } + return nil +} + +func (svc *lbSvc) batchCreateTargetDb(kt *kit.Kit, targets []*dataproto.TargetBaseReq, tgID, accountID string) ( + *core.BatchCreateResult, error) { + + addRsParams, err := svc.convTCloudAddTargetReq(kt, targets, "", tgID, accountID) + if err != nil { + return nil, err + } + + // 检查RS是否已绑定该目标组 + rsReq := &dataproto.TargetBatchCreateReq{} + for _, item := range addRsParams.RsList { + rsReq.Targets = append(rsReq.Targets, &dataproto.TargetBaseReq{ + AccountID: accountID, + TargetGroupID: item.TargetGroupID, + InstType: item.InstType, + CloudInstID: item.CloudInstID, + Port: item.Port, + Weight: item.Weight, + }) + } + return svc.client.DataService().Global.LoadBalancer.BatchCreateTCloudTarget(kt, rsReq) +} + +func (svc *lbSvc) buildAddTCloudTargetTasks(kt *kit.Kit, accountID, lbID string, + tgMap map[string][]*dataproto.TargetBaseReq) (*core.FlowStateResult, error) { + + // 预检测 + _, err := svc.checkResFlowRel(kt, lbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + // 创建Flow跟Task的初始化数据 + flowID, err := svc.initFlowAddTargetByLbID(kt, accountID, lbID, tgMap) + if err != nil { + return nil, err + } + + // 锁定资源跟Flow的状态 + err = svc.lockResFlowStatus(kt, lbID, enumor.LoadBalancerCloudResType, flowID, enumor.AddRSTaskType) + if err != nil { + return nil, err + } + + return &core.FlowStateResult{FlowID: flowID}, nil +} + +func (svc *lbSvc) initFlowAddTargetByLbID(kt *kit.Kit, accountID, lbID string, + tgMap map[string][]*dataproto.TargetBaseReq) (string, error) { + + tasks := make([]ts.CustomFlowTask, 0) + getActionID := counter.NewNumStringCounter(1, 10) + var tgIDs []string + var lastActionID action.ActIDType + for tgID, rsList := range tgMap { + tgIDs = append(tgIDs, tgID) + elems := slice.Split(rsList, constant.BatchAddRSCloudMaxLimit) + for _, parts := range elems { + addRsParams, err := svc.convTCloudAddTargetReq(kt, parts, lbID, tgID, accountID) + if err != nil { + logs.Errorf("add target build tcloud request failed, err: %v, tgID: %s, parts: %+v, rid: %s", + err, tgID, parts, kt.Rid) + return "", err + } + actionID := action.ActIDType(getActionID()) + tmpTask := ts.CustomFlowTask{ + ActionID: actionID, + ActionName: enumor.ActionTargetGroupAddRS, + Params: &actionlb.OperateRsOption{ + Vendor: enumor.TCloud, + TCloudBatchOperateTargetReq: *addRsParams, + }, + Retry: &tableasync.Retry{ + Enable: true, + Policy: &tableasync.RetryPolicy{ + Count: 3, + SleepRangeMS: [2]uint{100, 200}, + }, + }, + } + if len(lastActionID) > 0 { + tmpTask.DependOn = []action.ActIDType{lastActionID} + } + tasks = append(tasks, tmpTask) + lastActionID = actionID + } + } + addReq := &ts.AddCustomFlowReq{ + Name: enumor.FlowTargetGroupAddRS, + ShareData: tableasync.NewShareData(map[string]string{ + "lb_id": lbID, + }), + Tasks: tasks, + IsInitState: true, + } + result, err := svc.client.TaskServer().CreateCustomFlow(kt, addReq) + if err != nil { + logs.Errorf("call taskserver to batch add rs custom flow failed, err: %v, rid: %s", err, kt.Rid) + return "", err + } + + flowID := result.ID + // 从Flow,负责监听主Flow的状态 + flowWatchReq := &ts.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []ts.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowID, + ResID: lbID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: tgIDs, + SubResType: enumor.TargetGroupCloudResType, + TaskType: enumor.AddRSTaskType, + }, + }}, + } + _, err = svc.client.TaskServer().CreateTemplateFlow(kt, flowWatchReq) + if err != nil { + logs.Errorf("call taskserver to create res flow status watch task failed, err: %v, flowID: %s, rid: %s", + err, flowID, kt.Rid) + return "", err + } + + return flowID, nil +} + +// convTCloudAddTargetReq conv tcloud add target req. +func (svc *lbSvc) convTCloudAddTargetReq(kt *kit.Kit, targets []*dataproto.TargetBaseReq, lbID, targetGroupID, + accountID string) (*hcproto.TCloudBatchOperateTargetReq, error) { + + instMap, err := svc.getInstWithTargetMap(kt, targets) + if err != nil { + return nil, err + } + + rsReq := &hcproto.TCloudBatchOperateTargetReq{TargetGroupID: targetGroupID, LbID: lbID} + for _, item := range targets { + item.TargetGroupID = targetGroupID + item.AccountID = accountID + item.InstName = instMap[item.CloudInstID].Name + item.PrivateIPAddress = instMap[item.CloudInstID].PrivateIPv4Addresses + item.PublicIPAddress = instMap[item.CloudInstID].PublicIPv4Addresses + item.CloudVpcIDs = instMap[item.CloudInstID].CloudVpcIDs + item.Zone = instMap[item.CloudInstID].Zone + rsReq.RsList = append(rsReq.RsList, item) + } + return rsReq, nil +} + +func (svc *lbSvc) getInstWithTargetMap(kt *kit.Kit, targets []*dataproto.TargetBaseReq) ( + map[string]corecvm.BaseCvm, error) { + + cloudCvmIDs := make([]string, 0) + for _, item := range targets { + if item.InstType == enumor.CvmInstType { + cloudCvmIDs = append(cloudCvmIDs, item.CloudInstID) + } + } + + // 查询Cvm信息 + cvmMap := make(map[string]corecvm.BaseCvm) + if len(cloudCvmIDs) > 0 { + cvmReq := &core.ListReq{ + Filter: tools.ContainersExpression("cloud_id", cloudCvmIDs), + Page: core.NewDefaultBasePage(), + } + cvmList, err := svc.client.DataService().Global.Cvm.ListCvm(kt, cvmReq) + if err != nil { + logs.Errorf("failed to list cvm by cloudIDs, cloudIDs: %v, err: %v, rid: %s", cloudCvmIDs, err, kt.Rid) + return nil, err + } + + for _, item := range cvmList.Details { + cvmMap[item.CloudID] = item + } + } + + return cvmMap, nil +} + +func (svc *lbSvc) lockResFlowStatus(kt *kit.Kit, resID string, resType enumor.CloudResourceType, flowID string, + taskType enumor.TaskType) error { + + // 锁定资源跟Flow的状态 + opt := &dataproto.ResFlowLockReq{ + ResID: resID, + ResType: resType, + FlowID: flowID, + Status: enumor.ExecutingResFlowStatus, + TaskType: taskType, + } + err := svc.client.DataService().Global.LoadBalancer.ResFlowLock(kt, opt) + if err != nil { + logs.Errorf("call dataservice to lock res and flow failed, err: %v, opt: %+v, rid: %s", err, opt, kt.Rid) + return err + } + + // 更新Flow状态为pending + flowStateReq := &producer.UpdateCustomFlowStateOption{ + FlowInfos: []backend.UpdateFlowInfo{{ + ID: flowID, + Source: enumor.FlowInit, + Target: enumor.FlowPending, + }}, + } + err = svc.client.TaskServer().UpdateCustomFlowState(kt, flowStateReq) + if err != nil { + logs.Errorf("call taskserver to update flow state failed, err: %v, flowID: %s, rid: %s", err, flowID, kt.Rid) + return err + } + + return nil +} + +func (svc *lbSvc) checkResFlowRel(kt *kit.Kit, resID string, resType enumor.CloudResourceType) ( + *corelb.BaseResFlowLock, error) { + + // 预检测-当前资源是否有锁定中的数据 + lockReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("res_id", resID), + tools.RuleEqual("res_type", resType), + ), + Page: core.NewDefaultBasePage(), + } + lockRet, err := svc.client.DataService().Global.LoadBalancer.ListResFlowLock(kt, lockReq) + if err != nil { + logs.Errorf("list res flow lock failed, err: %v, resID: %s, resType: %s, rid: %s", err, resID, resType, + kt.Rid) + return nil, err + } + if len(lockRet.Details) > 0 { + return &lockRet.Details[0], errf.Newf(errf.LoadBalancerTaskExecuting, "resID: %s is processing", resID) + } + + // 预检测-当前资源是否有未终态的状态 + flowRelReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("res_id", resID), + tools.RuleEqual("res_type", resType), + tools.RuleEqual("status", enumor.ExecutingResFlowStatus), + ), + Page: core.NewDefaultBasePage(), + } + flowRelRet, err := svc.client.DataService().Global.LoadBalancer.ListResFlowRel(kt, flowRelReq) + if err != nil { + logs.Errorf("list res flow rel failed, err: %v, resID: %s, resType: %s, rid: %s", err, resID, resType, kt.Rid) + return nil, err + } + if len(flowRelRet.Details) > 0 { + return &corelb.BaseResFlowLock{ + ResID: resID, + ResType: resType, + Owner: flowRelRet.Details[0].FlowID, + }, + errf.Newf(errf.LoadBalancerTaskExecuting, "%s of resID: %s is processing", resType, resID) + } + + return nil, nil +} diff --git a/cmd/cloud-server/service/load-balancer/async_target_group_modify_port.go b/cmd/cloud-server/service/load-balancer/async_target_group_modify_port.go new file mode 100644 index 0000000000..01ea499590 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/async_target_group_modify_port.go @@ -0,0 +1,253 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "fmt" + + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + actionflow "hcm/cmd/task-server/logics/flow" + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/slice" +) + +// BatchModifyBizTargetsPort batch modify biz targets port. +func (svc *lbSvc) BatchModifyBizTargetsPort(cts *rest.Contexts) (any, error) { + return svc.batchModifyTargetPort(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) batchModifyTargetPort(cts *rest.Contexts, + authHandler handler.ValidWithAuthHandler) (any, error) { + + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch modify target port request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.TargetGroupCloudResType, tgID) + if err != nil { + logs.Errorf("get target group resource info failed, id: %s, err: %s, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // authorized instances + err = authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Update, BasicInfo: baseInfo}) + if err != nil { + logs.Errorf("batch modify target port auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch baseInfo.Vendor { + case enumor.TCloud: + return svc.buildModifyTCloudTargetPort(cts.Kit, req.Data, tgID, baseInfo.AccountID) + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } +} + +func (svc *lbSvc) buildModifyTCloudTargetPort(kt *kit.Kit, body json.RawMessage, + tgID, accountID string) (interface{}, error) { + + req := new(cslb.TCloudBatchModifyTargetPortReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, kt.Rid) + return nil, err + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + if err = svc.batchUpdateTargetPortDb(kt, req); err != nil { + return nil, err + } + return &core.FlowStateResult{State: enumor.FlowSuccess}, nil + } + + return svc.buildModifyTCloudTargetTasksPort(kt, req, ruleRelList.Details[0].LbID, tgID, accountID) +} + +func (svc *lbSvc) batchUpdateTargetPortDb(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetPortReq) error { + tgReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", req.TargetIDs), + Page: core.NewDefaultBasePage(), + } + rsList, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(rsList.Details) == 0 { + return errf.Newf(errf.RecordNotFound, "target_ids: %v is not found", req.TargetIDs) + } + + instExistsMap := make(map[string]struct{}, 0) + updateReq := &dataproto.TargetBatchUpdateReq{Targets: []*dataproto.TargetUpdate{}} + for _, item := range rsList.Details { + // 批量修改端口时,需要校验重复的实例ID的问题,否则云端接口也会报错 + if _, ok := instExistsMap[item.CloudInstID]; ok { + return errf.Newf(errf.RecordDuplicated, "duplicate modify same inst(%s) to new_port", item.CloudInstID) + } + + instExistsMap[item.CloudInstID] = struct{}{} + updateReq.Targets = append(updateReq.Targets, &dataproto.TargetUpdate{ + ID: item.ID, + Port: req.NewPort, + }) + } + + return svc.client.DataService().Global.LoadBalancer.BatchUpdateTarget(kt, updateReq) +} + +func (svc *lbSvc) buildModifyTCloudTargetTasksPort(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetPortReq, lbID, tgID, + accountID string) (interface{}, error) { + + // 预检测 + _, err := svc.checkResFlowRel(kt, lbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + // 创建Flow跟Task的初始化数据 + flowID, err := svc.initFlowTargetPort(kt, req, lbID, tgID, accountID) + if err != nil { + return nil, err + } + + // 锁定资源跟Flow的状态 + err = svc.lockResFlowStatus(kt, lbID, enumor.LoadBalancerCloudResType, flowID, enumor.ModifyPortTaskType) + if err != nil { + return nil, err + } + + return &core.FlowStateResult{FlowID: flowID}, nil +} + +func (svc *lbSvc) initFlowTargetPort(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetPortReq, + lbID, tgID, accountID string) (string, error) { + + tasks := make([]ts.CustomFlowTask, 0) + elems := slice.Split(req.TargetIDs, constant.BatchModifyTargetPortCloudMaxLimit) + getActionID := counter.NewNumStringCounter(1, 10) + var lastActionID action.ActIDType + for _, parts := range elems { + rsPortParams, err := svc.convTCloudOperateTargetReq(kt, parts, lbID, tgID, accountID, + cvt.ValToPtr(req.NewPort), nil) + if err != nil { + return "", err + } + actionID := action.ActIDType(getActionID()) + tmpTask := ts.CustomFlowTask{ + ActionID: actionID, + ActionName: enumor.ActionTargetGroupModifyPort, + Params: &actionlb.OperateRsOption{ + Vendor: enumor.TCloud, + TCloudBatchOperateTargetReq: *rsPortParams, + }, + Retry: &tableasync.Retry{ + Enable: true, + Policy: &tableasync.RetryPolicy{ + Count: constant.FlowRetryMaxLimit, + SleepRangeMS: [2]uint{100, 200}, + }, + }, + } + if len(lastActionID) > 0 { + tmpTask.DependOn = []action.ActIDType{lastActionID} + } + tasks = append(tasks, tmpTask) + lastActionID = actionID + } + portReq := &ts.AddCustomFlowReq{ + Name: enumor.FlowTargetGroupModifyPort, + ShareData: tableasync.NewShareData(map[string]string{ + "lb_id": lbID, + }), + Tasks: tasks, + IsInitState: true, + } + result, err := svc.client.TaskServer().CreateCustomFlow(kt, portReq) + if err != nil { + logs.Errorf("call taskserver to batch modify target port custom flow failed, err: %v, rid: %s", err, kt.Rid) + return "", err + } + + flowID := result.ID + // 从Flow,负责监听主Flow的状态 + flowWatchReq := &ts.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []ts.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowID, + ResID: lbID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: []string{tgID}, + SubResType: enumor.TargetGroupCloudResType, + TaskType: enumor.ModifyPortTaskType, + }, + }}, + } + _, err = svc.client.TaskServer().CreateTemplateFlow(kt, flowWatchReq) + if err != nil { + logs.Errorf("call taskserver to create res flow status watch task failed, err: %v, flowID: %s, rid: %s", + err, flowID, kt.Rid) + return "", err + } + return flowID, nil +} diff --git a/cmd/cloud-server/service/load-balancer/async_target_group_modify_weight.go b/cmd/cloud-server/service/load-balancer/async_target_group_modify_weight.go new file mode 100644 index 0000000000..75f23aad83 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/async_target_group_modify_weight.go @@ -0,0 +1,250 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "fmt" + + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + actionflow "hcm/cmd/task-server/logics/flow" + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/slice" +) + +// BatchModifyBizTargetsWeight batch modify biz targets weight. +func (svc *lbSvc) BatchModifyBizTargetsWeight(cts *rest.Contexts) (any, error) { + return svc.batchModifyTargetWeight(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) batchModifyTargetWeight(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) (any, error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch modify target weight request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.TargetGroupCloudResType, tgID) + if err != nil { + logs.Errorf("get target group resource info failed, id: %s, err: %s, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // authorized instances + err = authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Update, BasicInfo: baseInfo}) + if err != nil { + logs.Errorf("batch modify target weight auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch baseInfo.Vendor { + case enumor.TCloud: + return svc.buildModifyTCloudTargetWeight(cts.Kit, req.Data, tgID, baseInfo.AccountID) + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } +} + +func (svc *lbSvc) buildModifyTCloudTargetWeight(kt *kit.Kit, body json.RawMessage, + tgID, accountID string) (interface{}, error) { + + req := new(cslb.TCloudBatchModifyTargetWeightReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, kt.Rid) + return nil, err + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + err = svc.batchUpdateTargetWeightDb(kt, req) + if err != nil { + return nil, err + } + return &core.FlowStateResult{State: enumor.FlowSuccess}, nil + } + + return svc.buildModifyTCloudTargetTasksWeight(kt, req, ruleRelList.Details[0].LbID, tgID, accountID) +} + +func (svc *lbSvc) batchUpdateTargetWeightDb(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetWeightReq) error { + tgReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", req.TargetIDs), + Page: core.NewDefaultBasePage(), + } + rsList, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(rsList.Details) == 0 { + return errf.Newf(errf.RecordNotFound, "target_ids: %v is not found", req.TargetIDs) + } + + instExistsMap := make(map[string]struct{}, 0) + updateReq := &dataproto.TargetBatchUpdateReq{Targets: []*dataproto.TargetUpdate{}} + for _, item := range rsList.Details { + // 批量修改端口时,需要校验重复的实例ID的问题,否则云端接口也会报错 + if _, ok := instExistsMap[item.CloudInstID]; ok { + return errf.Newf(errf.RecordDuplicated, "duplicate modify same inst(%s) to new_port", item.CloudInstID) + } + + instExistsMap[item.CloudInstID] = struct{}{} + updateReq.Targets = append(updateReq.Targets, &dataproto.TargetUpdate{ + ID: item.ID, + Weight: req.NewWeight, + }) + } + + return svc.client.DataService().Global.LoadBalancer.BatchUpdateTarget(kt, updateReq) +} + +func (svc *lbSvc) buildModifyTCloudTargetTasksWeight(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetWeightReq, + lbID, tgID, accountID string) (interface{}, error) { + + // 预检测 + _, err := svc.checkResFlowRel(kt, lbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + // 创建Flow跟Task的初始化数据 + flowID, err := svc.initFlowTargetWeight(kt, req, lbID, tgID, accountID) + if err != nil { + return nil, err + } + + // 锁定资源跟Flow的状态 + err = svc.lockResFlowStatus(kt, lbID, enumor.LoadBalancerCloudResType, flowID, enumor.ModifyWeightTaskType) + if err != nil { + return nil, err + } + + return &core.FlowStateResult{FlowID: flowID}, nil +} + +func (svc *lbSvc) initFlowTargetWeight(kt *kit.Kit, req *cslb.TCloudBatchModifyTargetWeightReq, + lbID, tgID, accountID string) (string, error) { + + tasks := make([]ts.CustomFlowTask, 0) + elems := slice.Split(req.TargetIDs, constant.BatchModifyTargetWeightCloudMaxLimit) + getActionID := counter.NewNumStringCounter(1, 10) + var lastActionID action.ActIDType + for _, parts := range elems { + rsWeightParams, err := svc.convTCloudOperateTargetReq(kt, parts, lbID, tgID, accountID, nil, req.NewWeight) + if err != nil { + return "", err + } + actionID := action.ActIDType(getActionID()) + tmpTask := ts.CustomFlowTask{ + ActionID: actionID, + ActionName: enumor.ActionTargetGroupModifyWeight, + Params: &actionlb.OperateRsOption{ + Vendor: enumor.TCloud, + TCloudBatchOperateTargetReq: *rsWeightParams, + }, + Retry: &tableasync.Retry{ + Enable: true, + Policy: &tableasync.RetryPolicy{ + Count: constant.FlowRetryMaxLimit, + SleepRangeMS: [2]uint{100, 200}, + }, + }, + } + if len(lastActionID) > 0 { + tmpTask.DependOn = []action.ActIDType{lastActionID} + } + tasks = append(tasks, tmpTask) + lastActionID = actionID + } + rsWeightReq := &ts.AddCustomFlowReq{ + Name: enumor.FlowTargetGroupModifyWeight, + ShareData: tableasync.NewShareData(map[string]string{ + "lb_id": lbID, + }), + Tasks: tasks, + IsInitState: true, + } + result, err := svc.client.TaskServer().CreateCustomFlow(kt, rsWeightReq) + if err != nil { + logs.Errorf("call taskserver to batch modify target weight custom flow failed, err: %v, rid: %s", err, kt.Rid) + return "", err + } + + flowID := result.ID + // 从Flow,负责监听主Flow的状态 + flowWatchReq := &ts.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []ts.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowID, + ResID: lbID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: []string{tgID}, + SubResType: enumor.TargetGroupCloudResType, + TaskType: enumor.ModifyWeightTaskType, + }, + }}, + } + _, err = svc.client.TaskServer().CreateTemplateFlow(kt, flowWatchReq) + if err != nil { + logs.Errorf("call taskserver to create res flow status watch task failed, err: %v, flowID: %s, rid: %s", + err, flowID, kt.Rid) + return "", err + } + return flowID, nil +} diff --git a/cmd/cloud-server/service/load-balancer/async_target_group_remove_rs.go b/cmd/cloud-server/service/load-balancer/async_target_group_remove_rs.go new file mode 100644 index 0000000000..c2e3bf5dde --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/async_target_group_remove_rs.go @@ -0,0 +1,330 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "fmt" + + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + actionflow "hcm/cmd/task-server/logics/flow" + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + hcproto "hcm/pkg/api/hc-service/load-balancer" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/slice" +) + +// BatchRemoveBizTargets batch remove biz targets. +func (svc *lbSvc) BatchRemoveBizTargets(cts *rest.Contexts) (any, error) { + return svc.batchRemoveBizTarget(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) batchRemoveBizTarget(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) ( + any, error) { + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch remove target request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Update, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("batch remove target auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + accountInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch accountInfo.Vendor { + case enumor.TCloud: + return svc.buildRemoveTCloudTarget(cts.Kit, req.Data, accountInfo.AccountID) + default: + return nil, fmt.Errorf("vendor: %s not support", accountInfo.Vendor) + } +} + +func (svc *lbSvc) buildRemoveTCloudTarget(kt *kit.Kit, body json.RawMessage, accountID string) (interface{}, + error) { + + req := new(cslb.TCloudTargetBatchRemoveReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + targetIDs := make([]string, 0) + lbIDs := make([]string, 0) + targetGroupIDMap := make(map[string][]string, 0) + targetGroupRuleRelMap := make(map[string][]corelb.BaseTargetListenerRuleRel, 0) + for _, item := range req.TargetGroups { + if _, ok := targetGroupRuleRelMap[item.TargetGroupID]; !ok { + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", item.TargetGroupID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgItem: %+v, err: %v, rid: %s", item, err, kt.Rid) + return nil, err + } + targetGroupRuleRelMap[item.TargetGroupID] = ruleRelList.Details + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + ruleRelList := targetGroupRuleRelMap[item.TargetGroupID] + if len(ruleRelList) == 0 { + err := svc.batchDeleteTargetDb(kt, accountID, item.TargetGroupID, item.TargetIDs) + if err != nil { + return nil, err + } + targetIDs = append(targetIDs, item.TargetIDs...) + } else { + lbIDs = slice.Unique(slice.Map(ruleRelList, func(rel corelb.BaseTargetListenerRuleRel) string { + return rel.LbID + })) + targetGroupIDMap[item.TargetGroupID] = append(targetGroupIDMap[item.TargetGroupID], item.TargetIDs...) + } + } + + // 都是未绑定监听器的目标组,不需要云端操作 + if len(targetGroupIDMap) == 0 { + return &corelb.TargetOperateResult{TargetIDs: targetIDs}, nil + } + + // 目标组需要属于同一个负载均衡 + if len(lbIDs) > 1 { + return nil, errf.New(errf.InvalidParameter, "target group need belong to the same load balancer") + } + + return svc.buildRemoveTCloudTargetTasks(kt, accountID, lbIDs[0], targetGroupIDMap) +} + +func (svc *lbSvc) batchDeleteTargetDb(kt *kit.Kit, accountID, tgID string, targetIDs []string) error { + rsIDs := make([]string, 0) + tgReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", targetIDs), + Page: core.NewDefaultBasePage(), + } + rsList, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(rsList.Details) > 0 { + rsIDs = append(rsIDs, rsList.Details[0].ID) + } + + if len(rsIDs) == 0 { + return nil + } + + delReq := &dataproto.LoadBalancerBatchDeleteReq{ + Filter: tools.ExpressionAnd( + tools.RuleIn("id", rsIDs), + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("target_group_id", tgID), + ), + } + return svc.client.DataService().Global.LoadBalancer.BatchDeleteTarget(kt, delReq) +} + +func (svc *lbSvc) buildRemoveTCloudTargetTasks(kt *kit.Kit, accountID, lbID string, tgMap map[string][]string) ( + *core.FlowStateResult, error) { + + // 预检测 + _, err := svc.checkResFlowRel(kt, lbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + // 创建Flow跟Task的初始化数据 + flowID, err := svc.initFlowRemoveTargetByLbID(kt, accountID, lbID, tgMap) + if err != nil { + return nil, err + } + + // 锁定资源跟Flow的状态 + err = svc.lockResFlowStatus(kt, lbID, enumor.LoadBalancerCloudResType, flowID, enumor.RemoveRSTaskType) + if err != nil { + return nil, err + } + + return &core.FlowStateResult{FlowID: flowID}, nil +} + +func (svc *lbSvc) initFlowRemoveTargetByLbID(kt *kit.Kit, accountID string, lbID string, tgMap map[string][]string) ( + string, error) { + + tasks := make([]ts.CustomFlowTask, 0) + getActionID := counter.NewNumStringCounter(1, 10) + var tgIDs []string + var lastActionID action.ActIDType + for tgID, rsList := range tgMap { + tgIDs = append(tgIDs, tgID) + elems := slice.Split(rsList, constant.BatchRemoveRSCloudMaxLimit) + for _, parts := range elems { + removeRsParams, err := svc.convTCloudOperateTargetReq(kt, parts, lbID, tgID, accountID, nil, nil) + if err != nil { + return "", err + } + actionID := action.ActIDType(getActionID()) + tmpTask := ts.CustomFlowTask{ + ActionID: actionID, + ActionName: enumor.ActionTargetGroupRemoveRS, + Params: &actionlb.OperateRsOption{ + Vendor: enumor.TCloud, + TCloudBatchOperateTargetReq: *removeRsParams, + }, + Retry: &tableasync.Retry{ + Enable: true, + Policy: &tableasync.RetryPolicy{ + Count: constant.FlowRetryMaxLimit, + SleepRangeMS: [2]uint{100, 200}, + }, + }, + } + if len(lastActionID) > 0 { + tmpTask.DependOn = []action.ActIDType{lastActionID} + } + tasks = append(tasks, tmpTask) + lastActionID = actionID + } + } + removeReq := &ts.AddCustomFlowReq{ + Name: enumor.FlowTargetGroupRemoveRS, + ShareData: tableasync.NewShareData(map[string]string{ + "lb_id": lbID, + }), + Tasks: tasks, + IsInitState: true, + } + result, err := svc.client.TaskServer().CreateCustomFlow(kt, removeReq) + if err != nil { + logs.Errorf("call taskserver to batch remove rs custom flow failed, err: %v, rid: %s", err, kt.Rid) + return "", err + } + + flowID := result.ID + // 从Flow,负责监听主Flow的状态 + flowWatchReq := &ts.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []ts.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowID, + ResID: lbID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: tgIDs, + SubResType: enumor.TargetGroupCloudResType, + TaskType: enumor.RemoveRSTaskType, + }, + }}, + } + _, err = svc.client.TaskServer().CreateTemplateFlow(kt, flowWatchReq) + if err != nil { + logs.Errorf("call taskserver to create res flow status watch task failed, err: %v, flowID: %s, rid: %s", + err, flowID, kt.Rid) + return "", err + } + + return flowID, nil +} + +// convTCloudOperateTargetReq conv tcloud operate target req. +func (svc *lbSvc) convTCloudOperateTargetReq(kt *kit.Kit, targetIDs []string, lbID, targetGroupID, + accountID string, newPort, newWeight *int64) (*hcproto.TCloudBatchOperateTargetReq, error) { + + targetReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", targetIDs), + Page: core.NewDefaultBasePage(), + } + targetList, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, targetReq) + if err != nil { + logs.Errorf("failed to list target by id, targetIDs: %v, err: %v, rid: %s", targetIDs, err, kt.Rid) + return nil, err + } + if len(targetList.Details) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target_ids: %v is not found", targetIDs) + } + + instExistsMap := make(map[string]struct{}, 0) + rsReq := &hcproto.TCloudBatchOperateTargetReq{TargetGroupID: targetGroupID, LbID: lbID} + for _, item := range targetList.Details { + // 批量修改端口时,需要校验重复的实例ID的问题,否则云端接口也会报错 + if cvt.PtrToVal(newPort) > 0 { + if _, ok := instExistsMap[item.CloudInstID]; ok { + return nil, errf.Newf(errf.RecordDuplicated, "duplicate modify same inst(%s) to new_port: %d", + item.CloudInstID, cvt.PtrToVal(newPort)) + } + instExistsMap[item.CloudInstID] = struct{}{} + } + + rsReq.RsList = append(rsReq.RsList, &dataproto.TargetBaseReq{ + ID: item.ID, + InstType: item.InstType, + CloudInstID: item.CloudInstID, + Port: item.Port, + Weight: item.Weight, + AccountID: accountID, + TargetGroupID: targetGroupID, + InstName: item.InstName, + PrivateIPAddress: item.PrivateIPAddress, + PublicIPAddress: item.PublicIPAddress, + CloudVpcIDs: item.CloudVpcIDs, + Zone: item.Zone, + NewPort: newPort, + NewWeight: newWeight, + }) + } + return rsReq, nil +} diff --git a/cmd/cloud-server/service/load-balancer/create.go b/cmd/cloud-server/service/load-balancer/create.go new file mode 100644 index 0000000000..0bd4ee6ef7 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/create.go @@ -0,0 +1,430 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "errors" + "fmt" + + "hcm/cmd/cloud-server/service/common" + cloudserver "hcm/pkg/api/cloud-server" + "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + hcproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tabletype "hcm/pkg/dal/table/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" +) + +// BatchCreateLB 批量创建负载均衡 +func (svc *lbSvc) BatchCreateLB(cts *rest.Contexts) (any, error) { + return svc.batchCreateLB(cts, handler.ResOperateAuth, constant.UnassignedBiz) +} + +// BizBatchCreateLB 业务下直接创建 负载均衡,TODO: 用申请流程替换 +func (svc *lbSvc) BizBatchCreateLB(cts *rest.Contexts) (any, error) { + bizID, err := cts.PathParameter("bk_biz_id").Int64() + if err != nil { + return nil, err + } + return svc.batchCreateLB(cts, handler.BizOperateAuth, bizID) +} +func (svc *lbSvc) batchCreateLB(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler, bkBizID int64) ( + any, error) { + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("create clb request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // 权限校验 + err := validHandler(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Create, + BasicInfo: common.GetCloudResourceBasicInfo(req.AccountID, bkBizID), + }) + if err != nil { + logs.Errorf("create load balancer auth failed, err: %v, account id: %s, bk_biz_id: %d, rid: %s", + err, req.AccountID, bkBizID, cts.Kit.Rid) + return nil, err + } + + accountInfo, err := svc.client.DataService().Global.Cloud. + GetResBasicInfo(cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch accountInfo.Vendor { + case enumor.TCloud: + return svc.batchCreateTCloudLB(cts.Kit, req.Data, bkBizID) + default: + return nil, fmt.Errorf("vendor: %s not support", accountInfo.Vendor) + } +} + +func (svc *lbSvc) batchCreateTCloudLB(kt *kit.Kit, rawReq json.RawMessage, bkBizID int64) (any, error) { + req := new(cslb.TCloudBatchCreateReq) + if err := json.Unmarshal(rawReq, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + hcReq := &hcproto.TCloudBatchCreateReq{ + BkBizID: bkBizID, + AccountID: req.AccountID, + Region: req.Region, + Name: req.Name, + LoadBalancerType: req.LoadBalancerType, + AddressIPVersion: req.AddressIPVersion, + Zones: req.Zones, + BackupZones: req.BackupZones, + CloudVpcID: req.CloudVpcID, + CloudSubnetID: req.CloudSubnetID, + Vip: req.Vip, + CloudEipID: req.CloudEipID, + VipIsp: req.VipIsp, + InternetChargeType: req.InternetChargeType, + InternetMaxBandwidthOut: req.InternetMaxBandwidthOut, + BandwidthPackageID: req.BandwidthPackageID, + SlaType: req.SlaType, + AutoRenew: req.AutoRenew, + RequireCount: req.RequireCount, + Memo: req.Memo, + } + return svc.client.HCService().TCloud.Clb.BatchCreate(kt, hcReq) +} + +// CreateBizTargetGroup create biz target group. +func (svc *lbSvc) CreateBizTargetGroup(cts *rest.Contexts) (any, error) { + bkBizID, err := cts.PathParameter("bk_biz_id").Int64() + if err != nil { + return nil, err + } + return svc.createBizTargetGroup(cts, handler.BizOperateAuth, bkBizID) +} + +func (svc *lbSvc) createBizTargetGroup(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler, + bkBizID int64) (any, error) { + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("create target group request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if len(req.AccountID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "account_id is required") + } + + // authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Create, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("create target group auth failed, err: %v, account id: %s, rid: %s", + err, req.AccountID, cts.Kit.Rid) + return nil, err + } + + accountInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch accountInfo.Vendor { + case enumor.TCloud: + return svc.batchCreateTCloudTargetGroup(cts.Kit, req.Data, bkBizID) + default: + return nil, fmt.Errorf("vendor: %s not support", accountInfo.Vendor) + } +} + +func (svc *lbSvc) batchCreateTCloudTargetGroup(kt *kit.Kit, rawReq json.RawMessage, bkBizID int64) (any, error) { + req := new(cslb.TargetGroupCreateReq) + if err := json.Unmarshal(rawReq, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + if cvt.PtrToVal(req.HealthCheck.HealthSwitch) == 0 { + req.HealthCheck.HealthSwitch = cvt.ValToPtr(int64(0)) + } + healthJson, err := json.Marshal(req.HealthCheck) + if err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + opt := &dataproto.TCloudTargetGroupCreateReq{ + TargetGroups: []dataproto.TargetGroupBatchCreate[corelb.TCloudTargetGroupExtension]{ + { + Name: req.Name, + Vendor: enumor.TCloud, + AccountID: req.AccountID, + BkBizID: bkBizID, + Region: req.Region, + Protocol: req.Protocol, + Port: req.Port, + VpcID: "", + CloudVpcID: req.CloudVpcID, + TargetGroupType: enumor.LocalTargetGroupType, + Weight: 0, + HealthCheck: tabletype.JsonField(healthJson), + Memo: nil, + Extension: nil, + RsList: req.RsList, + }, + }, + } + return svc.client.DataService().TCloud.LoadBalancer.BatchCreateTCloudTargetGroup(kt, opt) +} + +// CreateBizListener create biz listener. +func (svc *lbSvc) CreateBizListener(cts *rest.Contexts) (any, error) { + bkBizID, err := cts.PathParameter("bk_biz_id").Int64() + if err != nil { + return nil, err + } + return svc.createListener(cts, handler.BizOperateAuth, bkBizID) +} + +func (svc *lbSvc) createListener(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler, + bkBizID int64) (any, error) { + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("create listener request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if len(req.AccountID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "account_id is required") + } + + lbID := cts.PathParameter("lb_id").String() + if len(lbID) == 0 { + return nil, errf.New(errf.InvalidParameter, "lb_id is required") + } + + // authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Listener, + Action: meta.Create, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("create listener auth failed, err: %v, account id: %s, rid: %s", err, req.AccountID, cts.Kit.Rid) + return nil, err + } + + accountInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch accountInfo.Vendor { + case enumor.TCloud: + return svc.batchCreateTCloudListener(cts.Kit, req.Data, bkBizID, lbID) + default: + return nil, fmt.Errorf("vendor: %s not support", accountInfo.Vendor) + } +} + +func (svc *lbSvc) batchCreateTCloudListener(kt *kit.Kit, rawReq json.RawMessage, bkBizID int64, + lbID string) (any, error) { + + req := new(hcproto.ListenerWithRuleCreateReq) + if err := json.Unmarshal(rawReq, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 预检测-是否有执行中的负载均衡 + _, err := svc.checkResFlowRel(kt, lbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + // 预检测-检查四层监听器,绑定的目标组里面的RS,是否已绑定其他监听器 + err = svc.checkLayerFourGlobalUniqueTarget(kt, req) + if err != nil { + return nil, err + } + + req.BkBizID = bkBizID + req.LbID = lbID + createResp, err := svc.client.HCService().TCloud.Clb.CreateListener(kt, req) + if err != nil { + logs.Errorf("fail to create tcloud url rule, err: %v, req: %+v, cert: %+v, rid: %s", + err, req, cvt.PtrToVal(req.Certificate), kt.Rid) + return nil, err + } + + if len(createResp.CloudLblID) == 0 { + logs.Errorf("no listener have been created, lbID: %s, req: %+v, rid: %s", lbID, req, kt.Rid) + return nil, errors.New("create listener failed") + } + + // 构建异步任务将目标组中的RS绑定到对应规则上 + lblInfo := &corelb.BaseListener{CloudID: createResp.CloudLblID, Protocol: req.Protocol, LbID: req.LbID} + err = svc.applyTargetToRule(kt, req.TargetGroupID, createResp.CloudRuleID, lblInfo) + if err != nil { + logs.Errorf("fail to bind listener and target group register flow, err: %v, req: %+v, createResp: %+v, rid: %s", + err, req, createResp, kt.Rid) + return nil, err + } + return &core.BatchCreateResult{IDs: []string{createResp.CloudLblID}}, nil +} + +// checkLayerFourGlobalUniqueTarget 检查四层监听器,绑定的目标组里面的RS,是否已绑定其他监听器 +func (svc *lbSvc) checkLayerFourGlobalUniqueTarget(kt *kit.Kit, req *hcproto.ListenerWithRuleCreateReq) error { + if req.Protocol.IsLayer7Protocol() { + return nil + } + + // 检查要绑定的目标组中是否有rs + listRsReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", req.TargetGroupID), + Page: core.NewDefaultBasePage(), + } + rsResp, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, listRsReq) + if err != nil { + logs.Errorf("fail to list target by target group id, err: %v, req: %+v, rid: %s", err, req, kt.Rid) + return err + } + if len(rsResp.Details) == 0 { + return nil + } + + // 查找该负载均衡下的4层监听器,绑定的所有目标组ID + targetGroupIDs, err := svc.getBindTargetGroupIDsByLbID(kt, req) + if err != nil { + return err + } + if len(targetGroupIDs) == 0 { + return nil + } + + // 查找关联表中所有目标组的rs + listRelRsReq := &core.ListReq{ + Filter: tools.ContainersExpression("target_group_id", targetGroupIDs), + Page: core.NewDefaultBasePage(), + } + relRsResp, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, listRelRsReq) + if err != nil { + logs.Errorf("fail to list target by target group ids, err: %v, tgIDs: %v, rid: %s", err, targetGroupIDs, kt.Rid) + return err + } + + existRsMap := make(map[string]struct{}) + for _, tgItem := range relRsResp.Details { + uniqueKey := fmt.Sprintf("%s:%d", tgItem.CloudInstID, tgItem.Port) + if _, exist := existRsMap[uniqueKey]; exist { + return errf.Newf(errf.RecordDuplicated, "(vip+protocol+rsip+rsport) should be globally unique for fourth "+ + "layer listeners, targetGroupID: %s, CloudInstID: %s, PrivateIPAddress: %v, Port: %d has bind listener", + req.TargetGroupID, tgItem.CloudInstID, tgItem.PrivateIPAddress, tgItem.Port) + } + existRsMap[uniqueKey] = struct{}{} + } + + return nil +} + +// getBindTargetGroupIDsByLbID 查找该负载均衡下的4层监听器,绑定的所有目标组ID +func (svc *lbSvc) getBindTargetGroupIDsByLbID(kt *kit.Kit, req *hcproto.ListenerWithRuleCreateReq) ([]string, error) { + listTGReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("lb_id", req.LbID), + tools.RuleEqual("binding_status", enumor.SuccessBindingStatus), + tools.RuleEqual("listener_rule_type", enumor.Layer4RuleType), + ), + Page: core.NewDefaultBasePage(), + } + tgResp, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, listTGReq) + if err != nil { + logs.Errorf("fail to list listener rule rel by lbid, err: %v, lbID: %s, rid: %s", err, req.LbID, kt.Rid) + return nil, err + } + if len(tgResp.Details) == 0 { + return nil, nil + } + + lblIDs := make([]string, len(tgResp.Details)) + lblTGMap := make(map[string][]string) + for _, item := range tgResp.Details { + lblIDs = append(lblIDs, item.LblID) + lblTGMap[item.LblID] = append(lblTGMap[item.LblID], item.TargetGroupID) + } + // 查找对应Protocol的监听器列表 + lblReq := &core.ListReq{ + Filter: tools.ExpressionAnd(tools.RuleIn("id", lblIDs), tools.RuleEqual("protocol", req.Protocol)), + Page: core.NewDefaultBasePage(), + } + lblResp, err := svc.client.DataService().Global.LoadBalancer.ListListener(kt, lblReq) + if err != nil { + logs.Errorf("fail to list listener by lblids, err: %v, lblIDs: %v, rid: %s", err, lblIDs, kt.Rid) + return nil, err + } + if len(lblResp.Details) == 0 { + return nil, nil + } + targetGroupIDs := make([]string, 0) + for _, item := range lblResp.Details { + tmpTGIDs, ok := lblTGMap[item.ID] + if !ok { + continue + } + targetGroupIDs = append(targetGroupIDs, tmpTGIDs...) + } + // 加入即将关联的目标组 + targetGroupIDs = append(targetGroupIDs, req.TargetGroupID) + + return targetGroupIDs, nil +} diff --git a/cmd/cloud-server/service/load-balancer/delete.go b/cmd/cloud-server/service/load-balancer/delete.go new file mode 100644 index 0000000000..80fd76b3c1 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/delete.go @@ -0,0 +1,305 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package loadbalancer ... +package loadbalancer + +import ( + "errors" + "fmt" + + "hcm/cmd/cloud-server/logics/async" + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + hcproto "hcm/pkg/api/hc-service/load-balancer" + ts "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" +) + +// DeleteBizTargetGroup delete biz target group. +func (svc *lbSvc) DeleteBizTargetGroup(cts *rest.Contexts) (interface{}, error) { + return svc.deleteTargetGroup(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) deleteTargetGroup(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + req := new(core.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.TargetGroupCloudResType, + IDs: req.IDs, + Fields: types.CommonBasicInfoFields, + } + basicInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, basicInfoReq) + if err != nil { + logs.Errorf("list target group basic info failed, req: %+v, err: %v, rid: %s", basicInfoReq, err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Delete, BasicInfos: basicInfoMap}) + if err != nil { + return nil, err + } + + if err = svc.audit.ResDeleteAudit(cts.Kit, enumor.TargetGroupAuditResType, basicInfoReq.IDs); err != nil { + logs.Errorf("create operation audit target group failed, ids: %v, err: %v, rid: %s", + basicInfoReq.IDs, err, cts.Kit.Rid) + return nil, err + } + + // delete tcloud cloud target group + err = svc.client.DataService().Global.LoadBalancer.DeleteTargetGroup(cts.Kit, &core.ListReq{ + Filter: tools.ContainersExpression("id", req.IDs), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("[%s] request dataservice to delete target group failed, ids: %s, err: %v, rid: %s", + enumor.TCloud, req.IDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// DeleteBizListener delete biz listener. +func (svc *lbSvc) DeleteBizListener(cts *rest.Contexts) (interface{}, error) { + return svc.deleteListener(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) deleteListener(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + req := new(core.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.ListenerCloudResType, + IDs: req.IDs, + Fields: types.CommonBasicInfoFields, + } + basicInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, basicInfoReq) + if err != nil { + logs.Errorf("list listener basic info failed, req: %+v, err: %v, rid: %s", basicInfoReq, err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Listener, + Action: meta.Delete, BasicInfos: basicInfoMap}) + if err != nil { + return nil, err + } + + if err = svc.audit.ResDeleteAudit(cts.Kit, enumor.ListenerAuditResType, basicInfoReq.IDs); err != nil { + logs.Errorf("create operation audit listener failed, ids: %v, err: %v, rid: %s", + basicInfoReq.IDs, err, cts.Kit.Rid) + return nil, err + } + + // delete tcloud cloud listener + err = svc.client.HCService().TCloud.Clb.DeleteListener(cts.Kit, req) + if err != nil { + logs.Errorf("[%s] request hcservice to delete listener failed, ids: %s, err: %v, rid: %s", + enumor.TCloud, req.IDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchDeleteLoadBalancer 批量删除负载均衡 +func (svc *lbSvc) BatchDeleteLoadBalancer(cts *rest.Contexts) (any, error) { + return svc.batchDeleteLoadBalancer(cts, handler.ResOperateAuth) +} + +// BatchDeleteBizLoadBalancer 业务下批量删除负载均衡 +func (svc *lbSvc) BatchDeleteBizLoadBalancer(cts *rest.Contexts) (any, error) { + return svc.batchDeleteLoadBalancer(cts, handler.BizOperateAuth) +} + +// batchDeleteLoadBalancer 批量删除负载均衡 +func (svc *lbSvc) batchDeleteLoadBalancer(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) (any, error) { + + req := new(core.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + infoReq := dataproto.ListResourceBasicInfoReq{ + ResourceType: enumor.LoadBalancerCloudResType, + IDs: req.IDs, + Fields: append(types.CommonBasicInfoFields, "region"), + } + lbInfoMap, err := svc.client.DataService().Global.Cloud.ListResBasicInfo(cts.Kit, infoReq) + if err != nil { + return nil, err + } + for _, lbID := range req.IDs { + info, exist := lbInfoMap[lbID] + if !exist { + return nil, fmt.Errorf("load balancer(%s) not found", lbID) + } + if info.Vendor != enumor.TCloud { + return nil, errors.New("only supports tcloud") + } + + } + + // 业务校验、鉴权 + err = validHandler(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Delete, + BasicInfos: lbInfoMap, + }) + if err != nil { + return nil, err + } + + if err = svc.loadBalancerDeleteCheck(cts.Kit, req.IDs); err != nil { + return nil, err + } + // 按规则删除审计 + err = svc.audit.ResDeleteAudit(cts.Kit, enumor.LoadBalancerAuditResType, req.IDs) + if err != nil { + logs.Errorf("create load balancer delete audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + // 按账号+地域分列表 + tasks := buildTCloudLBDeletionTasks(lbInfoMap) + flowReq := &ts.AddCustomFlowReq{ + Name: enumor.FlowDeleteLoadBalancer, + ShareData: nil, + Tasks: tasks, + IsInitState: false, + } + flowResp, err := svc.client.TaskServer().CreateCustomFlow(cts.Kit, flowReq) + if err != nil { + return nil, err + } + return nil, async.WaitTaskToEnd(cts.Kit, svc.client.TaskServer(), flowResp.ID) +} + +// 负载均衡删除检查 +func (svc *lbSvc) loadBalancerDeleteCheck(kt *kit.Kit, lbIDs []string) error { + // 检查是否启用删除检查 + lbReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", lbIDs), + Page: core.NewDefaultBasePage(), + } + lbResp, err := svc.client.DataService().TCloud.LoadBalancer.ListLoadBalancer(kt, lbReq) + if err != nil { + logs.Errorf("fail to query load balancer for delete load balancers, err: %v, lb ids: %v, rid: %s", + err, lbIDs, kt.Rid) + return nil + } + for _, lb := range lbResp.Details { + if cvt.PtrToVal(lb.Extension.DeleteProtect) { + return fmt.Errorf("%s(%s) is protected for delection", lb.Name, lb.CloudID) + } + } + + // 检查是否存在监听器 + lblListReq := &core.ListReq{ + Filter: tools.ContainersExpression("lb_id", lbIDs), + Page: &core.BasePage{Count: false, Start: 0, Limit: 1}, + } + listenerResp, err := svc.client.DataService().Global.LoadBalancer.ListListener(kt, lblListReq) + if err != nil { + logs.Errorf("fail to query listener for delete load balancers, err: %v, lb ids: %v, rid: %s", + err, lbIDs, kt.Rid) + return nil + } + if len(listenerResp.Details) != 0 { + lbl := listenerResp.Details[0] + return fmt.Errorf("load balancer(%s) with listener(%s:%s) can not be deleted", + lbl.CloudLbID, lbl.CloudID, lbl.Name) + } + return nil +} + +func buildTCloudLBDeletionTasks(infoMap map[string]types.CloudResourceBasicInfo) (tasks []ts.CustomFlowTask) { + + tcloudReqMap := make(map[string]*hcproto.TCloudBatchDeleteLoadbalancerReq, len(infoMap)) + // TODO: 后期支持多vendor + for id, info := range infoMap { + key := genAccountRegionKey(info) + if tcloudReqMap[key] == nil { + tcloudReqMap[key] = &hcproto.TCloudBatchDeleteLoadbalancerReq{ + AccountID: info.AccountID, + Region: info.Region, + IDs: []string{}, + } + + } + req := tcloudReqMap[key] + req.IDs = append(req.IDs, id) + } + getNextID := counter.NewNumStringCounter(1, 10) + for _, req := range tcloudReqMap { + tasks = append(tasks, ts.CustomFlowTask{ + ActionID: action.ActIDType(getNextID()), + ActionName: enumor.ActionDeleteLoadBalancer, + Params: actionlb.DeleteLoadBalancerOption{ + Vendor: enumor.TCloud, + TCloudBatchDeleteLoadbalancerReq: cvt.PtrToVal(req), + }, + Retry: tableasync.NewRetryWithPolicy(3, 1000, 5000), + }) + + } + return tasks +} + +func genAccountRegionKey(info types.CloudResourceBasicInfo) string { + return info.AccountID + "_" + info.Region +} diff --git a/cmd/cloud-server/service/load-balancer/inquiry_price.go b/cmd/cloud-server/service/load-balancer/inquiry_price.go new file mode 100644 index 0000000000..88826e4edc --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/inquiry_price.go @@ -0,0 +1,89 @@ +package loadbalancer + +import ( + "encoding/json" + "fmt" + + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + hcproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// InquiryPriceLoadBalancer inquiry price load balancer. +func (svc *lbSvc) InquiryPriceLoadBalancer(cts *rest.Contexts) (any, error) { + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("inquiry price load balancer request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + authRes := meta.ResourceAttribute{Basic: &meta.Basic{ + Type: meta.LoadBalancer, Action: meta.Create, ResourceID: req.AccountID}} + if err := svc.authorizer.AuthorizeWithPerm(cts.Kit, authRes); err != nil { + logs.Errorf("inquiry price load balancer auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + info, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch info.Vendor { + case enumor.TCloud: + return svc.inquiryPriceTCloudLoadBalancer(cts.Kit, req.Data) + default: + return nil, fmt.Errorf("vendor: %s not support", info.Vendor) + } +} + +func (svc *lbSvc) inquiryPriceTCloudLoadBalancer(kt *kit.Kit, body json.RawMessage) (any, error) { + req := new(cslb.TCloudBatchCreateReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + hcReq := &hcproto.TCloudBatchCreateReq{ + BkBizID: constant.UnassignedBiz, + AccountID: req.AccountID, + Region: req.Region, + Name: req.Name, + LoadBalancerType: req.LoadBalancerType, + AddressIPVersion: req.AddressIPVersion, + Zones: req.Zones, + BackupZones: req.BackupZones, + CloudVpcID: req.CloudVpcID, + CloudSubnetID: req.CloudSubnetID, + Vip: req.Vip, + CloudEipID: req.CloudEipID, + VipIsp: req.VipIsp, + InternetChargeType: req.InternetChargeType, + InternetMaxBandwidthOut: req.InternetMaxBandwidthOut, + BandwidthPackageID: req.BandwidthPackageID, + SlaType: req.SlaType, + AutoRenew: req.AutoRenew, + RequireCount: req.RequireCount, + Memo: req.Memo, + } + + result, err := svc.client.HCService().TCloud.Clb.InquiryPrice(kt, hcReq) + if err != nil { + logs.Errorf("inquiry price tcloud load balancer failed, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + return result, nil +} diff --git a/cmd/cloud-server/service/load-balancer/listener.go b/cmd/cloud-server/service/load-balancer/listener.go new file mode 100644 index 0000000000..67fef8f546 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/listener.go @@ -0,0 +1,427 @@ +package loadbalancer + +import ( + "fmt" + + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/runtime/filter" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" +) + +// ListListener list listener. +func (svc *lbSvc) ListListener(cts *rest.Contexts) (interface{}, error) { + return svc.listListener(cts, handler.ListResourceAuthRes) +} + +// ListBizListener list biz listener. +func (svc *lbSvc) ListBizListener(cts *rest.Contexts) (interface{}, error) { + return svc.listListener(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) listListener(cts *rest.Contexts, authHandler handler.ListAuthResHandler) (interface{}, error) { + lbID := cts.PathParameter("lb_id").String() + if len(lbID) == 0 { + return nil, errf.New(errf.InvalidParameter, "lb_id is required") + } + + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + filterWithLb, err := tools.And(tools.RuleEqual("lb_id", lbID), req.Filter) + if err != nil { + logs.Errorf("fail to merge loadbalancer id rule into request filter, err: %v, req.Filter: %+v, rid: %s", + err, req.Filter, cts.Kit.Rid) + return nil, err + } + // list authorized instances + expr, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, Action: meta.Find, Filter: filterWithLb}) + if err != nil { + logs.Errorf("list listener auth failed, lbID: %s, noPermFlag: %v, err: %v, rid: %s", + lbID, noPermFlag, err, cts.Kit.Rid) + return nil, err + } + + resList := &cslb.ListListenerResult{Count: 0, Details: make([]cslb.ListListenerBase, 0)} + if noPermFlag { + logs.Errorf("list listener no perm auth, lbID: %s, noPermFlag: %v, rid: %s", lbID, noPermFlag, cts.Kit.Rid) + return resList, nil + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.LoadBalancerCloudResType, lbID) + if err != nil { + logs.Errorf("fail to get load balancer basic info, lbID: %s, err: %v, rid: %s", lbID, err, cts.Kit.Rid) + return nil, err + } + + switch basicInfo.Vendor { + case enumor.TCloud: + urlRuleMap, targetWeightMap, lblTargetGroupMap, err := svc.getTCloudUrlRuleAndTargetGroupMap( + cts.Kit, expr, req, lbID, resList) + if err != nil { + return nil, err + } + + for idx, lblItem := range resList.Details { + tmpTargetGroupID := lblTargetGroupMap[lblItem.ID].TargetGroupID + resList.Details[idx].TargetGroupID = tmpTargetGroupID + resList.Details[idx].Scheduler = urlRuleMap[lblItem.ID].Scheduler + if lblItem.Protocol.IsLayer7Protocol() { + resList.Details[idx].DomainNum = urlRuleMap[lblItem.ID].DomainNum + resList.Details[idx].UrlNum = urlRuleMap[lblItem.ID].UrlNum + } + if len(tmpTargetGroupID) > 0 { + resList.Details[idx].RsWeightNonZeroNum = targetWeightMap[tmpTargetGroupID].RsWeightNonZeroNum + resList.Details[idx].RsWeightZeroNum = targetWeightMap[tmpTargetGroupID].RsWeightZeroNum + } + resList.Details[idx].BindingStatus = lblTargetGroupMap[lblItem.ID].BindingStatus + } + + return resList, nil + default: + return nil, errf.Newf(errf.InvalidParameter, "lbID: %s vendor: %s not support", lbID, basicInfo.Vendor) + } +} + +func (svc *lbSvc) getTCloudUrlRuleAndTargetGroupMap(kt *kit.Kit, expr *filter.Expression, req *core.ListReq, + lbID string, resList *cslb.ListListenerResult) (map[string]cslb.ListListenerBase, + map[string]cslb.ListListenerBase, map[string]cslb.ListListenerBase, error) { + + listenerReq := &core.ListReq{ + Filter: expr, + Page: req.Page, + } + listenerList, err := svc.client.DataService().Global.LoadBalancer.ListListener(kt, listenerReq) + if err != nil { + logs.Errorf("list listener failed, lbID: %s, err: %v, rid: %s", lbID, err, kt.Rid) + return nil, nil, nil, err + } + if req.Page.Count || len(listenerList.Details) == 0 { + resList.Count = listenerList.Count + return nil, nil, nil, nil + } + + resList.Count = listenerList.Count + lblIDs := make([]string, 0) + for _, listenerItem := range listenerList.Details { + lblIDs = append(lblIDs, listenerItem.ID) + resList.Details = append(resList.Details, cslb.ListListenerBase{ + BaseListener: listenerItem, + }) + } + + urlRuleMap, err := svc.listTCloudLbUrlRuleMap(kt, lbID, lblIDs) + if err != nil { + return nil, nil, nil, err + } + + // 根据lbID、lblID获取绑定的目标组ID列表 + ruleRelReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("lb_id", lbID), + tools.RuleIn("lbl_id", lblIDs), + ), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list target group listener rule rel failed, lbID: %s, lblIDs: %v, err: %v, rid: %s", + lbID, lblIDs, err, kt.Rid) + return nil, nil, nil, err + } + // 没有对应的目标组、监听器关联关系记录 + if len(ruleRelList.Details) == 0 { + return nil, nil, nil, nil + } + + targetGroupIDs := make([]string, 0) + lblTargetGroupMap := make(map[string]cslb.ListListenerBase, 0) + for _, item := range ruleRelList.Details { + targetGroupIDs = append(targetGroupIDs, item.TargetGroupID) + lblTargetGroupMap[item.LblID] = cslb.ListListenerBase{ + TargetGroupID: item.TargetGroupID, + BindingStatus: item.BindingStatus, + } + } + + // TODO 后面拆成独立接口,让前端异步调用 + targetWeightMap, err := svc.listTargetWeightNumMap(kt, targetGroupIDs) + if err != nil { + return nil, nil, nil, err + } + + return urlRuleMap, targetWeightMap, lblTargetGroupMap, nil +} + +func (svc *lbSvc) listTCloudLbUrlRuleMap(kt *kit.Kit, lbID string, lblIDs []string) ( + map[string]cslb.ListListenerBase, error) { + + urlRuleReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("lb_id", lbID), + tools.RuleIn("lbl_id", lblIDs), + ), + Page: core.NewDefaultBasePage(), + } + urlRuleList, err := svc.client.DataService().TCloud.LoadBalancer.ListUrlRule(kt, urlRuleReq) + if err != nil { + logs.Errorf("list tcloud url rule failed, lbID: %s, lblIDs: %v, err: %v, rid: %s", lbID, lblIDs, err, kt.Rid) + return nil, err + } + + listenerRuleMap := make(map[string]cslb.ListListenerBase, 0) + domainsExist := make(map[string]struct{}, 0) + for _, ruleItem := range urlRuleList.Details { + if _, ok := listenerRuleMap[ruleItem.LblID]; !ok { + listenerRuleMap[ruleItem.LblID] = cslb.ListListenerBase{ + TargetGroupID: ruleItem.TargetGroupID, + Scheduler: ruleItem.Scheduler, + SessionType: ruleItem.SessionType, + SessionExpire: ruleItem.SessionExpire, + HealthCheck: ruleItem.HealthCheck, + Certificate: ruleItem.Certificate, + } + } + + tmpListener := listenerRuleMap[ruleItem.LblID] + // 计算监听器下的域名数量 + calcDomainNumByListener(&tmpListener, ruleItem, domainsExist) + if len(ruleItem.URL) > 0 { + tmpListener.UrlNum++ + } + + listenerRuleMap[ruleItem.LblID] = tmpListener + } + + return listenerRuleMap, nil +} + +// 计算监听器下的域名数量 +func calcDomainNumByListener(tmpListener *cslb.ListListenerBase, ruleItem corelb.TCloudLbUrlRule, + domainsExist map[string]struct{}) { + + domainUnique := fmt.Sprintf("%s-%s", ruleItem.LblID, ruleItem.Domain) + if _, ok := domainsExist[domainUnique]; !ok && len(ruleItem.Domain) > 0 { + tmpListener.DomainNum++ + domainsExist[domainUnique] = struct{}{} + } + return +} + +func (svc *lbSvc) listListenerMap(kt *kit.Kit, lblIDs []string) (map[string]corelb.BaseListener, error) { + if len(lblIDs) == 0 { + return nil, nil + } + + lblReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", lblIDs), + Page: core.NewDefaultBasePage(), + } + lblList, err := svc.client.DataService().Global.LoadBalancer.ListListener(kt, lblReq) + if err != nil { + logs.Errorf("[clb] list clb listener failed, lblIDs: %v, err: %v, rid: %s", lblIDs, err, kt.Rid) + return nil, err + } + + lblMap := make(map[string]corelb.BaseListener, len(lblList.Details)) + for _, clbItem := range lblList.Details { + lblMap[clbItem.ID] = clbItem + } + + return lblMap, nil +} + +// GetListener get clb listener. +func (svc *lbSvc) GetListener(cts *rest.Contexts) (interface{}, error) { + return svc.getListener(cts, handler.ListResourceAuthRes) +} + +// GetBizListener get biz clb listener. +func (svc *lbSvc) GetBizListener(cts *rest.Contexts) (interface{}, error) { + return svc.getListener(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) getListener(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, id) + if err != nil { + logs.Errorf("fail to get listener basic info, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.Listener, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + return nil, errf.New(errf.PermissionDenied, "permission denied for get listener") + } + + switch basicInfo.Vendor { + case enumor.TCloud: + return svc.getTCloudListener(cts.Kit, id) + + default: + return nil, errf.Newf(errf.InvalidParameter, "id: %s vendor: %s not support", id, basicInfo.Vendor) + } +} + +func (svc *lbSvc) getTCloudListener(kt *kit.Kit, lblID string) (*cslb.GetTCloudListenerDetail, error) { + listenerInfo, err := svc.client.DataService().TCloud.LoadBalancer.GetListener(kt, lblID) + if err != nil { + logs.Errorf("get tcloud listener detail failed, lblID: %s, err: %v, rid: %s", lblID, err, kt.Rid) + return nil, err + } + + urlRuleMap, err := svc.listTCloudLbUrlRuleMap(kt, listenerInfo.LbID, []string{lblID}) + if err != nil { + return nil, err + } + + targetGroupID := urlRuleMap[listenerInfo.ID].TargetGroupID + result := &cslb.GetTCloudListenerDetail{ + TCloudListener: *listenerInfo, + LblID: listenerInfo.ID, + LblName: listenerInfo.Name, + CloudLblID: listenerInfo.CloudID, + TargetGroupID: targetGroupID, + Scheduler: urlRuleMap[listenerInfo.ID].Scheduler, + SessionType: urlRuleMap[listenerInfo.ID].SessionType, + SessionExpire: urlRuleMap[listenerInfo.ID].SessionExpire, + HealthCheck: urlRuleMap[listenerInfo.ID].HealthCheck, + } + if listenerInfo.Protocol.IsLayer7Protocol() { + result.DomainNum = urlRuleMap[listenerInfo.ID].DomainNum + result.UrlNum = urlRuleMap[listenerInfo.ID].UrlNum + // 只有SNI开启时,证书才会出现在域名上面,才需要返回Certificate字段 + if listenerInfo.SniSwitch == enumor.SniTypeOpen { + result.Certificate = urlRuleMap[listenerInfo.ID].Certificate + result.Extension.Certificate = nil + } + } + + // 只有4层监听器才显示目标组信息 + if !listenerInfo.Protocol.IsLayer7Protocol() { + tg, err := svc.getTargetGroupByID(kt, targetGroupID) + if err != nil { + return nil, err + } + if tg != nil { + result.TargetGroupName = tg.Name + result.CloudTargetGroupID = tg.CloudID + } + } + + return result, nil +} + +func (svc *lbSvc) listTargetWeightNumMap(kt *kit.Kit, targetGroupIDs []string) ( + map[string]cslb.ListListenerBase, error) { + + targetList, err := svc.getTargetByTGIDs(kt, targetGroupIDs) + if err != nil { + return nil, err + } + + targetWeightMap := make(map[string]cslb.ListListenerBase, 0) + for _, item := range targetList { + tmpTarget := targetWeightMap[item.TargetGroupID] + if cvt.PtrToVal(item.Weight) == 0 { + tmpTarget.RsWeightZeroNum++ + } else { + tmpTarget.RsWeightNonZeroNum++ + } + targetWeightMap[item.TargetGroupID] = tmpTarget + } + + return targetWeightMap, nil +} + +// ListListenerCountByLbIDs list listener count by lbIDs. +func (svc *lbSvc) ListListenerCountByLbIDs(cts *rest.Contexts) (interface{}, error) { + return svc.listListenerCountByLbIDs(cts, handler.ListResourceAuthRes) +} + +// ListBizListenerCountByLbIDs list biz listener count by lbIDs. +func (svc *lbSvc) ListBizListenerCountByLbIDs(cts *rest.Contexts) (interface{}, error) { + return svc.listListenerCountByLbIDs(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) listListenerCountByLbIDs(cts *rest.Contexts, + authHandler handler.ListAuthResHandler) (interface{}, error) { + + req := new(dataproto.ListListenerCountByLbIDsReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + filterLb, err := tools.And(tools.RuleIn("lb_id", req.LbIDs)) + if err != nil { + logs.Errorf("fail to merge load balancer id into request filter, err: %v, req: %+v, rid: %s", + err, req, cts.Kit.Rid) + return nil, err + } + + // list authorized instances + _, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, Action: meta.Find, Filter: filterLb}) + if err != nil { + logs.Errorf("list listener by lbIDs auth failed, lbIDs: %v, noPermFlag: %v, err: %v, rid: %s", + req.LbIDs, noPermFlag, err, cts.Kit.Rid) + return nil, err + } + + resList := &dataproto.ListListenerCountResp{Details: make([]*dataproto.ListListenerCountResult, 0)} + if noPermFlag { + logs.Errorf("list listener no perm auth, lbIDs: %v, noPermFlag: %v, rid: %s", + req.LbIDs, noPermFlag, cts.Kit.Rid) + return resList, nil + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.LoadBalancerCloudResType, req.LbIDs[0]) + if err != nil { + logs.Errorf("fail to get load balancer basic info, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + switch basicInfo.Vendor { + case enumor.TCloud: + resList, err = svc.client.DataService().Global.LoadBalancer.CountLoadBalancerListener(cts.Kit, req) + if err != nil { + logs.Errorf("tcloud count load balancer listener failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + return resList, nil + default: + return nil, errf.Newf(errf.InvalidParameter, "lbIDs: %v vendor: %s not support", req.LbIDs, basicInfo.Vendor) + } +} diff --git a/cmd/cloud-server/service/load-balancer/load_balancer.go b/cmd/cloud-server/service/load-balancer/load_balancer.go new file mode 100644 index 0000000000..a6a84cae1a --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/load_balancer.go @@ -0,0 +1,150 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package loadbalancer ... +package loadbalancer + +import ( + "net/http" + + "hcm/cmd/cloud-server/logics/audit" + "hcm/cmd/cloud-server/logics/cvm" + "hcm/cmd/cloud-server/logics/disk" + "hcm/cmd/cloud-server/logics/eip" + "hcm/cmd/cloud-server/service/capability" + "hcm/pkg/client" + "hcm/pkg/iam/auth" + "hcm/pkg/rest" +) + +// InitService initialize the load balancer service. +func InitService(c *capability.Capability) { + svc := &lbSvc{ + client: c.ApiClient, + authorizer: c.Authorizer, + audit: c.Audit, + } + + h := rest.NewHandler() + + // clb apis in res + h.Add("ListLoadBalancer", http.MethodPost, "/load_balancers/list", svc.ListLoadBalancer) + h.Add("ListLoadBalancerWithDeleteProtection", http.MethodPost, + "/load_balancers/with/delete_protection/list", svc.ListLoadBalancerWithDeleteProtect) + h.Add("BatchCreateLB", http.MethodPost, "/load_balancers/create", svc.BatchCreateLB) + h.Add("InquiryPriceLoadBalancer", http.MethodPost, "/load_balancers/prices/inquiry", svc.InquiryPriceLoadBalancer) + h.Add("AssignLbToBiz", http.MethodPost, "/load_balancers/assign/bizs", svc.AssignLbToBiz) + h.Add("GetLoadBalancer", http.MethodGet, "/load_balancers/{id}", svc.GetLoadBalancer) + h.Add("TCloudDescribeResources", http.MethodPost, + "/vendors/tcloud/load_balancers/resources/describe", svc.TCloudDescribeResources) + h.Add("BatchDeleteLoadBalancer", http.MethodDelete, "/load_balancers/batch", svc.BatchDeleteLoadBalancer) + h.Add("ListListenerCountByLbIDs", http.MethodPost, "/load_balancers/listeners/count", svc.ListListenerCountByLbIDs) + h.Add("GetLoadBalancerLockStatus", http.MethodGet, + "/load_balancers/{id}/lock/status", svc.GetLoadBalancerLockStatus) + h.Add("ListResLoadBalancerQuotas", http.MethodPost, "/load_balancers/quotas", svc.ListResLoadBalancerQuotas) + + bizH := rest.NewHandler() + bizH.Path("/bizs/{bk_biz_id}") + bizService(bizH, svc) + + h.Load(c.WebService) + bizH.Load(c.WebService) +} + +func bizService(h *rest.Handler, svc *lbSvc) { + h.Add("BizBatchCreateLB", http.MethodPost, "/load_balancers/create", svc.BizBatchCreateLB) + h.Add("UpdateBizTCloudLoadBalancer", http.MethodPatch, + "/vendors/tcloud/load_balancers/{id}", svc.UpdateBizTCloudLoadBalancer) + h.Add("ListBizLoadBalancer", http.MethodPost, "/load_balancers/list", svc.ListBizLoadBalancer) + h.Add("ListLoadBalancerWithDeleteProtection", http.MethodPost, + "/load_balancers/with/delete_protection/list", svc.ListBizLoadBalancerWithDeleteProtect) + h.Add("GetBizLoadBalancer", http.MethodGet, "/load_balancers/{id}", svc.GetBizLoadBalancer) + h.Add("BatchDeleteBizLoadBalancer", http.MethodDelete, "/load_balancers/batch", svc.BatchDeleteBizLoadBalancer) + + h.Add("ListBizListener", http.MethodPost, "/load_balancers/{lb_id}/listeners/list", svc.ListBizListener) + h.Add("GetBizListener", http.MethodGet, "/listeners/{id}", svc.GetBizListener) + h.Add("ListBizListenerDomains", http.MethodPost, + "/vendors/tcloud/listeners/{lbl_id}/domains/list", svc.ListBizListenerDomains) + h.Add("ListBizListenerCountByLbIDs", http.MethodPost, "/load_balancers/listeners/count", + svc.ListBizListenerCountByLbIDs) + h.Add("GetBizLoadBalancerLockStatus", http.MethodGet, + "/load_balancers/{id}/lock/status", svc.GetBizLoadBalancerLockStatus) + h.Add("ListBizLoadBalancerQuotas", http.MethodPost, "/load_balancers/quotas", svc.ListBizLoadBalancerQuotas) + + // 目标组 + h.Add("ListBizTargetsByTGID", http.MethodPost, + "/target_groups/{target_group_id}/targets/list", svc.ListBizTargetsByTGID) + h.Add("AssociateBizTargetGroupListenerRel", http.MethodPost, + "/listeners/associate/target_group", svc.AssociateBizTargetGroupListenerRel) + + h.Add("CreateBizTargetGroup", http.MethodPost, "/target_groups/create", svc.CreateBizTargetGroup) + h.Add("UpdateBizTargetGroup", http.MethodPatch, "/target_groups/{id}", svc.UpdateBizTargetGroup) + h.Add("UpdateBizTargetGroupHealth", http.MethodPatch, + "/target_groups/{id}/health_check", svc.UpdateBizTargetGroupHealth) + h.Add("DeleteBizTargetGroup", http.MethodDelete, "/target_groups/batch", svc.DeleteBizTargetGroup) + h.Add("ListBizTargetGroup", http.MethodPost, "/target_groups/list", svc.ListBizTargetGroup) + h.Add("GetBizTargetGroup", http.MethodGet, "/target_groups/{id}", svc.GetBizTargetGroup) + // 与异步任务相关的操作 + h.Add("BatchAddBizTargets", http.MethodPost, "/target_groups/targets/create", svc.BatchAddBizTargets) + h.Add("BatchRemoveBizTargets", http.MethodDelete, "/target_groups/targets/batch", svc.BatchRemoveBizTargets) + h.Add("BatchModifyBizTargetPort", + http.MethodPatch, "/target_groups/{target_group_id}/targets/port", svc.BatchModifyBizTargetsPort) + h.Add("BatchModifyBizTargetsWeight", http.MethodPatch, + "/target_groups/{target_group_id}/targets/weight", svc.BatchModifyBizTargetsWeight) + + h.Add("CancelFlow", http.MethodPost, "/load_balancers/{lb_id}/async_flows/terminate", svc.BizTerminateFlow) + h.Add("RetryTask", http.MethodPost, "/load_balancers/{lb_id}/async_tasks/retry", svc.BizRetryTask) + h.Add("CloneFlow", http.MethodPost, "/load_balancers/{lb_id}/async_flows/clone", svc.BizCloneFlow) + h.Add("GetResultAfterTerminate", http.MethodPost, + "/load_balancers/{lb_id}/async_flows/result_after_terminate", svc.BizGetResultAfterTerminate) + + h.Add("ListBizTargetsHealthByTGID", http.MethodPost, + "/target_groups/{target_group_id}/targets/health", svc.ListBizTargetsHealthByTGID) + + // 监听器 + h.Add("CreateBizListener", http.MethodPost, "/load_balancers/{lb_id}/listeners/create", svc.CreateBizListener) + h.Add("UpdateBizListener", http.MethodPatch, "/listeners/{id}", svc.UpdateBizListener) + h.Add("DeleteBizListener", http.MethodDelete, "/listeners/batch", svc.DeleteBizListener) + h.Add("UpdateBizDomainAttr", http.MethodPatch, "/listeners/{lbl_id}/domains", svc.UpdateBizDomainAttr) + + // 规则 + h.Add("GetBizTCloudUrlRule", http.MethodGet, + "/vendors/tcloud/listeners/{lbl_id}/rules/{rule_id}", svc.GetBizTCloudUrlRule) + h.Add("ListBizUrlRulesByListener", http.MethodPost, + "/vendors/tcloud/listeners/{lbl_id}/rules/list", svc.ListBizUrlRulesByListener) + h.Add("ListBizTCloudRuleByTG", http.MethodPost, + "/vendors/tcloud/target_groups/{target_group_id}/rules/list", svc.ListBizTCloudRuleByTG) + h.Add("CreateBizTCloudUrlRule", http.MethodPost, + "/vendors/tcloud/listeners/{lbl_id}/rules/create", svc.CreateBizTCloudUrlRule) + h.Add("UpdateBizTCloudUrlRule", http.MethodPatch, + "/vendors/tcloud/listeners/{lbl_id}/rules/{rule_id}", svc.UpdateBizTCloudUrlRule) + h.Add("BatchDeleteBizTCloudUrlRule", http.MethodDelete, + "/vendors/tcloud/listeners/{lbl_id}/rules/batch", svc.BatchDeleteBizTCloudUrlRule) + h.Add("BatchDeleteBizTCloudUrlRuleByDomain", http.MethodDelete, + "/vendors/tcloud/listeners/{lbl_id}/rules/by/domains/batch", svc.BatchDeleteBizTCloudUrlRuleByDomain) +} + +type lbSvc struct { + client *client.ClientSet + authorizer auth.Authorizer + audit audit.Interface + diskLgc disk.Interface + cvmLgc cvm.Interface + eipLgc eip.Interface +} diff --git a/cmd/cloud-server/service/load-balancer/query.go b/cmd/cloud-server/service/load-balancer/query.go new file mode 100644 index 0000000000..b7b99cf1a0 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/query.go @@ -0,0 +1,403 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + proto "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + hcproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" + "hcm/pkg/tools/json" + "hcm/pkg/tools/slice" +) + +// ListLoadBalancer list load balancer. +func (svc *lbSvc) ListLoadBalancer(cts *rest.Contexts) (interface{}, error) { + return svc.listLoadBalancer(cts, handler.ListResourceAuthRes) +} + +// ListBizLoadBalancer list biz load balancer. +func (svc *lbSvc) ListBizLoadBalancer(cts *rest.Contexts) (interface{}, error) { + return svc.listLoadBalancer(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) listLoadBalancer(cts *rest.Contexts, authHandler handler.ListAuthResHandler) (interface{}, error) { + req := new(proto.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // list authorized instances + expr, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Find, + Filter: req.Filter, + }) + if err != nil { + return nil, err + } + + if noPermFlag { + return &core.ListResult{Count: 0, Details: make([]interface{}, 0)}, nil + } + + listReq := &core.ListReq{ + Filter: expr, + Page: req.Page, + } + return svc.client.DataService().Global.LoadBalancer.ListLoadBalancer(cts.Kit, listReq) +} + +// ListLoadBalancerWithDeleteProtect list load balancer with delete protect +func (svc *lbSvc) ListLoadBalancerWithDeleteProtect(cts *rest.Contexts) (any, error) { + return svc.listLoadBalancerWithDeleteProtect(cts, handler.ListResourceAuthRes) +} + +// ListBizLoadBalancerWithDeleteProtect list biz load balancer with delete protect +func (svc *lbSvc) ListBizLoadBalancerWithDeleteProtect(cts *rest.Contexts) (any, error) { + return svc.listLoadBalancerWithDeleteProtect(cts, handler.ListBizAuthRes) +} + +// list load balancer with delete protect +func (svc *lbSvc) listLoadBalancerWithDeleteProtect(cts *rest.Contexts, authHandler handler.ListAuthResHandler) ( + any, error) { + + req := new(proto.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // list authorized instances + expr, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Find, + Filter: req.Filter, + }) + if err != nil { + return nil, err + } + + if noPermFlag { + return &core.ListResult{Count: 0, Details: make([]any, 0)}, nil + } + + listReq := &core.ListReq{ + Filter: expr, + Page: req.Page, + } + dataResp, err := svc.client.DataService().Global.LoadBalancer.ListLoadBalancerRaw(cts.Kit, listReq) + if err != nil { + logs.Errorf("fail to list load balancer with extension for delete protection, err: %v, rid: %s", err, + cts.Kit.Rid) + return nil, err + } + lbResult := core.ListResultT[*corelb.LoadBalancerWithDeleteProtect]{ + Count: dataResp.Count, + } + for _, detail := range dataResp.Details { + lb := &corelb.LoadBalancerWithDeleteProtect{BaseLoadBalancer: detail.BaseLoadBalancer} + + // 目前仅支持tcloud 的删除保护 + if detail.Vendor == enumor.TCloud { + extension := corelb.TCloudClbExtension{} + err := json.Unmarshal(detail.Extension, &extension) + if err != nil { + logs.Errorf("fail parse lb extension for delete protection, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + lb.DeleteProtect = cvt.PtrToVal(extension.DeleteProtect) + } + lbResult.Details = append(lbResult.Details, lb) + + } + return lbResult, nil +} + +// GetLoadBalancer getLoadBalancer clb. +func (svc *lbSvc) GetLoadBalancer(cts *rest.Contexts) (interface{}, error) { + return svc.getLoadBalancer(cts, handler.ListResourceAuthRes) +} + +// GetBizLoadBalancer getLoadBalancer biz clb. +func (svc *lbSvc) GetBizLoadBalancer(cts *rest.Contexts) (interface{}, error) { + return svc.getLoadBalancer(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) getLoadBalancer(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.LoadBalancerCloudResType, + id) + if err != nil { + logs.Errorf("fail to get clb basic info, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.LoadBalancer, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + return nil, errf.New(errf.PermissionDenied, "permission denied for get clb") + } + + switch basicInfo.Vendor { + case enumor.TCloud: + return svc.client.DataService().TCloud.LoadBalancer.Get(cts.Kit, id) + + default: + return nil, errf.Newf(errf.Unknown, "id: %s vendor: %s not support", id, basicInfo.Vendor) + } +} + +// ListTargetsByTGID ... +func (svc *lbSvc) ListTargetsByTGID(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetsByTGID(cts, handler.ResOperateAuth) +} + +// ListBizTargetsByTGID ... +func (svc *lbSvc) ListBizTargetsByTGID(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetsByTGID(cts, handler.BizOperateAuth) +} + +// listTargetsByTGID 目标组下RS列表 +func (svc *lbSvc) listTargetsByTGID(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) (interface{}, + error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(proto.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.TargetGroupCloudResType, tgID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = validHandler(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.TargetGroup, + Action: meta.Find, + BasicInfo: basicInfo, + }) + if err != nil { + return nil, err + } + filter, err := tools.And(req.Filter, tools.RuleEqual("target_group_id", tgID)) + if err != nil { + logs.Errorf("merge filter failed, err: %v, target_group_id: %s, rid: %s", err, tgID, cts.Kit.Rid) + return nil, err + } + listReq := &core.ListReq{ + Filter: filter, + Page: req.Page, + } + return svc.client.DataService().Global.LoadBalancer.ListTarget(cts.Kit, listReq) +} + +// ListTargetsHealthByTGID 查询业务下指定目标组绑定的负载均衡下的RS端口健康信息 +func (svc *lbSvc) ListTargetsHealthByTGID(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetsHealthByTGID(cts, handler.BizOperateAuth) +} + +// ListBizTargetsHealthByTGID 查询资源下指定目标组负载均衡下的RS端口健康信息 +func (svc *lbSvc) ListBizTargetsHealthByTGID(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetsHealthByTGID(cts, handler.ResOperateAuth) +} + +// listTargetsHealthByTGID 目标组绑定的负载均衡下的RS端口健康信息 +func (svc *lbSvc) listTargetsHealthByTGID(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(hcproto.TCloudTargetHealthReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.TargetGroupCloudResType, tgID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = validHandler(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.TargetGroup, + Action: meta.Find, + BasicInfo: basicInfo, + }) + if err != nil { + return nil, err + } + + switch basicInfo.Vendor { + case enumor.TCloud: + tgInfo, newCloudLbIDs, err := svc.checkBindGetTargetGroupInfo(cts.Kit, tgID, req.CloudLbIDs) + if err != nil { + return nil, err + } + + req.AccountID = tgInfo.AccountID + req.Region = tgInfo.Region + req.CloudLbIDs = newCloudLbIDs + return svc.client.HCService().TCloud.Clb.ListTargetHealth(cts.Kit, req) + default: + return nil, errf.Newf(errf.Unknown, "id: %s vendor: %s not support", tgID, basicInfo.Vendor) + } +} + +// checkBindGetTargetGroupInfo 检查目标组是否存在、是否已绑定其他监听器 +func (svc *lbSvc) checkBindGetTargetGroupInfo(kt *kit.Kit, tgID string, cloudLbIDs []string) ( + *corelb.BaseTargetGroup, []string, error) { + + // 查询目标组的基本信息 + tgInfo, err := svc.getTargetGroupByID(kt, tgID) + if err != nil { + return nil, nil, err + } + + if tgInfo == nil { + return nil, nil, errf.Newf(errf.RecordNotFound, "target group: %s is not found", tgID) + } + + // 查询该目标组绑定的负载均衡、监听器数据 + ruleRelReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("target_group_id", tgID), + tools.RuleIn("cloud_lb_id", cloudLbIDs), + ), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, kt.Rid) + return nil, nil, err + } + + if len(ruleRelList.Details) == 0 { + return nil, nil, errf.Newf(errf.RecordNotUpdate, "target group: %s has not bound listener", tgID) + } + + // 以当前目标组绑定的负载均衡ID为准 + newCloudLbIDs := slice.Map(ruleRelList.Details, func(one corelb.BaseTargetListenerRuleRel) string { + return one.CloudLbID + }) + return tgInfo, newCloudLbIDs, nil +} + +// GetLoadBalancerLockStatus get load balancer status. +func (svc *lbSvc) GetLoadBalancerLockStatus(cts *rest.Contexts) (interface{}, error) { + return svc.getLoadBalancerLockStatus(cts, handler.ListResourceAuthRes) +} + +// GetBizLoadBalancerLockStatus get biz load balancer status. +func (svc *lbSvc) GetBizLoadBalancerLockStatus(cts *rest.Contexts) (interface{}, error) { + return svc.getLoadBalancerLockStatus(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) getLoadBalancerLockStatus(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.LoadBalancerCloudResType, id) + if err != nil { + logs.Errorf("fail to get load balancer basic info, err: %v, id: %s, rid: %s", err, id, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.LoadBalancer, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + return nil, errf.New(errf.PermissionDenied, "permission denied for get load balancer") + } + + switch basicInfo.Vendor { + case enumor.TCloud: + // 预检测-是否有执行中的负载均衡 + flowRelResp, err := svc.checkResFlowRel(cts.Kit, id, enumor.LoadBalancerCloudResType) + if err != nil { + logs.Errorf("load balancer %s is executing flow, err: %v, rid: %s", id, err, cts.Kit.Rid) + flowStatus := &cslb.ResourceFlowStatusResp{Status: enumor.ExecutingResFlowStatus} + if flowRelResp != nil { + flowStatus.ResID = flowRelResp.ResID + flowStatus.ResType = flowRelResp.ResType + flowStatus.FlowID = flowRelResp.Owner + } + return flowStatus, nil + } + + return &cslb.ResourceFlowStatusResp{Status: enumor.SuccessResFlowStatus}, nil + default: + return nil, errf.Newf(errf.Unknown, "id: %s vendor: %s not support", id, basicInfo.Vendor) + } +} diff --git a/cmd/cloud-server/service/load-balancer/quota.go b/cmd/cloud-server/service/load-balancer/quota.go new file mode 100644 index 0000000000..c61d860ee9 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/quota.go @@ -0,0 +1,79 @@ +package loadbalancer + +import ( + "encoding/json" + "fmt" + + cloudserver "hcm/pkg/api/cloud-server" + hcproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// ListBizLoadBalancerQuotas 获取业务下的账号配额. +func (svc *lbSvc) ListBizLoadBalancerQuotas(cts *rest.Contexts) (interface{}, error) { + return svc.listLoadBalancerQuotas(cts, handler.BizOperateAuth) +} + +// ListResLoadBalancerQuotas 获取资源下的账号配额. +func (svc *lbSvc) ListResLoadBalancerQuotas(cts *rest.Contexts) (interface{}, error) { + return svc.listLoadBalancerQuotas(cts, handler.ResOperateAuth) +} + +// listLoadBalancerQuotas list load balancer quota. +func (svc *lbSvc) listLoadBalancerQuotas(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) (any, error) { + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("list quota load balancer request decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + // validate biz and authorize + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Quota, + Action: meta.Find, BasicInfo: basicInfo}) + if err != nil { + return nil, err + } + + info, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, err: %v, accountID: %s, rid: %s", err, req.AccountID, cts.Kit.Rid) + return nil, err + } + + switch info.Vendor { + case enumor.TCloud: + return svc.listTCloudLoadBalancerQuota(cts.Kit, req.Data) + default: + return nil, fmt.Errorf("vendor: %s not support", info.Vendor) + } +} + +func (svc *lbSvc) listTCloudLoadBalancerQuota(kt *kit.Kit, body json.RawMessage) (any, error) { + req := new(hcproto.TCloudListLoadBalancerQuotaReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.client.HCService().TCloud.Clb.ListQuota(kt, req) + if err != nil { + logs.Errorf("list tcloud load balancer quota failed, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + return result, nil +} diff --git a/cmd/cloud-server/service/load-balancer/target_group.go b/cmd/cloud-server/service/load-balancer/target_group.go new file mode 100644 index 0000000000..023868e10f --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/target_group.go @@ -0,0 +1,245 @@ +package loadbalancer + +import ( + lblogic "hcm/cmd/cloud-server/logics/load-balancer" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" +) + +// ListTargetGroup list target group. +func (svc *lbSvc) ListTargetGroup(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetGroup(cts, handler.ListResourceAuthRes) +} + +// ListBizTargetGroup list biz target group. +func (svc *lbSvc) ListBizTargetGroup(cts *rest.Contexts) (interface{}, error) { + return svc.listTargetGroup(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) listTargetGroup(cts *rest.Contexts, authHandler handler.ListAuthResHandler) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // list authorized instances + expr, noPermFlag, err := authHandler(cts, &handler.ListAuthResOption{Authorizer: svc.authorizer, + ResType: meta.TargetGroup, Action: meta.Find, Filter: req.Filter}) + if err != nil { + logs.Errorf("list target group auth failed, noPermFlag: %v, err: %v, rid: %s", noPermFlag, err, cts.Kit.Rid) + return nil, err + } + + resList := &cslb.ListTargetGroupResult{Count: 0, Details: make([]cslb.ListTargetGroupSummary, 0)} + if noPermFlag { + logs.Errorf("list target group no perm auth, noPermFlag: %v, expr: %+v, rid: %s", noPermFlag, expr, cts.Kit.Rid) + return resList, nil + } + + tgReq := &core.ListReq{ + Filter: expr, + Page: req.Page, + Fields: req.Fields, + } + targetGroupList, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroup(cts.Kit, tgReq) + if err != nil { + logs.Errorf("list target group db failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + if req.Page.Count { + resList.Count = targetGroupList.Count + return resList, nil + } + if len(targetGroupList.Details) == 0 { + return resList, nil + } + + resList.Count = targetGroupList.Count + targetGroupIDs := make([]string, 0) + for _, item := range targetGroupList.Details { + targetGroupIDs = append(targetGroupIDs, item.ID) + resList.Details = append(resList.Details, cslb.ListTargetGroupSummary{ + BaseTargetGroup: item, + }) + } + + lbMap, tgLbMap, tgLblMap, err := svc.getLbAndLblMapByTgIDs(cts.Kit, targetGroupIDs) + if err != nil { + logs.Errorf("get lb and lbl map by tgids failed, tgIDs: %v, err: %v, rid: %s", targetGroupIDs, err, cts.Kit.Rid) + return nil, err + } + + for idx, tgItem := range resList.Details { + resList.Details[idx].ListenerNum = tgLblMap[tgItem.ID] + tmpLbID := tgLbMap[tgItem.ID] + lbInfo, ok := lbMap[tmpLbID] + if !ok { + continue + } + resList.Details[idx].LbID = lbInfo.ID + resList.Details[idx].LbName = lbInfo.Name + resList.Details[idx].PrivateIPv4Addresses = lbInfo.PrivateIPv4Addresses + resList.Details[idx].PrivateIPv6Addresses = lbInfo.PrivateIPv6Addresses + resList.Details[idx].PublicIPv4Addresses = lbInfo.PublicIPv4Addresses + resList.Details[idx].PublicIPv6Addresses = lbInfo.PublicIPv6Addresses + } + + return resList, nil +} + +func (svc *lbSvc) getLbAndLblMapByTgIDs(kt *kit.Kit, targetGroupIDs []string) (map[string]corelb.BaseLoadBalancer, + map[string]string, map[string]int64, error) { + + // 根据目标组ID数组,批量查询负载均衡ID、监听器ID等信息 + tgListenerRelList, err := svc.listTGListenerRuleRelMapByTGIDs(kt, targetGroupIDs) + if err != nil { + return nil, nil, nil, err + } + + lbIDs := make([]string, 0) + tgLbMap := make(map[string]string, len(tgListenerRelList)) + tgLblMap := make(map[string]int64) + existLbl := make(map[string]struct{}, 0) + for _, rel := range tgListenerRelList { + lbIDs = append(lbIDs, rel.LbID) + tgLbMap[rel.TargetGroupID] = rel.LbID + if _, ok := existLbl[rel.TargetGroupID+rel.LblID]; ok { + continue + } + existLbl[rel.TargetGroupID+rel.LblID] = struct{}{} + tgLblMap[rel.TargetGroupID]++ + } + + // 根据负载均衡ID数组,批量查询负载均衡基本信息 + lbMap, err := lblogic.ListLoadBalancerMap(kt, svc.client.DataService(), lbIDs) + if err != nil { + return nil, nil, nil, err + } + + return lbMap, tgLbMap, tgLblMap, nil +} + +func (svc *lbSvc) listTGListenerRuleRelMapByTGIDs(kt *kit.Kit, tgIDs []string) ( + []corelb.BaseTargetListenerRuleRel, error) { + + req := &core.ListReq{ + Filter: tools.ContainersExpression("target_group_id", tgIDs), + Page: core.NewDefaultBasePage(), + } + list, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, req) + if err != nil { + logs.Errorf("list target group listener rel failed, tgIDs: %v, err: %v, rid: %s", tgIDs, err, kt.Rid) + return nil, err + } + + return list.Details, nil +} + +// GetTargetGroup get target group. +func (svc *lbSvc) GetTargetGroup(cts *rest.Contexts) (interface{}, error) { + return svc.getTargetGroup(cts, handler.ListResourceAuthRes) +} + +// GetBizTargetGroup get biz target group. +func (svc *lbSvc) GetBizTargetGroup(cts *rest.Contexts) (interface{}, error) { + return svc.getTargetGroup(cts, handler.ListBizAuthRes) +} + +func (svc *lbSvc) getTargetGroup(cts *rest.Contexts, validHandler handler.ListAuthResHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.TargetGroupCloudResType, id) + if err != nil { + logs.Errorf("get target group basic info failed, id: %s, err: %v, rid: %s", id, err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + _, noPerm, err := validHandler(cts, + &handler.ListAuthResOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, Action: meta.Find}) + if err != nil { + return nil, err + } + if noPerm { + logs.Errorf("get target group no perm auth, noPerm: %v, rid: %s", noPerm, cts.Kit.Rid) + return nil, errf.New(errf.PermissionDenied, "permission denied for get target group") + } + + switch basicInfo.Vendor { + case enumor.TCloud: + return svc.getTCloudTargetGroup(cts.Kit, id) + + default: + return nil, errf.Newf(errf.Unknown, "id: %s vendor: %s not support", id, basicInfo.Vendor) + } +} + +func (svc *lbSvc) getTCloudTargetGroup(kt *kit.Kit, tgID string) (*cslb.GetTargetGroupDetail, error) { + targetGroupInfo, err := svc.client.DataService().TCloud.LoadBalancer.GetTargetGroup(kt, tgID) + if err != nil { + logs.Errorf("get tcloud target group detail failed, tgID: %s, err: %v, rid: %s", tgID, err, kt.Rid) + return nil, err + } + + targetList, err := svc.getTargetByTGIDs(kt, []string{tgID}) + if err != nil { + logs.Errorf("list target db failed, tgID: %s, err: %v, rid: %s", tgID, err, kt.Rid) + return nil, err + } + + result := &cslb.GetTargetGroupDetail{ + BaseTargetGroup: targetGroupInfo.BaseTargetGroup, + TargetList: targetList, + } + + return result, nil +} + +// 查询目标组,查不到时返回nil +func (svc *lbSvc) getTargetGroupByID(kt *kit.Kit, targetGroupID string) (*corelb.BaseTargetGroup, error) { + + tgReq := &core.ListReq{ + Filter: tools.EqualExpression("id", targetGroupID), + Page: core.NewDefaultBasePage(), + } + targetGroupInfo, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroup(kt, tgReq) + if err != nil { + logs.Errorf("list target group failed, tgID: %s, err: %v, rid: %s", targetGroupID, err, kt.Rid) + return nil, err + } + if len(targetGroupInfo.Details) == 0 { + return nil, nil + } + return cvt.ValToPtr(targetGroupInfo.Details[0]), nil +} + +func (svc *lbSvc) getTargetByTGIDs(kt *kit.Kit, targetGroupIDs []string) ([]corelb.BaseTarget, error) { + tgReq := &core.ListReq{ + Filter: tools.ContainersExpression("target_group_id", targetGroupIDs), + Page: core.NewDefaultBasePage(), + } + targetResult, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + logs.Errorf("list target by tgIDs failed, tgIDs: %v, err: %v, rid: %s", targetGroupIDs, err, kt.Rid) + return nil, err + } + + return targetResult.Details, nil +} diff --git a/cmd/cloud-server/service/load-balancer/tcloud_describe_resources.go b/cmd/cloud-server/service/load-balancer/tcloud_describe_resources.go new file mode 100644 index 0000000000..e67d137e48 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/tcloud_describe_resources.go @@ -0,0 +1,59 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +func (svc *lbSvc) TCloudDescribeResources(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudDescribeResourcesOption) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // TODO: 权限校验 + + authRes := meta.ResourceAttribute{Basic: &meta.Basic{Type: meta.Account, Action: meta.Find, + ResourceID: req.AccountID}} + if err := svc.authorizer.AuthorizeWithPerm(cts.Kit, authRes); err != nil { + logs.Errorf("describe resources auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + _, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + // 这里校验账号是否存在,出现错误大概率是账号不存在 + logs.V(3).Errorf("fail to get account info, err: %s, account id: %s, rid: %s", + err, req.AccountID, cts.Kit.Rid) + return nil, err + } + return svc.client.HCService().TCloud.Clb.DescribeResources(cts.Kit, req) +} diff --git a/cmd/cloud-server/service/load-balancer/tcloud_url_rule.go b/cmd/cloud-server/service/load-balancer/tcloud_url_rule.go new file mode 100644 index 0000000000..a224dd9dd0 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/tcloud_url_rule.go @@ -0,0 +1,772 @@ +package loadbalancer + +import ( + "errors" + "fmt" + + lblogic "hcm/cmd/cloud-server/logics/load-balancer" + actionlb "hcm/cmd/task-server/logics/action/load-balancer" + actionflow "hcm/cmd/task-server/logics/flow" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + "hcm/pkg/api/core/cloud" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + hcproto "hcm/pkg/api/hc-service/load-balancer" + apits "hcm/pkg/api/task-server" + "hcm/pkg/async/action" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableasync "hcm/pkg/dal/table/async" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/runtime/filter" + "hcm/pkg/tools/converter" + "hcm/pkg/tools/counter" + "hcm/pkg/tools/hooks/handler" +) + +// ListTCloudRuleByTG ... +func (svc *lbSvc) ListTCloudRuleByTG(cts *rest.Contexts) (interface{}, error) { + return svc.listTCloudLbUrlRuleByTG(cts, handler.ResOperateAuth) +} + +// ListBizTCloudRuleByTG ... +func (svc *lbSvc) ListBizTCloudRuleByTG(cts *rest.Contexts) (interface{}, error) { + return svc.listTCloudLbUrlRuleByTG(cts, handler.BizOperateAuth) +} + +// listTCloudLbUrlRuleByTG 返回目标组绑定的四层监听器或者七层规则(都能绑定目标组或者rs) +func (svc *lbSvc) listTCloudLbUrlRuleByTG(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) (any, error) { + + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.TargetGroupCloudResType, tgID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.TargetGroup, + Action: meta.Find, BasicInfo: basicInfo}) + if err != nil { + return nil, err + } + + urlRuleList, err := svc.listRuleWithCondition(cts.Kit, req, tools.RuleEqual("target_group_id", tgID)) + if err != nil { + return nil, err + } + + resList, err := svc.fillRuleRelatedRes(cts.Kit, urlRuleList) + if err != nil { + return nil, err + } + + return resList, nil +} + +// fillRuleRelatedRes 填充 监听器的协议、端口信息、所在vpc信息 +func (svc *lbSvc) fillRuleRelatedRes(kt *kit.Kit, urlRuleList *dataproto.TCloudURLRuleListResult) ( + any, error) { + + if len(urlRuleList.Details) == 0 { + return &cslb.ListLbUrlRuleResult{Count: urlRuleList.Count, Details: make([]cslb.ListLbUrlRuleBase, 0)}, nil + } + + lbIDs := make([]string, 0) + lblIDs := make([]string, 0) + targetIDs := make([]string, 0) + resList := &cslb.ListLbUrlRuleResult{Count: urlRuleList.Count, Details: make([]cslb.ListLbUrlRuleBase, 0)} + for _, ruleItem := range urlRuleList.Details { + lbIDs = append(lbIDs, ruleItem.LbID) + lblIDs = append(lblIDs, ruleItem.LblID) + targetIDs = append(targetIDs, ruleItem.TargetGroupID) + resList.Details = append(resList.Details, cslb.ListLbUrlRuleBase{TCloudLbUrlRule: ruleItem}) + } + + // 批量获取lb信息 + lbMap, err := lblogic.ListLoadBalancerMap(kt, svc.client.DataService(), lbIDs) + if err != nil { + return nil, err + } + + // 批量获取listener信息 + listenerMap, err := svc.listListenerMap(kt, lblIDs) + if err != nil { + return nil, err + } + + // 批量获取vpc信息 + vpcIDs := make([]string, 0) + for _, item := range lbMap { + vpcIDs = append(vpcIDs, item.VpcID) + } + + vpcMap, err := svc.listVpcMap(kt, vpcIDs) + if err != nil { + return nil, err + } + + for idx, ruleItem := range resList.Details { + resList.Details[idx].LbName = lbMap[ruleItem.LbID].Name + tmpVpcID := lbMap[ruleItem.LbID].VpcID + resList.Details[idx].VpcID = tmpVpcID + resList.Details[idx].CloudVpcID = lbMap[ruleItem.LbID].CloudVpcID + resList.Details[idx].PrivateIPv4Addresses = lbMap[ruleItem.LbID].PrivateIPv4Addresses + resList.Details[idx].PrivateIPv6Addresses = lbMap[ruleItem.LbID].PrivateIPv6Addresses + resList.Details[idx].PublicIPv4Addresses = lbMap[ruleItem.LbID].PublicIPv4Addresses + resList.Details[idx].PublicIPv6Addresses = lbMap[ruleItem.LbID].PublicIPv6Addresses + + resList.Details[idx].VpcName = vpcMap[tmpVpcID].Name + + resList.Details[idx].LblName = listenerMap[ruleItem.LblID].Name + resList.Details[idx].Protocol = listenerMap[ruleItem.LblID].Protocol + resList.Details[idx].Port = listenerMap[ruleItem.LblID].Port + + } + + return resList, nil +} + +// listRuleWithCondition list rule with additional rules +func (svc *lbSvc) listRuleWithCondition(kt *kit.Kit, listReq *core.ListReq, conditions ...filter.RuleFactory) ( + *dataproto.TCloudURLRuleListResult, error) { + + req := &core.ListReq{ + Filter: listReq.Filter, + Page: listReq.Page, + Fields: listReq.Fields, + } + if len(conditions) > 0 { + conditions = append(conditions, listReq.Filter) + combinedFilter, err := tools.And(conditions...) + if err != nil { + logs.Errorf("fail to merge list request, err: %v, listReq: %+v, rid: %s", err, listReq, kt.Rid) + return nil, err + } + req.Filter = combinedFilter + } + + urlRuleList, err := svc.client.DataService().TCloud.LoadBalancer.ListUrlRule(kt, req) + if err != nil { + logs.Errorf("list tcloud url with rule failed, err: %v, req: %+v, conditions: %+v, rid: %s", + err, listReq, conditions, kt.Rid) + return nil, err + } + + return urlRuleList, nil +} + +func (svc *lbSvc) listVpcMap(kt *kit.Kit, vpcIDs []string) (map[string]cloud.BaseVpc, error) { + if len(vpcIDs) == 0 { + return nil, nil + } + + vpcReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", vpcIDs), + Page: core.NewDefaultBasePage(), + } + list, err := svc.client.DataService().Global.Vpc.List(kt.Ctx, kt.Header(), vpcReq) + if err != nil { + logs.Errorf("[clb] list vpc failed, vpcIDs: %v, err: %v, rid: %s", vpcIDs, err, kt.Rid) + return nil, err + } + + vpcMap := make(map[string]cloud.BaseVpc, len(list.Details)) + for _, item := range list.Details { + vpcMap[item.ID] = item + } + + return vpcMap, nil +} + +// ListBizUrlRulesByListener 指定监听器下的url规则 +func (svc *lbSvc) ListBizUrlRulesByListener(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + basicInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, lblID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = handler.BizOperateAuth(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.Listener, + Action: meta.Find, + BasicInfo: basicInfo, + }) + if err != nil { + return nil, err + } + + // 查询规则列表 + return svc.listRuleWithCondition(cts.Kit, req, + tools.RuleEqual("lbl_id", lblID), + tools.RuleEqual("rule_type", enumor.Layer7RuleType)) +} + +// ListBizListenerDomains 指定监听器下的域名列表 +func (svc *lbSvc) ListBizListenerDomains(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + req := new(core.ListReq) + req.Filter = tools.ExpressionAnd( + tools.RuleEqual("lbl_id", lblID), + tools.RuleEqual("rule_type", enumor.Layer7RuleType)) + req.Page = core.NewDefaultBasePage() + + lbl, err := svc.client.DataService().TCloud.LoadBalancer.GetListener(cts.Kit, lblID) + if err != nil { + logs.Errorf("fail to get listener, err: %v, id: %s, rid: %s", err, lblID, cts.Kit.Rid) + return nil, err + } + + // 业务校验、鉴权 + err = svc.authorizer.AuthorizeWithPerm(cts.Kit, meta.ResourceAttribute{ + Basic: &meta.Basic{ + Type: meta.Listener, + Action: meta.Find, + }, + BizID: lbl.BkBizID, + }) + if err != nil { + return nil, err + } + + if !lbl.Protocol.IsLayer7Protocol() { + return nil, errf.Newf(errf.InvalidParameter, "unsupported listener protocol type: %s", lbl.Protocol) + } + // 查询规则列表 + ruleList, err := svc.listRuleWithCondition(cts.Kit, req) + if err != nil { + logs.Errorf("fail list rule under listener(id=%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + + // 统计url数量 + domainList := make([]cslb.DomainInfo, 0) + domainIndex := make(map[string]int) + for _, detail := range ruleList.Details { + if _, exists := domainIndex[detail.Domain]; !exists { + domainIndex[detail.Domain] = len(domainList) + domainList = append(domainList, cslb.DomainInfo{Domain: detail.Domain}) + } + domainList[domainIndex[detail.Domain]].UrlCount += 1 + } + + return cslb.GetListenerDomainResult{ + DefaultDomain: lbl.DefaultDomain, + DomainList: domainList, + }, nil +} + +// GetBizTCloudUrlRule 业务下腾讯云url规则 +func (svc *lbSvc) GetBizTCloudUrlRule(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + ruleID := cts.PathParameter("rule_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "rule is required") + } + + lblInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, lblID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = handler.BizOperateAuth(cts, + &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.TargetGroup, + Action: meta.Find, + BasicInfo: lblInfo, + }) + if err != nil { + return nil, err + } + + req := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("id", ruleID), + tools.RuleEqual("lbl_id", lblID), + tools.RuleEqual("rule_type", enumor.Layer7RuleType), + ), + Page: core.NewDefaultBasePage(), + } + + urlRuleList, err := svc.client.DataService().TCloud.LoadBalancer.ListUrlRule(cts.Kit, req) + if err != nil { + logs.Errorf("list tcloud url failed, err: %v, lblID: %s, ruleID: %s, rid: %s", + err, lblID, ruleID, cts.Kit.Rid) + return nil, err + } + if len(urlRuleList.Details) == 0 { + return nil, errf.New(errf.RecordNotFound, "rule not found, id: "+ruleID) + } + + return urlRuleList.Details[0], nil +} + +// CreateBizTCloudUrlRule 业务下新建腾讯云url规则 TODO: 改成一次只创建一个规则 +func (svc *lbSvc) CreateBizTCloudUrlRule(cts *rest.Contexts) (any, error) { + + bizID, err := cts.PathParameter("bk_biz_id").Int64() + if err != nil { + return nil, err + } + if bizID < 0 { + return nil, errf.New(errf.InvalidParameter, "bk_biz_id id is required") + } + + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + // 限制一次只能创建一条规则 + req := new(cslb.TCloudRuleCreate) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lblInfo, lblBasicInfo, err := svc.getTCloudListenerByID(cts, bizID, lblID) + if err != nil { + return nil, err + } + + // if SNI Switch is off, certificates can only be set in listener not its rule + if lblInfo.SniSwitch == enumor.SniTypeClose && req.Certificate != nil { + return nil, errf.New(errf.InvalidParameter, "can not set certificate on rule of sni_switch off listener") + } + + // 业务校验、鉴权 + valOpt := &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.UrlRuleAuditResType, + Action: meta.Create, + BasicInfo: lblBasicInfo, + } + if err = handler.BizOperateAuth(cts, valOpt); err != nil { + return nil, err + } + + tg, err := svc.targetGroupBindCheck(cts.Kit, bizID, req.TargetGroupID) + if err != nil { + return nil, err + } + + // 预检测-是否有执行中的负载均衡 + _, err = svc.checkResFlowRel(cts.Kit, lblInfo.LbID, enumor.LoadBalancerCloudResType) + if err != nil { + return nil, err + } + + hcReq := &hcproto.TCloudRuleBatchCreateReq{Rules: []hcproto.TCloudRuleCreate{convRuleCreate(req, tg)}} + createResp, err := svc.client.HCService().TCloud.Clb.BatchCreateUrlRule(cts.Kit, lblID, hcReq) + if err != nil { + logs.Errorf("fail to create tcloud url rule, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + if len(createResp.SuccessCloudIDs) == 0 { + logs.Errorf("no rule have been created, lblID: %s, req: %+v, rid: %s", lblID, hcReq, cts.Kit.Rid) + return nil, errors.New("create failed, reason: unknown") + } + err = svc.applyTargetToRule(cts.Kit, tg.ID, createResp.SuccessCloudIDs[0], lblInfo) + if err != nil { + logs.Errorf("fail to create target register flow, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + return createResp, nil +} + +// 构建异步任务将目标组中的RS绑定到对应规则上 +func (svc *lbSvc) applyTargetToRule(kt *kit.Kit, tgID, ruleCloudID string, lblInfo *corelb.BaseListener) error { + + // 查找目标组中的rs + listRsReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: &core.BasePage{ + Count: false, + Start: 0, + Limit: constant.BatchAddRSCloudMaxLimit, + }, + } + // Build Task + tasks := make([]apits.CustomFlowTask, 0) + getNextID := counter.NewNumStringCounter(1, 10) + // 判断规则类型 + var ruleType enumor.RuleType + if lblInfo.Protocol.IsLayer7Protocol() { + ruleType = enumor.Layer7RuleType + } else { + ruleType = enumor.Layer4RuleType + } + // 按目标组数量拆分任务批次 + for { + rsResp, err := svc.client.DataService().Global.LoadBalancer.ListTarget(kt, listRsReq) + if err != nil { + logs.Errorf("fail to list target, err: %v, rid: %s", err, kt.Rid) + return err + } + if len(rsResp.Details) == 0 { + break + } + + rsReq := &hcproto.BatchRegisterTCloudTargetReq{ + CloudListenerID: lblInfo.CloudID, + CloudRuleID: ruleCloudID, + TargetGroupID: tgID, + RuleType: ruleType, + Targets: make([]*hcproto.RegisterTarget, 0, len(rsResp.Details)), + } + for _, target := range rsResp.Details { + rsReq.Targets = append(rsReq.Targets, &hcproto.RegisterTarget{ + CloudInstID: target.CloudInstID, + InstType: string(target.InstType), + Port: target.Port, + Weight: converter.PtrToVal(target.Weight), + Zone: target.Zone, + InstName: target.InstName, + PrivateIPAddress: target.PrivateIPAddress, + PublicIPAddress: target.PublicIPAddress, + }) + } + tasks = append(tasks, apits.CustomFlowTask{ + ActionID: action.ActIDType(getNextID()), + ActionName: enumor.ActionListenerRuleAddTarget, + Params: actionlb.ListenerRuleAddTargetOption{ + LoadBalancerID: lblInfo.LbID, + BatchRegisterTCloudTargetReq: rsReq, + }, + DependOn: nil, + Retry: tableasync.NewRetryWithPolicy(10, 100, 500), + }) + + if len(rsResp.Details) < constant.BatchAddRSCloudMaxLimit { + break + } + listRsReq.Page.Start += constant.BatchAddRSCloudMaxLimit + } + + if len(tasks) == 0 { + return nil + } + return svc.createApplyTGFlow(kt, tgID, lblInfo, tasks) +} + +func (svc *lbSvc) createApplyTGFlow(kt *kit.Kit, tgID string, lblInfo *corelb.BaseListener, + tasks []apits.CustomFlowTask) error { + + mainFlowResult, err := svc.client.TaskServer().CreateCustomFlow(kt, &apits.AddCustomFlowReq{ + Name: enumor.FlowApplyTargetGroupToListenerRule, + IsInitState: true, + Tasks: tasks, + }) + if err != nil { + logs.Errorf("fail to create target register flow, err: %v, rid: %s", err, kt.Rid) + return err + } + + flowID := mainFlowResult.ID + // 创建从任务并加锁 + flowWatchReq := &apits.AddTemplateFlowReq{ + Name: enumor.FlowLoadBalancerOperateWatch, + Tasks: []apits.TemplateFlowTask{{ + ActionID: "1", + Params: &actionflow.LoadBalancerOperateWatchOption{ + FlowID: flowID, + ResID: lblInfo.LbID, + ResType: enumor.LoadBalancerCloudResType, + SubResIDs: []string{tgID}, + SubResType: enumor.TargetGroupCloudResType, + TaskType: enumor.ApplyTargetGroupType, + }, + }}, + } + _, err = svc.client.TaskServer().CreateTemplateFlow(kt, flowWatchReq) + if err != nil { + logs.Errorf("call task server to create res flow status watch flow failed, err: %v, flowID: %s, rid: %s", + err, flowID, kt.Rid) + return err + } + + // 锁定负载均衡跟Flow的状态 + err = svc.lockResFlowStatus(kt, lblInfo.LbID, enumor.LoadBalancerCloudResType, flowID, enumor.ApplyTargetGroupType) + if err != nil { + logs.Errorf("fail to lock load balancer(%s) for flow(%s), err: %v, rid: %s", + lblInfo.LbID, flowID, err, kt.Rid) + return err + } + return nil +} + +func (svc *lbSvc) getTCloudListenerByID(cts *rest.Contexts, bizID int64, lblID string) (*corelb.BaseListener, + *types.CloudResourceBasicInfo, error) { + + lblResp, err := svc.client.DataService().Global.LoadBalancer.ListListener(cts.Kit, + &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("id", lblID), + tools.RuleEqual("vendor", enumor.TCloud), + tools.RuleEqual("bk_biz_id", bizID)), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list listener(%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, nil, err + } + if len(lblResp.Details) == 0 { + return nil, nil, errf.New(errf.RecordNotFound, "listener not found, id: "+lblID) + } + lblInfo := &lblResp.Details[0] + basicInfo := &types.CloudResourceBasicInfo{ + ResType: enumor.ListenerCloudResType, + ID: lblID, + Vendor: enumor.TCloud, + AccountID: lblInfo.AccountID, + BkBizID: lblInfo.BkBizID, + } + + return lblInfo, basicInfo, nil +} + +func convRuleCreate(rule *cslb.TCloudRuleCreate, tg *corelb.BaseTargetGroup) hcproto.TCloudRuleCreate { + return hcproto.TCloudRuleCreate{ + Url: rule.Url, + TargetGroupID: rule.TargetGroupID, + CloudTargetGroupID: tg.CloudID, + Domains: rule.Domains, + SessionExpireTime: rule.SessionExpireTime, + Scheduler: rule.Scheduler, + ForwardType: rule.ForwardType, + DefaultServer: rule.DefaultServer, + Http2: rule.Http2, + TargetType: rule.TargetType, + Quic: rule.Quic, + TrpcFunc: rule.TrpcFunc, + TrpcCallee: rule.TrpcCallee, + HealthCheck: tg.HealthCheck, + Certificates: rule.Certificate, + Memo: rule.Memo, + } +} + +// 目标组绑定检查,检查成功返回目标组id为索引的map +func (svc *lbSvc) targetGroupBindCheck(kt *kit.Kit, bizID int64, tgId string) (*corelb.BaseTargetGroup, error) { + + // 检查目标组是否存在 + tgResp, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroup(kt, &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("bk_biz_id", bizID), + tools.RuleEqual("id", tgId), + ), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to query target group(id:%s) info, err: %v, rid: %s", tgId, err, kt.Rid) + return nil, err + } + + if len(tgResp.Details) == 0 { + logs.Errorf("target group can not be found, id: %s, rid: %s", tgId, kt.Rid) + return nil, errf.Newf(errf.RecordNotFound, "target group(%s) can not be found", tgId) + } + tg := &tgResp.Details[0] + // 检查对应的目标组是否被绑定 + relResp, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgId), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + return nil, err + } + if len(relResp.Details) > 0 { + rel := relResp.Details[0] + return nil, fmt.Errorf("target group(%s) already been bound to rule or listener(%s)", + rel.TargetGroupID, rel.CloudListenerRuleID) + } + return tg, nil +} + +// UpdateBizTCloudUrlRule 更新规则 +func (svc *lbSvc) UpdateBizTCloudUrlRule(cts *rest.Contexts) (any, error) { + + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + ruleID := cts.PathParameter("rule_id").String() + if len(ruleID) == 0 { + return nil, errf.New(errf.InvalidParameter, "rule id is required") + } + + req := new(hcproto.TCloudRuleUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lblInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, + enumor.ListenerCloudResType, lblID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = handler.BizOperateAuth(cts, + &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.UrlRuleAuditResType, + Action: meta.Update, + BasicInfo: lblInfo, + }) + if err != nil { + return nil, err + } + + // 更新审计 + updateFields, err := converter.StructToMap(req) + if err != nil { + logs.Errorf("convert rule update request to map failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + err = svc.audit.ChildResUpdateAudit(cts.Kit, enumor.UrlRuleAuditResType, lblInfo.ID, ruleID, updateFields) + if err != nil { + logs.Errorf("create update rule audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return nil, svc.client.HCService().TCloud.Clb.UpdateUrlRule(cts.Kit, lblID, ruleID, req) +} + +// BatchDeleteBizTCloudUrlRule 批量删除规则 +func (svc *lbSvc) BatchDeleteBizTCloudUrlRule(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + req := new(hcproto.TCloudRuleDeleteByIDReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lblInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, lblID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = handler.BizOperateAuth(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.UrlRuleAuditResType, + Action: meta.Delete, + BasicInfo: lblInfo, + }) + if err != nil { + return nil, err + } + + // 按规则删除审计 + err = svc.audit.ChildResDeleteAudit(cts.Kit, enumor.UrlRuleAuditResType, lblID, req.RuleIDs) + if err != nil { + logs.Errorf("create url rule delete audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + return nil, svc.client.HCService().TCloud.Clb.BatchDeleteUrlRule(cts.Kit, lblID, req) + +} + +// BatchDeleteBizTCloudUrlRuleByDomain 批量按域名删除规则 +func (svc *lbSvc) BatchDeleteBizTCloudUrlRuleByDomain(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener is required") + } + + req := new(hcproto.TCloudRuleDeleteByDomainReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + // 参数校验 + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lblInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, lblID) + if err != nil { + return nil, err + } + + // 业务校验、鉴权 + err = handler.BizOperateAuth(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.UrlRuleAuditResType, + Action: meta.Delete, + BasicInfo: lblInfo, + }) + if err != nil { + return nil, err + } + + // 按域名删除审计 + err = svc.audit.ChildResDeleteAudit(cts.Kit, enumor.UrlRuleDomainAuditResType, lblID, req.Domains) + if err != nil { + logs.Errorf("create url rule delete audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return nil, svc.client.HCService().TCloud.Clb.BatchDeleteUrlRuleByDomain(cts.Kit, lblID, req) + +} diff --git a/cmd/cloud-server/service/load-balancer/update.go b/cmd/cloud-server/service/load-balancer/update.go new file mode 100644 index 0000000000..a82282d8d4 --- /dev/null +++ b/cmd/cloud-server/service/load-balancer/update.go @@ -0,0 +1,394 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "encoding/json" + "fmt" + + cloudserver "hcm/pkg/api/cloud-server" + cslb "hcm/pkg/api/cloud-server/load-balancer" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + hclbproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" + "hcm/pkg/tools/hooks/handler" +) + +// UpdateBizTCloudLoadBalancer 业务下更新clb +func (svc *lbSvc) UpdateBizTCloudLoadBalancer(cts *rest.Contexts) (any, error) { + + lbID := cts.PathParameter("id").String() + if len(lbID) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + req := new(hclbproto.TCloudLBUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.LoadBalancerCloudResType, + lbID) + if err != nil { + logs.Errorf("getLoadBalancer resource vendor failed, id: %s, err: %s, rid: %s", lbID, err, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = handler.BizOperateAuth(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Update, + BasicInfo: baseInfo}) + if err != nil { + return nil, err + } + + // create update audit. + updateFields, err := converter.StructToMap(req) + if err != nil { + logs.Errorf("convert request to map failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + if err = svc.audit.ResUpdateAudit(cts.Kit, enumor.LoadBalancerAuditResType, lbID, updateFields); err != nil { + logs.Errorf("create update audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + switch baseInfo.Vendor { + case enumor.TCloud: + return nil, svc.client.HCService().TCloud.Clb.Update(cts.Kit, lbID, req) + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } + +} + +// UpdateBizTargetGroup update biz target group. +func (svc *lbSvc) UpdateBizTargetGroup(cts *rest.Contexts) (any, error) { + return svc.updateTargetGroup(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) updateTargetGroup(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) (any, error) { + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.TargetGroupCloudResType, id) + if err != nil { + return nil, err + } + err = authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, + ResType: meta.TargetGroup, + Action: meta.Update, + BasicInfo: baseInfo, + }) + if err != nil { + logs.Errorf("update target group basic info auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch baseInfo.Vendor { + case enumor.TCloud: + return svc.batchUpdateTCloudTargetGroup(cts, id) + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } +} + +// 更新目标组基本信息 +func (svc *lbSvc) batchUpdateTCloudTargetGroup(cts *rest.Contexts, id string) (interface{}, error) { + req := new(cslb.TargetGroupUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + req.IDs = append(req.IDs, id) + + // 检查目标组是否已绑定RS,如已绑定则不能更新region、vpc + targetList, err := svc.getTargetByTGIDs(cts.Kit, req.IDs) + if err != nil { + return nil, err + } + + if len(targetList) > 0 && (len(req.Region) > 0 || len(req.CloudVpcID) > 0) { + return nil, errf.New(errf.InvalidParameter, "target group has bind rs, region or vpc cannot be update") + } + + dbReq := &dataproto.TargetGroupUpdateReq{ + IDs: req.IDs, + Name: req.Name, + VpcID: req.VpcID, + CloudVpcID: req.CloudVpcID, + Region: req.Region, + Protocol: req.Protocol, + Port: req.Port, + Weight: req.Weight, + } + err = svc.client.DataService().TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(cts.Kit, dbReq) + if err != nil { + logs.Errorf("update tcloud target group failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// UpdateBizTargetGroupHealth update biz target group health check +func (svc *lbSvc) UpdateBizTargetGroupHealth(cts *rest.Contexts) (any, error) { + return svc.updateTargetGroupHealth(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) updateTargetGroupHealth(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) ( + any, error) { + + tgID := cts.PathParameter("id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target group id is required") + } + + baseInfo, err := svc.client.DataService().Global.Cloud. + GetResBasicInfo(cts.Kit, enumor.TargetGroupCloudResType, tgID) + if err != nil { + return nil, err + } + err = authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, + ResType: meta.TargetGroup, + Action: meta.Update, + BasicInfo: baseInfo, + }) + if err != nil { + logs.Errorf("update target group health check auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch baseInfo.Vendor { + case enumor.TCloud: + return svc.updateTCloudTargetGroupHealthCheck(cts, tgID) + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } +} + +func (svc *lbSvc) updateTCloudTargetGroupHealthCheck(cts *rest.Contexts, tgID string) (any, error) { + + req := new(hclbproto.HealthCheckUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 更新云上监听器 + if err := svc.updateRelatedListenerHealthCheck(cts.Kit, tgID, req); err != nil { + return nil, err + } + + // 3. 更新db + dbReq := &dataproto.TargetGroupUpdateReq{ + IDs: []string{tgID}, + HealthCheck: req.HealthCheck, + } + + err := svc.client.DataService().TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(cts.Kit, dbReq) + if err != nil { + logs.Errorf("update db tcloud target group failed, err: %v, req: %+v, rid: %s", dbReq, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +func (svc *lbSvc) updateRelatedListenerHealthCheck(kt *kit.Kit, tgID string, + healthReq *hclbproto.HealthCheckUpdateReq) error { + // 1. 获取目标组关联监听器 + relListReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + relResp, err := svc.client.DataService().Global.LoadBalancer.ListTargetGroupListenerRel(kt, relListReq) + if err != nil { + return err + } + if len(relResp.Details) == 0 { + // 无关联关系 直接返回 + return nil + } + + // 本地目标组只有一个关联的规则或者监听器 + rel := relResp.Details[0] + // 2. 更新云上监听器/规则 + switch rel.ListenerRuleType { + case enumor.Layer7RuleType: + // 仅更新规则的健康检查字段 + req := &hclbproto.TCloudRuleUpdateReq{HealthCheck: healthReq.HealthCheck} + err := svc.client.HCService().TCloud.Clb.UpdateUrlRule(kt, rel.LblID, rel.ListenerRuleID, req) + if err != nil { + logs.Errorf("fail to update health check of rule, err: %v, listener id: %s, rule id: %s, rid: %s", + err, rel.LblID, rel.ListenerRuleID, kt.Rid) + return err + } + case enumor.Layer4RuleType: + err := svc.client.HCService().TCloud.Clb.UpdateListenerHealthCheck(kt, rel.LblID, healthReq) + if err != nil { + logs.Errorf("fail to update health check of listener, err: %v, listener id: %s, rid: %s", + err, rel.LblID, kt.Rid) + return err + } + } + return nil +} + +// UpdateBizListener update biz listener. +func (svc *lbSvc) UpdateBizListener(cts *rest.Contexts) (interface{}, error) { + return svc.updateListener(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) updateListener(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + req := new(cloudserver.ResourceCreateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("update listener request decode failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if len(req.AccountID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "account_id is required") + } + + // authorized instances + basicInfo := &types.CloudResourceBasicInfo{ + AccountID: req.AccountID, + } + err := authHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.Listener, + Action: meta.Update, BasicInfo: basicInfo}) + if err != nil { + logs.Errorf("update listener auth failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + info, err := svc.client.DataService().Global.Cloud.GetResBasicInfo( + cts.Kit, enumor.AccountCloudResType, req.AccountID) + if err != nil { + logs.Errorf("get account basic info failed, accID: %s, err: %v, rid: %s", req.AccountID, err, cts.Kit.Rid) + return nil, err + } + + switch info.Vendor { + case enumor.TCloud: + return svc.batchUpdateTCloudListener(cts.Kit, req.Data, id) + default: + return nil, fmt.Errorf("vendor: %s not support", info.Vendor) + } +} + +func (svc *lbSvc) batchUpdateTCloudListener(kt *kit.Kit, body json.RawMessage, id string) (interface{}, error) { + req := new(hclbproto.ListenerWithRuleUpdateReq) + if err := json.Unmarshal(body, req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.client.HCService().TCloud.Clb.UpdateListener(kt, id, req) + if err != nil { + logs.Errorf("update tcloud listener failed, req: %+v, err: %v, rid: %s", req, err, kt.Rid) + return nil, err + } + + return nil, nil +} + +// UpdateBizDomainAttr update biz domain attr. +func (svc *lbSvc) UpdateBizDomainAttr(cts *rest.Contexts) (interface{}, error) { + return svc.updateDomainAttr(cts, handler.BizOperateAuth) +} + +func (svc *lbSvc) updateDomainAttr(cts *rest.Contexts, authHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "lbl_id is required") + } + + req := new(hclbproto.DomainAttrUpdateReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("update listener request decode failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + // authorized instances + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, enumor.ListenerCloudResType, lblID) + if err != nil { + logs.Errorf("get listener resource vendor failed, lblID: %s, err: %s, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + + err = authHandler(cts, &handler.ValidWithAuthOption{ + Authorizer: svc.authorizer, + ResType: meta.LoadBalancer, + Action: meta.Update, + BasicInfo: baseInfo, + }) + if err != nil { + return nil, err + } + + switch baseInfo.Vendor { + case enumor.TCloud: + err = svc.client.HCService().TCloud.Clb.UpdateDomainAttr(cts.Kit, lblID, req) + if err != nil { + logs.Errorf("update tcloud listener url rule domain attr failed, lblID: %s, req: %+v, err: %v, rid: %s", + lblID, req, err, cts.Kit.Rid) + return nil, err + } + return nil, nil + default: + return nil, fmt.Errorf("vendor: %s not support", baseInfo.Vendor) + } +} diff --git a/cmd/cloud-server/service/security-group/associate_load_balancer.go b/cmd/cloud-server/service/security-group/associate_load_balancer.go new file mode 100644 index 0000000000..317e1a5534 --- /dev/null +++ b/cmd/cloud-server/service/security-group/associate_load_balancer.go @@ -0,0 +1,195 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package securitygroup + +import ( + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/api/data-service/cloud" + hclb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + "hcm/pkg/iam/meta" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/hooks/handler" +) + +// AssociateLb associate lb. +func (svc *securityGroupSvc) AssociateLb(cts *rest.Contexts) (interface{}, error) { + return svc.associateLb(cts, handler.ResOperateAuth) +} + +// AssociateBizLb associate biz lb. +func (svc *securityGroupSvc) AssociateBizLb(cts *rest.Contexts) (interface{}, error) { + return svc.associateLb(cts, handler.BizOperateAuth) +} + +func (svc *securityGroupSvc) associateLb(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + req := new(hclb.TCloudSetLbSecurityGroupReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 鉴权和校验资源分配状态和回收状态 + basicReq := &cloud.BatchListResourceBasicInfoReq{ + Items: []cloud.ListResourceBasicInfoReq{ + {ResourceType: enumor.SecurityGroupCloudResType, IDs: req.SecurityGroupIDs, + Fields: types.CommonBasicInfoFields}, + {ResourceType: enumor.LoadBalancerCloudResType, IDs: []string{req.LbID}, + Fields: types.CommonBasicInfoFields}, + }, + } + + basicInfos, err := svc.client.DataService().Global.Cloud.BatchListResBasicInfo(cts.Kit, basicReq) + if err != nil { + logs.Errorf("batch list lb resource basic info failed, err: %v, req: %+v, rid: %s", err, basicReq, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.SecurityGroup, + Action: meta.Associate, BasicInfos: basicInfos}) + if err != nil { + logs.Errorf("batch list lb resource basic info failed, err: %v, req: %+v, rid: %s", err, basicReq, cts.Kit.Rid) + return nil, err + } + + var vendor enumor.Vendor + for _, info := range basicInfos { + vendor = info.Vendor + break + } + + for _, sgID := range req.SecurityGroupIDs { + // create operation audit. + audit := protoaudit.CloudResourceOperationInfo{ + ResType: enumor.SecurityGroupRuleAuditResType, + ResID: sgID, + Action: protoaudit.Associate, + AssociatedResType: enumor.LoadBalancerAuditResType, + AssociatedResID: req.LbID, + } + if err = svc.audit.ResOperationAudit(cts.Kit, audit); err != nil { + logs.Errorf("create lb operation audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + } + + switch vendor { + case enumor.TCloud: + err = svc.client.HCService().TCloud.SecurityGroup.AssociateLb(cts.Kit.Ctx, cts.Kit.Header(), req) + default: + return nil, errf.Newf(errf.Unknown, "vendor: %s not support", vendor) + } + + if err != nil { + logs.Errorf("security group associate lb failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// DisassociateLb disassociate lb. +func (svc *securityGroupSvc) DisassociateLb(cts *rest.Contexts) (interface{}, error) { + return svc.disassociateLb(cts, handler.ResOperateAuth) +} + +// DisassociateBizLb disassociate biz lb. +func (svc *securityGroupSvc) DisassociateBizLb(cts *rest.Contexts) (interface{}, error) { + return svc.disassociateLb(cts, handler.BizOperateAuth) +} + +func (svc *securityGroupSvc) disassociateLb(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + req := new(hclb.TCloudDisAssociateLbSecurityGroupReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 鉴权和校验资源分配状态和回收状态 + basicReq := &cloud.BatchListResourceBasicInfoReq{ + Items: []cloud.ListResourceBasicInfoReq{ + {ResourceType: enumor.SecurityGroupCloudResType, IDs: []string{req.SecurityGroupID}, + Fields: types.CommonBasicInfoFields}, + {ResourceType: enumor.LoadBalancerCloudResType, + IDs: []string{req.LbID}, Fields: types.CommonBasicInfoFields}, + }, + } + + basicInfos, err := svc.client.DataService().Global.Cloud.BatchListResBasicInfo(cts.Kit, basicReq) + if err != nil { + logs.Errorf("batch list lb resource basic info failed, err: %v, req: %+v, rid: %s", err, basicReq, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.SecurityGroup, + Action: meta.Disassociate, BasicInfos: basicInfos}) + if err != nil { + logs.Errorf("batch list lb resource basic info failed, err: %v, req: %+v, rid: %s", err, basicReq, cts.Kit.Rid) + return nil, err + } + + var vendor enumor.Vendor + for _, info := range basicInfos { + vendor = info.Vendor + break + } + + // create operation audit. + audit := protoaudit.CloudResourceOperationInfo{ + ResType: enumor.SecurityGroupRuleAuditResType, + ResID: req.SecurityGroupID, + Action: protoaudit.Disassociate, + AssociatedResType: enumor.LoadBalancerAuditResType, + AssociatedResID: req.LbID, + } + if err = svc.audit.ResOperationAudit(cts.Kit, audit); err != nil { + logs.Errorf("create operation audit failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + switch vendor { + case enumor.TCloud: + err = svc.client.HCService().TCloud.SecurityGroup.DisassociateLb(cts.Kit.Ctx, cts.Kit.Header(), req) + default: + return nil, errf.Newf(errf.Unknown, "vendor: %s not support", vendor) + } + + if err != nil { + logs.Errorf("security group disassociate lb failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/cloud-server/service/security-group/init.go b/cmd/cloud-server/service/security-group/init.go index be3bbff5e5..e8dab6a4df 100644 --- a/cmd/cloud-server/service/security-group/init.go +++ b/cmd/cloud-server/service/security-group/init.go @@ -89,6 +89,12 @@ func InitSecurityGroupService(c *capability.Capability) { svc.AssociateBizNIC) h.Add("DisAssociateBizNIC", http.MethodPost, "/bizs/{bk_biz_id}/security_groups/disassociate/network_interfaces", svc.DisAssociateBizNIC) + h.Add("ListBizSecurityGroupsByResID", http.MethodGet, "/bizs/{bk_biz_id}/security_groups/res/{res_type}/{res_id}", + svc.ListBizSecurityGroupsByResID) + h.Add("AssociateBizLb", http.MethodPost, + "/bizs/{bk_biz_id}/security_groups/associate/load_balancers", svc.AssociateBizLb) + h.Add("DisassociateBizLb", http.MethodPost, "/bizs/{bk_biz_id}/security_groups/disassociate/load_balancers", + svc.DisassociateBizLb) h.Add("CreateBizSGRule", http.MethodPost, "/bizs/{bk_biz_id}/vendors/{vendor}/security_groups/{security_group_id}/rules/create", svc.CreateBizSGRule) diff --git a/cmd/cloud-server/service/security-group/query.go b/cmd/cloud-server/service/security-group/query.go index 7875f37615..4c6c972e41 100644 --- a/cmd/cloud-server/service/security-group/query.go +++ b/cmd/cloud-server/service/security-group/query.go @@ -351,3 +351,55 @@ func (svc *securityGroupSvc) listSGByCvmIDForAzure(kt *kit.Kit, cvmID string) (i return sgs, nil } + +// ListSecurityGroupsByResID list security groups by res_id. +func (svc *securityGroupSvc) ListSecurityGroupsByResID(cts *rest.Contexts) (interface{}, error) { + return svc.listSGByResID(cts, handler.ResOperateAuth) +} + +// ListBizSecurityGroupsByResID list biz security groups by res_id. +func (svc *securityGroupSvc) ListBizSecurityGroupsByResID(cts *rest.Contexts) (interface{}, error) { + return svc.listSGByResID(cts, handler.BizOperateAuth) +} + +func (svc *securityGroupSvc) listSGByResID(cts *rest.Contexts, validHandler handler.ValidWithAuthHandler) ( + interface{}, error) { + + resID := cts.PathParameter("res_id").String() + if len(resID) == 0 { + return nil, errf.New(errf.InvalidParameter, "res_id is required") + } + + resType := enumor.CloudResourceType(cts.PathParameter("res_type").String()) + if len(resType) == 0 { + return nil, errf.New(errf.InvalidParameter, "res_type is required") + } + + baseInfo, err := svc.client.DataService().Global.Cloud.GetResBasicInfo(cts.Kit, resType, resID) + if err != nil { + logs.Errorf("get resource vendor failed, err: %s, resID: %s, resType: %s, rid: %s", + err, resID, resType, cts.Kit.Rid) + return nil, err + } + + // validate biz and authorize + err = validHandler(cts, &handler.ValidWithAuthOption{Authorizer: svc.authorizer, ResType: meta.SecurityGroup, + Action: meta.Find, BasicInfo: baseInfo}) + if err != nil { + logs.Errorf("list security group by resID failed, id: %s, err: %v, rid: %s", resID, err, cts.Kit.Rid) + return nil, err + } + + listReq := &dataproto.SGCommonRelWithSecurityGroupListReq{ + ResIDs: []string{resID}, + ResType: resType, + } + result, err := svc.client.DataService().Global.SGCommonRel.ListWithSecurityGroup(cts.Kit, listReq) + if err != nil { + logs.Errorf("list security group by res_id failed, resID: %s, err: %v, req: %v, rid: %s", + resID, err, cts.Kit.Rid, cts.Kit.Rid) + return nil, err + } + + return result, nil +} diff --git a/cmd/cloud-server/service/service.go b/cmd/cloud-server/service/service.go index d40a76a320..155c0e2ef1 100644 --- a/cmd/cloud-server/service/service.go +++ b/cmd/cloud-server/service/service.go @@ -36,9 +36,11 @@ import ( approvalprocess "hcm/cmd/cloud-server/service/approval_process" argstpl "hcm/cmd/cloud-server/service/argument-template" "hcm/cmd/cloud-server/service/assign" + asynctask "hcm/cmd/cloud-server/service/async-task" "hcm/cmd/cloud-server/service/audit" "hcm/cmd/cloud-server/service/bill" "hcm/cmd/cloud-server/service/capability" + "hcm/cmd/cloud-server/service/cert" cloudselection "hcm/cmd/cloud-server/service/cloud-selection" "hcm/cmd/cloud-server/service/cvm" "hcm/cmd/cloud-server/service/disk" @@ -46,6 +48,7 @@ import ( "hcm/cmd/cloud-server/service/firewall" "hcm/cmd/cloud-server/service/image" instancetype "hcm/cmd/cloud-server/service/instance-type" + loadbalancer "hcm/cmd/cloud-server/service/load-balancer" networkinterface "hcm/cmd/cloud-server/service/network-interface" "hcm/cmd/cloud-server/service/recycle" "hcm/cmd/cloud-server/service/region" @@ -95,6 +98,38 @@ type Service struct { // NewService create a service instance. func NewService(sd serviced.ServiceDiscover) (*Service, error) { + apiClientSet, esbClient, svr, err := getCloudClientSvr(sd) + if err != nil { + return nil, err + } + + etcdCfg, err := cc.CloudServer().Service.Etcd.ToConfig() + if err != nil { + return nil, err + } + err = lock.InitManger(etcdCfg, int64(cc.CloudServer().CloudResource.Sync.SyncFrequencyLimitingTimeMin)*60) + if err != nil { + return nil, err + } + + if cc.CloudServer().CloudResource.Sync.Enable { + interval := time.Duration(cc.CloudServer().CloudResource.Sync.SyncIntervalMin) * time.Minute + go sync.CloudResourceSync(interval, sd, apiClientSet) + } + + if cc.CloudServer().BillConfig.Enable { + interval := time.Duration(cc.CloudServer().BillConfig.SyncIntervalMin) * time.Minute + go bill.CloudBillConfigCreate(interval, sd, apiClientSet) + } + + recycle.RecycleTiming(apiClientSet, sd, cc.CloudServer().Recycle, esbClient) + + go appcvm.TimingHandleDeliverApplication(svr.client, 2*time.Second) + + return svr, nil +} + +func getCloudClientSvr(sd serviced.ServiceDiscover) (*client.ClientSet, esb.Client, *Service, error) { tls := cc.CloudServer().Network.TLS var tlsConfig *ssl.TLSConfig if tls.Enable() { @@ -110,39 +145,39 @@ func NewService(sd serviced.ServiceDiscover) (*Service, error) { // initiate system api client set. restCli, err := restcli.NewClient(tlsConfig) if err != nil { - return nil, err + return nil, nil, nil, err } apiClientSet := client.NewClientSet(restCli, sd) authorizer, err := auth.NewAuthorizer(sd, tls) if err != nil { - return nil, err + return nil, nil, nil, err } // 加解密器 cipher, err := newCipherFromConfig(cc.CloudServer().Crypto) if err != nil { - return nil, err + return nil, nil, nil, err } // 创建ESB Client esbConfig := cc.CloudServer().Esb esbClient, err := esb.NewClient(&esbConfig, metrics.Register()) if err != nil { - return nil, err + return nil, nil, nil, err } itsmCfg := cc.CloudServer().Itsm itsmCli, err := itsm.NewClient(&itsmCfg, metrics.Register()) if err != nil { logs.Errorf("failed to create itsm client, err: %v", err) - return nil, err + return nil, nil, nil, err } bkbaseCfg := cc.CloudServer().CloudSelection.BkBase bkbaseCli, err := bkbase.NewClient(&bkbaseCfg.ApiGateway, metrics.Register()) if err != nil { logs.Errorf("failed to create bkbase client, err: %v", err) - return nil, err + return nil, nil, nil, err } svr := &Service{ @@ -155,26 +190,7 @@ func NewService(sd serviced.ServiceDiscover) (*Service, error) { bkBaseCli: bkbaseCli, } - etcdCfg, err := cc.CloudServer().Service.Etcd.ToConfig() - if err != nil { - return nil, err - } - err = lock.InitManger(etcdCfg, int64(cc.CloudServer().CloudResource.Sync.SyncFrequencyLimitingTimeMin)*60) - if err != nil { - return nil, err - } - if cc.CloudServer().CloudResource.Sync.Enable { - interval := time.Duration(cc.CloudServer().CloudResource.Sync.SyncIntervalMin) * time.Minute - go sync.CloudResourceSync(interval, sd, apiClientSet) - } - if cc.CloudServer().BillConfig.Enable { - interval := time.Duration(cc.CloudServer().BillConfig.SyncIntervalMin) * time.Minute - go bill.CloudBillConfigCreate(interval, sd, apiClientSet) - } - recycle.RecycleTiming(apiClientSet, sd, cc.CloudServer().Recycle, esbClient) - - go appcvm.TimingHandleDeliverApplication(svr.client, 2*time.Second) - return svr, nil + return apiClientSet, esbClient, svr, nil } // newCipherFromConfig 根据配置文件里的加密配置,选择配置的算法并生成对应的加解密器 @@ -286,6 +302,9 @@ func (s *Service) apiSet(bkHcmUrl string) *restful.Container { approvalprocess.InitService(c) cloudselection.InitService(c) argstpl.InitArgsTplService(c) + cert.InitCertService(c) + loadbalancer.InitService(c) + asynctask.InitService(c) return restful.NewContainer().Add(c.WebService) } diff --git a/cmd/cloud-server/service/sync/tcloud/argument_template.go b/cmd/cloud-server/service/sync/tcloud/argument_template.go index 12da1967df..4c53f77cd3 100644 --- a/cmd/cloud-server/service/sync/tcloud/argument_template.go +++ b/cmd/cloud-server/service/sync/tcloud/argument_template.go @@ -32,7 +32,8 @@ import ( ) // SyncArgsTpl ... -func SyncArgsTpl(kt *kit.Kit, cliSet *client.ClientSet, accountID string, sd *detail.SyncDetail) error { +func SyncArgsTpl(kt *kit.Kit, cliSet *client.ClientSet, accountID string, + regions []string, sd *detail.SyncDetail) error { // 重新设置rid方便定位 kt = kt.NewSubKit() diff --git a/cmd/cloud-server/service/sync/tcloud/cert.go b/cmd/cloud-server/service/sync/tcloud/cert.go new file mode 100644 index 0000000000..4a8a9224ed --- /dev/null +++ b/cmd/cloud-server/service/sync/tcloud/cert.go @@ -0,0 +1,67 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "time" + + "hcm/cmd/cloud-server/service/sync/detail" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/client" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +// SyncCert ... +func SyncCert(kt *kit.Kit, cliSet *client.ClientSet, accountID string, regions []string, sd *detail.SyncDetail) error { + // 重新设置rid方便定位 + kt = kt.NewSubKit() + + start := time.Now() + logs.V(3).Infof("tcloud account[%s] sync cert start, time: %v, rid: %s", accountID, start, kt.Rid) + + // 同步中 + if err := sd.ResSyncStatusSyncing(enumor.CertCloudResType); err != nil { + return err + } + + defer func() { + logs.V(3).Infof("tcloud account[%s] sync cert end, cost: %v, rid: %s", accountID, time.Since(start), kt.Rid) + }() + + if len(regions) > 0 { + req := &sync.TCloudSyncReq{ + AccountID: accountID, + Region: regions[0], + } + if err := cliSet.HCService().TCloud.Cert.SyncCert(kt.Ctx, kt.Header(), req); err != nil { + logs.Errorf("sync tcloud cert failed, req: %+v, err: %v, rid: %s", req, err, kt.Rid) + return err + } + } + + // 同步成功 + if err := sd.ResSyncStatusSuccess(enumor.CertCloudResType); err != nil { + return err + } + + return nil +} diff --git a/cmd/cloud-server/service/sync/tcloud/load_balancer.go b/cmd/cloud-server/service/sync/tcloud/load_balancer.go new file mode 100644 index 0000000000..0576182943 --- /dev/null +++ b/cmd/cloud-server/service/sync/tcloud/load_balancer.go @@ -0,0 +1,71 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "time" + + "hcm/cmd/cloud-server/service/sync/detail" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/client" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +// SyncLoadBalancer 同步负载均衡及其相关资源 +func SyncLoadBalancer(kt *kit.Kit, cliSet *client.ClientSet, accountID string, regions []string, + sd *detail.SyncDetail) error { + + // 重新设置rid方便定位 + kt = kt.NewSubKit() + + start := time.Now() + logs.V(3).Infof("tcloud account[%s] sync load balancer start, time: %v, rid: %s", accountID, start, kt.Rid) + + // 同步详情同步中 + if err := sd.ResSyncStatusSyncing(enumor.LoadBalancerCloudResType); err != nil { + return err + } + + defer func() { + logs.V(3).Infof("tcloud account[%s] sync load balancer end, cost: %v, rid: %s", + accountID, time.Since(start), kt.Rid) + }() + + for _, region := range regions { + req := &sync.TCloudSyncReq{ + AccountID: accountID, + Region: region, + } + if err := cliSet.HCService().TCloud.Clb.SyncLoadBalancer(kt, req); err != nil { + logs.Errorf("sync tcloud load balancer failed, err: %v, req: %v, rid: %s", err, req, kt.Rid) + return err + } + } + + // 同步详情同步成功 + if err := sd.ResSyncStatusSuccess(enumor.LoadBalancerCloudResType); err != nil { + return err + } + + return nil +} diff --git a/cmd/cloud-server/service/sync/tcloud/sub_account.go b/cmd/cloud-server/service/sync/tcloud/sub_account.go index 153523c861..4dd44b39b9 100644 --- a/cmd/cloud-server/service/sync/tcloud/sub_account.go +++ b/cmd/cloud-server/service/sync/tcloud/sub_account.go @@ -31,7 +31,7 @@ import ( ) // SyncSubAccount sync sub account -func SyncSubAccount(kt *kit.Kit, cliSet *client.ClientSet, accountID string, +func SyncSubAccount(kt *kit.Kit, cliSet *client.ClientSet, accountID string, regions []string, sd *detail.SyncDetail) error { // 重新设置rid方便定位 diff --git a/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go b/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go index 803871b0d0..3377d98164 100644 --- a/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go +++ b/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go @@ -38,6 +38,10 @@ type SyncAllResourceOption struct { SyncPublicResource bool `json:"sync_public_resource" validate:"omitempty"` } +// ResSyncFunc 资源同步函数 +type ResSyncFunc func(kt *kit.Kit, cliSet *client.ClientSet, accountID string, regions []string, + sd *detail.SyncDetail) error + // Validate SyncAllResourceOption func (opt *SyncAllResourceOption) Validate() error { return validator.Validate.Struct(opt) @@ -50,11 +54,9 @@ func SyncAllResource(kt *kit.Kit, cliSet *client.ClientSet, if err := opt.Validate(); err != nil { return "", err } - start := time.Now() logs.V(3).Infof("tcloud account[%s] sync all resource start, time: %v, opt: %v, rid: %s", opt.AccountID, start, opt, kt.Rid) - var hitErr error defer func() { if hitErr != nil { @@ -88,42 +90,41 @@ func SyncAllResource(kt *kit.Kit, cliSet *client.ClientSet, Vendor: string(enumor.TCloud), } - if hitErr = SyncDisk(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.DiskCloudResType, hitErr - } - - if hitErr = SyncVpc(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.VpcCloudResType, hitErr + syncFuncMap := map[enumor.CloudResourceType]ResSyncFunc{ + enumor.DiskCloudResType: SyncDisk, + enumor.VpcCloudResType: SyncVpc, + enumor.SubnetCloudResType: SyncSubnet, + enumor.EipCloudResType: SyncEip, + enumor.ArgumentTemplateResType: SyncArgsTpl, + enumor.SecurityGroupCloudResType: SyncSG, + enumor.CvmCloudResType: SyncCvm, + enumor.CertCloudResType: SyncCert, + enumor.LoadBalancerCloudResType: SyncLoadBalancer, + enumor.RouteTableCloudResType: SyncRouteTable, + enumor.SubAccountCloudResType: SyncSubAccount, } - if hitErr = SyncSubnet(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.SubnetCloudResType, hitErr - } - - if hitErr = SyncEip(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.EipCloudResType, hitErr - } - - // 参数模版同步需要放到安全组前面 - if hitErr = SyncArgsTpl(kt, cliSet, opt.AccountID, sd); hitErr != nil { - return enumor.ArgumentTemplateResType, hitErr - } - - if hitErr = SyncSG(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.SecurityGroupCloudResType, hitErr - } - - if hitErr = SyncCvm(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.CvmCloudResType, hitErr + for _, resType := range getSyncOrder() { + if hitErr = syncFuncMap[resType](kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { + return resType, hitErr + } } - if hitErr = SyncRouteTable(kt, cliSet, opt.AccountID, regions, sd); hitErr != nil { - return enumor.RouteTableCloudResType, hitErr - } + return "", nil +} - if hitErr = SyncSubAccount(kt, cliSet, opt.AccountID, sd); hitErr != nil { - return enumor.SubAccountCloudResType, hitErr +func getSyncOrder() []enumor.CloudResourceType { + return []enumor.CloudResourceType{ + enumor.DiskCloudResType, + enumor.VpcCloudResType, + enumor.SubnetCloudResType, + enumor.EipCloudResType, + enumor.ArgumentTemplateResType, + enumor.SecurityGroupCloudResType, + enumor.CvmCloudResType, + enumor.CertCloudResType, + enumor.LoadBalancerCloudResType, + enumor.RouteTableCloudResType, + enumor.SubAccountCloudResType, } - - return "", nil } diff --git a/cmd/data-service/service/audit/cloud/cert.go b/cmd/data-service/service/audit/cloud/cert.go new file mode 100644 index 0000000000..afe42cb662 --- /dev/null +++ b/cmd/data-service/service/audit/cloud/cert.go @@ -0,0 +1,141 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cloud + +import ( + "hcm/pkg/api/core" + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableaudit "hcm/pkg/dal/table/audit" + tablecert "hcm/pkg/dal/table/cloud/cert" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +func (ad Audit) certAssignAuditBuild(kt *kit.Kit, assigns []protoaudit.CloudResourceAssignInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(assigns)) + for _, one := range assigns { + ids = append(ids, one.ResID) + } + idMap, err := ad.listCert(kt, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(assigns)) + for _, one := range assigns { + tmpData, exist := idMap[one.ResID] + if !exist { + continue + } + + changed := make(map[string]interface{}) + if one.AssignedResType != enumor.BizAuditAssignedResType { + return nil, errf.New(errf.InvalidParameter, "assigned resource type is invalid") + } + changed["bk_biz_id"] = one.AssignedResID + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: tmpData.CloudID, + ResName: tmpData.Name, + ResType: enumor.SslCertAuditResType, + Action: enumor.Assign, + BkBizID: tmpData.BkBizID, + Vendor: tmpData.Vendor, + AccountID: tmpData.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Changed: changed, + }, + }) + } + + return audits, nil +} + +func (ad Audit) certDeleteAuditBuild(kt *kit.Kit, deletes []protoaudit.CloudResourceDeleteInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(deletes)) + for _, one := range deletes { + ids = append(ids, one.ResID) + } + + idMap, err := ad.listCert(kt, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0) + for _, one := range deletes { + resData, exist := idMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: resData.CloudID, + ResName: resData.Name, + ResType: enumor.SslCertAuditResType, + Action: enumor.Delete, + BkBizID: resData.BkBizID, + Vendor: resData.Vendor, + AccountID: resData.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: resData, + }, + }) + } + + return audits, nil +} + +func (ad Audit) listCert(kt *kit.Kit, ids []string) (map[string]*tablecert.SslCertTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := ad.dao.Cert().List(kt, opt) + if err != nil { + logs.Errorf("list cert db failed, ids: %v, err: %v, rid: %s", ids, err, kt.Rid) + return nil, err + } + + result := make(map[string]*tablecert.SslCertTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = &one + } + + return result, nil +} diff --git a/cmd/data-service/service/audit/cloud/cloud_audit.go b/cmd/data-service/service/audit/cloud/cloud_audit.go index e79e45663f..e351d8328d 100644 --- a/cmd/data-service/service/audit/cloud/cloud_audit.go +++ b/cmd/data-service/service/audit/cloud/cloud_audit.go @@ -22,6 +22,7 @@ package cloud import ( "hcm/cmd/data-service/service/audit/cloud/cvm" "hcm/cmd/data-service/service/audit/cloud/firewall" + loadbalancer "hcm/cmd/data-service/service/audit/cloud/load-balancer" networkinterface "hcm/cmd/data-service/service/audit/cloud/network-interface" routetable "hcm/cmd/data-service/service/audit/cloud/route-table" securitygroup "hcm/cmd/data-service/service/audit/cloud/security-group" @@ -39,6 +40,7 @@ func NewCloudAudit(dao dao.Set) *Audit { subnet: subnet.NewSubnet(dao), networkInterface: networkinterface.NewNetworkInterface(dao), routeTable: routetable.NewRouteTable(dao), + loadBalancer: loadbalancer.NewLoadBalancer(dao), } } @@ -51,4 +53,5 @@ type Audit struct { subnet *subnet.Subnet networkInterface *networkinterface.NetworkInterface routeTable *routetable.RouteTable + loadBalancer *loadbalancer.LoadBalancer } diff --git a/cmd/data-service/service/audit/cloud/cloud_resource_assign_audit.go b/cmd/data-service/service/audit/cloud/cloud_resource_assign_audit.go index 6a790f10b2..3e193d1766 100644 --- a/cmd/data-service/service/audit/cloud/cloud_resource_assign_audit.go +++ b/cmd/data-service/service/audit/cloud/cloud_resource_assign_audit.go @@ -114,6 +114,10 @@ func (ad Audit) buildAssignAuditInfo(kt *kit.Kit, resType enumor.AuditResourceTy audits, err = ad.routeTable.RouteTableAssignAuditBuild(kt, assigns) case enumor.ArgumentTemplateAuditResType: audits, err = ad.argsTplAssignAuditBuild(kt, assigns) + case enumor.SslCertAuditResType: + audits, err = ad.certAssignAuditBuild(kt, assigns) + case enumor.LoadBalancerAuditResType: + audits, err = ad.loadBalancer.LoadBalancerAssignAuditBuild(kt, assigns) default: return nil, fmt.Errorf("cloud resource type: %s not support", resType) } diff --git a/cmd/data-service/service/audit/cloud/create_resource_delete_audit.go b/cmd/data-service/service/audit/cloud/create_resource_delete_audit.go index 80e6aafbe5..97493ff031 100644 --- a/cmd/data-service/service/audit/cloud/create_resource_delete_audit.go +++ b/cmd/data-service/service/audit/cloud/create_resource_delete_audit.go @@ -97,9 +97,21 @@ func (ad Audit) buildDeleteAuditInfo(kt *kit.Kit, resType enumor.AuditResourceTy audits, err = ad.diskDeleteAuditBuild(kt, deletes) case enumor.ArgumentTemplateAuditResType: audits, err = ad.argsTplDeleteAuditBuild(kt, deletes) + case enumor.SslCertAuditResType: + audits, err = ad.certDeleteAuditBuild(kt, deletes) + case enumor.TargetGroupAuditResType: + audits, err = ad.targetGroupDeleteAuditBuild(kt, deletes) + case enumor.UrlRuleAuditResType: + audits, err = ad.loadBalancer.UrlRuleDeleteAuditBuild(kt, parentID, deletes) + case enumor.UrlRuleDomainAuditResType: + audits, err = ad.loadBalancer.UrlRuleDeleteByDomainAuditBuild(kt, parentID, deletes) + case enumor.ListenerAuditResType: + audits, err = ad.listenerDeleteAuditBuild(kt, deletes) + case enumor.LoadBalancerAuditResType: + audits, err = ad.loadBalancer.LoadBalancerDeleteAuditBuild(kt, deletes) default: - return nil, fmt.Errorf("cloud resource type: %s not support", resType) + return nil, fmt.Errorf("build delete audit cloud resource type: %s not support", resType) } if err != nil { return nil, err diff --git a/cmd/data-service/service/audit/cloud/create_resource_operation_audit.go b/cmd/data-service/service/audit/cloud/create_resource_operation_audit.go index 476db70f9b..524e414bf5 100644 --- a/cmd/data-service/service/audit/cloud/create_resource_operation_audit.go +++ b/cmd/data-service/service/audit/cloud/create_resource_operation_audit.go @@ -87,6 +87,8 @@ func (ad Audit) buildOperationAuditInfo(kt *kit.Kit, resType enumor.AuditResourc audits, err = ad.eipOperationAuditBuild(kt, operations) case enumor.DiskAuditResType: audits, err = ad.diskOperationAuditBuild(kt, operations) + case enumor.TargetGroupAuditResType: + audits, err = ad.loadBalancer.TargetGroupOperationAuditBuild(kt, operations) default: return nil, fmt.Errorf("cloud resource type: %s not support", resType) } diff --git a/cmd/data-service/service/audit/cloud/create_resource_update_audit.go b/cmd/data-service/service/audit/cloud/create_resource_update_audit.go index d46f14c14c..d55bb28ef3 100644 --- a/cmd/data-service/service/audit/cloud/create_resource_update_audit.go +++ b/cmd/data-service/service/audit/cloud/create_resource_update_audit.go @@ -93,6 +93,10 @@ func (ad Audit) buildUpdateAuditInfo(kt *kit.Kit, resType enumor.AuditResourceTy audits, err = ad.subnet.SubnetUpdateAuditBuild(kt, updates) case enumor.CvmAuditResType: audits, err = ad.cvm.CvmUpdateAuditBuild(kt, updates) + case enumor.LoadBalancerAuditResType: + audits, err = ad.loadBalancer.LoadBalancerUpdateAuditBuild(kt, updates) + case enumor.UrlRuleAuditResType: + audits, err = ad.loadBalancer.UrlRuleUpdateAuditBuild(kt, parentID, updates) default: return nil, fmt.Errorf("cloud resource type: %s not support", resType) diff --git a/cmd/data-service/service/audit/cloud/cvm/base.go b/cmd/data-service/service/audit/cloud/cvm/base.go index 28cecbdc25..c4d8e4d504 100644 --- a/cmd/data-service/service/audit/cloud/cvm/base.go +++ b/cmd/data-service/service/audit/cloud/cvm/base.go @@ -193,7 +193,7 @@ func ListCvm(kt *kit.Kit, dao dao.Set, ids []string) (map[string]tablecvm.Table, } list, err := dao.Cvm().List(kt, opt) if err != nil { - logs.Errorf("list c failed, err: %v, ids: %v, rid: %f", err, ids, kt.Rid) + logs.Errorf("list cvm failed, err: %v, ids: %v, rid: %s", err, ids, kt.Rid) return nil, err } diff --git a/cmd/data-service/service/audit/cloud/firewall/firewall.go b/cmd/data-service/service/audit/cloud/firewall/firewall.go index 166065057e..a396c6cde2 100644 --- a/cmd/data-service/service/audit/cloud/firewall/firewall.go +++ b/cmd/data-service/service/audit/cloud/firewall/firewall.go @@ -181,7 +181,7 @@ func (f *Firewall) listFirewallRule(kt *kit.Kit, ids []string) (map[string]table } list, err := f.dao.GcpFirewallRule().List(kt, opt) if err != nil { - logs.Errorf("list gcp firewall rule failed, err: %v, ids: %v, rid: %f", err, ids, kt.Rid) + logs.Errorf("list gcp firewall rule failed, err: %v, ids: %v, rid: %s", err, ids, kt.Rid) return nil, err } diff --git a/cmd/data-service/service/audit/cloud/listener.go b/cmd/data-service/service/audit/cloud/listener.go new file mode 100644 index 0000000000..2cfbc08bf9 --- /dev/null +++ b/cmd/data-service/service/audit/cloud/listener.go @@ -0,0 +1,93 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cloud + +import ( + "hcm/pkg/api/core" + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/criteria/enumor" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableaudit "hcm/pkg/dal/table/audit" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +func (ad Audit) listenerDeleteAuditBuild(kt *kit.Kit, deletes []protoaudit.CloudResourceDeleteInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(deletes)) + for _, one := range deletes { + ids = append(ids, one.ResID) + } + + idMap, err := ad.listListener(kt, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0) + for _, one := range deletes { + resData, exist := idMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: resData.CloudID, + ResName: resData.Name, + ResType: enumor.ListenerAuditResType, + Action: enumor.Delete, + BkBizID: resData.BkBizID, + Vendor: resData.Vendor, + AccountID: resData.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: resData, + }, + }) + } + + return audits, nil +} + +func (ad Audit) listListener(kt *kit.Kit, ids []string) (map[string]*tablelb.LoadBalancerListenerTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := ad.dao.LoadBalancerListener().List(kt, opt) + if err != nil { + logs.Errorf("list listener db failed, ids: %v, err: %v, rid: %s", ids, err, kt.Rid) + return nil, err + } + + result := make(map[string]*tablelb.LoadBalancerListenerTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = &one + } + + return result, nil +} diff --git a/cmd/data-service/service/audit/cloud/load-balancer/base.go b/cmd/data-service/service/audit/cloud/load-balancer/base.go new file mode 100644 index 0000000000..5906cd9041 --- /dev/null +++ b/cmd/data-service/service/audit/cloud/load-balancer/base.go @@ -0,0 +1,503 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + + "hcm/pkg/api/core" + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableaudit "hcm/pkg/dal/table/audit" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/slice" +) + +// NewLoadBalancer new clb. +func NewLoadBalancer(dao dao.Set) *LoadBalancer { + return &LoadBalancer{ + dao: dao, + } +} + +// LoadBalancer define clb audit. +type LoadBalancer struct { + dao dao.Set +} + +// LoadBalancerUpdateAuditBuild clb update audit build. +func (c *LoadBalancer) LoadBalancerUpdateAuditBuild(kt *kit.Kit, updates []protoaudit.CloudResourceUpdateInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(updates)) + for _, one := range updates { + ids = append(ids, one.ResID) + } + idMap, err := ListLoadBalancer(kt, c.dao, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(updates)) + for _, one := range updates { + clbInfo, exist := idMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: clbInfo.CloudID, + ResName: clbInfo.Name, + ResType: enumor.LoadBalancerAuditResType, + Action: enumor.Update, + BkBizID: clbInfo.BkBizID, + Vendor: clbInfo.Vendor, + AccountID: clbInfo.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: clbInfo, + Changed: one.UpdateFields, + }, + }) + } + + return audits, nil +} + +// LoadBalancerDeleteAuditBuild clb delete audit build. +func (c *LoadBalancer) LoadBalancerDeleteAuditBuild(kt *kit.Kit, deletes []protoaudit.CloudResourceDeleteInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(deletes)) + for _, one := range deletes { + ids = append(ids, one.ResID) + } + idMap, err := ListLoadBalancer(kt, c.dao, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(deletes)) + for _, one := range deletes { + clbInfo, exist := idMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: clbInfo.CloudID, + ResName: clbInfo.Name, + ResType: enumor.LoadBalancerAuditResType, + Action: enumor.Delete, + BkBizID: clbInfo.BkBizID, + Vendor: clbInfo.Vendor, + AccountID: clbInfo.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: clbInfo, + }, + }) + } + + return audits, nil +} + +// LoadBalancerAssignAuditBuild clb assign audit build. +func (c *LoadBalancer) LoadBalancerAssignAuditBuild(kt *kit.Kit, assigns []protoaudit.CloudResourceAssignInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(assigns)) + for _, one := range assigns { + ids = append(ids, one.ResID) + } + idMap, err := ListLoadBalancer(kt, c.dao, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(assigns)) + for _, one := range assigns { + clbInfo, exist := idMap[one.ResID] + if !exist { + continue + } + + audit := &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: clbInfo.CloudID, + ResName: clbInfo.Name, + ResType: enumor.LoadBalancerAuditResType, + Action: enumor.Assign, + BkBizID: clbInfo.BkBizID, + Vendor: clbInfo.Vendor, + AccountID: clbInfo.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Changed: map[string]interface{}{ + "bk_biz_id": one.AssignedResID, + }, + }, + } + + switch one.AssignedResType { + case enumor.BizAuditAssignedResType: + audit.Action = enumor.Assign + case enumor.DeliverAssignedResType: + audit.Action = enumor.Deliver + default: + return nil, errf.New(errf.InvalidParameter, "assigned resource type is invalid") + } + + audits = append(audits, audit) + } + + return audits, nil +} + +// UrlRuleUpdateAuditBuild url 规则更新 +func (c *LoadBalancer) UrlRuleUpdateAuditBuild(kt *kit.Kit, lblID string, + updates []protoaudit.CloudResourceUpdateInfo) ([]*tableaudit.AuditTable, error) { + + idListenerMap, err := ListListener(kt, c.dao, []string{lblID}) + if err != nil { + return nil, err + } + + lbl, exist := idListenerMap[lblID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "listener: %s not found", lblID) + } + + switch lbl.Vendor { + case enumor.TCloud: + return c.tcloudUrlRuleUpdateAuditBuild(kt, lbl, updates) + default: + return nil, fmt.Errorf("vendor: %s not support", lbl.Vendor) + } +} + +func (c *LoadBalancer) tcloudUrlRuleUpdateAuditBuild(kt *kit.Kit, lbl tablelb.LoadBalancerListenerTable, + updates []protoaudit.CloudResourceUpdateInfo) ([]*tableaudit.AuditTable, error) { + + ids := slice.Map(updates, func(u protoaudit.CloudResourceUpdateInfo) string { return u.ResID }) + + idListenerRuleMap, err := ListTCloudUrlRule(kt, c.dao, lbl.ID, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(updates)) + for _, one := range updates { + rule, exist := idListenerRuleMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: lbl.CloudID, + ResName: lbl.Name, + ResType: enumor.ListenerAuditResType, + Action: enumor.Update, + BkBizID: lbl.BkBizID, + Vendor: lbl.Vendor, + AccountID: lbl.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: &tableaudit.ChildResAuditData{ + ChildResType: enumor.UrlRuleAuditResType, + Action: enumor.Update, + ChildRes: rule, + }, + Changed: one.UpdateFields, + }, + }) + } + + return audits, nil + +} + +// UrlRuleDeleteAuditBuild 删除规则审计 +func (c *LoadBalancer) UrlRuleDeleteAuditBuild(kt *kit.Kit, lblID string, + deletes []protoaudit.CloudResourceDeleteInfo) ([]*tableaudit.AuditTable, error) { + + idListenerMap, err := ListListener(kt, c.dao, []string{lblID}) + if err != nil { + return nil, err + } + + lbl, exist := idListenerMap[lblID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "listener: %s not found", lblID) + } + + switch lbl.Vendor { + case enumor.TCloud: + return c.tcloudUrlRuleDeleteAuditBuild(kt, lbl, deletes) + default: + return nil, fmt.Errorf("vendor: %s not support", lbl.Vendor) + } +} +func (c *LoadBalancer) tcloudUrlRuleDeleteAuditBuild(kt *kit.Kit, lbl tablelb.LoadBalancerListenerTable, + deletes []protoaudit.CloudResourceDeleteInfo) ([]*tableaudit.AuditTable, error) { + + ids := slice.Map(deletes, func(u protoaudit.CloudResourceDeleteInfo) string { return u.ResID }) + + idRuleMap, err := ListTCloudUrlRule(kt, c.dao, lbl.ID, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(deletes)) + for _, one := range deletes { + ruleInfo, exist := idRuleMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: ruleInfo.CloudID, + ResName: ruleInfo.Name, + ResType: enumor.UrlRuleAuditResType, + Action: enumor.Delete, + BkBizID: lbl.BkBizID, + Vendor: lbl.Vendor, + AccountID: lbl.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: ruleInfo, + }, + }) + } + + return audits, nil +} + +// ListTCloudUrlRule ... +func ListTCloudUrlRule(kt *kit.Kit, dao dao.Set, lblID string, + ruleIds []string) (map[string]tablelb.TCloudLbUrlRuleTable, error) { + + opt := &types.ListOption{ + Filter: tools.ExpressionAnd(tools.RuleEqual("lbl_id", lblID), tools.RuleIn("id", ruleIds)), + Page: core.NewDefaultBasePage(), + } + list, err := dao.LoadBalancerTCloudUrlRule().List(kt, opt) + if err != nil { + logs.Errorf("list tcloud url rule of listener(id=%s) failed, err: %v, ids: %v, rid: %s", + lblID, err, ruleIds, kt.Rid) + return nil, err + } + + result := make(map[string]tablelb.TCloudLbUrlRuleTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = one + } + + return result, nil +} + +// UrlRuleDeleteByDomainAuditBuild 按域名删除url规则 +func (c *LoadBalancer) UrlRuleDeleteByDomainAuditBuild(kt *kit.Kit, lblID string, + deletes []protoaudit.CloudResourceDeleteInfo) ([]*tableaudit.AuditTable, error) { + + idListenerMap, err := ListListener(kt, c.dao, []string{lblID}) + if err != nil { + return nil, err + } + + lbl, exist := idListenerMap[lblID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "listener: %s not found", lblID) + } + + switch lbl.Vendor { + case enumor.TCloud: + return c.tcloudUrlRuleDeleteByDomainAuditBuild(kt, lbl, deletes) + default: + return nil, fmt.Errorf("vendor: %s not support", lbl.Vendor) + } +} + +func (c *LoadBalancer) tcloudUrlRuleDeleteByDomainAuditBuild(kt *kit.Kit, lbl tablelb.LoadBalancerListenerTable, + deletes []protoaudit.CloudResourceDeleteInfo) ([]*tableaudit.AuditTable, error) { + + domains := slice.Map(deletes, func(u protoaudit.CloudResourceDeleteInfo) string { return u.ResID }) + + domainRuleMap, err := ListTCloudUrlRuleByDomain(kt, c.dao, lbl.ID, domains) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(deletes)) + for _, one := range deletes { + rules, exist := domainRuleMap[one.ResID] + if !exist { + // 找不到与域名,返回错误 + return nil, fmt.Errorf("fail to find rule while delete url by domain: %s", one.ResID) + } + // add domain and each into audits + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: one.ResID, + ResName: one.ResID, + ResType: enumor.UrlRuleDomainAuditResType, + Action: enumor.Delete, + BkBizID: lbl.BkBizID, + Vendor: lbl.Vendor, + AccountID: lbl.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: one.ResID, + }, + }) + for _, rule := range rules { + audits = append(audits, &tableaudit.AuditTable{ + ResID: rule.ID, + CloudResID: rule.CloudID, + ResName: rule.Name, + ResType: enumor.UrlRuleAuditResType, + Action: enumor.Delete, + BkBizID: lbl.BkBizID, + Vendor: lbl.Vendor, + AccountID: lbl.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: rule, + }, + }) + } + } + + return audits, nil +} + +// ListTCloudUrlRuleByDomain ... +func ListTCloudUrlRuleByDomain(kt *kit.Kit, dao dao.Set, lblID string, + domains []string) (map[string][]tablelb.TCloudLbUrlRuleTable, error) { + + opt := &types.ListOption{ + Filter: tools.ExpressionAnd(tools.RuleEqual("lbl_id", lblID), tools.RuleIn("domain", domains)), + Page: core.NewDefaultBasePage(), + } + list, err := dao.LoadBalancerTCloudUrlRule().List(kt, opt) + if err != nil { + logs.Errorf("list tcloud url rule of listener(id=%s) failed, err: %v, domains: %v, rid: %s", + lblID, err, domains, kt.Rid) + return nil, err + } + + result := make(map[string][]tablelb.TCloudLbUrlRuleTable, len(list.Details)) + for _, one := range list.Details { + result[one.Domain] = append(result[one.Domain], one) + } + + return result, nil +} + +// ListLoadBalancer list load balancer. +func ListLoadBalancer(kt *kit.Kit, dao dao.Set, ids []string) (map[string]tablelb.LoadBalancerTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := dao.LoadBalancer().List(kt, opt) + if err != nil { + logs.Errorf("list load balancer failed, err: %v, ids: %v, rid: %s", err, ids, kt.Rid) + return nil, err + } + + result := make(map[string]tablelb.LoadBalancerTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = one + } + + return result, nil +} + +// ListTargetGroup list target group. +func ListTargetGroup(kt *kit.Kit, dao dao.Set, ids []string) (map[string]tablelb.LoadBalancerTargetGroupTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := dao.LoadBalancerTargetGroup().List(kt, opt) + if err != nil { + logs.Errorf("list target group failed, err: %v, ids: %v, rid: %s", err, ids, kt.Rid) + return nil, err + } + + result := make(map[string]tablelb.LoadBalancerTargetGroupTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = one + } + + return result, nil +} + +// ListListener list listener. +func ListListener(kt *kit.Kit, dao dao.Set, ids []string) (map[string]tablelb.LoadBalancerListenerTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := dao.LoadBalancerListener().List(kt, opt) + if err != nil { + logs.Errorf("list listener failed, err: %v, ids: %v, rid: %s", err, ids, kt.Rid) + return nil, err + } + + result := make(map[string]tablelb.LoadBalancerListenerTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = one + } + + return result, nil +} diff --git a/cmd/data-service/service/audit/cloud/load-balancer/operation_audit.go b/cmd/data-service/service/audit/cloud/load-balancer/operation_audit.go new file mode 100644 index 0000000000..066a388425 --- /dev/null +++ b/cmd/data-service/service/audit/cloud/load-balancer/operation_audit.go @@ -0,0 +1,127 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + tableaudit "hcm/pkg/dal/table/audit" + "hcm/pkg/kit" +) + +// TargetGroupOperationAuditBuild target group operation audit build. +func (c *LoadBalancer) TargetGroupOperationAuditBuild(kt *kit.Kit, operations []protoaudit.CloudResourceOperationInfo) ( + []*tableaudit.AuditTable, error) { + + lblAssOperations := make([]protoaudit.CloudResourceOperationInfo, 0) + for _, operation := range operations { + switch operation.Action { + case protoaudit.Associate, protoaudit.Disassociate: + switch operation.AssociatedResType { + case enumor.ListenerAuditResType: + lblAssOperations = append(lblAssOperations, operation) + default: + return nil, fmt.Errorf("audit associated resource type: %s not support", operation.AssociatedResType) + } + + default: + return nil, fmt.Errorf("audit action: %s not support", operation.Action) + } + } + + audits := make([]*tableaudit.AuditTable, 0, len(operations)) + if len(lblAssOperations) != 0 { + audit, err := c.listenerAssOperationAuditBuild(kt, lblAssOperations) + if err != nil { + return nil, err + } + + audits = append(audits, audit...) + } + + return audits, nil +} + +func (c *LoadBalancer) listenerAssOperationAuditBuild(kt *kit.Kit, + operations []protoaudit.CloudResourceOperationInfo) ([]*tableaudit.AuditTable, error) { + + tgIDs := make([]string, 0) + lblIDs := make([]string, 0) + for _, one := range operations { + tgIDs = append(tgIDs, one.ResID) + lblIDs = append(lblIDs, one.AssociatedResID) + } + + tgIDMap, err := ListTargetGroup(kt, c.dao, tgIDs) + if err != nil { + return nil, err + } + + lblIDMap, err := ListListener(kt, c.dao, lblIDs) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(operations)) + for _, one := range operations { + tgInfo, exist := tgIDMap[one.ResID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", one.ResID) + } + + lblInfo, exist := lblIDMap[one.AssociatedResID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "listener: %s not found", one.AssociatedResID) + } + + action, err := one.Action.ConvAuditAction() + if err != nil { + return nil, err + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: tgInfo.ID, + CloudResID: tgInfo.CloudID, + ResName: tgInfo.Name, + ResType: enumor.TargetGroupAuditResType, + Action: action, + BkBizID: tgInfo.BkBizID, + Vendor: tgInfo.Vendor, + AccountID: tgInfo.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: &tableaudit.AssociatedOperationAudit{ + AssResType: enumor.ListenerAuditResType, + AssResID: lblInfo.ID, + AssResCloudID: lblInfo.CloudID, + AssResName: lblInfo.Name, + }, + }, + }) + } + + return audits, nil +} diff --git a/cmd/data-service/service/audit/cloud/security-group/security_group.go b/cmd/data-service/service/audit/cloud/security-group/security_group.go index 7c3af52ec8..d3f431173e 100644 --- a/cmd/data-service/service/audit/cloud/security-group/security_group.go +++ b/cmd/data-service/service/audit/cloud/security-group/security_group.go @@ -23,6 +23,7 @@ import ( "fmt" "hcm/cmd/data-service/service/audit/cloud/cvm" + auditlb "hcm/cmd/data-service/service/audit/cloud/load-balancer" networkinterface "hcm/cmd/data-service/service/audit/cloud/network-interface" "hcm/cmd/data-service/service/audit/cloud/subnet" "hcm/pkg/api/core" @@ -209,6 +210,7 @@ func (s *SecurityGroup) OperationAuditBuild(kt *kit.Kit, operations []protoaudit cvmAssOperations := make([]protoaudit.CloudResourceOperationInfo, 0) subnetAssOperations := make([]protoaudit.CloudResourceOperationInfo, 0) niAssOperations := make([]protoaudit.CloudResourceOperationInfo, 0) + clbAssOperations := make([]protoaudit.CloudResourceOperationInfo, 0) for _, operation := range operations { switch operation.Action { case protoaudit.Associate, protoaudit.Disassociate: @@ -219,6 +221,8 @@ func (s *SecurityGroup) OperationAuditBuild(kt *kit.Kit, operations []protoaudit subnetAssOperations = append(subnetAssOperations, operation) case enumor.NetworkInterfaceAuditResType: niAssOperations = append(niAssOperations, operation) + case enumor.LoadBalancerAuditResType: + clbAssOperations = append(clbAssOperations, operation) default: return nil, fmt.Errorf("audit associated resource type: %s not support", operation.AssociatedResType) } @@ -256,6 +260,15 @@ func (s *SecurityGroup) OperationAuditBuild(kt *kit.Kit, operations []protoaudit audits = append(audits, audit...) } + if len(clbAssOperations) != 0 { + audit, err := s.clbAssOperationAuditBuild(kt, clbAssOperations) + if err != nil { + return nil, err + } + + audits = append(audits, audit...) + } + return audits, nil } @@ -286,7 +299,7 @@ func (s *SecurityGroup) cvmAssOperationAuditBuild(kt *kit.Kit, operations []prot return nil, errf.Newf(errf.RecordNotFound, "security group: %s not found", one.ResID) } - cvm, exist := cvmIDMap[one.AssociatedResID] + cvmInfo, exist := cvmIDMap[one.AssociatedResID] if !exist { return nil, errf.Newf(errf.RecordNotFound, "cvm: %s not found", one.AssociatedResID) } @@ -312,9 +325,9 @@ func (s *SecurityGroup) cvmAssOperationAuditBuild(kt *kit.Kit, operations []prot Detail: &tableaudit.BasicDetail{ Data: &tableaudit.AssociatedOperationAudit{ AssResType: enumor.CvmAuditResType, - AssResID: cvm.ID, - AssResCloudID: cvm.CloudID, - AssResName: cvm.Name, + AssResID: cvmInfo.ID, + AssResCloudID: cvmInfo.CloudID, + AssResName: cvmInfo.Name, }, }, }) @@ -350,7 +363,7 @@ func (s *SecurityGroup) subnetAssOperationAuditBuild(kt *kit.Kit, operations []p return nil, errf.Newf(errf.RecordNotFound, "security group: %s not found", one.ResID) } - subnet, exist := subnetIDMap[one.AssociatedResID] + subnetInfo, exist := subnetIDMap[one.AssociatedResID] if !exist { return nil, errf.Newf(errf.RecordNotFound, "subnet: %s not found", one.AssociatedResID) } @@ -361,8 +374,8 @@ func (s *SecurityGroup) subnetAssOperationAuditBuild(kt *kit.Kit, operations []p } subnetName := "" - if subnet.Name != nil { - subnetName = *subnet.Name + if subnetInfo.Name != nil { + subnetName = *subnetInfo.Name } audits = append(audits, &tableaudit.AuditTable{ ResID: sg.ID, @@ -380,8 +393,8 @@ func (s *SecurityGroup) subnetAssOperationAuditBuild(kt *kit.Kit, operations []p Detail: &tableaudit.BasicDetail{ Data: &tableaudit.AssociatedOperationAudit{ AssResType: enumor.SubnetAuditResType, - AssResID: subnet.ID, - AssResCloudID: subnet.CloudID, + AssResID: subnetInfo.ID, + AssResCloudID: subnetInfo.CloudID, AssResName: subnetName, }, }, @@ -454,3 +467,67 @@ func (s *SecurityGroup) niAssOperationAuditBuild(kt *kit.Kit, operations []proto return audits, nil } + +func (s *SecurityGroup) clbAssOperationAuditBuild(kt *kit.Kit, operations []protoaudit.CloudResourceOperationInfo) ( + []*tableaudit.AuditTable, error) { + + sgIDs := make([]string, 0) + clbIDs := make([]string, 0) + for _, one := range operations { + sgIDs = append(sgIDs, one.ResID) + clbIDs = append(clbIDs, one.AssociatedResID) + } + + sgIDMap, err := s.listSecurityGroup(kt, sgIDs) + if err != nil { + return nil, err + } + + clbIDMap, err := auditlb.ListLoadBalancer(kt, s.dao, clbIDs) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(operations)) + for _, one := range operations { + sg, exist := sgIDMap[one.ResID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "security group: %s not found", one.ResID) + } + + clbInfo, exist := clbIDMap[one.AssociatedResID] + if !exist { + return nil, errf.Newf(errf.RecordNotFound, "clb: %s not found", one.AssociatedResID) + } + + action, err := one.Action.ConvAuditAction() + if err != nil { + return nil, err + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: sg.ID, + CloudResID: sg.CloudID, + ResName: sg.Name, + ResType: enumor.SecurityGroupAuditResType, + Action: action, + BkBizID: sg.BkBizID, + Vendor: sg.Vendor, + AccountID: sg.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: &tableaudit.AssociatedOperationAudit{ + AssResType: enumor.LoadBalancerAuditResType, + AssResID: clbInfo.ID, + AssResCloudID: clbInfo.CloudID, + AssResName: clbInfo.Name, + }, + }, + }) + } + + return audits, nil +} diff --git a/cmd/data-service/service/audit/cloud/target_group.go b/cmd/data-service/service/audit/cloud/target_group.go new file mode 100644 index 0000000000..476f32689e --- /dev/null +++ b/cmd/data-service/service/audit/cloud/target_group.go @@ -0,0 +1,141 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cloud + +import ( + "hcm/pkg/api/core" + protoaudit "hcm/pkg/api/data-service/audit" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tableaudit "hcm/pkg/dal/table/audit" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +func (ad Audit) targetGroupAssignAuditBuild(kt *kit.Kit, assigns []protoaudit.CloudResourceAssignInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(assigns)) + for _, one := range assigns { + ids = append(ids, one.ResID) + } + idMap, err := ad.listTargetGroup(kt, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0, len(assigns)) + for _, one := range assigns { + tmpData, exist := idMap[one.ResID] + if !exist { + continue + } + + changed := make(map[string]interface{}) + if one.AssignedResType != enumor.BizAuditAssignedResType { + return nil, errf.New(errf.InvalidParameter, "assigned resource type is invalid") + } + changed["bk_biz_id"] = one.AssignedResID + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: tmpData.CloudID, + ResName: tmpData.Name, + ResType: enumor.TargetGroupAuditResType, + Action: enumor.Assign, + BkBizID: tmpData.BkBizID, + Vendor: tmpData.Vendor, + AccountID: tmpData.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Changed: changed, + }, + }) + } + + return audits, nil +} + +func (ad Audit) targetGroupDeleteAuditBuild(kt *kit.Kit, deletes []protoaudit.CloudResourceDeleteInfo) ( + []*tableaudit.AuditTable, error) { + + ids := make([]string, 0, len(deletes)) + for _, one := range deletes { + ids = append(ids, one.ResID) + } + + idMap, err := ad.listTargetGroup(kt, ids) + if err != nil { + return nil, err + } + + audits := make([]*tableaudit.AuditTable, 0) + for _, one := range deletes { + resData, exist := idMap[one.ResID] + if !exist { + continue + } + + audits = append(audits, &tableaudit.AuditTable{ + ResID: one.ResID, + CloudResID: resData.CloudID, + ResName: resData.Name, + ResType: enumor.TargetGroupAuditResType, + Action: enumor.Delete, + BkBizID: resData.BkBizID, + Vendor: resData.Vendor, + AccountID: resData.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: resData, + }, + }) + } + + return audits, nil +} + +func (ad Audit) listTargetGroup(kt *kit.Kit, ids []string) (map[string]*tablelb.LoadBalancerTargetGroupTable, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: core.NewDefaultBasePage(), + } + list, err := ad.dao.LoadBalancerTargetGroup().List(kt, opt) + if err != nil { + logs.Errorf("list target group db failed, ids: %v, err: %v, rid: %s", ids, err, kt.Rid) + return nil, err + } + + result := make(map[string]*tablelb.LoadBalancerTargetGroupTable, len(list.Details)) + for _, one := range list.Details { + result[one.ID] = &one + } + + return result, nil +} diff --git a/cmd/data-service/service/cloud/account-biz-rel/query.go b/cmd/data-service/service/cloud/account-biz-rel/query.go index ee6d07770c..8e11d5ef04 100644 --- a/cmd/data-service/service/cloud/account-biz-rel/query.go +++ b/cmd/data-service/service/cloud/account-biz-rel/query.go @@ -83,7 +83,7 @@ func (a *service) ListWithAccount(cts *rest.Contexts) (interface{}, error) { details, err := a.dao.AccountBizRel().ListJoinAccount(cts.Kit, req.BkBizIDs) if err != nil { - logs.Errorf("list account biz rels join account failed, err: %v, cvmIDs: %v, rid: %s", err, + logs.Errorf("list account biz rels join account failed, err: %v, bkBizIds: %v, rid: %s", err, req.BkBizIDs, cts.Kit.Rid) return nil, err } diff --git a/cmd/data-service/service/cloud/cert/cert.go b/cmd/data-service/service/cloud/cert/cert.go new file mode 100644 index 0000000000..569007192a --- /dev/null +++ b/cmd/data-service/service/cloud/cert/cert.go @@ -0,0 +1,53 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert 托管证书的DB接口 +package cert + +import ( + "net/http" + + "hcm/cmd/data-service/service/capability" + "hcm/pkg/dal/dao" + "hcm/pkg/rest" +) + +var svc *certSvc + +// InitService initial the cert service +func InitService(cap *capability.Capability) { + svc = &certSvc{ + dao: cap.Dao, + } + + h := rest.NewHandler() + + h.Add("ListCert", http.MethodPost, "/certs/list", svc.ListCert) + h.Add("ListCertExt", http.MethodPost, "/vendors/{vendor}/certs/list", svc.ListCertExt) + h.Add("CreateCert", http.MethodPost, "/vendors/{vendor}/certs/create", svc.CreateCert) + h.Add("BatchUpdateCert", http.MethodPatch, "/certs", svc.BatchUpdateCert) + h.Add("BatchUpdateCertExt", http.MethodPatch, "/vendors/{vendor}/certs", svc.BatchUpdateCertExt) + h.Add("BatchDeleteCert", http.MethodDelete, "/certs/batch", svc.BatchDeleteCert) + + h.Load(cap.WebService) +} + +type certSvc struct { + dao dao.Set +} diff --git a/cmd/data-service/service/cloud/cert/create.go b/cmd/data-service/service/cloud/cert/create.go new file mode 100644 index 0000000000..b133a9470a --- /dev/null +++ b/cmd/data-service/service/cloud/cert/create.go @@ -0,0 +1,104 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cert + +import ( + "fmt" + "reflect" + + "hcm/pkg/api/core" + corecert "hcm/pkg/api/core/cloud/cert" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + tablecert "hcm/pkg/dal/table/cloud/cert" + "hcm/pkg/rest" + + "github.com/jmoiron/sqlx" +) + +// CreateCert create cert. +func (svc *certSvc) CreateCert(cts *rest.Contexts) (interface{}, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchCreateCert[corecert.TCloudCertExtension](cts, svc, vendor) + default: + return nil, fmt.Errorf("unsupport %s vendor for now", vendor) + } +} + +func batchCreateCert[T corecert.Extension](cts *rest.Contexts, svc *certSvc, vendor enumor.Vendor) ( + interface{}, error) { + + req := new(protocloud.CertBatchCreateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + models := make([]*tablecert.SslCertTable, 0, len(req.Certs)) + for _, one := range req.Certs { + models = append(models, &tablecert.SslCertTable{ + CloudID: one.CloudID, + Name: one.Name, + Vendor: vendor, + BkBizID: one.BkBizID, + AccountID: one.AccountID, + Domain: one.Domain, + CertType: one.CertType, + CertStatus: one.CertStatus, + EncryptAlgorithm: one.EncryptAlgorithm, + CloudCreatedTime: one.CloudCreatedTime, + CloudExpiredTime: one.CloudExpiredTime, + Memo: one.Memo, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + }) + } + + ids, err := svc.dao.Cert().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + return nil, fmt.Errorf("batch create cert failed, err: %v", err) + } + + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create cert but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} diff --git a/cmd/data-service/service/cloud/cert/delete.go b/cmd/data-service/service/cloud/cert/delete.go new file mode 100644 index 0000000000..296cfa7d5b --- /dev/null +++ b/cmd/data-service/service/cloud/cert/delete.go @@ -0,0 +1,84 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cert + +import ( + "fmt" + + "hcm/pkg/api/core" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + "hcm/pkg/logs" + "hcm/pkg/rest" + + "github.com/jmoiron/sqlx" +) + +// BatchDeleteCert batch delete cert. +func (svc *certSvc) BatchDeleteCert(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.CertBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch delete cert decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + if err := req.Validate(); err != nil { + logs.Errorf("batch delete cert validate failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "vendor", "cloud_id", "bk_biz_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.Cert().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list cert db failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list cert failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + delIDs := make([]string, len(listResp.Details)) + for index, one := range listResp.Details { + delIDs[index] = one.ID + } + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + delFilter := tools.ContainersExpression("id", delIDs) + if err = svc.dao.Cert().DeleteWithTx(cts.Kit, txn, delFilter); err != nil { + return nil, err + } + + return nil, nil + }) + if err != nil { + logs.Errorf("delete cert failed, delIDs: %v, err: %v, rid: %s", delIDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/data-service/service/cloud/cert/query.go b/cmd/data-service/service/cloud/cert/query.go new file mode 100644 index 0000000000..efed0889a9 --- /dev/null +++ b/cmd/data-service/service/cloud/cert/query.go @@ -0,0 +1,168 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cert + +import ( + "fmt" + + "hcm/pkg/api/core" + corecert "hcm/pkg/api/core/cloud/cert" + protocloud "hcm/pkg/api/data-service/cloud" + dataproto "hcm/pkg/api/data-service/cloud/eip" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + tablecert "hcm/pkg/dal/table/cloud/cert" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" + "hcm/pkg/tools/json" +) + +// ListCert list cert. +func (svc *certSvc) ListCert(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.CertListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Field, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.Cert().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list cert failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list cert failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.CertListResult{Count: result.Count}, nil + } + + details := make([]corecert.BaseCert, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne, tErr := convTableToBaseCert(&one) + if tErr != nil { + logs.Errorf("list loop cert detail failed, err: %v, rid: %s", err, cts.Kit.Rid) + continue + } + + details = append(details, *tmpOne) + } + + return &protocloud.CertListResult{Details: details}, nil +} + +func convTableToBaseCert(one *tablecert.SslCertTable) (*corecert.BaseCert, error) { + domain := new([]*string) + err := json.UnmarshalFromString(string(one.Domain), domain) + if err != nil { + return nil, fmt.Errorf("UnmarshalFromString db domain failed, err: %v", err) + } + + base := &corecert.BaseCert{ + ID: one.ID, + CloudID: one.CloudID, + Name: one.Name, + Vendor: one.Vendor, + BkBizID: one.BkBizID, + AccountID: one.AccountID, + Domain: converter.PtrToVal(domain), + CertType: one.CertType, + CertStatus: one.CertStatus, + EncryptAlgorithm: one.EncryptAlgorithm, + CloudCreatedTime: one.CloudCreatedTime, + CloudExpiredTime: one.CloudExpiredTime, + Memo: one.Memo, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + } + + return base, nil +} + +// ListCertExt list cert ext. +func (svc *certSvc) ListCertExt(cts *rest.Contexts) (interface{}, error) { + vendor := enumor.Vendor(cts.Request.PathParameter("vendor")) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + req := new(dataproto.EipListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Filter: req.Filter, + Page: req.Page, + Fields: req.Fields, + } + + data, err := svc.dao.Cert().List(cts.Kit, opt) + if err != nil { + return nil, err + } + + switch vendor { + case enumor.TCloud: + return convCertListResult[corecert.TCloudCertExtension](cts.Kit, data.Details) + default: + return nil, errf.Newf(errf.InvalidParameter, "unsupported vendor: %s", vendor) + } +} + +func convCertListResult[T corecert.Extension](kt *kit.Kit, tables []tablecert.SslCertTable) ( + *protocloud.CertExtListResult[T], error) { + + details := make([]corecert.Cert[T], 0, len(tables)) + for _, one := range tables { + tmpCert, err := convTableToBaseCert(&one) + if err != nil { + logs.Errorf("list loop cert detail failed, err: %v, rid: %s", err, kt.Rid) + continue + } + + extension := new(T) + details = append(details, corecert.Cert[T]{ + BaseCert: *tmpCert, + Extension: extension, + }) + } + + return &protocloud.CertExtListResult[T]{ + Details: details, + }, nil +} diff --git a/cmd/data-service/service/cloud/cert/update.go b/cmd/data-service/service/cloud/cert/update.go new file mode 100644 index 0000000000..21d3cfec50 --- /dev/null +++ b/cmd/data-service/service/cloud/cert/update.go @@ -0,0 +1,131 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package cert + +import ( + "fmt" + + corecert "hcm/pkg/api/core/cloud/cert" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + tablecert "hcm/pkg/dal/table/cloud/cert" + "hcm/pkg/logs" + "hcm/pkg/rest" + + "github.com/jmoiron/sqlx" +) + +// BatchUpdateCert batch update cert +func (svc *certSvc) BatchUpdateCert(cts *rest.Contexts) (interface{}, error) { + req := new(dataproto.CertBatchUpdateExprReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + updateData := &tablecert.SslCertTable{ + BkBizID: req.BkBizID, + Reviser: cts.Kit.User, + } + + if len(req.Domain) > 0 && !req.Domain.IsEmpty() { + updateData.Domain = req.Domain + } + + if len(req.CertType) > 0 { + updateData.CertType = req.CertType + } + + if len(req.EncryptAlgorithm) > 0 { + updateData.EncryptAlgorithm = req.EncryptAlgorithm + } + + if len(req.CertStatus) > 0 { + updateData.CertStatus = req.CertStatus + } + + if len(req.CloudExpiredTime) > 0 { + updateData.CloudExpiredTime = req.CloudExpiredTime + } + + if err := svc.dao.Cert().Update(cts.Kit, tools.ContainersExpression("id", req.IDs), updateData); err != nil { + return nil, err + } + + return nil, nil +} + +// BatchUpdateCertExt batch update cert ext +func (svc *certSvc) BatchUpdateCertExt(cts *rest.Contexts) (interface{}, error) { + vendor := enumor.Vendor(cts.Request.PathParameter("vendor")) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchUpdateCertExt[corecert.TCloudCertExtension](cts, svc) + default: + return nil, errf.Newf(errf.InvalidParameter, "unsupported vendor: %s", vendor) + } +} + +func batchUpdateCertExt[T corecert.Extension](cts *rest.Contexts, svc *certSvc) (interface{}, error) { + req := new(dataproto.CertExtBatchUpdateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + for _, item := range *req { + updateData := &tablecert.SslCertTable{ + Name: item.Name, + BkBizID: int64(item.BkBizID), + Domain: item.Domain, + CertType: item.CertType, + CertStatus: item.CertStatus, + EncryptAlgorithm: item.EncryptAlgorithm, + CloudExpiredTime: item.CloudExpiredTime, + Reviser: cts.Kit.User, + } + + if err := svc.dao.Cert().UpdateByIDWithTx(cts.Kit, txn, item.ID, updateData); err != nil { + return nil, fmt.Errorf("update cert db failed, err: %v", err) + } + } + return nil, nil + }) + if err != nil { + logs.Errorf("batch update cert ext db failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/data-service/service/cloud/load-balancer/create.go b/cmd/data-service/service/cloud/load-balancer/create.go new file mode 100644 index 0000000000..3025e2c896 --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/create.go @@ -0,0 +1,949 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + "reflect" + + "hcm/pkg/api/core" + "hcm/pkg/api/core/audit" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + typesdao "hcm/pkg/dal/dao/types" + tableaudit "hcm/pkg/dal/table/audit" + "hcm/pkg/dal/table/cloud" + tablecvm "hcm/pkg/dal/table/cloud/cvm" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/dal/table/types" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/json" + "hcm/pkg/tools/slice" + + "github.com/jmoiron/sqlx" +) + +// BatchCreateLoadBalancer 批量创建负载均衡 +func (svc *lbSvc) BatchCreateLoadBalancer(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchCreateLoadBalancer[corelb.TCloudClbExtension](cts, svc, vendor) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } + +} +func batchCreateLoadBalancer[T corelb.Extension](cts *rest.Contexts, svc *lbSvc, vendor enumor.Vendor) (any, error) { + req := new(dataproto.LoadBalancerBatchCreateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + models := make([]*tablelb.LoadBalancerTable, 0, len(req.Lbs)) + for _, lb := range req.Lbs { + lbTable, err := convClbReqToTable(cts.Kit, vendor, lb) + if err != nil { + return nil, err + } + models = append(models, lbTable) + } + + ids, err := svc.dao.LoadBalancer().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("[%s]fail to batch create load balancer, err: %v, rid:%s", vendor, err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create load balancer failed, err: %v", err) + } + + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create clb but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +func convClbReqToTable[T corelb.Extension](kt *kit.Kit, vendor enumor.Vendor, lb dataproto.LbBatchCreate[T]) ( + *tablelb.LoadBalancerTable, error) { + extension, err := json.MarshalToString(lb.Extension) + if err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + return &tablelb.LoadBalancerTable{ + CloudID: lb.CloudID, + Name: lb.Name, + Vendor: vendor, + AccountID: lb.AccountID, + BkBizID: lb.BkBizID, + Region: lb.Region, + Zones: lb.Zones, + BackupZones: lb.BackupZones, + LBType: lb.LoadBalancerType, + IPVersion: string(lb.IPVersion), + VpcID: lb.VpcID, + CloudVpcID: lb.CloudVpcID, + SubnetID: lb.SubnetID, + CloudSubnetID: lb.CloudSubnetID, + PrivateIPv4Addresses: lb.PrivateIPv4Addresses, + PrivateIPv6Addresses: lb.PrivateIPv6Addresses, + PublicIPv4Addresses: lb.PublicIPv4Addresses, + PublicIPv6Addresses: lb.PublicIPv6Addresses, + Domain: lb.Domain, + Status: lb.Status, + Memo: lb.Memo, + CloudCreatedTime: lb.CloudCreatedTime, + CloudStatusTime: lb.CloudStatusTime, + CloudExpiredTime: lb.CloudExpiredTime, + Extension: types.JsonField(extension), + Creator: kt.User, + Reviser: kt.User, + }, nil +} + +// BatchCreateTargetGroup 批量创建目标组 +func (svc *lbSvc) BatchCreateTargetGroup(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchCreateTargetGroup[corelb.TCloudTargetGroupExtension](cts, svc, vendor) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } +} + +func batchCreateTargetGroup[T corelb.TargetGroupExtension](cts *rest.Contexts, + svc *lbSvc, vendor enumor.Vendor) (any, error) { + + req := new(dataproto.TargetGroupBatchCreateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + vpcCloudIDs := slice.Map(req.TargetGroups, + func(g dataproto.TargetGroupBatchCreate[T]) string { return g.CloudVpcID }) + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + + vpcInfoMap, err := getVpcMapByIDs(cts.Kit, vpcCloudIDs) + if err != nil { + return nil, err + } + + tgIDs := make([]string, 0, len(req.TargetGroups)) + for _, tgReq := range req.TargetGroups { + // 创建目标组 + tgTable, err := convTargetGroupCreateReqToTable(cts.Kit, vendor, tgReq, vpcInfoMap) + if err != nil { + return nil, err + } + + models := []*tablelb.LoadBalancerTargetGroupTable{tgTable} + tgNewIDs, err := svc.dao.LoadBalancerTargetGroup().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("[%s]fail to batch create target group, err: %v, rid:%s", vendor, err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target group failed, err: %v", err) + } + tgIDs = append(tgIDs, tgNewIDs...) + + // 添加RS + if tgReq.RsList != nil { + _, err = svc.batchCreateTargetWithGroupID(cts.Kit, txn, tgReq.AccountID, tgNewIDs[0], tgReq.RsList) + if err != nil { + logs.Errorf("[%s]fail to batch create target, err: %v, rid:%s", vendor, err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target failed, err: %v", err) + } + } + } + + return tgIDs, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create target group but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +func convTargetGroupCreateReqToTable[T corelb.TargetGroupExtension](kt *kit.Kit, vendor enumor.Vendor, + tg dataproto.TargetGroupBatchCreate[T], vpcInfoMap map[string]cloud.VpcTable) ( + *tablelb.LoadBalancerTargetGroupTable, error) { + + extensionJSON, err := types.NewJsonField(tg.Extension) + if err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + vpcInfo, ok := vpcInfoMap[tg.CloudVpcID] + if !ok { + return nil, errf.Newf(errf.RecordNotFound, "cloudVpcID[%s] not found", tg.CloudVpcID) + } + + targetGroup := &tablelb.LoadBalancerTargetGroupTable{ + Name: tg.Name, + Vendor: vendor, + AccountID: tg.AccountID, + BkBizID: tg.BkBizID, + TargetGroupType: tg.TargetGroupType, + VpcID: vpcInfo.ID, + CloudVpcID: vpcInfo.CloudID, + Region: tg.Region, + Protocol: tg.Protocol, + Port: tg.Port, + Weight: cvt.ValToPtr(tg.Weight), + HealthCheck: tg.HealthCheck, + Memo: tg.Memo, + Extension: extensionJSON, + Creator: kt.User, + Reviser: kt.User, + } + if len(tg.TargetGroupType) == 0 { + targetGroup.TargetGroupType = enumor.LocalTargetGroupType + } + if tg.Weight == 0 { + targetGroup.Weight = cvt.ValToPtr(int64(-1)) + } + return targetGroup, nil +} + +func (svc *lbSvc) batchCreateTargetWithGroupID(kt *kit.Kit, txn *sqlx.Tx, accountID, tgID string, + rsList []*dataproto.TargetBaseReq) ([]string, error) { + + rsModels := make([]*tablelb.LoadBalancerTargetTable, 0) + cloudCvmIDs := make([]string, 0) + for _, item := range rsList { + if item.InstType == enumor.CvmInstType { + cloudCvmIDs = append(cloudCvmIDs, item.CloudInstID) + } + if len(tgID) > 0 { + item.TargetGroupID = tgID + } + } + + // 查询Cvm信息 + cvmMap := make(map[string]tablecvm.Table) + if len(cloudCvmIDs) > 0 { + cvmReq := &typesdao.ListOption{ + Filter: tools.ContainersExpression("cloud_id", cloudCvmIDs), + Page: core.NewDefaultBasePage(), + } + cvmList, err := svc.dao.Cvm().List(kt, cvmReq) + if err != nil { + logs.Errorf("failed to list cvm, cloudIDs: %v, err: %v, rid: %s", cloudCvmIDs, err, kt.Rid) + return nil, err + } + + for _, item := range cvmList.Details { + cvmMap[item.CloudID] = item + } + } + + for _, item := range rsList { + tmpRs := &tablelb.LoadBalancerTargetTable{ + AccountID: accountID, + InstType: item.InstType, + CloudInstID: item.CloudInstID, + TargetGroupID: item.TargetGroupID, + // for local target group its cloud id is same as local id + CloudTargetGroupID: item.TargetGroupID, + Port: item.Port, + Weight: item.Weight, + Memo: nil, + Creator: kt.User, + Reviser: kt.User, + } + // 实例类型-CVM + if item.InstType == enumor.CvmInstType { + tmpRs.InstID = cvmMap[item.CloudInstID].ID + tmpRs.InstName = cvmMap[item.CloudInstID].Name + tmpRs.PrivateIPAddress = cvmMap[item.CloudInstID].PrivateIPv4Addresses + tmpRs.PublicIPAddress = cvmMap[item.CloudInstID].PublicIPv4Addresses + tmpRs.Zone = cvmMap[item.CloudInstID].Zone + tmpRs.AccountID = cvmMap[item.CloudInstID].AccountID + tmpRs.CloudVpcIDs = cvmMap[item.CloudInstID].CloudVpcIDs + } + + rsModels = append(rsModels, tmpRs) + } + return svc.dao.LoadBalancerTarget().BatchCreateWithTx(kt, txn, rsModels) +} + +func getVpcMapByIDs(kt *kit.Kit, cloudIDs []string) ( + map[string]cloud.VpcTable, error) { + + vpcOpt := &typesdao.ListOption{ + Filter: tools.ContainersExpression("cloud_id", cloudIDs), + Page: core.NewDefaultBasePage(), + } + vpcResult, err := svc.dao.Vpc().List(kt, vpcOpt) + if err != nil { + logs.Errorf("list vpc by ids failed, vpcCloudIDs: %v, err: %v, rid: %s", cloudIDs, err, kt.Rid) + return nil, fmt.Errorf("list vpc by cloudIDs failed, err: %v", err) + } + + idMap := make(map[string]cloud.VpcTable, len(vpcResult.Details)) + for _, item := range vpcResult.Details { + idMap[item.CloudID] = item + } + + return idMap, nil +} + +// CreateTargetGroupListenerRel 批量创建目标组与监听器的绑定关系 +func (svc *lbSvc) CreateTargetGroupListenerRel(cts *rest.Contexts) (any, error) { + req := new(dataproto.TargetGroupListenerRelCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + if len(req.CloudTargetGroupID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "cloud_target_group_id can not empty") + } + ruleModel := &tablelb.TCloudLbUrlRuleTable{ + TargetGroupID: req.TargetGroupID, + CloudTargetGroupID: req.CloudTargetGroupID, + Reviser: cts.Kit.User, + } + err := svc.dao.LoadBalancerTCloudUrlRule().UpdateByIDWithTx(cts.Kit, txn, req.ListenerRuleID, ruleModel) + if err != nil { + return nil, err + } + + models := make([]*tablelb.TargetGroupListenerRuleRelTable, 0) + models = append(models, &tablelb.TargetGroupListenerRuleRelTable{ + ListenerRuleID: req.ListenerRuleID, + CloudListenerRuleID: req.CloudListenerRuleID, + ListenerRuleType: req.ListenerRuleType, + TargetGroupID: req.TargetGroupID, + CloudTargetGroupID: req.CloudTargetGroupID, + LbID: req.LbID, + CloudLbID: req.CloudLbID, + LblID: req.LblID, + CloudLblID: req.CloudLblID, + BindingStatus: req.BindingStatus, + Detail: req.Detail, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + }) + ids, err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("[%s]fail to batch create target group listener rel, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target group listener rel failed, err: %v", err) + } + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create target group listener rel but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +// BatchCreateTCloudUrlRule 批量创建腾讯云url规则 纯规则条目创建,不校验监听器, 有目标组则一起创建关联关系 +func (svc *lbSvc) BatchCreateTCloudUrlRule(cts *rest.Contexts) (any, error) { + req := new(dataproto.TCloudUrlRuleBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + logs.Errorf("[ds] BatchCreateTCloudUrlRule request validate failed, err:%v, req: %+v, rid: %s", + err, req, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + ruleModels := make([]*tablelb.TCloudLbUrlRuleTable, 0, len(req.UrlRules)) + for _, rule := range req.UrlRules { + ruleModel, err := svc.convRule(cts.Kit, rule) + if err != nil { + return nil, err + } + ruleModels = append(ruleModels, ruleModel) + } + + // 创建规则和关联关系 + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + + ids, err := svc.dao.LoadBalancerTCloudUrlRule().BatchCreateWithTx(cts.Kit, txn, ruleModels) + if err != nil { + logs.Errorf("fail to batch create lb rule, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create lb rule failed, err: %v", err) + } + // 根据id 创建关联关系 + relModels := make([]*tablelb.TargetGroupListenerRuleRelTable, 0, len(req.UrlRules)) + for i, rule := range req.UrlRules { + // 跳过没有设置目标组id的规则 + if len(rule.TargetGroupID) == 0 { + continue + } + // 默认设置为绑定中状态,防止同步时本地目标组rs被清掉 + relModels = append(relModels, svc.convRuleRel(cts.Kit, ids[i], rule, enumor.BindingBindingStatus)) + } + if len(relModels) == 0 { + return ids, nil + } + _, err = svc.dao.LoadBalancerTargetGroupListenerRuleRel().BatchCreateWithTx(cts.Kit, txn, relModels) + if err != nil { + logs.Errorf("fail to create rule rel, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create tcloud url rule but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +func (svc *lbSvc) convRuleRel(kt *kit.Kit, listenerRuleID string, rule dataproto.TCloudUrlRuleCreate, + bindingStatus enumor.BindingStatus) *tablelb.TargetGroupListenerRuleRelTable { + + return &tablelb.TargetGroupListenerRuleRelTable{ + ListenerRuleID: listenerRuleID, + CloudListenerRuleID: rule.CloudID, + ListenerRuleType: enumor.Layer7RuleType, + TargetGroupID: rule.TargetGroupID, + CloudTargetGroupID: rule.CloudTargetGroupID, + LbID: rule.LbID, + CloudLbID: rule.CloudLbID, + LblID: rule.LblID, + CloudLblID: rule.CloudLBLID, + BindingStatus: bindingStatus, + Detail: "{}", + Creator: kt.User, + Reviser: kt.User, + } +} + +func (svc *lbSvc) convRule(kt *kit.Kit, rule dataproto.TCloudUrlRuleCreate) ( + *tablelb.TCloudLbUrlRuleTable, error) { + + ruleModel := &tablelb.TCloudLbUrlRuleTable{ + CloudID: rule.CloudID, + Name: rule.Name, + RuleType: rule.RuleType, + LbID: rule.LbID, + CloudLbID: rule.CloudLbID, + LblID: rule.LblID, + CloudLBLID: rule.CloudLBLID, + TargetGroupID: rule.TargetGroupID, + CloudTargetGroupID: rule.CloudTargetGroupID, + Domain: rule.Domain, + URL: rule.URL, + Scheduler: rule.Scheduler, + SessionType: rule.SessionType, + SessionExpire: rule.SessionExpire, + Memo: rule.Memo, + + Creator: kt.User, + Reviser: kt.User, + } + healthCheckJson, err := json.MarshalToString(rule.HealthCheck) + if err != nil { + logs.Errorf("fail to marshal health check into json, err: %v, healthcheck: %+v, rid: %s", + err, rule.HealthCheck, kt.Rid) + return nil, err + } + ruleModel.HealthCheck = types.JsonField(healthCheckJson) + certJson, err := json.MarshalToString(rule.Certificate) + if err != nil { + logs.Errorf("fail to marshal certificate into json, err: %v, certificate: %+v, rid: %s", + err, rule.Certificate, kt.Rid) + return nil, err + } + ruleModel.Certificate = types.JsonField(certJson) + return ruleModel, nil +} + +// BatchCreateListener 批量创建监听器 +func (svc *lbSvc) BatchCreateListener(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchCreateListener(cts, svc) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } +} + +func batchCreateListener(cts *rest.Contexts, svc *lbSvc) (any, error) { + req := new(dataproto.ListenerBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + models := make([]*tablelb.LoadBalancerListenerTable, 0, len(req.Listeners)) + for _, item := range req.Listeners { + models = append(models, &tablelb.LoadBalancerListenerTable{ + CloudID: item.CloudID, + Name: item.Name, + Vendor: item.Vendor, + AccountID: item.AccountID, + BkBizID: item.BkBizID, + LBID: item.LbID, + CloudLBID: item.CloudLbID, + Protocol: item.Protocol, + Port: item.Port, + DefaultDomain: item.DefaultDomain, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + }) + } + ids, err := svc.dao.LoadBalancerListener().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("fail to batch create listener, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create listener failed, err: %v", err) + } + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create listener but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +// BatchCreateListenerWithRule 批量创建监听器及规则 +func (svc *lbSvc) BatchCreateListenerWithRule(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return svc.batchCreateTCloudListenerWithRule(cts) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } +} + +func (svc *lbSvc) batchCreateTCloudListenerWithRule(cts *rest.Contexts) (any, error) { + req := new(dataproto.ListenerWithRuleBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + ids, err := svc.insertListenerWithRule(cts.Kit, req) + if err != nil { + return nil, err + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +func (svc *lbSvc) insertListenerWithRule(kt *kit.Kit, req *dataproto.ListenerWithRuleBatchCreateReq) ([]string, error) { + result, err := svc.dao.Txn().AutoTxn(kt, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + lblIDs := make([]string, 0, len(req.ListenerWithRules)) + for _, item := range req.ListenerWithRules { + lblID, ruleID, err := svc.createListenerWithRule(kt, txn, item) + if err != nil { + logs.Errorf("fail to create listener with rule, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + lblIDs = append(lblIDs, lblID) + if len(item.TargetGroupID) == 0 { + continue + } + // 目标组如果没有RS,则直接绑定成功 + rsReq := &typesdao.ListOption{ + Filter: tools.EqualExpression("target_group_id", item.TargetGroupID), + Page: core.NewDefaultBasePage(), + } + targetResp, err := svc.dao.LoadBalancerTarget().List(kt, rsReq) + if err != nil { + logs.Errorf("fail to list target by target group id, err: %v, tgID: %s, rid: %s", + err, item.TargetGroupID, kt.Rid) + return nil, err + } + bindStatus := enumor.BindingBindingStatus + if len(targetResp.Details) == 0 { + bindStatus = enumor.SuccessBindingStatus + } + + ruleRelModels := []*tablelb.TargetGroupListenerRuleRelTable{{ + ListenerRuleID: ruleID, + CloudListenerRuleID: item.CloudRuleID, + ListenerRuleType: item.RuleType, + TargetGroupID: item.TargetGroupID, + CloudTargetGroupID: item.CloudTargetGroupID, + LbID: item.LbID, + CloudLbID: item.CloudLbID, + LblID: lblID, + CloudLblID: item.CloudID, + BindingStatus: bindStatus, + Creator: kt.User, + Reviser: kt.User, + }} + _, err = svc.dao.LoadBalancerTargetGroupListenerRuleRel().BatchCreateWithTx(kt, txn, ruleRelModels) + if err != nil { + logs.Errorf("fail to batch create listener rule rel, err: %v, rid:%s", err, kt.Rid) + return nil, fmt.Errorf("batch create listener rule rel failed, err: %v", err) + } + } + return lblIDs, nil + }) + if err != nil { + return nil, err + } + + return result.([]string), nil +} + +func (svc *lbSvc) createListenerWithRule(kt *kit.Kit, txn *sqlx.Tx, item dataproto.ListenerWithRuleCreateReq) ( + lblID string, ruleID string, err error) { + + models := []*tablelb.LoadBalancerListenerTable{{ + CloudID: item.CloudID, + Name: item.Name, + Vendor: item.Vendor, + AccountID: item.AccountID, + BkBizID: item.BkBizID, + LBID: item.LbID, + CloudLBID: item.CloudLbID, + Protocol: item.Protocol, + Port: item.Port, + DefaultDomain: item.Domain, + SniSwitch: item.SniSwitch, + Creator: kt.User, + Reviser: kt.User, + }} + lblIDs, err := svc.dao.LoadBalancerListener().BatchCreateWithTx(kt, txn, models) + if err != nil { + logs.Errorf("fail to batch create listener, err: %v, rid:%s", err, kt.Rid) + return "", "", fmt.Errorf("batch create listener failed, err: %v", err) + } + certJSON, err := json.MarshalToString(item.Certificate) + if err != nil { + logs.Errorf("json marshal Certificate failed, err: %v", err) + return "", "", errf.NewFromErr(errf.InvalidParameter, err) + } + + ruleModels := []*tablelb.TCloudLbUrlRuleTable{{ + CloudID: item.CloudRuleID, + RuleType: item.RuleType, + LbID: item.LbID, + CloudLbID: item.CloudLbID, + LblID: lblIDs[0], + CloudLBLID: item.CloudID, + TargetGroupID: item.TargetGroupID, + CloudTargetGroupID: item.CloudTargetGroupID, + Domain: item.Domain, + URL: item.Url, + Scheduler: item.Scheduler, + SessionType: item.SessionType, + SessionExpire: item.SessionExpire, + Certificate: types.JsonField(certJSON), + Creator: kt.User, + Reviser: kt.User, + }} + ruleIDs, err := svc.dao.LoadBalancerTCloudUrlRule().BatchCreateWithTx(kt, txn, ruleModels) + if err != nil { + logs.Errorf("fail to batch create listener url rule, err: %v, rid:%s", err, kt.Rid) + return "", "", fmt.Errorf("batch create listener url rule failed, err: %v", err) + } + return lblIDs[0], ruleIDs[0], nil +} + +// CreateResFlowLock 创建资源跟Flow的锁定关系 +func (svc *lbSvc) CreateResFlowLock(cts *rest.Contexts) (any, error) { + req := new(dataproto.ResFlowLockCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + model := &tablelb.ResourceFlowLockTable{ + ResID: req.ResID, + ResType: req.ResType, + Owner: req.Owner, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + } + err := svc.dao.ResourceFlowLock().CreateWithTx(cts.Kit, txn, model) + if err != nil { + logs.Errorf("[%s]fail to create load balancer flow lock, req: %+v, err: %v, rid:%s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("create load balancer flow lock failed, err: %v", err) + } + return nil, nil + }) + if err != nil { + return nil, err + } + + return nil, nil +} + +// BatchCreateResFlowRel 批量创建资源跟Flow的关系记录 +func (svc *lbSvc) BatchCreateResFlowRel(cts *rest.Contexts) (any, error) { + req := new(dataproto.ResFlowRelBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + models := make([]*tablelb.ResourceFlowRelTable, 0, len(req.ResFlowRels)) + for _, item := range req.ResFlowRels { + models = append(models, &tablelb.ResourceFlowRelTable{ + ResID: item.ResID, + ResType: item.ResType, + FlowID: item.FlowID, + TaskType: item.TaskType, + Status: item.Status, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + }) + } + ids, err := svc.dao.ResourceFlowRel().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("[%s]fail to batch create load balancer flow rel, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create load balancer flow rel failed, err: %v", err) + } + return ids, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create load balancer flow rel but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} + +// ResFlowLock 锁定资源跟Flow 1. 锁表 2. 创建关联记录 +func (svc *lbSvc) ResFlowLock(cts *rest.Contexts) (any, error) { + req := new(dataproto.ResFlowLockReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + lockModel := &tablelb.ResourceFlowLockTable{ + ResID: req.ResID, + ResType: req.ResType, + Owner: req.FlowID, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + } + err := svc.dao.ResourceFlowLock().CreateWithTx(cts.Kit, txn, lockModel) + if err != nil { + logs.Errorf("fail to create load balancer flow lock, req: %+v, err: %v, rid:%s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("create load balancer flow lock failed, err: %v", err) + } + + relModels := []*tablelb.ResourceFlowRelTable{{ + ResID: req.ResID, + ResType: req.ResType, + FlowID: req.FlowID, + TaskType: req.TaskType, + Status: req.Status, + Creator: cts.Kit.User, + Reviser: cts.Kit.User, + }} + _, err = svc.dao.ResourceFlowRel().BatchCreateWithTx(cts.Kit, txn, relModels) + if err != nil { + logs.Errorf("fail to create load balancer flow rel, err: %v, req: %+v, rid:%s", err, req, cts.Kit.Rid) + return nil, fmt.Errorf("create load balancer flow rel failed, err: %v", err) + } + + // 创建目标组的操作记录 + if req.ResType == enumor.LoadBalancerCloudResType { + err = svc.createTargetGroupOfResFlowAudit(cts.Kit, req, txn) + if err != nil { + logs.Errorf("fail to create res flow audits, err: %v, req: %+v, rid:%s", err, req, cts.Kit.Rid) + return nil, fmt.Errorf("create res flow audits failed, err: %v", err) + } + } + + return nil, nil + }) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (svc *lbSvc) createTargetGroupOfResFlowAudit(kt *kit.Kit, req *dataproto.ResFlowLockReq, txn *sqlx.Tx) error { + resReq := &typesdao.ListOption{ + Filter: tools.EqualExpression("id", req.ResID), + Page: core.NewDefaultBasePage(), + } + resList, err := svc.dao.LoadBalancer().List(kt, resReq) + if err != nil { + return err + } + if len(resList.Details) == 0 { + return errf.Newf(errf.RecordNotFound, "resID: %s, resType: %s, is not found", req.ResID, req.ResType) + } + + resInfo := resList.Details[0] + + var auditData = audit.TargetGroupAsyncAuditDetail{ + LoadBalancer: resInfo, + ResFlow: req, + } + + audits := make([]*tableaudit.AuditTable, 0) + audits = append(audits, &tableaudit.AuditTable{ + ResID: resInfo.ID, + CloudResID: resInfo.CloudID, + ResName: resInfo.Name, + ResType: enumor.AuditResourceType(req.ResType), + Action: enumor.Update, + BkBizID: resInfo.BkBizID, + Vendor: resInfo.Vendor, + AccountID: resInfo.AccountID, + Operator: kt.User, + Source: kt.GetRequestSource(), + Rid: kt.Rid, + AppCode: kt.AppCode, + Detail: &tableaudit.BasicDetail{ + Data: auditData, + }, + }) + if err = svc.dao.Audit().BatchCreateWithTx(kt, txn, audits); err != nil { + logs.Errorf("batch create %s audit failed, err: %v, req: %+v, rid: %s", req.ResType, err, req, kt.Rid) + return err + } + + return nil +} + +// BatchCreateTarget 批量创建目标 +func (svc *lbSvc) BatchCreateTarget(cts *rest.Contexts) (any, error) { + req := new(dataproto.TargetBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + rsIDs, err := svc.batchCreateTargetWithGroupID(cts.Kit, txn, "", "", req.Targets) + if err != nil { + logs.Errorf("fail to batch create target, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target failed, err: %v", err) + } + return rsIDs, nil + }) + if err != nil { + return nil, err + } + + ids, ok := result.([]string) + if !ok { + return nil, fmt.Errorf("batch create target but return id type is not []string, id type: %v", + reflect.TypeOf(result).String()) + } + + return &core.BatchCreateResult{IDs: ids}, nil +} diff --git a/cmd/data-service/service/cloud/load-balancer/delete.go b/cmd/data-service/service/cloud/load-balancer/delete.go new file mode 100644 index 0000000000..dad77e018d --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/delete.go @@ -0,0 +1,417 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + + "hcm/pkg/api/core" + dataservice "hcm/pkg/api/data-service" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/slice" + + "github.com/jmoiron/sqlx" +) + +// BatchDeleteLoadBalancer delete load balancer +func (svc *lbSvc) BatchDeleteLoadBalancer(cts *rest.Contexts) (any, error) { + req := new(dataproto.LoadBalancerBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "vendor", "cloud_id", "bk_biz_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.LoadBalancer().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list lb failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list lb failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + lbIds := slice.Map(listResp.Details, func(one tablelb.LoadBalancerTable) string { return one.ID }) + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + + // 本层直接级联删除,有数据不报错 + // 删除对应监听器 + for _, lbId := range lbIds { + err := svc.deleteListenerByLb(cts.Kit, txn, lbId) + if err != nil { + logs.Errorf("fail to delete listener of load balancer(%s), err: %v, rid: %s", lbId, err, cts.Kit.Rid) + return nil, err + } + } + // 删除安全组关联关系 + sgRelFilter := tools.ExpressionAnd( + tools.RuleIn("res_id", lbIds), + tools.RuleEqual("res_type", enumor.LoadBalancerCloudResType), + ) + err := svc.dao.SGCommonRel().DeleteWithTx(cts.Kit, txn, sgRelFilter) + if err != nil { + logs.Errorf("delete lb sg rel failed , err: %v, lb_ids: %v, rid: %s", err, lbIds, cts.Kit.Rid) + return nil, err + } + + // 删除负载均衡 + delFilter := tools.ContainersExpression("id", lbIds) + return nil, svc.dao.LoadBalancer().DeleteWithTx(cts.Kit, txn, delFilter) + }) + if err != nil { + logs.Errorf("delete lb(ids=%v) failed, err: %v, rid: %s", lbIds, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// 删除负载均衡关联规则 +func (svc *lbSvc) deleteListenerByLb(kt *kit.Kit, txn *sqlx.Tx, lbId string) error { + listenerResp, err := svc.dao.LoadBalancerListener().List(kt, &types.ListOption{ + Filter: tools.EqualExpression("lb_id", lbId), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list listener of load balancer(%s), err: %v, rid: %s", lbId, err, kt.Rid) + return err + } + if len(listenerResp.Details) == 0 { + return nil + } + // 删除对应的规则 + for _, listener := range listenerResp.Details { + err := svc.deleteRuleByListener(kt, txn, listener.ID) + if err != nil { + logs.Errorf("fail to delete load balancer rule of listener(%s), err: %v, rid: %s", + listener.ID, err, kt.Rid) + return err + } + } + // 删除监听器本身 + listenerIds := slice.Map(listenerResp.Details, func(r tablelb.LoadBalancerListenerTable) string { return r.ID }) + listenerIdFilter := tools.ContainersExpression("id", listenerIds) + return svc.dao.LoadBalancerListener().DeleteWithTx(kt, txn, listenerIdFilter) +} + +// 删除监听器关联规则 +func (svc *lbSvc) deleteRuleByListener(kt *kit.Kit, txn *sqlx.Tx, listenerID string) error { + ruleResp, err := svc.dao.LoadBalancerTCloudUrlRule().List(kt, &types.ListOption{ + Filter: tools.EqualExpression("lbl_id", listenerID), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list load balancer rule of listener(%s), err: %v, rid: %s", listenerID, err, kt.Rid) + return err + } + if len(ruleResp.Details) == 0 { + return nil + } + ruleIds := slice.Map(ruleResp.Details, func(r tablelb.TCloudLbUrlRuleTable) string { return r.ID }) + + // 删除跟目标组的绑定关系 + err = svc.deleteTargetGroupListenerRuleRelByListener(kt, txn, []string{listenerID}) + if err != nil { + logs.Errorf("fail to delete target rule rel of listener(%s), err: %v, rid: %s", listenerID, err, kt.Rid) + return err + } + ruleIDFilter := tools.ContainersExpression("id", ruleIds) + return svc.dao.LoadBalancerTCloudUrlRule().DeleteWithTx(kt, txn, ruleIDFilter) +} + +// BatchDeleteTargetGroup batch delete target group. +func (svc *lbSvc) BatchDeleteTargetGroup(cts *rest.Contexts) (interface{}, error) { + req := new(dataproto.TargetGroupBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch delete target group decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + if err := req.Validate(); err != nil { + logs.Errorf("batch delete target group validate failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "vendor", "cloud_id", "bk_biz_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target group db failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list target group failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + delIDs := make([]string, len(listResp.Details)) + for index, one := range listResp.Details { + delIDs[index] = one.ID + } + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + delFilter := tools.ContainersExpression("id", delIDs) + if err = svc.dao.LoadBalancerTargetGroup().DeleteWithTx(cts.Kit, txn, delFilter); err != nil { + logs.Errorf("fail to delete target group, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + rsDelFilter := tools.ContainersExpression("target_group_id", delIDs) + // 删除关联RS + if err = svc.dao.LoadBalancerTarget().DeleteWithTx(cts.Kit, txn, rsDelFilter); err != nil { + logs.Errorf("fail to delete target, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + return nil, nil + }) + if err != nil { + logs.Errorf("delete target group failed, delIDs: %v, err: %v, rid: %s", delIDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchDeleteTCloudUrlRule 批量删除腾讯云规则 +func (svc *lbSvc) BatchDeleteTCloudUrlRule(cts *rest.Contexts) (any, error) { + req := new(dataproto.LoadBalancerBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "cloud_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.LoadBalancerTCloudUrlRule().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list tcloud lb rule failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list tcloud lb rule failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + ruleIds := slice.Map(listResp.Details, func(one tablelb.TCloudLbUrlRuleTable) string { return one.ID }) + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + + // 删除关联关系 + ruleFilter := tools.ContainersExpression("listener_rule_id", ruleIds) + err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().DeleteWithTx(cts.Kit, txn, ruleFilter) + if err != nil { + logs.Errorf("fail to delete rule target group relations, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + // 删除对应的规则 + delFilter := tools.ContainersExpression("id", ruleIds) + return nil, svc.dao.LoadBalancerTCloudUrlRule().DeleteWithTx(cts.Kit, txn, delFilter) + }) + if err != nil { + logs.Errorf("delete rules(ids=%v) failed, err: %v, rid: %s", ruleIds, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchDeleteListener delete listener +func (svc *lbSvc) BatchDeleteListener(cts *rest.Contexts) (any, error) { + req := new(dataproto.LoadBalancerBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "vendor", "cloud_id", "bk_biz_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.LoadBalancerListener().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list listener failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list listener failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + lblIds := slice.Map(listResp.Details, func(one tablelb.LoadBalancerListenerTable) string { return one.ID }) + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + // 本层直接级联删除,有数据不报错 + // 删除对应监听器规则 + for _, lblId := range lblIds { + err = svc.deleteRuleByListener(cts.Kit, txn, lblId) + if err != nil { + logs.Errorf("fail to delete rule of listener(%s), err: %v, rid: %s", lblId, err, cts.Kit.Rid) + return nil, err + } + } + + // 删除跟目标组的绑定关系 + err = svc.deleteTargetGroupListenerRuleRelByListener(cts.Kit, txn, lblIds) + if err != nil { + logs.Errorf("fail to delete target rule rel of listener(%v), err: %v, rid: %s", lblIds, err, cts.Kit.Rid) + return nil, err + } + + // 删除监听器 + delFilter := tools.ContainersExpression("id", lblIds) + return nil, svc.dao.LoadBalancerListener().DeleteWithTx(cts.Kit, txn, delFilter) + }) + if err != nil { + logs.Errorf("delete listener(ids=%v) failed, err: %v, rid: %s", lblIds, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// 删除监听器关联目标组关系数据 +func (svc *lbSvc) deleteTargetGroupListenerRuleRelByListener(kt *kit.Kit, txn *sqlx.Tx, listenerIDs []string) error { + ruleRelResp, err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().List(kt, &types.ListOption{ + Filter: tools.ContainersExpression("lbl_id", listenerIDs), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list listener rule and target group relation(ids=%v), err: %v, rid: %s", + listenerIDs, err, kt.Rid) + return err + } + if len(ruleRelResp.Details) == 0 { + return nil + } + + relIDs := slice.Map(ruleRelResp.Details, func(r tablelb.TargetGroupListenerRuleRelTable) string { return r.ID }) + return svc.dao.LoadBalancerTargetGroupListenerRuleRel().DeleteWithTx( + kt, txn, tools.ContainersExpression("id", relIDs)) +} + +// BatchDeleteResFlowRel batch delete res flow rel. +func (svc *lbSvc) BatchDeleteResFlowRel(cts *rest.Contexts) (interface{}, error) { + req := new(dataservice.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch delete res flow rel decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + if err := req.Validate(); err != nil { + logs.Errorf("batch delete res flow rel validate failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "res_id", "flow_id", "task_type"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.ResourceFlowRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list res flow rel db failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list res flow rel failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + delIDs := make([]string, len(listResp.Details)) + for index, one := range listResp.Details { + delIDs[index] = one.ID + } + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + delFilter := tools.ContainersExpression("id", delIDs) + if err = svc.dao.ResourceFlowRel().DeleteWithTx(cts.Kit, txn, delFilter); err != nil { + return nil, err + } + return nil, nil + }) + if err != nil { + logs.Errorf("delete res flow rel failed, delIDs: %v, err: %v, rid: %s", delIDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// DeleteResFlowLock batch delete res flow lock. +func (svc *lbSvc) DeleteResFlowLock(cts *rest.Contexts) (interface{}, error) { + req := new(dataproto.ResFlowLockDeleteReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("batch delete res flow lock decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + if err := req.Validate(); err != nil { + logs.Errorf("batch delete res flow lock validate failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + delFilter := tools.ExpressionAnd( + tools.RuleEqual("res_id", req.ResID), + tools.RuleEqual("res_type", req.ResType), + tools.RuleEqual("owner", req.Owner), + ) + if err := svc.dao.ResourceFlowLock().DeleteWithTx(cts.Kit, txn, delFilter); err != nil { + return nil, err + } + return nil, nil + }) + if err != nil { + logs.Errorf("delete res flow lock failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/data-service/service/cloud/load-balancer/load_balancer.go b/cmd/data-service/service/cloud/load-balancer/load_balancer.go new file mode 100644 index 0000000000..63c0f17b6b --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/load_balancer.go @@ -0,0 +1,118 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package loadbalancer 负载均衡的DB接口 +package loadbalancer + +import ( + "net/http" + + "hcm/cmd/data-service/service/capability" + "hcm/pkg/dal/dao" + "hcm/pkg/rest" +) + +var svc *lbSvc + +// InitService initial the clb service +func InitService(cap *capability.Capability) { + svc = &lbSvc{ + dao: cap.Dao, + } + + h := rest.NewHandler() + + // 负载均衡 + h.Add("GetLoadBalancer", http.MethodGet, "/vendors/{vendor}/load_balancers/{id}", svc.GetLoadBalancer) + h.Add("ListLoadBalancer", http.MethodPost, "/load_balancers/list", svc.ListLoadBalancer) + h.Add("ListLoadBalancerRaw", http.MethodPost, "/load_balancers/list_with_extension", svc.ListLoadBalancerRaw) + h.Add("ListLoadBalancerExt", http.MethodPost, "/vendors/{vendor}/load_balancers/list", svc.ListLoadBalancerExt) + h.Add("BatchCreateLoadBalancer", http.MethodPost, "/vendors/{vendor}/load_balancers/batch/create", + svc.BatchCreateLoadBalancer) + h.Add("BatchUpdateLoadBalancer", + http.MethodPatch, "/vendors/{vendor}/load_balancers/batch/update", svc.BatchUpdateLoadBalancer) + h.Add("BatchUpdateLbBizInfo", http.MethodPatch, "/load_balancers/bizs/batch/update", svc.BatchUpdateLbBizInfo) + h.Add("BatchDeleteLoadBalancer", http.MethodDelete, "/load_balancers/batch", svc.BatchDeleteLoadBalancer) + + // 监听器 + h.Add("GetListener", http.MethodGet, "/vendors/{vendor}/listeners/{id}", svc.GetListener) + h.Add("ListListener", http.MethodPost, "/load_balancers/listeners/list", svc.ListListener) + h.Add("ListListenerExt", http.MethodPost, "/vendors/tcloud/load_balancers/listeners/list", svc.ListListenerExt) + h.Add("BatchCreateListener", http.MethodPost, "/vendors/{vendor}/listeners/batch/create", svc.BatchCreateListener) + h.Add("BatchCreateListenerWithRule", http.MethodPost, "/vendors/{vendor}/listeners/rules/batch/create", + svc.BatchCreateListenerWithRule) + h.Add("BatchUpdateListener", http.MethodPatch, "/vendors/{vendor}/listeners/batch/update", svc.BatchUpdateListener) + h.Add("BatchDeleteListener", http.MethodDelete, "/listeners/batch", svc.BatchDeleteListener) + h.Add("CountListenerByLbIDs", http.MethodPost, "/load_balancers/listeners/count", svc.CountListenerByLbIDs) + h.Add("BatchUpdateListenerBizInfo", http.MethodPatch, + "/load_balancers/listeners/bizs/batch/update", svc.BatchUpdateListenerBizInfo) + + // url规则 + h.Add("BatchCreateTCloudUrlRule", + http.MethodPost, "/vendors/tcloud/url_rules/batch/create", svc.BatchCreateTCloudUrlRule) + h.Add("BatchUpdateTCloudUrlRule", + http.MethodPatch, "/vendors/tcloud/url_rules/batch/update", svc.BatchUpdateTCloudUrlRule) + h.Add("BatchDeleteTCloudUrlRule", + http.MethodDelete, "/vendors/tcloud/url_rules/batch", svc.BatchDeleteTCloudUrlRule) + h.Add("ListTCloudUrlRule", http.MethodPost, "/vendors/tcloud/load_balancers/url_rules/list", svc.ListTCloudUrlRule) + + // 目标组 + h.Add("BatchCreateTargetGroup", http.MethodPost, + "/vendors/{vendor}/target_groups/batch/create", svc.BatchCreateTargetGroup) + h.Add("BatchCreateTargetGroupWithRel", http.MethodPost, + "/vendors/{vendor}/target_groups/with/rels/batch/create", svc.BatchCreateTargetGroupWithRel) + h.Add("GetTargetGroup", http.MethodGet, "/vendors/{vendor}/target_groups/{id}", svc.GetTargetGroup) + h.Add("ListTargetGroup", http.MethodPost, "/load_balancers/target_groups/list", svc.ListTargetGroup) + h.Add("UpdateTargetGroup", http.MethodPatch, "/vendors/{vendor}/target_groups", svc.UpdateTargetGroup) + h.Add("BatchDeleteTargetGroup", http.MethodDelete, "/target_groups/batch", svc.BatchDeleteTargetGroup) + h.Add("BatchUpdateListenerBizInfo", http.MethodPatch, + "/load_balancers/target_groups/bizs/batch/update", svc.BatchUpdateTargetGroupBizInfo) + // RS + h.Add("BatchDeleteTarget", http.MethodDelete, "/load_balancers/targets/batch", svc.BatchDeleteTarget) + h.Add("BatchUpdateTarget", http.MethodPatch, "/load_balancers/targets/batch/update", svc.BatchUpdateTarget) + h.Add("ListTarget", http.MethodPost, "/load_balancers/targets/list", svc.ListTarget) + h.Add("BatchCreateTarget", http.MethodPost, "/targets/batch/create", svc.BatchCreateTarget) + + // 目标组 规则关联关系 + h.Add("CreateTargetGroupListenerRel", http.MethodPost, + "/target_group_listener_rels/create", svc.CreateTargetGroupListenerRel) + h.Add("ListTargetGroupListenerRel", http.MethodPost, + "/target_group_listener_rels/list", svc.ListTargetGroupListenerRel) + h.Add("BatchUpdateListenerRuleRelStatusByTGID", http.MethodPatch, + "/target_group_listener_rels/target_groups/{tg_id}/update", svc.BatchUpdateListenerRuleRelStatusByTGID) + + // 资源跟Flow锁定 + h.Add("CreateResFlowLock", http.MethodPost, "/res_flow_locks/create", svc.CreateResFlowLock) + h.Add("DeleteResFlowLock", http.MethodDelete, "/res_flow_locks/batch", svc.DeleteResFlowLock) + h.Add("ListResFlowLock", http.MethodPost, "/res_flow_locks/list", svc.ListResFlowLock) + h.Add("ResFlowLock", http.MethodPost, "/res_flow_locks/lock", svc.ResFlowLock) + h.Add("ResFlowUnLock", http.MethodPost, "/res_flow_locks/unlock", svc.ResFlowUnLock) + + // 资源跟Flow关联关系 + h.Add("BatchCreateResFlowRel", http.MethodPost, "/res_flow_rels/batch/create", svc.BatchCreateResFlowRel) + h.Add("BatchUpdateResFlowRel", http.MethodPatch, "/res_flow_rels/batch/update", svc.BatchUpdateResFlowRel) + h.Add("BatchDeleteResFlowRel", http.MethodDelete, "/res_flow_rels/batch", svc.BatchDeleteResFlowRel) + h.Add("ListResFlowRel", http.MethodPost, "/res_flow_rels/list", svc.ListResFlowRel) + + h.Load(cap.WebService) +} + +type lbSvc struct { + dao dao.Set +} diff --git a/cmd/data-service/service/cloud/load-balancer/query.go b/cmd/data-service/service/cloud/load-balancer/query.go new file mode 100644 index 0000000000..db57907a08 --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/query.go @@ -0,0 +1,827 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + rawjson "encoding/json" + "fmt" + + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + protocloud "hcm/pkg/api/data-service/cloud" + dataproto "hcm/pkg/api/data-service/cloud/eip" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/json" +) + +// ListLoadBalancer list load balancer. +func (svc *lbSvc) ListLoadBalancer(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancer().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list lb failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list lb failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.LbListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseLoadBalancer, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne := convTableToBaseLB(&one) + details = append(details, *tmpOne) + } + + return &protocloud.LbListResult{Details: details}, nil +} + +// ListLoadBalancerRaw ... +func (svc *lbSvc) ListLoadBalancerRaw(cts *rest.Contexts) (any, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancer().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list lb failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list lb failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.LbRawListResult{Count: result.Count}, nil + } + + details := make([]corelb.LoadBalancerRaw, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne := convTableToBaseLB(&one) + details = append(details, corelb.LoadBalancerRaw{ + BaseLoadBalancer: *tmpOne, + Extension: rawjson.RawMessage(one.Extension), + }) + } + + return &protocloud.LbRawListResult{Details: details}, nil +} + +func convTableToBaseLB(one *tablelb.LoadBalancerTable) *corelb.BaseLoadBalancer { + return &corelb.BaseLoadBalancer{ + ID: one.ID, + CloudID: one.CloudID, + Name: one.Name, + Vendor: one.Vendor, + AccountID: one.AccountID, + LoadBalancerType: one.LBType, + IPVersion: enumor.IPAddressType(one.IPVersion), + BkBizID: one.BkBizID, + Region: one.Region, + Zones: one.Zones, + BackupZones: one.BackupZones, + VpcID: one.VpcID, + CloudVpcID: one.CloudVpcID, + SubnetID: one.SubnetID, + CloudSubnetID: one.CloudSubnetID, + PrivateIPv4Addresses: one.PrivateIPv4Addresses, + PrivateIPv6Addresses: one.PrivateIPv6Addresses, + PublicIPv4Addresses: one.PublicIPv4Addresses, + PublicIPv6Addresses: one.PublicIPv6Addresses, + Domain: one.Domain, + Status: one.Status, + CloudCreatedTime: one.CloudCreatedTime, + CloudStatusTime: one.CloudStatusTime, + CloudExpiredTime: one.CloudExpiredTime, + Memo: one.Memo, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + } +} + +// ListLoadBalancerExt list load balancer ext. +func (svc *lbSvc) ListLoadBalancerExt(cts *rest.Contexts) (interface{}, error) { + vendor := enumor.Vendor(cts.Request.PathParameter("vendor")) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + req := new(dataproto.EipListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Filter: req.Filter, + Page: req.Page, + Fields: req.Fields, + } + + data, err := svc.dao.LoadBalancer().List(cts.Kit, opt) + if err != nil { + return nil, err + } + + switch vendor { + case enumor.TCloud: + return convLbListResult[corelb.TCloudClbExtension](data.Details) + default: + return nil, errf.Newf(errf.InvalidParameter, "unsupported vendor: %s", vendor) + } +} + +// GetLoadBalancer ... +func (svc *lbSvc) GetLoadBalancer(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "lb id is required") + } + + opt := &types.ListOption{ + Filter: tools.EqualExpression("id", id), + Page: core.NewDefaultBasePage(), + } + result, err := svc.dao.LoadBalancer().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list lb(%s) failed, err: %v, rid: %s", err, id, cts.Kit.Rid) + return nil, fmt.Errorf("list lb failed, err: %v", err) + } + + if len(result.Details) != 1 { + return nil, errf.New(errf.RecordNotFound, "load balancer not found") + } + + lbTable := result.Details[0] + switch lbTable.Vendor { + case enumor.TCloud: + return convLoadBalancerWithExt(&lbTable) + default: + return nil, fmt.Errorf("unsupport vendor: %s", vendor) + } +} +func convLoadBalancerWithExt[T corelb.Extension](tableLB *tablelb.LoadBalancerTable) (*corelb.LoadBalancer[T], error) { + base := convTableToBaseLB(tableLB) + extension := new(T) + if tableLB.Extension != "" { + if err := json.UnmarshalFromString(string(tableLB.Extension), extension); err != nil { + return nil, fmt.Errorf("fail unmarshal load balancer extension, err: %v", err) + } + } + return &corelb.LoadBalancer[T]{ + BaseLoadBalancer: *base, + Extension: extension, + }, nil +} + +func convLbListResult[T corelb.Extension](tables []tablelb.LoadBalancerTable) ( + *protocloud.LbExtListResult[T], error) { + + details := make([]corelb.LoadBalancer[T], 0, len(tables)) + for _, tableLB := range tables { + base := convTableToBaseLB(&tableLB) + extension := new(T) + if tableLB.Extension != "" { + if err := json.UnmarshalFromString(string(tableLB.Extension), extension); err != nil { + return nil, fmt.Errorf("fail unmarshal load balancer extension, err: %v", err) + } + } + details = append(details, corelb.LoadBalancer[T]{ + BaseLoadBalancer: *base, + Extension: extension, + }) + } + + return &protocloud.LbExtListResult[T]{ + Details: details, + }, nil +} + +// ListListener list listener. +func (svc *lbSvc) ListListener(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerListener().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list listener failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("list listener failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.ListenerListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseListener, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne := convTableToBaseListener(&one) + details = append(details, *tmpOne) + } + + return &protocloud.ListenerListResult{Details: details}, nil +} + +// ListListenerExt list listener with extension. +func (svc *lbSvc) ListListenerExt(cts *rest.Contexts) (any, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerListener().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list listener failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("list listener failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.ListenerListResult{Count: result.Count}, nil + } + + details := make([]corelb.Listener[corelb.TCloudListenerExtension], 0, len(result.Details)) + for _, one := range result.Details { + tmpOne, err := convTableToListener[corelb.TCloudListenerExtension](&one) + if err != nil { + logs.Errorf("fail to conv listener with extension, err: %v, rid: %s", err, cts.Kit.Rid) + } + details = append(details, *tmpOne) + } + + return &protocloud.TCloudListenerListResult{Details: details}, nil +} + +func convTableToBaseListener(one *tablelb.LoadBalancerListenerTable) *corelb.BaseListener { + return &corelb.BaseListener{ + ID: one.ID, + CloudID: one.CloudID, + Name: one.Name, + Vendor: one.Vendor, + AccountID: one.AccountID, + BkBizID: one.BkBizID, + LbID: one.LBID, + CloudLbID: one.CloudLBID, + Protocol: one.Protocol, + Port: one.Port, + DefaultDomain: one.DefaultDomain, + Zones: one.Zones, + Memo: one.Memo, + SniSwitch: one.SniSwitch, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + } +} + +func convTableToListener[T corelb.ListenerExtension](table *tablelb.LoadBalancerListenerTable) ( + *corelb.Listener[T], error) { + base := convTableToBaseListener(table) + extension := new(T) + if table.Extension != "" { + if err := json.UnmarshalFromString(string(table.Extension), extension); err != nil { + return nil, fmt.Errorf("fail unmarshal listener extension, err: %v", err) + } + } + return &corelb.Listener[T]{ + BaseListener: base, + Extension: extension, + }, nil +} + +// ListTCloudUrlRule list tcloud url rule. +func (svc *lbSvc) ListTCloudUrlRule(cts *rest.Contexts) (any, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerTCloudUrlRule().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list tcloud lb url rule failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("list tcloud lb url rule failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.TCloudURLRuleListResult{Count: result.Count}, nil + } + + details := make([]corelb.TCloudLbUrlRule, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne, err := convTableToBaseTCloudLbURLRule(cts.Kit, &one) + if err != nil { + continue + } + details = append(details, *tmpOne) + } + + return &protocloud.TCloudURLRuleListResult{Details: details}, nil +} + +func convTableToBaseTCloudLbURLRule(kt *kit.Kit, one *tablelb.TCloudLbUrlRuleTable) ( + *corelb.TCloudLbUrlRule, error) { + + var healthCheck *corelb.TCloudHealthCheckInfo + err := json.UnmarshalFromString(string(one.HealthCheck), &healthCheck) + if err != nil { + logs.Errorf("unmarshal healthCheck failed, one: %+v, err: %v, rid: %s", one, err, kt.Rid) + return nil, err + } + + var certInfo *corelb.TCloudCertificateInfo + err = json.UnmarshalFromString(string(one.Certificate), &certInfo) + if err != nil { + logs.Errorf("unmarshal certificate failed, one: %+v, err: %v, rid: %s", one, err, kt.Rid) + return nil, err + } + + return &corelb.TCloudLbUrlRule{ + ID: one.ID, + CloudID: one.CloudID, + Name: one.Name, + RuleType: one.RuleType, + LbID: one.LbID, + CloudLbID: one.CloudLbID, + LblID: one.LblID, + CloudLBLID: one.CloudLBLID, + TargetGroupID: one.TargetGroupID, + CloudTargetGroupID: one.CloudTargetGroupID, + Domain: one.Domain, + URL: one.URL, + Scheduler: one.Scheduler, + SessionType: one.SessionType, + SessionExpire: one.SessionExpire, + HealthCheck: healthCheck, + Certificate: certInfo, + Memo: one.Memo, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + }, nil +} + +// ListTarget list target. +func (svc *lbSvc) ListTarget(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerTarget().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list lb target failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list lb target failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.TargetListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseTarget, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne := convTableToBaseTarget(&one) + details = append(details, *tmpOne) + } + + return &protocloud.TargetListResult{Details: details}, nil +} + +func convTableToBaseTarget(one *tablelb.LoadBalancerTargetTable) *corelb.BaseTarget { + return &corelb.BaseTarget{ + ID: one.ID, + AccountID: one.AccountID, + InstType: one.InstType, + InstID: one.InstID, + CloudInstID: one.CloudInstID, + InstName: one.InstName, + TargetGroupID: one.TargetGroupID, + CloudTargetGroupID: one.CloudTargetGroupID, + Port: one.Port, + Weight: one.Weight, + PrivateIPAddress: one.PrivateIPAddress, + PublicIPAddress: one.PublicIPAddress, + CloudVpcIDs: one.CloudVpcIDs, + Zone: one.Zone, + Memo: one.Memo, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + } +} + +// ListTargetGroup list target group. +func (svc *lbSvc) ListTargetGroup(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target group failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list target group failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.TargetGroupListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseTargetGroup, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne, err := convTableToBaseTargetGroup(cts.Kit, &one) + if err != nil { + continue + } + details = append(details, *tmpOne) + } + + return &protocloud.TargetGroupListResult{Details: details}, nil +} + +// GetTargetGroup ... +func (svc *lbSvc) GetTargetGroup(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "target group id is required") + } + + opt := &types.ListOption{ + Filter: tools.EqualExpression("id", id), + Page: core.NewDefaultBasePage(), + } + result, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target group failed, lblID: %s, err: %v, rid: %s", id, err, cts.Kit.Rid) + return nil, fmt.Errorf("get target group failed, err: %v", err) + } + + if len(result.Details) != 1 { + return nil, errf.New(errf.RecordNotFound, "target group is not found") + } + + tgInfo := result.Details[0] + switch tgInfo.Vendor { + case enumor.TCloud: + return convTableToBaseTargetGroup(cts.Kit, &tgInfo) + default: + return nil, fmt.Errorf("unsupport vendor: %s", vendor) + } +} + +func convTableToBaseTargetGroup(kt *kit.Kit, one *tablelb.LoadBalancerTargetGroupTable) ( + *corelb.BaseTargetGroup, error) { + + var healthCheck *corelb.TCloudHealthCheckInfo + // 支持不返回该字段 + if len(one.HealthCheck) != 0 { + err := json.UnmarshalFromString(string(one.HealthCheck), &healthCheck) + if err != nil { + logs.Errorf("unmarshal healthCheck failed, one: %+v, err: %v, rid: %s", one, err, kt.Rid) + return nil, err + } + } + + return &corelb.BaseTargetGroup{ + ID: one.ID, + CloudID: one.CloudID, + Name: one.Name, + Vendor: one.Vendor, + AccountID: one.AccountID, + BkBizID: one.BkBizID, + TargetGroupType: one.TargetGroupType, + VpcID: one.VpcID, + CloudVpcID: one.CloudVpcID, + Protocol: one.Protocol, + Region: one.Region, + Port: one.Port, + Weight: cvt.PtrToVal(one.Weight), + HealthCheck: healthCheck, + Memo: one.Memo, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + }, nil +} + +// GetListener ... +func (svc *lbSvc) GetListener(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + id := cts.PathParameter("id").String() + if len(id) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + opt := &types.ListOption{ + Filter: tools.EqualExpression("id", id), + Page: core.NewDefaultBasePage(), + } + result, err := svc.dao.LoadBalancerListener().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list listener failed, lblID: %s, err: %v, rid: %s", id, err, cts.Kit.Rid) + return nil, fmt.Errorf("get listener failed, err: %v", err) + } + + if len(result.Details) != 1 { + return nil, errf.New(errf.RecordNotFound, "listener is not found") + } + + lblInfo := result.Details[0] + switch lblInfo.Vendor { + case enumor.TCloud: + newLblInfo, err := convTableToListener[corelb.TCloudListenerExtension](&lblInfo) + if err != nil { + logs.Errorf("fail to conv listener with extension, lblID: %s, err: %v, rid: %s", id, err, cts.Kit.Rid) + return nil, err + } + return newLblInfo, nil + default: + return nil, fmt.Errorf("unsupport vendor: %s", vendor) + } +} + +// ListTargetGroupListenerRel list target group listener rel. +func (svc *lbSvc) ListTargetGroupListenerRel(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target group listener rule rel failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list target listener rule rel failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.TargetListenerRuleRelListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseTargetListenerRuleRel, 0, len(result.Details)) + for _, one := range result.Details { + tmpOne := convTableToBaseTargetListenerRuleRel(&one) + details = append(details, *tmpOne) + } + + return &protocloud.TargetListenerRuleRelListResult{Details: details}, nil +} + +func convTableToBaseTargetListenerRuleRel( + one *tablelb.TargetGroupListenerRuleRelTable) *corelb.BaseTargetListenerRuleRel { + + return &corelb.BaseTargetListenerRuleRel{ + ID: one.ID, + ListenerRuleID: one.ListenerRuleID, + ListenerRuleType: one.ListenerRuleType, + CloudListenerRuleID: one.CloudListenerRuleID, + TargetGroupID: one.TargetGroupID, + CloudTargetGroupID: one.CloudTargetGroupID, + LbID: one.LbID, + CloudLbID: one.CloudLbID, + LblID: one.LblID, + CloudLblID: one.CloudLblID, + BindingStatus: one.BindingStatus, + Detail: one.Detail, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + } +} + +// ListResFlowLock list res flow lock. +func (svc *lbSvc) ListResFlowLock(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.ResourceFlowLock().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list res flow lock failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list res flow lock failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.ResFlowLockListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseResFlowLock, 0, len(result.Details)) + for _, one := range result.Details { + details = append(details, corelb.BaseResFlowLock{ + ResID: one.ResID, + ResType: one.ResType, + Owner: one.Owner, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + }) + } + + return &protocloud.ResFlowLockListResult{Details: details}, nil +} + +// ListResFlowRel list res flow rel. +func (svc *lbSvc) ListResFlowRel(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.ResourceFlowRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list res flow rel failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list res flow rel failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.ResFlowRelListResult{Count: result.Count}, nil + } + + details := make([]corelb.BaseResFlowRel, 0, len(result.Details)) + for _, one := range result.Details { + details = append(details, corelb.BaseResFlowRel{ + ID: one.ID, + ResID: one.ResID, + FlowID: one.FlowID, + TaskType: one.TaskType, + Status: one.Status, + Revision: &core.Revision{ + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + }) + } + + return &protocloud.ResFlowRelListResult{Details: details}, nil +} + +// CountListenerByLbIDs count listener by lbIDs. +func (svc *lbSvc) CountListenerByLbIDs(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.ListListenerCountByLbIDsReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + return svc.dao.LoadBalancerListener().CountListenerByLbIDs(cts.Kit, req.LbIDs) +} diff --git a/cmd/data-service/service/cloud/load-balancer/target_group.go b/cmd/data-service/service/cloud/load-balancer/target_group.go new file mode 100644 index 0000000000..674130b9ef --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/target_group.go @@ -0,0 +1,233 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/slice" + + "github.com/jmoiron/sqlx" +) + +// BatchCreateTargetGroupWithRel 批量创建目标组,并绑定监听器/规则 +func (svc *lbSvc) BatchCreateTargetGroupWithRel(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchCreateTargetGroupWithRel[corelb.TCloudTargetGroupExtension](cts, svc, vendor) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } +} + +func batchCreateTargetGroupWithRel[T corelb.TargetGroupExtension](cts *rest.Contexts, + svc *lbSvc, vendor enumor.Vendor) (any, error) { + + req := new(dataproto.BatchCreateTgWithRelReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + vpcCloudIDs := slice.Map(req.TargetGroups, + func(g dataproto.CreateTargetGroupWithRel[T]) string { return g.TargetGroup.CloudVpcID }) + + result, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + vpcInfoMap, err := getVpcMapByIDs(cts.Kit, vpcCloudIDs) + if err != nil { + return nil, err + } + + tgIDs := make([]string, 0, len(req.TargetGroups)) + for _, tgReq := range req.TargetGroups { + // 创建目标组 + tgTable, err := convTargetGroupCreateReqToTable(cts.Kit, vendor, tgReq.TargetGroup, vpcInfoMap) + if err != nil { + return nil, err + } + + models := []*tablelb.LoadBalancerTargetGroupTable{tgTable} + createTGIDs, err := svc.dao.LoadBalancerTargetGroup().BatchCreateWithTx(cts.Kit, txn, models) + if err != nil { + logs.Errorf("[%s]fail to batch create target group for create tg, err: %v, rid:%s", + vendor, err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target group failed, err: %v", err) + } + tgIDs = append(tgIDs, createTGIDs...) + tgID := createTGIDs[0] + // 添加RS + if len(tgReq.TargetGroup.RsList) != 0 { + _, err = svc.batchCreateTargetWithGroupID(cts.Kit, txn, "", tgID, tgReq.TargetGroup.RsList) + if err != nil { + logs.Errorf("fail to batch create target for create tg, err: %v, rid:%s", err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create target failed, err: %v", err) + } + } + // 创建绑定关系 + if _, err := createRel(cts.Kit, svc, txn, tgReq, tgID, vendor); err != nil { + logs.Errorf("fail to batch create listener rule rel for create tg, err: %v, rid:%s", + err, cts.Kit.Rid) + return nil, fmt.Errorf("batch create listener rule rel failed, err: %v", err) + } + } + + return tgIDs, nil + }) + if err != nil { + return nil, err + } + return &core.BatchCreateResult{IDs: result.([]string)}, nil +} + +func createRel[T corelb.TargetGroupExtension](kt *kit.Kit, svc *lbSvc, txn *sqlx.Tx, + tgReq dataproto.CreateTargetGroupWithRel[T], tgID string, vendor enumor.Vendor) ([]string, error) { + + // 创建关系 + ruleRelModels := []*tablelb.TargetGroupListenerRuleRelTable{{ + ListenerRuleID: tgReq.ListenerRuleID, + CloudListenerRuleID: tgReq.CloudListenerRuleID, + ListenerRuleType: tgReq.ListenerRuleType, + TargetGroupID: tgID, + CloudTargetGroupID: tgID, + LbID: tgReq.LbID, + CloudLbID: tgReq.CloudLbID, + LblID: tgReq.LblID, + CloudLblID: tgReq.CloudLblID, + BindingStatus: tgReq.BindingStatus, + Detail: tgReq.Detail, + Creator: kt.User, + Reviser: kt.User, + }} + switch vendor { + case enumor.TCloud: + // 更新规则表 + rule := &tablelb.TCloudLbUrlRuleTable{ + TargetGroupID: tgID, + CloudTargetGroupID: tgID, + Reviser: kt.User, + } + err := svc.dao.LoadBalancerTCloudUrlRule().UpdateByIDWithTx(kt, txn, tgReq.ListenerRuleID, rule) + if err != nil { + logs.Errorf("fail to update rule while creating target group with rel, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + } + + tx, err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().BatchCreateWithTx(kt, txn, ruleRelModels) + if err != nil { + logs.Errorf("fail to create tg rel while creating target group with rel, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + return tx, err +} + +// BatchDeleteTarget 批量删除本地RS +func (svc *lbSvc) BatchDeleteTarget(cts *rest.Contexts) (any, error) { + + req := new(dataproto.LoadBalancerBatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id", "vendor", "cloud_inst_id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.LoadBalancerTarget().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target for deletion failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list target failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + targetIds := slice.Map(listResp.Details, func(one tablelb.LoadBalancerTargetTable) string { return one.ID }) + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + delFilter := tools.ContainersExpression("id", targetIds) + return nil, svc.dao.LoadBalancerTarget().DeleteWithTx(cts.Kit, txn, delFilter) + }) + if err != nil { + logs.Errorf("delete target(ids=%v) failed, err: %v, rid: %s", targetIds, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchUpdateTarget 批量更新RS +func (svc *lbSvc) BatchUpdateTarget(cts *rest.Contexts) (any, error) { + req := new(dataproto.TargetBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + return svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + for _, target := range req.Targets { + update := &tablelb.LoadBalancerTargetTable{ + InstName: target.InstName, + Port: target.Port, + Weight: target.Weight, + PrivateIPAddress: target.PrivateIPAddress, + PublicIPAddress: target.PublicIPAddress, + Memo: target.Memo, + Reviser: cts.Kit.User, + } + + if err := svc.dao.LoadBalancerTarget().UpdateByIDWithTx(cts.Kit, txn, target.ID, update); err != nil { + logs.Errorf("update tcloud target by id failed, err: %v, id: %s, rid: %s", err, target.ID, cts.Kit.Rid) + return nil, fmt.Errorf("update target failed, err: %v", err) + } + } + + return nil, nil + }) +} diff --git a/cmd/data-service/service/cloud/load-balancer/update.go b/cmd/data-service/service/cloud/load-balancer/update.go new file mode 100644 index 0000000000..1c92a10ea7 --- /dev/null +++ b/cmd/data-service/service/cloud/load-balancer/update.go @@ -0,0 +1,534 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "errors" + "fmt" + + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + tabletype "hcm/pkg/dal/table/types" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" + "hcm/pkg/tools/json" + "hcm/pkg/tools/slice" + + "github.com/jmoiron/sqlx" +) + +// BatchUpdateLoadBalancer 批量跟新clb信息 +func (svc *lbSvc) BatchUpdateLoadBalancer(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchUpdateLoadBalancer[corelb.TCloudClbExtension](cts, svc) + + default: + return nil, fmt.Errorf("unsupport vendor %s", vendor) + } + +} + +func batchUpdateLoadBalancer[T corelb.Extension](cts *rest.Contexts, svc *lbSvc) (any, error) { + + req := new(dataproto.LbExtBatchUpdateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lbIds := slice.Map(req.Lbs, func(one *dataproto.LoadBalancerExtUpdateReq[T]) string { return one.ID }) + + extensionMap, err := svc.listClbExt(cts.Kit, lbIds) + if err != nil { + return nil, err + } + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + for _, lb := range req.Lbs { + update := &tablelb.LoadBalancerTable{ + Name: lb.Name, + BkBizID: lb.BkBizID, + Domain: lb.Domain, + Status: lb.Status, + VpcID: lb.VpcID, + CloudVpcID: lb.CloudVpcID, + SubnetID: lb.SubnetID, + CloudSubnetID: lb.CloudSubnetID, + IPVersion: string(lb.IPVersion), + PrivateIPv4Addresses: lb.PrivateIPv4Addresses, + PrivateIPv6Addresses: lb.PrivateIPv6Addresses, + PublicIPv4Addresses: lb.PublicIPv4Addresses, + PublicIPv6Addresses: lb.PublicIPv6Addresses, + + CloudCreatedTime: lb.CloudCreatedTime, + CloudStatusTime: lb.CloudStatusTime, + CloudExpiredTime: lb.CloudExpiredTime, + Memo: lb.Memo, + Reviser: cts.Kit.User, + } + + if lb.Extension != nil { + extension, exist := extensionMap[lb.ID] + if !exist { + continue + } + + merge, err := json.UpdateMerge(lb.Extension, string(extension)) + if err != nil { + return nil, fmt.Errorf("json UpdateMerge extension failed, err: %v", err) + } + update.Extension = tabletype.JsonField(merge) + } + + if err := svc.dao.LoadBalancer().UpdateByIDWithTx(cts.Kit, txn, lb.ID, update); err != nil { + logs.Errorf("update load balancer by id failed, err: %v, id: %s, rid: %s", err, lb.ID, cts.Kit.Rid) + return nil, fmt.Errorf("update load balancer failed, err: %v", err) + } + } + + return nil, nil + }) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (svc *lbSvc) listClbExt(kt *kit.Kit, ids []string) (map[string]tabletype.JsonField, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: &core.BasePage{Limit: core.DefaultMaxPageLimit}, + } + + resp, err := svc.dao.LoadBalancer().List(kt, opt) + if err != nil { + return nil, err + } + + return converter.SliceToMap(resp.Details, func(t tablelb.LoadBalancerTable) (string, tabletype.JsonField) { + return t.ID, t.Extension + }), nil + +} + +// BatchUpdateLbBizInfo 批量更新业务信息 +func (svc *lbSvc) BatchUpdateLbBizInfo(cts *rest.Contexts) (any, error) { + req := new(dataproto.BizBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + updateFilter := tools.ContainersExpression("id", req.IDs) + updateField := &tablelb.LoadBalancerTable{ + BkBizID: req.BkBizID, + Reviser: cts.Kit.User, + } + return nil, svc.dao.LoadBalancer().Update(cts.Kit, updateFilter, updateField) +} + +// BatchUpdateTargetGroupBizInfo 批量更新目标组业务信息 +func (svc *lbSvc) BatchUpdateTargetGroupBizInfo(cts *rest.Contexts) (any, error) { + req := new(dataproto.BizBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + updateFilter := tools.ContainersExpression("id", req.IDs) + updateField := &tablelb.LoadBalancerTargetGroupTable{ + BkBizID: req.BkBizID, + Reviser: cts.Kit.User, + } + return nil, svc.dao.LoadBalancerTargetGroup().Update(cts.Kit, updateFilter, updateField) +} + +// BatchUpdateListenerBizInfo 批量更新监听器业务信息 +func (svc *lbSvc) BatchUpdateListenerBizInfo(cts *rest.Contexts) (any, error) { + req := new(dataproto.BizBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + updateFilter := tools.ContainersExpression("id", req.IDs) + updateField := &tablelb.LoadBalancerListenerTable{ + BkBizID: req.BkBizID, + Reviser: cts.Kit.User, + } + return nil, svc.dao.LoadBalancerListener().Update(cts.Kit, updateFilter, updateField) +} + +// UpdateTargetGroup batch update argument template +func (svc *lbSvc) UpdateTargetGroup(cts *rest.Contexts) (interface{}, error) { + req := new(dataproto.TargetGroupUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + if len(req.IDs) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "ids is empty") + } + + tgReq := &types.ListOption{ + Filter: tools.ContainersExpression("id", req.IDs), + Page: &core.BasePage{Limit: 1}, + } + tgList, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, tgReq) + if err != nil { + return nil, err + } + if len(tgList.Details) != len(req.IDs) { + return nil, errors.New("not all target groups can be found") + } + updateDataList := make([]*tablelb.LoadBalancerTargetGroupTable, 0, len(req.IDs)) + for _, oldTg := range tgList.Details { + + updateData := &tablelb.LoadBalancerTargetGroupTable{ + ID: oldTg.ID, + Name: req.Name, + BkBizID: req.BkBizID, + TargetGroupType: req.TargetGroupType, + Region: req.Region, + Protocol: req.Protocol, + Port: req.Port, + Weight: req.Weight, + Reviser: cts.Kit.User, + } + + if len(req.CloudVpcID) > 0 { + // 根据cloudVpcID查询VPC信息,如查不到vpcInfo则报错 + vpcInfoMap, err := getVpcMapByIDs(cts.Kit, []string{req.CloudVpcID}) + if err != nil { + return nil, err + } + vpcInfo, ok := vpcInfoMap[req.CloudVpcID] + if !ok { + return nil, errf.Newf(errf.RecordNotFound, "vpcID[%s] not found", req.VpcID) + } + updateData.VpcID = vpcInfo.ID + updateData.CloudVpcID = vpcInfo.CloudID + } + if req.HealthCheck != nil { + mergedHealth, err := json.UpdateMerge(req.HealthCheck, string(oldTg.HealthCheck)) + if err != nil { + return nil, fmt.Errorf("json UpdateMerge rule health check failed, err: %v", err) + } + updateData.HealthCheck = tabletype.JsonField(mergedHealth) + } + updateDataList = append(updateDataList, updateData) + } + if err := svc.dao.LoadBalancerTargetGroup().UpdateBatch(cts.Kit, updateDataList); err != nil { + return nil, err + } + + return nil, nil +} + +// BatchUpdateTCloudUrlRule .. +func (svc *lbSvc) BatchUpdateTCloudUrlRule(cts *rest.Contexts) (any, error) { + req := new(dataproto.TCloudUrlRuleBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + ruleIds := slice.Map(req.UrlRules, func(one *dataproto.TCloudUrlRuleUpdate) string { return one.ID }) + + healthCertMap, err := svc.listRuleHealthAndCert(cts.Kit, ruleIds) + if err != nil { + logs.Errorf("fail to list health and cert of tcloud url rule, err: %s, ruleIds: %v, rid: %s", + err, ruleIds, cts.Kit.Rid) + return nil, err + } + + return svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + for _, rule := range req.UrlRules { + update := &tablelb.TCloudLbUrlRuleTable{ + Name: rule.Name, + Domain: rule.Domain, + URL: rule.URL, + TargetGroupID: rule.TargetGroupID, + CloudTargetGroupID: rule.CloudTargetGroupID, + Scheduler: rule.Scheduler, + SessionExpire: converter.PtrToVal(rule.SessionExpire), + SessionType: rule.SessionType, + Memo: rule.Memo, + Reviser: cts.Kit.User, + } + + if rule.HealthCheck != nil { + hc := healthCertMap[rule.ID] + mergedHealth, err := json.UpdateMerge(rule.HealthCheck, string(hc.Health)) + if err != nil { + return nil, fmt.Errorf("json UpdateMerge rule health check failed, err: %v", err) + } + update.HealthCheck = tabletype.JsonField(mergedHealth) + + } + if rule.Certificate != nil { + hc := healthCertMap[rule.ID] + mergedCert, err := json.UpdateMerge(rule.Certificate, string(hc.Cert)) + if err != nil { + return nil, fmt.Errorf("json UpdateMerge rule cert failed, err: %v", err) + } + update.Certificate = tabletype.JsonField(mergedCert) + } + + if err = svc.dao.LoadBalancerTCloudUrlRule().UpdateByIDWithTx(cts.Kit, txn, rule.ID, update); err != nil { + logs.Errorf("update tcloud rule by id failed, err: %v, id: %s, rid: %s", err, rule.ID, cts.Kit.Rid) + return nil, fmt.Errorf("update rule failed, err: %v", err) + } + } + + return nil, nil + }) +} + +// 更新目标组健康检查 +func (svc *lbSvc) updateTGHealth(kt *kit.Kit, txn *sqlx.Tx, tgID string, health tabletype.JsonField) error { + if len(tgID) == 0 { + return nil + } + tgUpdate := &tablelb.LoadBalancerTargetGroupTable{ + HealthCheck: health, + Reviser: kt.User, + } + return svc.dao.LoadBalancerTargetGroup().UpdateByIDWithTx(kt, txn, tgID, tgUpdate) +} + +// tcloudHealthCert 腾讯云监听器、规则健康检查和证书信息 +type tcloudHealthCert struct { + Health tabletype.JsonField + Cert tabletype.JsonField +} + +func (svc *lbSvc) listRuleHealthAndCert(kt *kit.Kit, ruleIds []string) (map[string]tcloudHealthCert, error) { + opt := &types.ListOption{ + Filter: tools.ContainersExpression("id", ruleIds), + Page: &core.BasePage{Limit: core.DefaultMaxPageLimit}, + } + + resp, err := svc.dao.LoadBalancerTCloudUrlRule().List(kt, opt) + if err != nil { + return nil, err + } + + return converter.SliceToMap(resp.Details, func(t tablelb.TCloudLbUrlRuleTable) (string, tcloudHealthCert) { + return t.ID, tcloudHealthCert{Health: t.HealthCheck, Cert: t.Certificate} + }), nil +} + +// BatchUpdateListener 批量更新监听器基本信息 +func (svc *lbSvc) BatchUpdateListener(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchUpdateListener[corelb.TCloudListenerExtension](cts) + default: + return nil, errf.New(errf.InvalidParameter, "unsupported vendor: "+string(vendor)) + } +} + +func batchUpdateListener[T corelb.ListenerExtension](cts *rest.Contexts) (any, error) { + req := new(dataproto.ListenerBatchUpdateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + return svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + for _, item := range req.Listeners { + extensionJSON, err := tabletype.NewJsonField(item.Extension) + if err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 更新监听器 + lblInfo := &tablelb.LoadBalancerListenerTable{ + Name: item.Name, + BkBizID: item.BkBizID, + SniSwitch: item.SniSwitch, + DefaultDomain: item.DefaultDomain, + Extension: extensionJSON, + Reviser: cts.Kit.User, + } + if err = svc.dao.LoadBalancerListener().Update( + cts.Kit, tools.EqualExpression("id", item.ID), lblInfo); err != nil { + logs.Errorf("update listener by id failed, err: %v, id: %s, rid: %s", err, item.ID, cts.Kit.Rid) + return nil, fmt.Errorf("update listener by id failed, lblID: %s, serr: %v", item.ID, err) + } + } + return nil, nil + }) +} + +// BatchUpdateResFlowRel 批量更新资源跟Flow关联关系的记录 +func (svc *lbSvc) BatchUpdateResFlowRel(cts *rest.Contexts) (any, error) { + req := new(dataproto.ResFlowRelBatchUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + return svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + for _, item := range req.ResFlowRels { + model := &tablelb.ResourceFlowRelTable{ + TaskType: item.TaskType, + Status: item.Status, + Reviser: cts.Kit.User, + } + filter := tools.ExpressionAnd( + tools.RuleEqual("id", item.ID), + tools.RuleEqual("res_id", item.ResID), + tools.RuleEqual("res_type", item.ResType), + tools.RuleEqual("flow_id", item.FlowID), + ) + if err := svc.dao.ResourceFlowRel().Update(cts.Kit, filter, model); err != nil { + logs.Errorf("update res flow rel failed, err: %v, id: %s, rid: %s", err, item.ID, cts.Kit.Rid) + return nil, fmt.Errorf("update res flow rel failed, id: %s, serr: %v", item.ID, err) + } + } + return nil, nil + }) +} + +// ResFlowUnLock res flow unlock. +func (svc *lbSvc) ResFlowUnLock(cts *rest.Contexts) (interface{}, error) { + req := new(dataproto.ResFlowLockReq) + if err := cts.DecodeInto(req); err != nil { + logs.Errorf("res flow unlock decode failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + if err := req.Validate(); err != nil { + logs.Errorf("res flow unlock validate failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + lockDelFilter := tools.ExpressionAnd( + tools.RuleEqual("res_id", req.ResID), + tools.RuleEqual("res_type", req.ResType), + tools.RuleEqual("owner", req.FlowID), + ) + if err := svc.dao.ResourceFlowLock().DeleteWithTx(cts.Kit, txn, lockDelFilter); err != nil { + logs.Errorf("delete res flow lock failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + + relModel := &tablelb.ResourceFlowRelTable{ + Status: req.Status, + Reviser: cts.Kit.User, + } + filter := tools.ExpressionAnd( + tools.RuleEqual("res_id", req.ResID), + tools.RuleEqual("res_type", req.ResType), + tools.RuleEqual("flow_id", req.FlowID), + ) + if err := svc.dao.ResourceFlowRel().Update(cts.Kit, filter, relModel); err != nil { + logs.Errorf("update res flow rel failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, fmt.Errorf("update res flow rel failed, err: %v", err) + } + return nil, nil + }) + if err != nil { + logs.Errorf("res flow unlock failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchUpdateListenerRuleRelStatusByTGID 根据目标组id 批量修改目标组和规则、监听器关系的状态 +func (svc *lbSvc) BatchUpdateListenerRuleRelStatusByTGID(cts *rest.Contexts) (any, error) { + tgID := cts.PathParameter("tg_id").String() + + req := new(dataproto.TGListenerRelStatusUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + model := &tablelb.TargetGroupListenerRuleRelTable{ + BindingStatus: req.BindingStatus, + Detail: req.Detail, + Reviser: cts.Kit.User, + } + tgFilter := tools.EqualExpression("target_group_id", tgID) + return svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (any, error) { + + err := svc.dao.LoadBalancerTargetGroupListenerRuleRel().Update(cts.Kit, tgFilter, model) + if err != nil { + logs.Errorf("fail to update listener rule rel status by target group(%s), err: %v, rid:%s", + tgID, err, cts.Kit.Rid) + return nil, fmt.Errorf("update target group listener rel by target group(%s) failed, err: %v", tgID, err) + } + return nil, nil + }) +} diff --git a/cmd/data-service/service/cloud/security-group-common-rel/create.go b/cmd/data-service/service/cloud/security-group-common-rel/create.go new file mode 100644 index 0000000000..b52be2a854 --- /dev/null +++ b/cmd/data-service/service/cloud/security-group-common-rel/create.go @@ -0,0 +1,139 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sgcomrel + +import ( + "fmt" + + "hcm/pkg/api/core" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + tablecloud "hcm/pkg/dal/table/cloud" + "hcm/pkg/logs" + "hcm/pkg/rest" + + "github.com/jmoiron/sqlx" +) + +// BatchCreate rels. +func (svc *sgComRelSvc) BatchCreate(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.SGCommonRelBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + models := make([]tablecloud.SecurityGroupCommonRelTable, 0, len(req.Rels)) + for _, one := range req.Rels { + models = append(models, tablecloud.SecurityGroupCommonRelTable{ + Vendor: one.Vendor, + ResID: one.ResID, + ResType: one.ResType, + SecurityGroupID: one.SecurityGroupID, + Priority: one.Priority, + Creator: cts.Kit.User, + }) + } + + if err := svc.dao.SGCommonRel().BatchCreateWithTx(cts.Kit, txn, models); err != nil { + return nil, fmt.Errorf("batch create sg common rels failed, err: %v", err) + } + + return nil, nil + }) + if err != nil { + logs.Errorf("batch create sg common rels failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// BatchUpsert rels. +func (svc *sgComRelSvc) BatchUpsert(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.SGCommonRelBatchUpsertReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + delIDs := make([]uint64, 0) + if req.DeleteReq != nil && req.DeleteReq.Filter != nil { + opt := &types.ListOption{ + Fields: []string{"id"}, + Filter: req.DeleteReq.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.SGCommonRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list security group common rels failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list security group common rels failed, err: %v", err) + } + + if len(listResp.Details) == 0 && len(req.Rels) == 0 { + return nil, nil + } + + for _, one := range listResp.Details { + delIDs = append(delIDs, one.ID) + } + } + + _, err := svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + if len(delIDs) > 0 { + if err := svc.dao.SGCommonRel().DeleteWithTx( + cts.Kit, txn, tools.ContainersExpression("id", delIDs)); err != nil { + return nil, err + } + } + + models := make([]tablecloud.SecurityGroupCommonRelTable, 0, len(req.Rels)) + for _, one := range req.Rels { + models = append(models, tablecloud.SecurityGroupCommonRelTable{ + Vendor: one.Vendor, + ResID: one.ResID, + ResType: one.ResType, + SecurityGroupID: one.SecurityGroupID, + Priority: one.Priority, + Creator: cts.Kit.User, + }) + } + if err := svc.dao.SGCommonRel().BatchCreateWithTx(cts.Kit, txn, models); err != nil { + return nil, fmt.Errorf("batch create sg common rels failed, err: %v", err) + } + return nil, nil + }) + if err != nil { + logs.Errorf("batch upsert sg common rels failed, err: %v, req: %+v, rid: %s", err, req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/data-service/service/cloud/security-group-common-rel/delete.go b/cmd/data-service/service/cloud/security-group-common-rel/delete.go new file mode 100644 index 0000000000..71cb04df4d --- /dev/null +++ b/cmd/data-service/service/cloud/security-group-common-rel/delete.go @@ -0,0 +1,82 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sgcomrel + +import ( + "fmt" + + "hcm/pkg/api/core" + proto "hcm/pkg/api/data-service" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + "hcm/pkg/logs" + "hcm/pkg/rest" + + "github.com/jmoiron/sqlx" +) + +// BatchDelete rels. +func (svc *sgComRelSvc) BatchDelete(cts *rest.Contexts) (interface{}, error) { + req := new(proto.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: []string{"id"}, + Filter: req.Filter, + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dao.SGCommonRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list security group common rels failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list security group common rels failed, err: %v", err) + } + + if len(listResp.Details) == 0 { + return nil, nil + } + + delIDs := make([]uint64, len(listResp.Details)) + for index, one := range listResp.Details { + delIDs[index] = one.ID + } + + _, err = svc.dao.Txn().AutoTxn(cts.Kit, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + if err = svc.dao.SGCommonRel().DeleteWithTx( + cts.Kit, txn, tools.ContainersExpression("id", delIDs)); err != nil { + return nil, err + } + + return nil, nil + }) + if err != nil { + logs.Errorf("delete security group common rels failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/data-service/service/cloud/security-group-common-rel/init.go b/cmd/data-service/service/cloud/security-group-common-rel/init.go new file mode 100644 index 0000000000..990a08e93a --- /dev/null +++ b/cmd/data-service/service/cloud/security-group-common-rel/init.go @@ -0,0 +1,50 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sgcomrel + +import ( + "net/http" + + "hcm/cmd/data-service/service/capability" + "hcm/pkg/dal/dao" + "hcm/pkg/rest" +) + +// InitService initial the security group common rel service +func InitService(cap *capability.Capability) { + svc := &sgComRelSvc{ + dao: cap.Dao, + } + + h := rest.NewHandler() + + h.Add("BatchCreate", http.MethodPost, "/security_group_common_rels/batch/create", svc.BatchCreate) + h.Add("BatchUpsert", http.MethodPost, "/security_group_common_rels/batch/upsert", svc.BatchUpsert) + h.Add("BatchDelete", http.MethodDelete, "/security_group_common_rels/batch", svc.BatchDelete) + h.Add("List", http.MethodPost, "/security_group_common_rels/list", svc.List) + h.Add("ListWithSecurityGroup", http.MethodPost, "/security_group_common_rels/with/security_group/list", + svc.ListWithSecurityGroup) + + h.Load(cap.WebService) +} + +type sgComRelSvc struct { + dao dao.Set +} diff --git a/cmd/data-service/service/cloud/security-group-common-rel/join_security_group.go b/cmd/data-service/service/cloud/security-group-common-rel/join_security_group.go new file mode 100644 index 0000000000..ce9ebfcc04 --- /dev/null +++ b/cmd/data-service/service/cloud/security-group-common-rel/join_security_group.go @@ -0,0 +1,74 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sgcomrel + +import ( + corecloud "hcm/pkg/api/core/cloud" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/errf" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// ListWithSecurityGroup ... +func (svc *sgComRelSvc) ListWithSecurityGroup(cts *rest.Contexts) (interface{}, error) { + req := new(protocloud.SGCommonRelWithSecurityGroupListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + details, err := svc.dao.SGCommonRel().ListJoinSecurityGroup(cts.Kit, req.ResIDs, req.ResType) + if err != nil { + logs.Errorf("list sg common rels join security group failed, err: %v, resIDs: %v, resType: %s, rid: %s", + err, req.ResIDs, req.ResType, cts.Kit.Rid) + return nil, err + } + + sgs := make([]corecloud.SGCommonRelWithBaseSecurityGroup, 0, len(details.Details)) + for _, one := range details.Details { + sgs = append(sgs, corecloud.SGCommonRelWithBaseSecurityGroup{ + BaseSecurityGroup: corecloud.BaseSecurityGroup{ + ID: one.ID, + Vendor: one.Vendor, + CloudID: one.CloudID, + Region: one.Region, + Name: one.Name, + Memo: one.Memo, + AccountID: one.AccountID, + BkBizID: one.BkBizID, + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }, + ResID: one.ResID, + ResType: one.ResType, + Priority: one.Priority, + RelCreator: one.RelCreator, + RelCreatedAt: one.RelCreatedAt.String(), + }) + } + + return sgs, nil +} diff --git a/cmd/data-service/service/cloud/security-group-common-rel/query.go b/cmd/data-service/service/cloud/security-group-common-rel/query.go new file mode 100644 index 0000000000..9549b5c7d5 --- /dev/null +++ b/cmd/data-service/service/cloud/security-group-common-rel/query.go @@ -0,0 +1,77 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sgcomrel + +import ( + "fmt" + + "hcm/pkg/api/core" + corecloud "hcm/pkg/api/core/cloud" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/types" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// List rels. +func (svc *sgComRelSvc) List(cts *rest.Contexts) (interface{}, error) { + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + opt := &types.ListOption{ + Fields: req.Fields, + Filter: req.Filter, + Page: req.Page, + } + result, err := svc.dao.SGCommonRel().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list security group common rels failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, fmt.Errorf("list security group common rels failed, err: %v", err) + } + + if req.Page.Count { + return &protocloud.SGCommonRelListResult{Count: result.Count}, nil + } + + details := make([]corecloud.SecurityGroupCommonRel, 0, len(result.Details)) + for _, one := range result.Details { + details = append(details, corecloud.SecurityGroupCommonRel{ + ID: one.ID, + Vendor: one.Vendor, + ResID: one.ResID, + ResType: one.ResType, + Priority: one.Priority, + SecurityGroupID: one.SecurityGroupID, + Creator: one.Creator, + Reviser: one.Reviser, + CreatedAt: one.CreatedAt.String(), + UpdatedAt: one.UpdatedAt.String(), + }) + } + + return &protocloud.SGCommonRelListResult{Details: details}, nil +} diff --git a/cmd/data-service/service/service.go b/cmd/data-service/service/service.go index 1ff480f213..14dae19dce 100644 --- a/cmd/data-service/service/service.go +++ b/cmd/data-service/service/service.go @@ -38,17 +38,20 @@ import ( accountbizrel "hcm/cmd/data-service/service/cloud/account-biz-rel" argstpl "hcm/cmd/data-service/service/cloud/argument-template" "hcm/cmd/data-service/service/cloud/bill" + "hcm/cmd/data-service/service/cloud/cert" "hcm/cmd/data-service/service/cloud/cvm" "hcm/cmd/data-service/service/cloud/disk" diskcvmrel "hcm/cmd/data-service/service/cloud/disk-cvm-rel" "hcm/cmd/data-service/service/cloud/eip" eipcvmrel "hcm/cmd/data-service/service/cloud/eip-cvm-rel" "hcm/cmd/data-service/service/cloud/image" + "hcm/cmd/data-service/service/cloud/load-balancer" networkinterface "hcm/cmd/data-service/service/cloud/network-interface" networkcvmrel "hcm/cmd/data-service/service/cloud/network-interface-cvm-rel" "hcm/cmd/data-service/service/cloud/region" resourcegroup "hcm/cmd/data-service/service/cloud/resource-group" routetable "hcm/cmd/data-service/service/cloud/route-table" + sgcomrel "hcm/cmd/data-service/service/cloud/security-group-common-rel" "hcm/cmd/data-service/service/cloud/security-group" sgcvmrel "hcm/cmd/data-service/service/cloud/security-group-cvm-rel" subaccount "hcm/cmd/data-service/service/cloud/sub-account" @@ -216,6 +219,9 @@ func (s *Service) apiSet() *restful.Container { user.InitService(capability) cloudselection.InitService(capability) argstpl.InitService(capability) + cert.InitService(capability) + loadbalancer.InitService(capability) + sgcomrel.InitService(capability) return restful.NewContainer().Add(capability.WebService) } diff --git a/cmd/hc-service/logics/res-sync/common/diff.go b/cmd/hc-service/logics/res-sync/common/diff.go index fbd409e6a5..89a376be1e 100644 --- a/cmd/hc-service/logics/res-sync/common/diff.go +++ b/cmd/hc-service/logics/res-sync/common/diff.go @@ -23,11 +23,13 @@ import ( "hcm/pkg/adaptor/types" "hcm/pkg/adaptor/types/account" typeargstpl "hcm/pkg/adaptor/types/argument-template" + "hcm/pkg/adaptor/types/cert" typescvm "hcm/pkg/adaptor/types/cvm" typesdisk "hcm/pkg/adaptor/types/disk" typeseip "hcm/pkg/adaptor/types/eip" firewallrule "hcm/pkg/adaptor/types/firewall-rule" typesimage "hcm/pkg/adaptor/types/image" + typeslb "hcm/pkg/adaptor/types/load-balancer" typesni "hcm/pkg/adaptor/types/network-interface" typesregion "hcm/pkg/adaptor/types/region" typesresourcegroup "hcm/pkg/adaptor/types/resource-group" @@ -38,9 +40,11 @@ import ( typeszone "hcm/pkg/adaptor/types/zone" cloudcore "hcm/pkg/api/core/cloud" coreargstpl "hcm/pkg/api/core/cloud/argument-template" + corecert "hcm/pkg/api/core/cloud/cert" corecvm "hcm/pkg/api/core/cloud/cvm" coredisk "hcm/pkg/api/core/cloud/disk" coreimage "hcm/pkg/api/core/cloud/image" + corelb "hcm/pkg/api/core/cloud/load-balancer" corecloudni "hcm/pkg/api/core/cloud/network-interface" coreregion "hcm/pkg/api/core/cloud/region" coreresourcegroup "hcm/pkg/api/core/cloud/resource-group" @@ -51,6 +55,7 @@ import ( dataeip "hcm/pkg/api/data-service/cloud/eip" ) +// CloudResType 云资源类型 type CloudResType interface { GetCloudID() string @@ -141,9 +146,16 @@ type CloudResType interface { typeargstpl.TCloudArgsTplAddress | typeargstpl.TCloudArgsTplAddressGroup | typeargstpl.TCloudArgsTplService | - typeargstpl.TCloudArgsTplServiceGroup + typeargstpl.TCloudArgsTplServiceGroup | + + cert.TCloudCert | + typeslb.TCloudClb | + typeslb.TCloudListener | + typeslb.TCloudUrlRule | + typeslb.Backend } +// DBResType 本地资源类型 type DBResType interface { GetID() string GetCloudID() string @@ -229,7 +241,14 @@ type DBResType interface { corerecyclerecord.EipBindInfo | corerecyclerecord.DiskAttachInfo | - *coreargstpl.ArgsTpl[coreargstpl.TCloudArgsTplExtension] + *coreargstpl.ArgsTpl[coreargstpl.TCloudArgsTplExtension] | + + *corecert.Cert[corecert.TCloudCertExtension] | + + corelb.TCloudLoadBalancer | + corelb.TCloudLbUrlRule | + corelb.TCloudListener | + corelb.BaseTarget } // Diff 对比云和db资源,划分出新增数据,更新数据,删除数据。 diff --git a/cmd/hc-service/logics/res-sync/common/types.go b/cmd/hc-service/logics/res-sync/common/types.go index d5efb8e15b..88552a9caf 100644 --- a/cmd/hc-service/logics/res-sync/common/types.go +++ b/cmd/hc-service/logics/res-sync/common/types.go @@ -25,3 +25,10 @@ type VpcDB struct { VpcID string BkCloudID int64 } + +// OrderedRel 有序关系,用于比较类似于多个安全组但是有序的情况 +type OrderedRel struct { + CloudResID string `json:"cloud_res_id"` + ResID string `json:"res_id"` + Priority int64 `json:"priority"` +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/argument_template.go b/cmd/hc-service/logics/res-sync/tcloud/argument_template.go index 0e37a0707b..d718991d82 100644 --- a/cmd/hc-service/logics/res-sync/tcloud/argument_template.go +++ b/cmd/hc-service/logics/res-sync/tcloud/argument_template.go @@ -66,16 +66,16 @@ func (cli *client) ArgsTplAddress(kt *kit.Kit, params *SyncBaseParams, opt *Sync if err != nil { return nil, err } - logs.Infof("[%s] hcservice argument template address listFromCloud success, params: %+v, cloud_count: %d, rid: %s", - enumor.TCloud, params, len(fromCloud), kt.Rid) + // logs.Infof("[%s] hcservice argument template address listFromCloud success, params: %+v, cloud_count: %d, rid: %s", + // enumor.TCloud, params, len(fromCloud), kt.Rid) fromDB, err := cli.listFromDB(kt, params, enumor.AddressType) if err != nil { return nil, err } - logs.Infof("[%s] hcservice sync argument template address listFromDB success, db_count: %d, rid: %s", - enumor.TCloud, len(fromDB), kt.Rid) + // logs.Infof("[%s] hcservice sync argument template address listFromDB success, db_count: %d, rid: %s", + // enumor.TCloud, len(fromDB), kt.Rid) if len(fromCloud) == 0 && len(fromDB) == 0 { return new(SyncResult), nil diff --git a/cmd/hc-service/logics/res-sync/tcloud/cert.go b/cmd/hc-service/logics/res-sync/tcloud/cert.go new file mode 100644 index 0000000000..30d9073604 --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/cert.go @@ -0,0 +1,401 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "fmt" + "strconv" + "time" + + "hcm/cmd/hc-service/logics/res-sync/common" + typecert "hcm/pkg/adaptor/types/cert" + adcore "hcm/pkg/adaptor/types/core" + "hcm/pkg/api/core" + corecert "hcm/pkg/api/core/cloud/cert" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/table/types" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/runtime/filter" + "hcm/pkg/tools/assert" + "hcm/pkg/tools/converter" +) + +// SyncCertOption ... +type SyncCertOption struct { + BkBizID int64 `json:"bk_biz_id" validate:"omitempty"` +} + +// Validate ... +func (opt SyncCertOption) Validate() error { + return validator.Validate.Struct(opt) +} + +// Cert ... +func (cli *client) Cert(kt *kit.Kit, params *SyncBaseParams, opt *SyncCertOption) (*SyncResult, error) { + if err := validator.ValidateTool(params, opt); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + certFromCloud, err := cli.listCertFromCloud(kt, params) + if err != nil { + return nil, err + } + + logs.Infof("[%s] hcservice sync cert listCertFromCloud success, params: %+v, cloud_cert_count: %d, rid: %s", + enumor.TCloud, params, len(certFromCloud), kt.Rid) + + certFromDB, err := cli.listCertFromDB(kt, params) + if err != nil { + return nil, err + } + + logs.Infof("[%s] hcservice sync cert listCertFromDB success, db_cert_count: %d, rid: %s", + enumor.TCloud, len(certFromDB), kt.Rid) + + if len(certFromCloud) == 0 && len(certFromDB) == 0 { + return new(SyncResult), nil + } + + addSlice, updateMap, delCloudIDs := common.Diff[typecert.TCloudCert, *corecert.Cert[corecert.TCloudCertExtension]]( + certFromCloud, certFromDB, isCertChange) + + logs.Infof("[%s] hcservice sync cert diff success, addNum: %d, updateNum: %d, delNum: %d, rid: %s", + enumor.TCloud, len(addSlice), len(updateMap), len(delCloudIDs), kt.Rid) + + if len(delCloudIDs) > 0 { + if err = cli.deleteCert(kt, params.AccountID, params.Region, delCloudIDs); err != nil { + return nil, err + } + } + + if len(addSlice) > 0 { + if err = cli.createCert(kt, params.AccountID, opt, addSlice); err != nil { + return nil, err + } + } + + if len(updateMap) > 0 { + if err = cli.updateCert(kt, params.AccountID, updateMap); err != nil { + return nil, err + } + } + + return new(SyncResult), nil +} + +func (cli *client) deleteCert(kt *kit.Kit, accountID, region string, delCloudIDs []string) error { + if len(delCloudIDs) <= 0 { + return fmt.Errorf("hcservice resource sync failed, delCloudIDs is <= 0, not delete") + } + + checkParams := &SyncBaseParams{ + AccountID: accountID, + Region: region, + CloudIDs: delCloudIDs, + } + delFromCloud, err := cli.listCertFromCloud(kt, checkParams) + if err != nil { + return err + } + + if len(delFromCloud) > 0 { + logs.Errorf("[%s] validate cert not exist failed, before delete, opt: %v, failed_count: %d, rid: %s", + enumor.TCloud, checkParams, len(delFromCloud), kt.Rid) + return fmt.Errorf("validate cert not exist failed, before delete") + } + + deleteReq := &protocloud.CertBatchDeleteReq{ + Filter: tools.ContainersExpression("cloud_id", delCloudIDs), + } + if err = cli.dbCli.Global.BatchDeleteCert(kt.Ctx, kt.Header(), deleteReq); err != nil { + logs.Errorf("[%s] request dataservice to batch delete cert failed, err: %v, rid: %s", enumor.TCloud, + err, kt.Rid) + return err + } + + logs.Infof("[%s] sync cert to delete cert success, accountID: %s, count: %d, rid: %s", enumor.TCloud, + accountID, len(delCloudIDs), kt.Rid) + + return nil +} + +func (cli *client) updateCert(kt *kit.Kit, accountID string, updateMap map[string]typecert.TCloudCert) error { + if len(updateMap) <= 0 { + return fmt.Errorf("hcservice resource sync failed, updateMap is <= 0, not update") + } + + certs := make([]*protocloud.CertExtUpdateReq[corecert.TCloudCertExtension], 0) + + for id, one := range updateMap { + domainJson, err := types.NewJsonField(one.SubjectAltName) + if err != nil { + return fmt.Errorf("json marshal extension failed, err: %w", err) + } + + cert := &protocloud.CertExtUpdateReq[corecert.TCloudCertExtension]{ + ID: id, + Name: converter.PtrToVal(one.Alias), + Vendor: string(enumor.TCloud), + AccountID: accountID, + Domain: domainJson, + CertType: enumor.CertType(converter.PtrToVal(one.CertificateType)), + EncryptAlgorithm: converter.PtrToVal(one.EncryptAlgorithm), + CertStatus: strconv.FormatUint(converter.PtrToVal(one.Status), 10), + CloudCreatedTime: converter.PtrToVal(one.InsertTime), + CloudExpiredTime: converter.PtrToVal(one.CertEndTime), + } + + certs = append(certs, cert) + } + + var updateReq protocloud.CertExtBatchUpdateReq[corecert.TCloudCertExtension] + for _, item := range certs { + updateReq = append(updateReq, item) + } + if _, err := cli.dbCli.TCloud.BatchUpdateCert(kt.Ctx, kt.Header(), &updateReq); err != nil { + logs.Errorf("[%s] request dataservice BatchUpdateCert failed, err: %v, rid: %s", enumor.TCloud, + err, kt.Rid) + return err + } + + logs.Infof("[%s] sync cert to update cert success, accountID: %s, count: %d, rid: %s", enumor.TCloud, + accountID, len(updateMap), kt.Rid) + + return nil +} + +func (cli *client) createCert(kt *kit.Kit, accountID string, opt *SyncCertOption, addSlice []typecert.TCloudCert) error { + if len(addSlice) <= 0 { + return fmt.Errorf("hcservice resource sync failed, addSlice is <= 0, not create") + } + + var createReq = new(protocloud.CertBatchCreateReq[corecert.TCloudCertExtension]) + + for _, one := range addSlice { + domainJson, err := types.NewJsonField(one.SubjectAltName) + if err != nil { + return fmt.Errorf("json marshal extension failed, err: %w", err) + } + + cert := []protocloud.CertBatchCreate[corecert.TCloudCertExtension]{ + { + CloudID: one.GetCloudID(), + Name: converter.PtrToVal(one.Alias), + Vendor: string(enumor.TCloud), + AccountID: accountID, + BkBizID: opt.BkBizID, + Domain: domainJson, + CertType: enumor.CertType(converter.PtrToVal(one.CertificateType)), + EncryptAlgorithm: converter.PtrToVal(one.EncryptAlgorithm), + CertStatus: strconv.FormatUint(converter.PtrToVal(one.Status), 10), + CloudCreatedTime: converter.PtrToVal(one.InsertTime), + CloudExpiredTime: converter.PtrToVal(one.CertEndTime), + }, + } + + createReq.Certs = append(createReq.Certs, cert...) + } + + newIDs, err := cli.dbCli.TCloud.BatchCreateCert(kt.Ctx, kt.Header(), createReq) + if err != nil { + logs.Errorf("[%s] request dataservice to create tcloud cert failed, createReq: %+v, err: %v, rid: %s", + enumor.TCloud, createReq, err, kt.Rid) + return err + } + + logs.Infof("[%s] sync cert to create cert success, accountID: %s, count: %d, newIDs: %v, opt: %+v, rid: %s", enumor.TCloud, + accountID, len(addSlice), newIDs, opt, kt.Rid) + + return nil +} + +func (cli *client) listCertFromCloud(kt *kit.Kit, params *SyncBaseParams) ([]typecert.TCloudCert, error) { + if err := params.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + list := make([]typecert.TCloudCert, 0) + for _, tmpCloudID := range params.CloudIDs { + opt := &typecert.TCloudListOption{ + SearchKey: tmpCloudID, + Page: &adcore.TCloudPage{Offset: 0, Limit: 1}, + } + result, err := cli.cloudCli.ListCert(kt, opt) + if err != nil { + logs.Errorf("[%s] list cert from cloud failed, account: %s, opt: %v, err: %v, rid: %s", enumor.TCloud, + params.AccountID, opt, err, kt.Rid) + return nil, err + } + + list = append(list, result...) + } + + return list, nil +} + +func (cli *client) listCertFromDB(kt *kit.Kit, params *SyncBaseParams) ( + []*corecert.Cert[corecert.TCloudCertExtension], error) { + + if err := params.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + req := &core.ListReq{ + Filter: &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{ + &filter.AtomRule{ + Field: "account_id", + Op: filter.Equal.Factory(), + Value: params.AccountID, + }, + &filter.AtomRule{ + Field: "cloud_id", + Op: filter.In.Factory(), + Value: params.CloudIDs, + }, + }, + }, + Page: core.NewDefaultBasePage(), + } + result, err := cli.dbCli.TCloud.ListCert(kt.Ctx, kt.Header(), req) + if err != nil { + logs.Errorf("[%s] list cert from db failed, account: %s, req: %v, err: %v, rid: %s", + enumor.TCloud, params.AccountID, req, err, kt.Rid) + return nil, err + } + + return result.Details, nil +} + +func isCertChange(cloud typecert.TCloudCert, db *corecert.Cert[corecert.TCloudCertExtension]) bool { + if converter.PtrToVal(cloud.Alias) != db.Name { + return true + } + + if !assert.IsPtrStringSliceEqual(cloud.SubjectAltName, db.Domain) { + return true + } + + if enumor.CertType(converter.PtrToVal(cloud.CertificateType)) != db.CertType { + return true + } + + statusCloud := strconv.FormatUint(converter.PtrToVal(cloud.Status), 10) + if statusCloud != db.CertStatus { + return true + } + + cloudEndTime := converter.PtrToVal(cloud.CertEndTime) + if len(cloudEndTime) == 0 && len(db.CloudExpiredTime) > 0 { + return true + } + + if len(cloudEndTime) > 0 && len(db.CloudExpiredTime) == 0 { + return true + } + + expireTime, err := time.Parse(constant.TimeStdFormat, db.CloudExpiredTime) + if err != nil { + logs.Errorf("cert sync expired time parse failed, dbExpireTime: %s, err: %v", db.CloudExpiredTime, err) + return true + } + + if cloudEndTime != expireTime.Format(constant.DateTimeLayout) { + return true + } + + if converter.PtrToVal(cloud.EncryptAlgorithm) != db.EncryptAlgorithm { + return true + } + + return false +} + +// RemoveCertDeleteFromCloud ... +func (cli *client) RemoveCertDeleteFromCloud(kt *kit.Kit, accountID, region string) error { + req := &core.ListReq{ + Fields: []string{"id", "cloud_id"}, + Filter: &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{ + &filter.AtomRule{Field: "account_id", Op: filter.Equal.Factory(), Value: accountID}, + }, + }, + Page: &core.BasePage{ + Start: 0, + Limit: constant.BatchOperationMaxLimit, + }, + } + for { + resultFromDB, err := cli.dbCli.Global.ListCert(kt, req) + if err != nil { + logs.Errorf("[%s] request dataservice to list cert failed, req: %v, err: %v, rid: %s", + enumor.TCloud, req, err, kt.Rid) + return err + } + + cloudIDs := make([]string, 0) + for _, one := range resultFromDB.Details { + cloudIDs = append(cloudIDs, one.CloudID) + } + + if len(cloudIDs) == 0 { + break + } + + params := &SyncBaseParams{ + AccountID: accountID, + Region: region, + CloudIDs: cloudIDs, + } + resultFromCloud, err := cli.listCertFromCloud(kt, params) + if err != nil { + return err + } + + // 如果有资源没有查询出来,说明数据被从云上删除 + if len(resultFromCloud) != len(cloudIDs) { + cloudIDMap := converter.StringSliceToMap(cloudIDs) + for _, one := range resultFromCloud { + delete(cloudIDMap, converter.PtrToVal(one.CertificateId)) + } + + cloudIDs = converter.MapKeyToStringSlice(cloudIDMap) + if err = cli.deleteCert(kt, accountID, region, cloudIDs); err != nil { + return err + } + } + + if len(resultFromDB.Details) < constant.BatchOperationMaxLimit { + break + } + + req.Page.Start += constant.BatchOperationMaxLimit + } + + return nil +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/client.go b/cmd/hc-service/logics/res-sync/tcloud/client.go index ea5a8dccac..247cae29ed 100644 --- a/cmd/hc-service/logics/res-sync/tcloud/client.go +++ b/cmd/hc-service/logics/res-sync/tcloud/client.go @@ -72,6 +72,15 @@ type Interface interface { RemoveArgsTplServiceDeleteFromCloud(kt *kit.Kit, accountID string, region string) error ArgsTplServiceGroup(kt *kit.Kit, params *SyncBaseParams, opt *SyncArgsTplOption) (*SyncResult, error) RemoveArgsTplServiceGroupDeleteFromCloud(kt *kit.Kit, accountID string, region string) error + + Cert(kt *kit.Kit, params *SyncBaseParams, opt *SyncCertOption) (*SyncResult, error) + RemoveCertDeleteFromCloud(kt *kit.Kit, accountID string, region string) error + + LoadBalancer(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) + RemoveLoadBalancerDeleteFromCloud(kt *kit.Kit, accountID string, region string) error + + LoadBalancerWithListener(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) + Listener(kt *kit.Kit, opt *SyncListenerOfSingleLBOption) (*SyncResult, error) } var _ Interface = new(client) diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer.go new file mode 100644 index 0000000000..8b46df59b6 --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer.go @@ -0,0 +1,708 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http!=//opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "fmt" + "strconv" + + "hcm/cmd/hc-service/logics/res-sync/common" + typecore "hcm/pkg/adaptor/types/core" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/assert" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/slice" + + tclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" +) + +// LoadBalancerWithListener 同步指定负载均衡及下属监听器、规则 +// 1. 同步该负载均衡自身属性,同步关联安全组信息 +// 2. 同步该负载均衡下的监听器 +// 3. 同步监听器下的规则 +func (cli *client) LoadBalancerWithListener(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, + error) { + + _, err := cli.LoadBalancer(kt, params, opt) + if err != nil { + logs.Errorf("fail to sync load balancer with rel, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + // 同步下属监听器 + requiredLBCloudIds := params.CloudIDs + // 获取同步后的lb数据 + params.CloudIDs = nil + lbList, err := cli.listLBFromDB(kt, params) + if err != nil { + logs.Errorf("fail to get lb from db after lb layer sync, before Listener sync, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + // 同步对应安全组关联关系 + err = cli.lbSgRel(kt, params, lbList) + if err != nil { + logs.Errorf("fail to sync load balancer sg rel, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + if len(lbList) == 0 { + return new(SyncResult), nil + } + + lblParams := &SyncListenerOption{ + AccountID: params.AccountID, + Region: params.Region, + LbInfos: lbList, + } + + if _, err = cli.listenerByLbBatch(kt, lblParams); err != nil { + logs.Errorf("fail to sync Listener of lbs(ids: %v), err: %v, rid: %s", requiredLBCloudIds, err, kt.Rid) + return nil, err + } + + return new(SyncResult), nil +} + +// LoadBalancer 同步指定负载均衡自身属性,不同步关联资源 +func (cli *client) LoadBalancer(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) { + if err := validator.ValidateTool(params, opt); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lbFromCloud, err := cli.listLBFromCloud(kt, params) + if err != nil { + return nil, err + } + + lbFromDB, err := cli.listLBFromDB(kt, params) + if err != nil { + return nil, err + } + + if len(lbFromCloud) == 0 && len(lbFromDB) == 0 { + return new(SyncResult), nil + } + + addSlice, updateMap, delCloudIDs := common.Diff[typeslb.TCloudClb, corelb.TCloudLoadBalancer]( + lbFromCloud, lbFromDB, isLBChange) + + // 删除云上已经删除的负载均衡实例 + if err = cli.deleteLoadBalancer(kt, params.AccountID, params.Region, delCloudIDs); err != nil { + return nil, err + } + + // 创建云上新增负载均衡实例 + _, err = cli.createLoadBalancer(kt, params.AccountID, params.Region, addSlice) + if err != nil { + return nil, err + } + // 更新变更负载均衡 + if err = cli.updateLoadBalancer(kt, params.AccountID, params.Region, updateMap); err != nil { + return nil, err + } + return new(SyncResult), nil +} + +// RemoveLoadBalancerDeleteFromCloud 删除存在本地但是在云上被删除的数据 +func (cli *client) RemoveLoadBalancerDeleteFromCloud(kt *kit.Kit, accountID string, region string) error { + req := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("region", region), + ), + Page: &core.BasePage{ + Start: 0, + Limit: constant.BatchOperationMaxLimit, + }, + } + for { + lbFromDB, err := cli.dbCli.Global.LoadBalancer.ListLoadBalancer(kt, req) + if err != nil { + logs.Errorf("[%s] request dataservice to list lb failed, err: %v, req: %v, rid: %s", + enumor.TCloud, err, req, kt.Rid) + return err + } + + cloudIDs := slice.Map(lbFromDB.Details, func(lb corelb.BaseLoadBalancer) string { return lb.CloudID }) + + if len(cloudIDs) == 0 { + break + } + + var delCloudIDs []string + + params := &SyncBaseParams{AccountID: accountID, Region: region, CloudIDs: cloudIDs} + delCloudIDs, err = cli.listRemovedLBID(kt, params) + if err != nil { + return err + } + + if len(delCloudIDs) != 0 { + if err = cli.deleteLoadBalancer(kt, accountID, region, delCloudIDs); err != nil { + return err + } + } + + if len(lbFromDB.Details) < constant.BatchOperationMaxLimit { + break + } + + req.Page.Start += constant.BatchOperationMaxLimit + } + + return nil +} + +// listRemovedLBID check lb exists, return its id if one can not be found +func (cli *client) listRemovedLBID(kt *kit.Kit, params *SyncBaseParams) ([]string, error) { + if err := params.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + batchParam := &SyncBaseParams{ + AccountID: params.AccountID, + Region: params.Region, + } + lbMap := cvt.StringSliceToMap(params.CloudIDs) + + for _, batchCloudID := range slice.Split(params.CloudIDs, constant.TCLBDescribeMax) { + batchParam.CloudIDs = batchCloudID + found, err := cli.listLBFromCloud(kt, batchParam) + if err != nil { + return nil, err + } + for _, lb := range found { + delete(lbMap, lb.GetCloudID()) + } + } + + return cvt.MapKeyToSlice(lbMap), nil +} + +// createLoadBalancer call data service to create lb +func (cli *client) createLoadBalancer(kt *kit.Kit, accountID string, region string, + addSlice []typeslb.TCloudClb) (interface{}, error) { + + if len(addSlice) <= 0 { + return nil, nil + } + + cloudVpcIds := slice.Map(addSlice, func(lb typeslb.TCloudClb) string { return cvt.PtrToVal(lb.VpcId) }) + cloudSubnetIDs := slice.Map(addSlice, func(lb typeslb.TCloudClb) string { return cvt.PtrToVal(lb.SubnetId) }) + + vpcMap, subnetMap, err := cli.getLoadBalancerRelatedRes(kt, accountID, region, cloudVpcIds, cloudSubnetIDs) + if err != nil { + return nil, err + } + + var lbCreateReq protocloud.TCloudCLBCreateReq + + for _, cloud := range addSlice { + lbCreateReq.Lbs = append(lbCreateReq.Lbs, convCloudToDBCreate(cloud, accountID, region, vpcMap, subnetMap)) + } + + if _, err := cli.dbCli.TCloud.LoadBalancer.BatchCreateTCloudClb(kt, &lbCreateReq); err != nil { + logs.Errorf("[%s] call data service to create tcloud load balancer failed, err: %v, rid: %s", + enumor.TCloud, err, kt.Rid) + return nil, err + } + + logs.Infof("[%s] sync load balancer to create lb success, accountID: %s, count: %d, rid: %s", + enumor.TCloud, accountID, len(addSlice), kt.Rid) + + return nil, nil +} + +// getLoadBalancerRelatedRes return vpc map and subnet map of given cloud id +func (cli *client) getLoadBalancerRelatedRes(kt *kit.Kit, accountID string, region string, cloudVpcIds []string, + cloudSubnetIDs []string) (vpcMap map[string]*common.VpcDB, subnetMap map[string]string, err error) { + + vpcMap, err = cli.getVpcMap(kt, accountID, region, cloudVpcIds) + if err != nil { + logs.Errorf("fail to get vpc of load balancer during syncing, err: %v, account: %s, vpcIds: %v, rid:%s", + err, accountID, cloudVpcIds, kt.Rid) + return nil, nil, err + } + + subnetMap, err = cli.getSubnetMap(kt, accountID, region, cloudSubnetIDs) + if err != nil { + logs.Errorf("fail to get subnet of load balancer during syncing, err: %v, account: %s, subnetIDs: %v, rid:%s", + err, accountID, cloudSubnetIDs, kt.Rid) + return nil, nil, err + } + return vpcMap, subnetMap, nil +} + +// updateLoadBalancer call data service to update lb +func (cli *client) updateLoadBalancer(kt *kit.Kit, accountID string, region string, + updateMap map[string]typeslb.TCloudClb) error { + + if len(updateMap) == 0 { + return nil + } + var cloudSubnetIDs, cloudVpcIds []string + for _, clb := range updateMap { + cloudVpcIds = append(cloudVpcIds, cvt.PtrToVal(clb.VpcId)) + cloudSubnetIDs = append(cloudSubnetIDs, cvt.PtrToVal(clb.SubnetId)) + } + + vpcMap, subnetMap, err := cli.getLoadBalancerRelatedRes(kt, accountID, region, cloudVpcIds, cloudSubnetIDs) + if err != nil { + logs.Errorf("fail to get load balancer related res for update db, err: %v, account: %s, "+ + "vpcIds: %v, cloud subnet ids: %v, rid: %s", err, accountID, cloudVpcIds, cloudSubnetIDs, kt.Rid) + return err + } + + var updateReq protocloud.TCloudClbBatchUpdateReq + + for id, clb := range updateMap { + updateReq.Lbs = append(updateReq.Lbs, convCloudToDBUpdate(id, clb, vpcMap, subnetMap)) + } + if err := cli.dbCli.TCloud.LoadBalancer.BatchUpdate(kt, &updateReq); err != nil { + logs.Errorf("[%s] call data service to update tcloud load balancer failed, err: %v, rid: %s", + enumor.TCloud, err, kt.Rid) + return err + } + return nil +} + +// deleteLoadBalancer call data service to delete lb +func (cli *client) deleteLoadBalancer(kt *kit.Kit, accountID string, region string, delCloudIDs []string) error { + + if len(delCloudIDs) <= 0 { + return nil + } + + checkParams := &SyncBaseParams{ + AccountID: accountID, + Region: region, + CloudIDs: delCloudIDs, + } + delLBFromCloud, err := cli.listLBFromCloud(kt, checkParams) + if err != nil { + return err + } + + if len(delLBFromCloud) > 0 { + logs.Errorf("[%s] lb not exist before sync deletion, opt: %v, failed_count: %d, rid: %s", + enumor.TCloud, checkParams, len(delLBFromCloud), kt.Rid) + return fmt.Errorf("lb not exist before sync deletion") + } + + deleteReq := &protocloud.LoadBalancerBatchDeleteReq{ + Filter: tools.ContainersExpression("cloud_id", delCloudIDs), + } + if err = cli.dbCli.Global.LoadBalancer.BatchDelete(kt, deleteReq); err != nil { + logs.Errorf("[%s] call data service to batch delete lb failed, err: %v, rid: %s", + enumor.TCloud, err, kt.Rid) + return err + } + + logs.Infof("[%s] sync to delete lb success, accountID: %s, count: %d, rid: %s", + enumor.TCloud, accountID, len(delCloudIDs), kt.Rid) + + return nil +} + +// listLBFromCloud list load balancer from cloud vendor +func (cli *client) listLBFromCloud(kt *kit.Kit, params *SyncBaseParams) ([]typeslb.TCloudClb, error) { + opt := &typeslb.TCloudListOption{ + Region: params.Region, + CloudIDs: params.CloudIDs, + Page: &typecore.TCloudPage{ + Offset: 0, + Limit: typecore.TCloudQueryLimit, + }, + } + result, err := cli.cloudCli.ListLoadBalancer(kt, opt) + if err != nil { + logs.Errorf("[%s] list lb from cloud failed, err: %v, account: %s, opt: %v, rid: %s", + enumor.TCloud, err, params.AccountID, opt, kt.Rid) + return nil, err + } + + return result, nil + +} + +// listLBFromDB list load balancer from database +func (cli *client) listLBFromDB(kt *kit.Kit, params *SyncBaseParams) ([]corelb.TCloudLoadBalancer, error) { + + req := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", params.AccountID), + tools.RuleEqual("region", params.Region), + ), + Page: core.NewDefaultBasePage(), + } + // 支持查询所有,或者指定cloud_id + if len(params.CloudIDs) > 0 { + req.Filter.Rules = append(req.Filter.Rules, tools.RuleIn("cloud_id", params.CloudIDs)) + } + result, err := cli.dbCli.TCloud.LoadBalancer.ListLoadBalancer(kt, req) + if err != nil { + logs.Errorf("[%s] list lb from db failed, err: %v, account: %s, req: %v, rid: %s", + enumor.TCloud, err, params.AccountID, req, kt.Rid) + return nil, err + } + + return result.Details, nil +} + +func convCloudToDBCreate(cloud typeslb.TCloudClb, accountID string, region string, vpcMap map[string]*common.VpcDB, + subnetMap map[string]string) protocloud.LbBatchCreate[corelb.TCloudClbExtension] { + + cloudVpcID := cvt.PtrToVal(cloud.VpcId) + cloudSubnetID := cvt.PtrToVal(cloud.SubnetId) + lb := protocloud.LbBatchCreate[corelb.TCloudClbExtension]{ + CloudID: cloud.GetCloudID(), + Name: cvt.PtrToVal(cloud.LoadBalancerName), + Vendor: enumor.TCloud, + AccountID: accountID, + BkBizID: constant.UnassignedBiz, + LoadBalancerType: cvt.PtrToVal(cloud.LoadBalancerType), + IPVersion: cloud.GetIPVersion(), + Region: region, + VpcID: vpcMap[cloudVpcID].VpcID, + CloudVpcID: cloudVpcID, + SubnetID: subnetMap[cloudSubnetID], + CloudSubnetID: cloudSubnetID, + Domain: cvt.PtrToVal(cloud.LoadBalancerDomain), + Status: strconv.FormatUint(cvt.PtrToVal(cloud.Status), 10), + CloudCreatedTime: cvt.PtrToVal(cloud.CreateTime), + CloudStatusTime: cvt.PtrToVal(cloud.StatusTime), + CloudExpiredTime: cvt.PtrToVal(cloud.ExpireTime), + // 备注字段云上没有 + Memo: nil, + } + + // IP地址判断 + if len(cloud.LoadBalancerVips) != 0 { + switch typeslb.TCloudLoadBalancerType(cvt.PtrToVal(cloud.LoadBalancerType)) { + case typeslb.InternalLoadBalancerType: + lb.PrivateIPv4Addresses = cvt.PtrToSlice(cloud.LoadBalancerVips) + case typeslb.OpenLoadBalancerType: + lb.PublicIPv4Addresses = cvt.PtrToSlice(cloud.LoadBalancerVips) + } + } + if ipv6 := cvt.PtrToVal(cloud.AddressIPv6); len(ipv6) > 0 { + lb.PublicIPv6Addresses = []string{ipv6} + } + + // 可用区判断 + if typeslb.TCloudLoadBalancerType(lb.LoadBalancerType) == typeslb.OpenLoadBalancerType && cloud.MasterZone != nil { + lb.Zones = []string{cvt.PtrToVal(cloud.MasterZone.Zone)} + } + + lb.Extension = convertTCloudExtension(cloud) + + return lb +} + +func convertTCloudExtension(cloud typeslb.TCloudClb) *corelb.TCloudClbExtension { + ext := &corelb.TCloudClbExtension{ + SlaType: cloud.SlaType, + VipIsp: cloud.VipIsp, + LoadBalancerPassToTarget: cloud.LoadBalancerPassToTarget, + IPv6Mode: cloud.IPv6Mode, + Snat: cloud.Snat, + SnatPro: cloud.SnatPro, + MixIpTarget: cloud.MixIpTarget, + ChargeType: cloud.ChargeType, + // 该接口无法获取下列字段 + BandwidthPackageId: nil, + } + if cloud.NetworkAttributes != nil { + ext.InternetMaxBandwidthOut = cloud.NetworkAttributes.InternetMaxBandwidthOut + ext.InternetChargeType = cloud.NetworkAttributes.InternetChargeType + ext.BandwidthpkgSubType = cloud.NetworkAttributes.BandwidthpkgSubType + } + if cloud.SnatIps != nil { + ipList := make([]corelb.SnatIp, 0, len(cloud.SnatIps)) + for _, snatIP := range cloud.SnatIps { + if snatIP == nil { + continue + } + ipList = append(ipList, corelb.SnatIp{SubnetId: snatIP.SubnetId, Ip: snatIP.Ip}) + } + ext.SnatIps = ipList + } + + flagMap := make(map[string]bool) + // 没有碰到的则默认是false + for _, flag := range cloud.AttributeFlags { + flagMap[cvt.PtrToVal(flag)] = true + } + // 逐个赋值flag + ext.DeleteProtect = cvt.ValToPtr(flagMap[constant.TCLBDeleteProtect]) + + return ext +} + +func convCloudToDBUpdate(id string, cloud typeslb.TCloudClb, vpcMap map[string]*common.VpcDB, + subnetMap map[string]string) *protocloud.LoadBalancerExtUpdateReq[corelb.TCloudClbExtension] { + + cloudVpcID := cvt.PtrToVal(cloud.VpcId) + cloudSubnetID := cvt.PtrToVal(cloud.SubnetId) + lb := protocloud.LoadBalancerExtUpdateReq[corelb.TCloudClbExtension]{ + ID: id, + Name: cvt.PtrToVal(cloud.LoadBalancerName), + Domain: cvt.PtrToVal(cloud.LoadBalancerDomain), + IPVersion: cloud.GetIPVersion(), + Status: strconv.FormatUint(cvt.PtrToVal(cloud.Status), 10), + CloudCreatedTime: cvt.PtrToVal(cloud.CreateTime), + CloudStatusTime: cvt.PtrToVal(cloud.StatusTime), + CloudExpiredTime: cvt.PtrToVal(cloud.ExpireTime), + VpcID: vpcMap[cloudVpcID].VpcID, + CloudVpcID: cloudVpcID, + SubnetID: subnetMap[cloudSubnetID], + CloudSubnetID: cloudSubnetID, + Extension: &corelb.TCloudClbExtension{ + SlaType: cloud.SlaType, + VipIsp: cloud.VipIsp, + LoadBalancerPassToTarget: cloud.LoadBalancerPassToTarget, + ChargeType: cloud.ChargeType, + + IPv6Mode: cloud.IPv6Mode, + Snat: cloud.Snat, + SnatPro: cloud.SnatPro, + }, + } + if cloud.NetworkAttributes != nil { + lb.Extension.InternetMaxBandwidthOut = cloud.NetworkAttributes.InternetMaxBandwidthOut + lb.Extension.InternetChargeType = cloud.NetworkAttributes.InternetChargeType + lb.Extension.BandwidthpkgSubType = cloud.NetworkAttributes.BandwidthpkgSubType + } + if cloud.SnatIps != nil { + ipList := make([]corelb.SnatIp, 0, len(cloud.SnatIps)) + for _, snatIP := range cloud.SnatIps { + if snatIP == nil { + continue + } + ipList = append(ipList, corelb.SnatIp{SubnetId: snatIP.SubnetId, Ip: snatIP.Ip}) + } + lb.Extension.SnatIps = ipList + } + + if len(cloud.LoadBalancerVips) != 0 { + switch typeslb.TCloudLoadBalancerType(cvt.PtrToVal(cloud.LoadBalancerType)) { + case typeslb.InternalLoadBalancerType: + lb.PrivateIPv4Addresses = cvt.PtrToSlice(cloud.LoadBalancerVips) + case typeslb.OpenLoadBalancerType: + lb.PublicIPv4Addresses = cvt.PtrToSlice(cloud.LoadBalancerVips) + } + } + if ipv6 := cvt.PtrToVal(cloud.AddressIPv6); len(ipv6) > 0 { + lb.PublicIPv6Addresses = []string{ipv6} + } + + // AttributeFlags + flagMap := make(map[string]bool) + // 没有碰到的则默认是false + for _, flag := range cloud.AttributeFlags { + flagMap[cvt.PtrToVal(flag)] = true + } + // 逐个赋值flag + lb.Extension.DeleteProtect = cvt.ValToPtr(flagMap[constant.TCLBDeleteProtect]) + + if cloud.Egress != nil { + lb.Extension.Egress = cloud.Egress + } + return &lb +} + +func isLBChange(cloud typeslb.TCloudClb, db corelb.TCloudLoadBalancer) bool { + + if db.Name != cvt.PtrToVal(cloud.LoadBalancerName) { + return true + } + + if db.IPVersion != cloud.GetIPVersion() { + return true + } + if db.Domain != cvt.PtrToVal(cloud.LoadBalancerDomain) { + return true + } + if db.Status != strconv.FormatUint(cvt.PtrToVal(cloud.Status), 10) { + return true + } + if db.CloudCreatedTime != cvt.PtrToVal(cloud.CreateTime) { + return true + } + if db.CloudStatusTime != cvt.PtrToVal(cloud.StatusTime) { + return true + } + if db.CloudExpiredTime != cvt.PtrToVal(cloud.ExpireTime) { + return true + } + if db.CloudVpcID != cvt.PtrToVal(cloud.VpcId) { + return true + } + if db.CloudSubnetID != cvt.PtrToVal(cloud.SubnetId) { + return true + } + + if len(cloud.LoadBalancerVips) != 0 { + var dbIPList []string + switch typeslb.TCloudLoadBalancerType(cvt.PtrToVal(cloud.LoadBalancerType)) { + case typeslb.InternalLoadBalancerType: + dbIPList = db.PrivateIPv4Addresses + case typeslb.OpenLoadBalancerType: + dbIPList = db.PublicIPv4Addresses + } + if len(dbIPList) == 0 { + return true + } + + tmpMap := cvt.StringSliceToMap(cvt.PtrToSlice(cloud.LoadBalancerVips)) + for _, address := range dbIPList { + delete(tmpMap, address) + } + + if len(tmpMap) != 0 { + return true + } + } + ipv6 := cvt.PtrToVal(cloud.AddressIPv6) + if len(db.PublicIPv6Addresses) == 0 && len(ipv6) != 0 || + len(db.PublicIPv6Addresses) > 0 && db.PublicIPv6Addresses[0] != ipv6 { + return true + } + + return isLBExtensionChange(cloud, db) +} + +func isLBExtensionChange(cloud typeslb.TCloudClb, db corelb.TCloudLoadBalancer) bool { + if db.Extension == nil { + return true + } + + if cloud.NetworkAttributes != nil { + if !assert.IsPtrInt64Equal(db.Extension.InternetMaxBandwidthOut, + cloud.NetworkAttributes.InternetMaxBandwidthOut) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.InternetChargeType, cloud.NetworkAttributes.InternetChargeType) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.BandwidthpkgSubType, cloud.NetworkAttributes.BandwidthpkgSubType) { + return true + } + } + + if !assert.IsPtrStringEqual(db.Extension.SlaType, cloud.SlaType) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.VipIsp, cloud.VipIsp) { + return true + } + + if !assert.IsPtrBoolEqual(db.Extension.LoadBalancerPassToTarget, cloud.LoadBalancerPassToTarget) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.IPv6Mode, cloud.IPv6Mode) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.Egress, cloud.Egress) { + return true + } + if !assert.IsPtrBoolEqual(db.Extension.Snat, cloud.Snat) { + return true + } + if !assert.IsPtrBoolEqual(db.Extension.SnatPro, cloud.SnatPro) { + return true + } + if !assert.IsPtrStringEqual(db.Extension.ChargeType, cloud.ChargeType) { + return true + } + // SnatIP列表对比 + if isSnatIPChange(cloud, db) { + return true + } + + // 云上AttributeFlags 转map + attrs := make(map[string]bool, len(cloud.AttributeFlags)) + for _, flag := range cloud.AttributeFlags { + attrs[cvt.PtrToVal(flag)] = true + } + + // 逐个判断每种类型 + if attrs[constant.TCLBDeleteProtect] != cvt.PtrToVal(db.Extension.DeleteProtect) { + return true + } + + return false +} + +// 云上SnatIP列表与本地对比 +func isSnatIPChange(cloud typeslb.TCloudClb, db corelb.TCloudLoadBalancer) bool { + + if len(db.Extension.SnatIps) != len(cloud.SnatIps) { + return true + } + if len(cloud.SnatIps) == 0 { + // 相等,且都为零 + return false + } + // 转为map逐个比较 + cloudSnatMap := cloudSnatSliceToMap(cloud.SnatIps) + for _, local := range db.Extension.SnatIps { + delete(cloudSnatMap, local.Hash()) + } + // 数量相等的情况下,应该刚好删除干净。因此非零就是存在不同 + return len(cloudSnatMap) != 0 +} + +// 将云上的SnatIP转化为map,key为 {SubnetId},{Ip} +func cloudSnatSliceToMap(cloudSlice []*tclb.SnatIp) map[string]struct{} { + cloudSnatMap := make(map[string]struct{}, len(cloudSlice)) + for _, ip := range cloudSlice { + cloudSnatMap[hashCloudSnatIP(ip)] = struct{}{} + } + return cloudSnatMap +} + +// hashCloudSnatIP key为 {SubnetId},{Ip} +func hashCloudSnatIP(ip *tclb.SnatIp) string { + if ip == nil { + return "," + } + return cvt.PtrToVal(ip.SubnetId) + "," + cvt.PtrToVal(ip.Ip) +} + +// SyncLBOption ... +type SyncLBOption struct { +} + +// Validate ... +func (o *SyncLBOption) Validate() error { + return validator.Validate.Struct(o) +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_listener.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_listener.go new file mode 100644 index 0000000000..257b3ff9e9 --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_listener.go @@ -0,0 +1,497 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "hcm/cmd/hc-service/logics/res-sync/common" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/assert" + "hcm/pkg/tools/concurrence" + cvt "hcm/pkg/tools/converter" + + tclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" +) + +// listenerByLbBatch 同步多个负载均衡下的监听器: +func (cli *client) listenerByLbBatch(kt *kit.Kit, params *SyncListenerOption) (*SyncResult, error) { + + if err := validator.ValidateTool(params); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 并发同步多个负载均衡下的监听器 + var syncResult *SyncResult + err := concurrence.BaseExec(constant.SyncConcurrencyDefaultMaxLimit, params.LbInfos, + func(lb corelb.TCloudLoadBalancer) error { + syncOpt := &SyncListenerOfSingleLBOption{ + AccountID: params.AccountID, + Region: params.Region, + BizID: lb.BkBizID, + LBID: lb.ID, + CloudLBID: lb.CloudID, + } + var err error + if syncResult, err = cli.Listener(kt, syncOpt); err != nil { + logs.ErrorDepthf(1, "[%s] account: %s lb: %s sync listener failed, err: %v, rid: %s", + enumor.TCloud, params.AccountID, lb.CloudID, err, kt.Rid) + return err + } + + return nil + }) + if err != nil { + return nil, err + } + return syncResult, nil +} + +// Listener 2. 同步指定负载均衡均衡下的监听器 +func (cli *client) Listener(kt *kit.Kit, opt *SyncListenerOfSingleLBOption) ( + *SyncResult, error) { + + if err := opt.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + cloudListeners, err := cli.listListenerFromCloud(kt, opt) + if err != nil { + logs.Errorf("fail to list listener for sync, err: %v, opt:%+v, rid: %s", err, opt, kt.Rid) + return nil, err + } + + dbListeners, err := cli.listListenerFromDB(kt, opt) + if err != nil { + return nil, err + } + + if len(cloudListeners) == 0 && len(dbListeners) == 0 { + return new(SyncResult), nil + } + + addSlice, updateMap, delCloudIDs := common.Diff[typeslb.TCloudListener, corelb.TCloudListener]( + cloudListeners, dbListeners, isListenerChange) + + // 删除云上已经删除的监听器实例 + if err = cli.deleteListener(kt, opt, delCloudIDs); err != nil { + return nil, err + } + + // 创建云上新增监听器实例, 对于四层规则一起创建对应的规则 + _, err = cli.createListener(kt, opt, addSlice) + if err != nil { + return nil, err + } + // 更新变更监听器,不更新对应四层/七层 规则 + if err = cli.updateListener(kt, opt.BizID, updateMap); err != nil { + return nil, err + } + + // 同步监听器下的四层/七层规则 + _, err = cli.loadBalancerRule(kt, opt, cloudListeners) + if err != nil { + logs.Errorf("fail to sync listener rule for sync listener, err: %v, opt: %+v, rid: %s", err, opt, kt.Rid) + return nil, err + } + targetParam := &SyncBaseParams{ + AccountID: opt.AccountID, + Region: opt.Region, + CloudIDs: nil, + } + + // 同步相关目标组中的rs + err = cli.ListenerTargets(kt, targetParam, opt) + if err != nil { + logs.Errorf("fail to sync listener targets for sync listener, err: %v, opt: %+v, rid: %s", err, opt, kt.Rid) + return nil, err + } + + // 同步本地目标组 + err = cli.LocalTargetGroup(kt, targetParam, opt, cloudListeners) + if err != nil { + logs.Errorf("fail to sync target group for listener, err: %v, opt: %+v, rid: %s", err, opt, kt.Rid) + return nil, err + } + + return new(SyncResult), nil +} + +// 获取云上监听器列表 +func (cli *client) listListenerFromCloud(kt *kit.Kit, opt *SyncListenerOfSingleLBOption) ([]typeslb.TCloudListener, + error) { + listOpt := &typeslb.TCloudListListenersOption{ + Region: opt.Region, + LoadBalancerId: opt.CloudLBID, + } + return cli.cloudCli.ListListener(kt, listOpt) +} + +// 获取本地监听器列表 +func (cli *client) listListenerFromDB(kt *kit.Kit, opt *SyncListenerOfSingleLBOption) ([]corelb.TCloudListener, + error) { + + listReq := &core.ListReq{ + Filter: tools.EqualExpression("lb_id", opt.LBID), + Page: core.NewDefaultBasePage(), + } + lblResp, err := cli.dbCli.TCloud.LoadBalancer.ListListener(kt, listReq) + if err != nil { + logs.Errorf("fail to list listener of lb(%s) for sync, err: %v, rid: %s", opt.LBID, err, kt.Rid) + return nil, err + } + return lblResp.Details, nil +} + +func (cli *client) deleteListener(kt *kit.Kit, opt *SyncListenerOfSingleLBOption, cloudIds []string) error { + if len(cloudIds) == 0 { + return nil + } + delReq := &dataproto.LoadBalancerBatchDeleteReq{Filter: tools.ContainersExpression("cloud_id", cloudIds)} + err := cli.dbCli.Global.LoadBalancer.DeleteListener(kt, delReq) + if err != nil { + logs.Errorf("fail to delete listeners(ids:%v) while sync, err: %v, syncOpt: %+v, rid: %s", + cloudIds, err, opt, kt.Rid) + return err + } + return nil +} + +func (cli *client) createListener(kt *kit.Kit, syncOpt *SyncListenerOfSingleLBOption, + addSlice []typeslb.TCloudListener) ([]string, error) { + + if len(addSlice) == 0 { + return nil, nil + } + dbListeners := make([]dataproto.ListenersCreateReq, 0, len(addSlice)) + dbRules := make([]dataproto.ListenerWithRuleCreateReq, 0) + for _, lbl := range addSlice { + if lbl.GetProtocol().IsLayer7Protocol() { + dbListeners = append(dbListeners, dataproto.ListenersCreateReq{ + CloudID: lbl.GetCloudID(), + Name: cvt.PtrToVal(lbl.ListenerName), + Vendor: enumor.TCloud, + AccountID: syncOpt.AccountID, + BkBizID: syncOpt.BizID, + LbID: syncOpt.LBID, + CloudLbID: syncOpt.CloudLBID, + Protocol: lbl.GetProtocol(), + Port: cvt.PtrToVal(lbl.Port), + DefaultDomain: getDefaultDomain(lbl), + }) + // for layer 7 only create listeners itself + continue + } + // layer 4 create with rule + dbRules = append(dbRules, dataproto.ListenerWithRuleCreateReq{ + CloudID: lbl.GetCloudID(), + Name: cvt.PtrToVal(lbl.ListenerName), + Vendor: enumor.TCloud, + AccountID: syncOpt.AccountID, + BkBizID: syncOpt.BizID, + LbID: syncOpt.LBID, + CloudLbID: syncOpt.CloudLBID, + Protocol: lbl.GetProtocol(), + Port: cvt.PtrToVal(lbl.Port), + CloudRuleID: lbl.GetCloudID(), + Scheduler: cvt.PtrToVal(lbl.Scheduler), + RuleType: enumor.Layer4RuleType, + SessionType: cvt.PtrToVal(lbl.SessionType), + SessionExpire: cvt.PtrToVal(lbl.SessionExpireTime), + SniSwitch: enumor.SniType(cvt.PtrToVal(lbl.SniSwitch)), + Certificate: convCert(lbl.Certificate), + }) + } + createdIDs := make([]string, 0, len(addSlice)) + if len(dbListeners) > 0 { + lblCreated, err := cli.dbCli.TCloud.LoadBalancer.BatchCreateTCloudListener(kt, + &dataproto.ListenerBatchCreateReq{Listeners: dbListeners}) + if err != nil { + logs.Errorf("fail to create listener while sync, err: %v syncOpt: %+v, rid: %s", + err, syncOpt, kt.Rid) + return nil, err + } + createdIDs = append(createdIDs, lblCreated.IDs...) + } + + if len(dbRules) > 0 { + ruleCreated, err := cli.dbCli.TCloud.LoadBalancer.BatchCreateTCloudListenerWithRule(kt, + &dataproto.ListenerWithRuleBatchCreateReq{ListenerWithRules: dbRules}) + if err != nil { + logs.Errorf("fail to create listener with rule while sync, err: %v syncOpt: %+v, rid: %s", + err, syncOpt, kt.Rid) + return nil, err + } + createdIDs = append(createdIDs, ruleCreated.IDs...) + } + + return createdIDs, nil +} + +func (cli *client) updateListener(kt *kit.Kit, bizID int64, updateMap map[string]typeslb.TCloudListener) error { + + if len(updateMap) == 0 { + return nil + } + updates := make([]*dataproto.TCloudListenerUpdate, 0, len(updateMap)) + + for id, lbl := range updateMap { + + updates = append(updates, &dataproto.TCloudListenerUpdate{ + ID: id, + Name: cvt.PtrToVal(lbl.ListenerName), + BkBizID: bizID, + SniSwitch: enumor.SniType(cvt.PtrToVal(lbl.SniSwitch)), + DefaultDomain: getDefaultDomain(lbl), + Extension: &corelb.TCloudListenerExtension{ + Certificate: convCert(lbl.Certificate), + }, + }) + } + + err := cli.dbCli.TCloud.LoadBalancer.BatchUpdateTCloudListener(kt, + &dataproto.TCloudListenerUpdateReq{Listeners: updates}) + if err != nil { + logs.Errorf("fail to update listener while sync, err: %v, rid: %s", err, kt.Rid) + return err + } + // 更新规则 + return nil +} +func convCert(cloud *tclb.CertificateOutput) *corelb.TCloudCertificateInfo { + if cloud == nil { + return nil + } + db := &corelb.TCloudCertificateInfo{ + SSLMode: cloud.SSLMode, + CaCloudID: cloud.CertCaId, + } + if cloud.CertId != nil { + db.CertCloudIDs = append(db.CertCloudIDs, cvt.PtrToVal(cloud.CertId)) + } + for _, cloudCertID := range cloud.ExtCertIds { + db.CertCloudIDs = append(db.CertCloudIDs, cvt.PtrToVal(cloudCertID)) + } + return db +} + +// isListenerChange 四层规则有健康检查这类信息在监听器上,七层规则可能有0-n条规则,对应字段在规则同步时处理 +func isListenerChange(cloud typeslb.TCloudListener, db corelb.TCloudListener) bool { + + // 通用字段 + if cvt.PtrToVal(cloud.ListenerName) != db.Name { + return true + } + switch cloud.GetProtocol() { + case enumor.HttpProtocol: + // http 只有名称和默认域名可以变 + if getDefaultDomain(cloud) != db.DefaultDomain { + return true + } + case enumor.HttpsProtocol: + if isHttpsListenerChanged(cloud, db) { + return true + } + default: + // 其他为4层协议 + if isLayer4ListenerChanged(cloud, db) { + return true + } + } + + return false +} + +func isLayer4ListenerChanged(cloud typeslb.TCloudListener, db corelb.TCloudListener) bool { + + if isListenerCertChange(cloud.Certificate, db.Extension.Certificate) { + return true + } + // 规则单独检查 + + return false +} + +// 七层规则不支持设置检查端口 +func isHealthCheckChange(cloud *tclb.HealthCheck, db *corelb.TCloudHealthCheckInfo, isL7 bool) bool { + if cloud == nil || db == nil { + // 云上和本地都为空 则是未变化,否则需要更新本地 + return !(cloud == nil && db == nil) + } + if !assert.IsPtrInt64Equal(cloud.HealthSwitch, db.HealthSwitch) { + return true + } + if !assert.IsPtrInt64Equal(cloud.TimeOut, db.TimeOut) { + return true + } + if !assert.IsPtrInt64Equal(cloud.IntervalTime, db.IntervalTime) { + return true + } + if !assert.IsPtrInt64Equal(cloud.HealthNum, db.HealthNum) { + return true + } + if !assert.IsPtrInt64Equal(cloud.UnHealthNum, db.UnHealthNum) { + return true + } + if !assert.IsPtrInt64Equal(cloud.HttpCode, db.HttpCode) { + return true + } + if !assert.IsPtrStringEqual(cloud.HttpCheckPath, db.HttpCheckPath) { + return true + } + if !assert.IsPtrStringEqual(cloud.HttpCheckDomain, db.HttpCheckDomain) { + return true + } + if !assert.IsPtrStringEqual(cloud.HttpCheckMethod, db.HttpCheckMethod) { + return true + } + // 七层规则不支持设置检查端口, 这里不比较该数据 + if isL7 && !assert.IsPtrInt64Equal(cloud.CheckPort, db.CheckPort) { + return true + } + if !assert.IsPtrStringEqual(cloud.ContextType, db.ContextType) { + return true + } + if !assert.IsPtrStringEqual(cloud.SendContext, db.SendContext) { + return true + } + if !assert.IsPtrStringEqual(cloud.RecvContext, db.RecvContext) { + return true + } + if !assert.IsPtrStringEqual(cloud.CheckType, db.CheckType) { + return true + } + if !assert.IsPtrStringEqual(cloud.HttpVersion, db.HttpVersion) { + return true + } + + if !assert.IsPtrInt64Equal(cloud.SourceIpType, db.SourceIpType) { + return true + } + if !assert.IsPtrStringEqual(cloud.ExtendedCode, db.ExtendedCode) { + return true + } + + return false +} + +func isHttpsListenerChanged(cloud typeslb.TCloudListener, db corelb.TCloudListener) bool { + if db.DefaultDomain != getDefaultDomain(cloud) { + return true + } + if cvt.PtrToVal(cloud.SniSwitch) != int64(db.SniSwitch) { + return true + } + if db.Extension == nil { + return true + } + + if isListenerCertChange(cloud.Certificate, db.Extension.Certificate) { + return true + } + return false +} + +func isListenerCertChange(cloud *tclb.CertificateOutput, db *corelb.TCloudCertificateInfo) bool { + if cloud == nil || db == nil { + // 云上和本地都为空 则是未变化 + return !(cloud == nil && db == nil) + } + + if !assert.IsPtrStringEqual(cloud.SSLMode, db.SSLMode) { + return true + } + + if !assert.IsPtrStringEqual(cloud.CertCaId, db.CaCloudID) { + return true + } + // 云上有,本地没有 + if len(cvt.PtrToVal(cloud.CertId)) != 0 && len(db.CertCloudIDs) == 0 { + return true + } + // 云上没有,本地有 + if len(cvt.PtrToVal(cloud.CertId)) == 0 && len(db.CertCloudIDs) > 0 { + return true + } + + // 本地和云上都有,但是和云上不相等 + if len(db.CertCloudIDs) > 0 && cvt.PtrToVal(cloud.CertId) != db.CertCloudIDs[0] { + return true + } + // 本地和云上都有,但是数量不相等 + if len(db.CertCloudIDs) != (len(cloud.ExtCertIds) + 1) { + // 数量不相等 + return true + } + // 要求证书按顺序相等。 + for i := range cloud.ExtCertIds { + if db.CertCloudIDs[i+1] != cvt.PtrToVal(cloud.ExtCertIds[i]) { + return true + } + } + return false +} + +func getDefaultDomain(cloud typeslb.TCloudListener) string { + // 需要去规则中捞 + for _, rule := range cloud.Rules { + if rule != nil && cvt.PtrToVal(rule.DefaultServer) { + return cvt.PtrToVal(rule.Domain) + } + } + return "" +} + +// SyncListenerOfSingleLBOption ... +type SyncListenerOfSingleLBOption struct { + AccountID string `json:"account_id" validate:"required"` + Region string `json:"region" validate:"required"` + BizID int64 `json:"biz_id" validate:"required"` + + // 对应的负载均衡 + LBID string `json:"lbid" validate:"required"` + CloudLBID string `json:"cloud_lbid" validate:"required"` +} + +// Validate ... +func (o *SyncListenerOfSingleLBOption) Validate() error { + return validator.Validate.Struct(o) +} + +// SyncListenerOption ... +type SyncListenerOption struct { + AccountID string `json:"account_id" validate:"required"` + Region string `json:"region" validate:"required"` + LbInfos []corelb.TCloudLoadBalancer `json:"lb_infos" validate:"required,min=1"` +} + +// Validate ... +func (o *SyncListenerOption) Validate() error { + return validator.Validate.Struct(o) +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_rule.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_rule.go new file mode 100644 index 0000000000..37e744732c --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_rule.go @@ -0,0 +1,375 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "hcm/cmd/hc-service/logics/res-sync/common" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + cvt "hcm/pkg/tools/converter" + + tclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" +) + +// LoadBalancerRule 规则同步 +func (cli *client) loadBalancerRule(kt *kit.Kit, opt *SyncListenerOfSingleLBOption, + cloudListeners []typeslb.TCloudListener) (any, error) { + + var l4Listeners, l7Listeners []typeslb.TCloudListener + for _, listener := range cloudListeners { + if listener.GetProtocol().IsLayer7Protocol() { + l7Listeners = append(l7Listeners, listener) + continue + } + l4Listeners = append(l4Listeners, listener) + } + _, err := cli.LoadBalancerLayer4Rule(kt, opt.LBID, l4Listeners) + if err != nil { + return nil, err + } + l7Opt := &SyncLayer7RuleOption{ + LBID: opt.LBID, + CloudLBID: opt.CloudLBID, + ListenerID: "", + CloudListenerID: "", + } + dbListeners, err := cli.listListenerFromDB(kt, opt) + if err != nil { + return nil, err + } + dbListenerMap := make(map[string]*corelb.TCloudListener) + for _, dbLbl := range dbListeners { + dbListenerMap[dbLbl.CloudID] = cvt.ValToPtr(dbLbl) + } + + // 逐个同步监听器下的规则 + for _, listener := range l7Listeners { + l7Opt.CloudListenerID = cvt.PtrToVal(listener.ListenerId) + dbLbl := dbListenerMap[listener.GetCloudID()] + if dbLbl == nil { + // 云上新建的监听器,等待下次同步 + logs.Infof("found new listener from cloud, id: %s", listener.GetCloudID()) + continue + } + l7Opt.ListenerID = dbLbl.ID + _, err := cli.ListenerLayer7Rule(kt, l7Opt, listener) + if err != nil { + logs.Errorf("fail to sync rules of listener, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + } + + return new(SyncResult), nil +} + +// LoadBalancerLayer4Rule 同步负载均衡下的4层监听器规则,四层规则一次同步 +func (cli *client) LoadBalancerLayer4Rule(kt *kit.Kit, lbID string, l4Listeners []typeslb.TCloudListener) ( + *SyncResult, error) { + + dbRules, err := cli.listL4RuleFromDB(kt, lbID) + if err != nil { + return nil, err + } + + if len(l4Listeners) == 0 && len(dbRules) == 0 { + return new(SyncResult), nil + } + + // 新增实例应该在同步监听器的时候附带创建,云上已删除的规则应该在监听器同步时被删除 + _, updateMap, _ := common.Diff[typeslb.TCloudListener, corelb.TCloudLbUrlRule]( + l4Listeners, dbRules, isLayer4RuleChange) + + // 更新变更监听器,更新对应四层/七层 规则 + if err = cli.updateLayer4Rule(kt, updateMap); err != nil { + return nil, err + } + + return new(SyncResult), nil + +} + +// ListenerLayer7Rule 同步指定监听器下的7层规则,7层按监听器同步 +func (cli *client) ListenerLayer7Rule(kt *kit.Kit, opt *SyncLayer7RuleOption, cloudListener typeslb.TCloudListener) ( + *SyncResult, error) { + // 对于七层规则逐个监听器进行同步 + + dbRules, err := cli.listL7RuleFromDB(kt, cvt.PtrToVal(cloudListener.ListenerId)) + if err != nil { + return nil, err + } + + if len(cloudListener.Rules) == 0 && len(dbRules) == 0 { + return new(SyncResult), nil + } + + cloudRules := make([]typeslb.TCloudUrlRule, 0, len(cloudListener.Rules)) + for _, rule := range cloudListener.Rules { + cloudRules = append(cloudRules, typeslb.TCloudUrlRule{RuleOutput: rule}) + } + + addSlice, updateMap, delCloudIDs := common.Diff[typeslb.TCloudUrlRule, corelb.TCloudLbUrlRule]( + cloudRules, dbRules, isLayer7RuleChange) + + if err = cli.deleteLayer7Rule(kt, delCloudIDs); err != nil { + return nil, err + } + + if err = cli.updateLayer7Rule(kt, updateMap); err != nil { + return nil, err + } + + if _, err = cli.createLayer7Rule(kt, opt, addSlice); err != nil { + return nil, err + } + return nil, nil +} + +func (cli *client) listL4RuleFromDB(kt *kit.Kit, lbID string) ([]corelb.TCloudLbUrlRule, error) { + + listReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("lb_id", lbID), + tools.RuleEqual("rule_type", enumor.Layer4RuleType)), + Page: core.NewDefaultBasePage(), + } + + ruleResp, err := cli.dbCli.TCloud.LoadBalancer.ListUrlRule(kt, listReq) + if err != nil { + logs.Errorf("fail to list rule of lb(%s) for sync, err: %v, rid: %s", lbID, err, kt.Rid) + return nil, err + } + return ruleResp.Details, nil +} + +func (cli *client) listL7RuleFromDB(kt *kit.Kit, cloudLBLID string) ([]corelb.TCloudLbUrlRule, error) { + listReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("cloud_lbl_id", cloudLBLID), + tools.RuleEqual("rule_type", enumor.Layer7RuleType)), + Page: core.NewDefaultBasePage(), + } + + ruleResp, err := cli.dbCli.TCloud.LoadBalancer.ListUrlRule(kt, listReq) + if err != nil { + logs.Errorf("fail to list rule of lbl(%s) for sync, err: %v, rid: %s", cloudLBLID, err, kt.Rid) + return nil, err + } + return ruleResp.Details, nil +} +func (cli *client) updateLayer4Rule(kt *kit.Kit, updateMap map[string]typeslb.TCloudListener) error { + + if len(updateMap) == 0 { + return nil + } + updateReq := &dataproto.TCloudUrlRuleBatchUpdateReq{} + for id, listener := range updateMap { + updateReq.UrlRules = append(updateReq.UrlRules, + &dataproto.TCloudUrlRuleUpdate{ + ID: id, + Scheduler: cvt.PtrToVal(listener.Scheduler), + SessionType: cvt.PtrToVal(listener.SessionType), + SessionExpire: listener.SessionExpireTime, + HealthCheck: convHealthCheck(listener.HealthCheck), + Certificate: convCert(listener.Certificate), + }, + ) + } + err := cli.dbCli.TCloud.LoadBalancer.BatchUpdateTCloudUrlRule(kt, updateReq) + if err != nil { + logs.Errorf("fail to update tcloud url rule, err: %v, rid: %s", err, kt.Rid) + return err + } + return nil +} + +func (cli *client) deleteLayer7Rule(kt *kit.Kit, cloudIds []string) error { + + if len(cloudIds) == 0 { + return nil + } + delReq := &dataproto.LoadBalancerBatchDeleteReq{Filter: tools.ContainersExpression("cloud_id", cloudIds)} + err := cli.dbCli.TCloud.LoadBalancer.BatchDeleteTCloudUrlRule(kt, delReq) + if err != nil { + logs.Errorf("fail to delete listeners(ids:%v) while sync, err: %v, rid: %s", + cloudIds, err, kt.Rid) + return err + } + return nil +} + +func (cli *client) createLayer7Rule(kt *kit.Kit, opt *SyncLayer7RuleOption, + addSlice []typeslb.TCloudUrlRule) ([]string, error) { + + if len(addSlice) == 0 { + return nil, nil + } + + dbRules := make([]dataproto.TCloudUrlRuleCreate, 0) + for _, cloud := range addSlice { + + dbRules = append(dbRules, dataproto.TCloudUrlRuleCreate{ + LbID: opt.LBID, + CloudLbID: opt.CloudLBID, + LblID: opt.ListenerID, + CloudLBLID: opt.CloudListenerID, + CloudID: cloud.GetCloudID(), + RuleType: enumor.Layer7RuleType, + + Domain: cvt.PtrToVal(cloud.Domain), + URL: cvt.PtrToVal(cloud.Url), + Scheduler: cvt.PtrToVal(cloud.Scheduler), + + SessionExpire: cvt.PtrToVal(cloud.SessionExpireTime), + HealthCheck: convHealthCheck(cloud.HealthCheck), + Certificate: convCert(cloud.Certificate), + }) + } + + ruleCreated, err := cli.dbCli.TCloud.LoadBalancer.BatchCreateTCloudUrlRule(kt, + &dataproto.TCloudUrlRuleBatchCreateReq{UrlRules: dbRules}) + if err != nil { + logs.Errorf("fail to create rule while sync, err: %v syncOpt: %+v, rid: %s", + err, opt, kt.Rid) + return nil, err + } + + return ruleCreated.IDs, nil +} + +func (cli *client) updateLayer7Rule(kt *kit.Kit, updateMap map[string]typeslb.TCloudUrlRule) error { + + if len(updateMap) == 0 { + return nil + } + updates := make([]*dataproto.TCloudUrlRuleUpdate, 0, len(updateMap)) + + for id, rule := range updateMap { + + updates = append(updates, &dataproto.TCloudUrlRuleUpdate{ + ID: id, + Domain: cvt.PtrToVal(rule.Domain), + URL: cvt.PtrToVal(rule.Url), + Scheduler: cvt.PtrToVal(rule.Scheduler), + SessionExpire: rule.SessionExpireTime, + HealthCheck: convHealthCheck(rule.HealthCheck), + Certificate: convCert(rule.Certificate), + }) + } + + err := cli.dbCli.TCloud.LoadBalancer.BatchUpdateTCloudUrlRule(kt, + &dataproto.TCloudUrlRuleBatchUpdateReq{UrlRules: updates}) + if err != nil { + logs.Errorf("fail to update rule while sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + return nil +} +func convHealthCheck(cloud *tclb.HealthCheck) *corelb.TCloudHealthCheckInfo { + if cloud == nil { + return nil + } + + db := &corelb.TCloudHealthCheckInfo{ + // 确保0值时不会存储为nil + HealthSwitch: cvt.ValToPtr(cvt.PtrToVal(cloud.HealthSwitch)), + TimeOut: cloud.TimeOut, + IntervalTime: cloud.IntervalTime, + HealthNum: cloud.HealthNum, + UnHealthNum: cloud.UnHealthNum, + HttpCode: cloud.HttpCode, + CheckPort: cloud.CheckPort, + CheckType: cloud.CheckType, + HttpVersion: cloud.HttpVersion, + HttpCheckPath: cloud.HttpCheckPath, + HttpCheckDomain: cloud.HttpCheckDomain, + HttpCheckMethod: cloud.HttpCheckMethod, + SourceIpType: cloud.SourceIpType, + ContextType: cloud.ContextType, + SendContext: cloud.SendContext, + RecvContext: cloud.RecvContext, + ExtendedCode: cloud.ExtendedCode, + } + + return db +} + +// 四层监听器的健康检查这些信息保存在规则里,需要检查对应的规则 +func isLayer4RuleChange(cloud typeslb.TCloudListener, db corelb.TCloudLbUrlRule) bool { + + if cvt.PtrToVal(cloud.Scheduler) != db.Scheduler { + return true + } + if cvt.PtrToVal(cloud.SessionType) != db.SessionType { + return true + } + + if isHealthCheckChange(cloud.HealthCheck, db.HealthCheck, false) { + return true + } + if isListenerCertChange(cloud.Certificate, db.Certificate) { + return true + } + return false +} + +func isLayer7RuleChange(cloud typeslb.TCloudUrlRule, db corelb.TCloudLbUrlRule) bool { + + if cvt.PtrToVal(cloud.Url) != db.URL { + return true + } + if cvt.PtrToVal(cloud.SessionExpireTime) != db.SessionExpire { + return true + } + if cvt.PtrToVal(cloud.Scheduler) != db.Scheduler { + return true + } + if cvt.PtrToVal(cloud.Domain) != db.Domain { + return true + } + + if isHealthCheckChange(cloud.HealthCheck, db.HealthCheck, true) { + return true + } + if isListenerCertChange(cloud.Certificate, db.Certificate) { + return true + } + + return false +} + +// SyncLayer7RuleOption 同步7层规则选项,包含 监听器信息 +type SyncLayer7RuleOption struct { + + // 对应的负载均衡 + LBID string `json:"lb_id" validate:"required"` + CloudLBID string `json:"cloud_lb_id" validate:"required"` + + // 对应的监听器 + ListenerID string `json:"lbl_id" validate:"required"` + CloudListenerID string `json:"cloud_lbl_id" validate:"required"` +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_security_group.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_security_group.go new file mode 100644 index 0000000000..0e27915060 --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_security_group.go @@ -0,0 +1,208 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "sort" + + "hcm/cmd/hc-service/logics/res-sync/common" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + "hcm/pkg/api/core/cloud" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataservice "hcm/pkg/api/data-service" + protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/slice" +) + +// lbSgRel 同步于安全组的关联关系,按lb、有序 +func (cli *client) lbSgRel(kt *kit.Kit, params *SyncBaseParams, lbInfo []corelb.TCloudLoadBalancer) error { + + lbIDs := make([]string, 0, len(lbInfo)) + // lb cloud id -> lb local id + cloudIDLbMap := make(map[string]string, len(lbInfo)) + for _, lb := range lbInfo { + lbIDs = append(lbIDs, lb.ID) + cloudIDLbMap[lb.CloudID] = lb.ID + } + // 1. 删除本地多余关联关系 + delFilter := tools.ExpressionAnd( + tools.RuleEqual("res_type", enumor.LoadBalancerCloudResType), + tools.RuleNotIn("res_id", lbIDs), + ) + err := cli.dbCli.Global.SGCommonRel.BatchDelete(kt, &dataservice.BatchDeleteReq{Filter: delFilter}) + if err != nil { + logs.Errorf("fail to del load balancer rel, err: %v, rid: %s", err, kt.Rid) + return err + } + // 2. 获取云上安全组绑定信息 + sgCloudLocalIdMap, lbSgCloudMap, err := cli.getCloudLbSgBinding(kt, params, lbInfo, cloudIDLbMap) + if err != nil { + logs.Errorf("fail to get cloud lb sg bind for rel sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + return cli.compareLbSgRel(kt, lbIDs, lbSgCloudMap, sgCloudLocalIdMap) +} + +func (cli *client) compareLbSgRel(kt *kit.Kit, lbIDs []string, lbSgCloudMap map[string][]string, + sgCloudLocalIdMap map[string]string) error { + + // 获取本地关联关系 + relReq := &protocloud.SGCommonRelWithSecurityGroupListReq{ + ResIDs: lbIDs, + ResType: enumor.LoadBalancerCloudResType, + } + sgRelResp, err := cli.dbCli.Global.SGCommonRel.ListWithSecurityGroup(kt, relReq) + if err != nil { + logs.Errorf("fail to list sg rel for lb sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + // lb本地id-> 关联的本地sg列表 + lbLocalSgMap := make(map[string][]common.OrderedRel, len(*sgRelResp)) + for _, rel := range *sgRelResp { + lbLocalSgMap[rel.ResID] = append(lbLocalSgMap[rel.ResID], + common.OrderedRel{CloudResID: rel.CloudID, ResID: rel.ResID, Priority: rel.Priority}) + } + // compare with priority + for lbId, cloudSgList := range lbSgCloudMap { + + localSlice := lbLocalSgMap[lbId] + // 按优先级从小到大排序 + sort.Slice(localSlice, func(i, j int) bool { + return localSlice[i].Priority < localSlice[j].Priority + }) + localLen := len(localSlice) + cloudLen := len(cloudSgList) + // 找到所有相等的列表 + var idx int + var cloudID string + var stayLocalIDs []string + for ; idx < cloudLen; idx++ { + cloudID = cloudSgList[idx] + if idx >= localLen || localSlice[idx].CloudResID != cloudID || localSlice[idx].Priority != int64(idx+1) { + // 剩下的全部加入新增列表里 + break + } + // 加入可以保留的安全组id列表中 + stayLocalIDs = append(stayLocalIDs, sgCloudLocalIdMap[cloudID]) + } + err := cli.upsertSgRelForLb(kt, lbId, idx, stayLocalIDs, cloudSgList[idx:], sgCloudLocalIdMap) + if err != nil { + return err + } + } + return nil +} + +func (cli *client) upsertSgRelForLb(kt *kit.Kit, lbId string, startIdx int, stayLocalIDs []string, sgCloudList []string, + cloudSgMap map[string]string) error { + + createDel := &protocloud.SGCommonRelBatchUpsertReq{Rels: make([]protocloud.SGCommonRelCreate, 0)} + // 删除所有不在给定id中的安全组,防止误删 + createDel.DeleteReq = &dataservice.BatchDeleteReq{Filter: tools.ExpressionAnd( + tools.RuleEqual("res_type", enumor.LoadBalancerCloudResType), + tools.RuleEqual("res_id", lbId), + )} + for i, cloudID := range sgCloudList { + // 填充云上id + createDel.Rels = append(createDel.Rels, protocloud.SGCommonRelCreate{ + SecurityGroupID: cloudSgMap[cloudID], + Vendor: enumor.TCloud, + ResID: lbId, + ResType: enumor.LoadBalancerCloudResType, + Priority: int64(i + startIdx + 1), + }) + + } + if len(stayLocalIDs) > 0 { + createDel.DeleteReq.Filter.Rules = append(createDel.DeleteReq.Filter.Rules, + tools.RuleNotIn("security_group_id", stayLocalIDs)) + } + if len(createDel.Rels) > 0 { + // 同时需要删除和创建 + err := cli.dbCli.Global.SGCommonRel.BatchUpsert(kt, createDel) + if err != nil { + logs.Errorf("fail to upsert lb(%s) security group rel, err: %v, req: %+v, rid: %s", + lbId, err, createDel, kt.Rid) + return err + } + return nil + } + + // 只需要尝试删除多余关联关系即可 + err := cli.dbCli.Global.SGCommonRel.BatchDelete(kt, createDel.DeleteReq) + if err != nil { + logs.Errorf("fail to delete lb(%s) security group rel, err: %v, req: %+v, rid: %s", + lbId, err, createDel.DeleteReq, kt.Rid) + return err + } + + return nil + +} + +func (cli *client) getCloudLbSgBinding(kt *kit.Kit, params *SyncBaseParams, lbInfo []corelb.TCloudLoadBalancer, + cloudIDLbMap map[string]string) (sgCloudLocalMap map[string]string, lbSgCloudMap map[string][]string, err error) { + + lbCloudIDs := slice.Map(lbInfo, func(lb corelb.TCloudLoadBalancer) string { return lb.CloudID }) + cloudLBs, err := cli.listLBFromCloud(kt, &SyncBaseParams{ + AccountID: params.AccountID, + Region: params.Region, + CloudIDs: lbCloudIDs, + }) + if err != nil { + logs.Errorf("fail to list cloud load balancers for sg rel sync, err: %v, rid: %s", err, kt.Rid) + return nil, nil, err + } + // 1. 获取 负载均衡安全组关联关系,并组合安全组id列表 + allSgCloudIDs := make([]string, 0, len(lbCloudIDs)) + // lbLocalID-> cloud sg ids, 本地负载均衡id索引的,云上安全组id列表 + lbSgCloudMap = cvt.SliceToMap(cloudLBs, func(lb typeslb.TCloudClb) (string, []string) { + cloudSlice := cvt.PtrToSlice(lb.SecureGroups) + allSgCloudIDs = append(allSgCloudIDs, cloudSlice...) + return cloudIDLbMap[lb.GetCloudID()], cloudSlice + }) + if len(allSgCloudIDs) == 0 { + return make(map[string]string), make(map[string][]string), nil + } + // 2. 获取本地id 映射 + sgReq := &protocloud.SecurityGroupListReq{ + Field: []string{"id", "cloud_id"}, + Filter: tools.ExpressionAnd(tools.RuleIn("cloud_id", allSgCloudIDs)), + Page: core.NewDefaultBasePage(), + } + sgResp, err := cli.dbCli.Global.SecurityGroup.ListSecurityGroup(kt.Ctx, kt.Header(), sgReq) + if err != nil { + logs.Errorf("fail to get sg list, err: %v, rid: %s", err, kt.Rid) + return nil, nil, err + } + // cloudID->localID + cloudSgMap := cvt.SliceToMap(sgResp.Details, func(sg cloud.BaseSecurityGroup) (string, string) { + return sg.CloudID, sg.ID + }) + return cloudSgMap, lbSgCloudMap, nil +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go new file mode 100644 index 0000000000..a8de7485ca --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go @@ -0,0 +1,641 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "fmt" + + "hcm/cmd/hc-service/logics/res-sync/common" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/table/types" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/assert" + "hcm/pkg/tools/classifier" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/json" + "hcm/pkg/tools/slice" + + tclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" +) + +// LocalTargetGroup 同步本地目标组 +func (cli *client) LocalTargetGroup(kt *kit.Kit, param *SyncBaseParams, opt *SyncListenerOfSingleLBOption, + cloudListeners []typeslb.TCloudListener) error { + // 目前主要是同步健康检查 + healthMap := make(map[string]*tclb.HealthCheck, len(cloudListeners)) + cloudIDs := make([]string, 0, len(cloudListeners)) + // 收集云端健康检查 + for _, listener := range cloudListeners { + if !listener.GetProtocol().IsLayer7Protocol() { + // 四层监听器,直接获取健康检查 + healthMap[listener.GetCloudID()] = listener.HealthCheck + cloudIDs = append(cloudIDs, listener.GetCloudID()) + continue + } + for _, rule := range listener.Rules { + healthMap[cvt.PtrToVal(rule.LocationId)] = rule.HealthCheck + cloudIDs = append(cloudIDs, cvt.PtrToVal(rule.LocationId)) + } + } + tgCloudHealthMap, tgList, err := cli.getTargetGruop(kt, opt.LBID, cloudIDs, healthMap) + if err != nil { + return err + } + for _, tg := range tgList { + + if !isHealthCheckChange(tgCloudHealthMap[tg.CloudID], tg.HealthCheck, false) { + continue + } + + // 更新 健康检查 + updateReq := &dataproto.TargetGroupUpdateReq{ + IDs: []string{tg.ID}, + HealthCheck: convHealthCheck(tgCloudHealthMap[tg.CloudID]), + } + err = cli.dbCli.TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(kt, updateReq) + if err != nil { + logs.Errorf("fail to update target group health check during sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + } + return nil +} + +func (cli *client) getTargetGruop(kt *kit.Kit, lbId string, cloudIDs []string, + healthMap map[string]*tclb.HealthCheck) (map[string]*tclb.HealthCheck, []corelb.BaseTargetGroup, error) { + + // 查找本地 目标组 + relReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("lb_id", lbId), + tools.RuleIn("cloud_listener_rule_id", cloudIDs)), + Page: core.NewDefaultBasePage(), + } + relResp, err := cli.dbCli.Global.LoadBalancer.ListTargetGroupListenerRel(kt, relReq) + if err != nil { + logs.Errorf("fail to get target group rel for sync, err: %v, rid: %s", err, kt.Rid) + return nil, nil, err + } + if len(relResp.Details) == 0 { + return nil, nil, nil + } + + tgIds := make([]string, 0, len(relResp.Details)) + tgCloudHealthMap := make(map[string]*tclb.HealthCheck, len(relResp.Details)) + for _, rel := range relResp.Details { + tgIds = append(tgIds, rel.TargetGroupID) + tgCloudHealthMap[rel.TargetGroupID] = healthMap[rel.CloudListenerRuleID] + } + // 查找目标组 + tgReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", tgIds), + Page: core.NewDefaultBasePage(), + } + tgResp, err := cli.dbCli.Global.LoadBalancer.ListTargetGroup(kt, tgReq) + if err != nil { + logs.Errorf("fail to get target group for sync, err: %v, rid: %s", err, kt.Rid) + return nil, nil, err + } + return tgCloudHealthMap, tgResp.Details, nil +} + +// ListenerTargets 监听器下的target,用来更新目标组. +// SyncBaseParams 中的CloudID作为监听器id筛选,不传的话就是同步当前LB下的全部监听器 +func (cli *client) ListenerTargets(kt *kit.Kit, param *SyncBaseParams, opt *SyncListenerOfSingleLBOption) error { + + cloudListenerTargets, relMap, tgRsMap, lb, err := cli.listTargetRelated(kt, param, opt) + if err != nil { + logs.Errorf("fail to list related res during targets syncing, err: %v, rid: %s", err, kt.Rid) + return err + } + // 一个目标组只处理一次 + isTGHandled := genExists[string]() + compareWrapper := func(rel *corelb.BaseTargetListenerRuleRel, cloudTargets []*tclb.Backend) error { + if rel.BindingStatus == enumor.BindingBindingStatus { + return nil + } + tgId := rel.TargetGroupID + if isTGHandled(tgId) { + return nil + } + + // 存在则比较 + return cli.compareTargetsChange(kt, opt.AccountID, tgId, cloudTargets, tgRsMap[tgId]) + } + // 遍历云上的监听器、规则 + for _, listener := range cloudListenerTargets { + if !listener.GetProtocol().IsLayer7Protocol() { + // ---- for layer 4 对比监听器变化 ---- + rel, exists := relMap[cvt.PtrToVal(listener.ListenerId)] + if !exists { + // 云上监听器、但是没有对应目标组,则在同步时自动创建目标组,并将RS加入目标组。 + if err := cli.createLocalTargetGroupL4(kt, opt, lb, listener); err != nil { + logs.Errorf("fail to create local target group for layer 4 listener, rid: %s", kt.Rid) + return err + } + // 只要本地没有目标组就跳过RS同步 + continue + } + if err := compareWrapper(rel, listener.Targets); err != nil { + logs.Errorf("fail to compare L4 listener rs change, err: %v, rid:%s", err, kt.Rid) + return err + } + continue + } + // ---- for layer 7 对比规则变化 ---- + for _, rule := range listener.Rules { + rel, exists := relMap[cvt.PtrToVal(rule.LocationId)] + if !exists { + // 没有对应目标组关系,则在同步时自动创建目标组,并将RS加入目标组。 + if err := cli.createLocalTargetGroupL7(kt, opt, lb, listener, rule); err != nil { + logs.Errorf("fail to create local target group for layer 7 rule, rid: %s", kt.Rid) + return err + } + // 跳过比较 + continue + } + // 存在则比较 + if err := compareWrapper(rel, rule.Targets); err != nil { + logs.Errorf("fail to compare L7 rule rs change, err: %v, rid:%s", err, kt.Rid) + return err + } + } + } + return nil +} + +// 获取同步rs所需关联资源 +func (cli *client) listTargetRelated(kt *kit.Kit, param *SyncBaseParams, opt *SyncListenerOfSingleLBOption) ( + []typeslb.TCloudListenerTarget, map[string]*corelb.BaseTargetListenerRuleRel, + map[string][]corelb.BaseTarget, *corelb.TCloudLoadBalancer, error) { + + // 获取监听器详情 + cloudListenerTargets, err := cli.listTargetsFromCloud(kt, param, opt) + if err != nil { + logs.Errorf("fail to list target from cloud while syncing, err: %v, rid: %s", err, kt.Rid) + return nil, nil, nil, nil, err + } + + // 获取db中的目标组关系和rs列表 + relMap, tgRsMap, err := cli.listTargetsFromDB(kt, param, opt) + if err != nil { + logs.Errorf("fail to list target from db while syncing, err: %v, rid: %s", err, kt.Rid) + return nil, nil, nil, nil, err + } + + lbResp, err := cli.listLBFromDB(kt, &SyncBaseParams{ + AccountID: opt.AccountID, + Region: opt.Region, + CloudIDs: []string{opt.CloudLBID}, + }) + if err != nil { + logs.Errorf("fail to list lb from db for sync tg, err: %v, lb_id: %s, rid: %s", err, opt.CloudLBID, kt.Rid) + return nil, nil, nil, nil, err + } + if len(lbResp) == 0 { + logs.Errorf("can not find lb for sync tg, err: %v, opt: %+v, rid: %s", err, opt, kt.Rid) + return nil, nil, nil, nil, errf.Newf(errf.RecordNotFound, "lb not found: %s", opt.CloudLBID) + } + + return cloudListenerTargets, relMap, tgRsMap, cvt.ValToPtr(lbResp[0]), nil +} + +func (cli *client) compareTargetsChange(kt *kit.Kit, accountID, tgID string, cloudTargets []*tclb.Backend, + dbRsList []corelb.BaseTarget) ( + err error) { + + // 增加包裹类型 + cloudRsList := slice.Map(cloudTargets, func(rs *tclb.Backend) typeslb.Backend { + return typeslb.Backend{Backend: rs} + }) + addSlice, updateMap, delLocalIDs := diff[typeslb.Backend, corelb.BaseTarget](cloudRsList, dbRsList, isRsChange) + + if err = cli.deleteRs(kt, delLocalIDs); err != nil { + return err + } + + if err = cli.updateRs(kt, updateMap); err != nil { + return err + } + if _, err = cli.createRs(kt, accountID, tgID, addSlice); err != nil { + return err + } + return nil +} + +// 为rs创建目标组不跳过没有rs的规则 +func (cli *client) createLocalTargetGroupL7(kt *kit.Kit, opt *SyncListenerOfSingleLBOption, + lb *corelb.TCloudLoadBalancer, listener typeslb.TCloudListenerTarget, cloudRule *tclb.RuleTargets) error { + + // 获取数据库中的规则 + listReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("cloud_id", cloudRule.LocationId), + tools.RuleEqual("cloud_lbl_id", listener.ListenerId)), + Page: core.NewDefaultBasePage(), + } + + ruleResp, err := cli.dbCli.TCloud.LoadBalancer.ListUrlRule(kt, listReq) + if err != nil { + logs.Errorf("fail to list rule of l7 listener, err: %v, rule id: %s, lbl_cloud_id: %s, rid: %s", + err, cloudRule.LocationId, listener.ListenerId, kt.Rid) + return err + } + if len(ruleResp.Details) == 0 { + logs.Errorf("rule of listener can not be found by id(%s),err: %v, lbl_cloud_id: %s, rid: %s ", + err, cloudRule.LocationId, listener.ListenerId, kt.Rid) + return fmt.Errorf("rule of listener can not be found by id(%+v)", cloudRule.LocationId) + } + dbRule := ruleResp.Details[0] + healthcheck, err := json.MarshalToString(dbRule.HealthCheck) + if err != nil { + logs.Errorf("fail to marshal rule health check to string, err: %v, rid: %s", err, kt.Rid) + return err + } + tgCreate := dataproto.CreateTargetGroupWithRel[corelb.TCloudTargetGroupExtension]{ + TargetGroup: dataproto.TargetGroupBatchCreate[corelb.TCloudTargetGroupExtension]{ + Name: genTargetGroupNameL7(dbRule), + Vendor: enumor.TCloud, + AccountID: opt.AccountID, + BkBizID: opt.BizID, + Region: opt.Region, + Protocol: listener.GetProtocol(), + Port: cvt.PtrToVal(listener.Port), + VpcID: lb.VpcID, + CloudVpcID: lb.CloudVpcID, + TargetGroupType: enumor.LocalTargetGroupType, + Weight: 0, + HealthCheck: types.JsonField(healthcheck), + Memo: cvt.ValToPtr("auto created for rule " + cvt.PtrToVal(cloudRule.LocationId)), + RsList: slice.Map(cloudRule.Targets, convTarget(opt.AccountID)), + }, + ListenerRuleID: dbRule.ID, + CloudListenerRuleID: dbRule.CloudID, + ListenerRuleType: enumor.Layer7RuleType, + LbID: dbRule.LbID, + CloudLbID: dbRule.CloudLbID, + LblID: dbRule.LblID, + CloudLblID: dbRule.CloudLBLID, + BindingStatus: enumor.SuccessBindingStatus, + } + + tgCreateReq := &dataproto.TCloudBatchCreateTgWithRelReq{ + TargetGroups: []dataproto.CreateTargetGroupWithRel[corelb.TCloudTargetGroupExtension]{tgCreate}, + } + _, err = cli.dbCli.TCloud.LoadBalancer.BatchCreateTargetGroupWithRel(kt, tgCreateReq) + if err != nil { + logs.Errorf("fail to create tcloud target group with rel, err: %v, rid: %s", err, kt.Rid) + return err + } + return nil +} + +func convTarget(accountID string) func(cloudTarget *tclb.Backend) *dataproto.TargetBaseReq { + return func(cloudTarget *tclb.Backend) *dataproto.TargetBaseReq { + return &dataproto.TargetBaseReq{ + InstType: cvt.PtrToVal((*enumor.InstType)(cloudTarget.Type)), + CloudInstID: cvt.PtrToVal(cloudTarget.InstanceId), + Port: cvt.PtrToVal(cloudTarget.Port), + Weight: cloudTarget.Weight, + AccountID: accountID, + } + } +} + +// 创建本地目标组以及关系,不会跳过没有rs的监听器 +func (cli *client) createLocalTargetGroupL4(kt *kit.Kit, opt *SyncListenerOfSingleLBOption, + lb *corelb.TCloudLoadBalancer, listener typeslb.TCloudListenerTarget) error { + + lbl, rule, err := cli.listListenerWithRule(kt, cvt.PtrToVal(listener.ListenerId)) + if err != nil { + logs.Errorf("fail to list listener with rule, err: %v, rid:%s", err, kt.Rid) + return err + } + + healthcheck, err := json.MarshalToString(rule.HealthCheck) + if err != nil { + logs.Errorf("fail to marshal rule health check to string, err: %v, rid: %s", err, kt.Rid) + return err + } + tgCreate := dataproto.CreateTargetGroupWithRel[corelb.TCloudTargetGroupExtension]{ + TargetGroup: dataproto.TargetGroupBatchCreate[corelb.TCloudTargetGroupExtension]{ + Name: genTargetGroupNameL4(lbl), + Vendor: enumor.TCloud, + AccountID: lbl.AccountID, + BkBizID: lbl.BkBizID, + Region: opt.Region, + Protocol: lbl.Protocol, + Port: lbl.Port, + VpcID: lb.VpcID, + CloudVpcID: lb.CloudVpcID, + TargetGroupType: enumor.LocalTargetGroupType, + Weight: 0, + HealthCheck: types.JsonField(healthcheck), + Memo: cvt.ValToPtr("auto created for listener " + cvt.PtrToVal(listener.ListenerId)), + RsList: slice.Map(listener.Targets, convTarget(opt.AccountID)), + }, + // 需要用4层对应的规则id + ListenerRuleID: rule.ID, + CloudListenerRuleID: lbl.CloudID, + ListenerRuleType: enumor.Layer4RuleType, + LbID: lbl.LbID, + CloudLbID: lbl.CloudLbID, + LblID: lbl.ID, + CloudLblID: lbl.CloudID, + BindingStatus: enumor.SuccessBindingStatus, + } + + tgCreateReq := &dataproto.TCloudBatchCreateTgWithRelReq{ + TargetGroups: []dataproto.CreateTargetGroupWithRel[corelb.TCloudTargetGroupExtension]{tgCreate}, + } + _, err = cli.dbCli.TCloud.LoadBalancer.BatchCreateTargetGroupWithRel(kt, tgCreateReq) + if err != nil { + logs.Errorf("fail to create tcloud target group with rel, err: %v, rid: %s", err, kt.Rid) + return err + } + + return nil +} + +func (cli *client) listListenerWithRule(kt *kit.Kit, listenerCloudID string) ( + *corelb.Listener[corelb.TCloudListenerExtension], *corelb.TCloudLbUrlRule, error) { + + listReq := &core.ListReq{ + Filter: tools.EqualExpression("cloud_id", listenerCloudID), + Page: core.NewDefaultBasePage(), + } + lblResp, err := cli.dbCli.TCloud.LoadBalancer.ListListener(kt, listReq) + if err != nil { + logs.Errorf("fail to list listener of lb(%s) for create local target group, err: %v, rid: %s", + listenerCloudID, err, kt.Rid) + return nil, nil, err + } + if len(lblResp.Details) == 0 { + // 出现云上新增的监听器,本地没有的,跳过, 等待下次同步 + logs.Errorf("listener can not be found by id(%s) while target group sync, rid: %s", + listenerCloudID, kt.Rid) + return nil, nil, fmt.Errorf("listener can not be found by id(%s) while target group syncing", listenerCloudID) + } + lbl := lblResp.Details[0] + // 获取对应规则 + listReq.Filter = tools.ExpressionAnd( + tools.RuleEqual("cloud_id", lbl.CloudID), + tools.RuleEqual("lbl_id", lbl.ID)) + ruleResp, err := cli.dbCli.TCloud.LoadBalancer.ListUrlRule(kt, listReq) + if err != nil { + logs.Errorf("fail to list rule of l4 listener, err: %v, lbl_id: %s, lbl_cloud_id: %s, rid: %s", + err, lbl.ID, lbl.CloudID, kt.Rid) + return nil, nil, err + } + if len(ruleResp.Details) == 0 { + logs.Errorf("rule of listener can not be found by id(%s), lbl_id: %s, lbl_cloud_id: %s, rid: %s ", + lbl.ID, lbl.CloudID, kt.Rid) + return nil, nil, fmt.Errorf("rule of listener can not be found by id(%s) while target group syncing", + listenerCloudID) + } + return cvt.ValToPtr(lbl), cvt.ValToPtr(ruleResp.Details[0]), nil +} + +// 按cloudInstID 删除目标组中的rs +func (cli *client) deleteRs(kt *kit.Kit, localIds []string) error { + if len(localIds) == 0 { + return nil + } + + delReq := &dataproto.LoadBalancerBatchDeleteReq{Filter: tools.ContainersExpression("id", localIds)} + err := cli.dbCli.Global.LoadBalancer.BatchDeleteTarget(kt, delReq) + if err != nil { + logs.Errorf("fail to delete rs (ids=%v), err: %v, rid: %s", localIds, err, kt.Rid) + return err + } + + return nil +} + +// 更新rs中的信息 +func (cli *client) updateRs(kt *kit.Kit, updateMap map[string]typeslb.Backend) (err error) { + + if len(updateMap) == 0 { + return nil + } + updates := make([]*dataproto.TargetUpdate, 0, len(updateMap)) + for id, backend := range updateMap { + updates = append(updates, &dataproto.TargetUpdate{ + ID: id, + Port: cvt.PtrToVal(backend.Port), + Weight: backend.Weight, + PrivateIPAddress: cvt.PtrToSlice(backend.PrivateIpAddresses), + PublicIPAddress: cvt.PtrToSlice(backend.PublicIpAddresses), + InstName: cvt.PtrToVal(backend.InstanceName), + }) + } + updateReq := &dataproto.TargetBatchUpdateReq{Targets: updates} + if err = cli.dbCli.Global.LoadBalancer.BatchUpdateTarget(kt, updateReq); err != nil { + logs.Errorf("fail to update targets while syncing, err: %v, rid:%s", err, kt.Rid) + } + + return err +} + +func (cli *client) createRs(kt *kit.Kit, accountID, tgId string, addSlice []typeslb.Backend) ([]string, error) { + + if len(addSlice) == 0 { + return nil, nil + } + + var targets []*dataproto.TargetBaseReq + for _, backend := range addSlice { + targets = append(targets, &dataproto.TargetBaseReq{ + InstType: cvt.PtrToVal((*enumor.InstType)(backend.Type)), + CloudInstID: cvt.PtrToVal(backend.InstanceId), + Port: cvt.PtrToVal(backend.Port), + Weight: backend.Weight, + AccountID: accountID, + TargetGroupID: tgId, + }) + } + + created, err := cli.dbCli.Global.LoadBalancer.BatchCreateTCloudTarget(kt, + &dataproto.TargetBatchCreateReq{Targets: targets}) + if err != nil { + logs.Errorf("fail to create target for target group syncing, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + return created.IDs, nil +} + +// 获取云上监听器列表 +func (cli *client) listTargetsFromCloud(kt *kit.Kit, param *SyncBaseParams, + opt *SyncListenerOfSingleLBOption) ([]typeslb.TCloudListenerTarget, error) { + + listOpt := &typeslb.TCloudListTargetsOption{ + Region: opt.Region, + LoadBalancerId: opt.CloudLBID, + ListenerIds: param.CloudIDs, + } + return cli.cloudCli.ListTargets(kt, listOpt) +} + +// 获取云上监听器列表 +func (cli *client) listTargetsFromDB(kt *kit.Kit, param *SyncBaseParams, opt *SyncListenerOfSingleLBOption) ( + relMap map[string]*corelb.BaseTargetListenerRuleRel, tgRsMap map[string][]corelb.BaseTarget, err error) { + + listReq := &core.ListReq{ + Filter: tools.EqualExpression("lb_id", opt.LBID), + Page: core.NewDefaultBasePage(), + } + + if len(param.CloudIDs) > 0 { + listReq.Filter.Rules = append(listReq.Filter.Rules, tools.RuleIn("cloud_lbl_id", param.CloudIDs)) + } + // 获取关系 + relResp, err := cli.dbCli.Global.LoadBalancer.ListTargetGroupListenerRel(kt, listReq) + if err != nil { + logs.Errorf("fail to ListTargetGroupListenerRel, err: %v, rid: %s ", err, kt.Rid) + return nil, nil, err + } + relMap = make(map[string]*corelb.BaseTargetListenerRuleRel) + tgRsMap = make(map[string][]corelb.BaseTarget) + if len(relResp.Details) == 0 { + return relMap, tgRsMap, nil + } + + tgIDMap := make(map[string]struct{}, len(relResp.Details)) + + for i, rel := range relResp.Details { + tgIDMap[rel.TargetGroupID] = struct{}{} + relMap[rel.CloudListenerRuleID] = cvt.ValToPtr(relResp.Details[i]) + } + relResp.Details = nil + // 目标组ID 去重 + tgIDs := cvt.MapKeyToStringSlice(tgIDMap) + + // 查询对应的rs列表 + rsList, err := cli.dbCli.Global.LoadBalancer.ListTarget(kt, &core.ListReq{ + Filter: tools.ExpressionAnd(tools.RuleIn("target_group_id", tgIDs)), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list targets of target group(ids=%v), err: %v, rid: %s", tgIDs, err, kt.Rid) + return nil, nil, err + } + // 按目标组分 + tgRsMap = classifier.ClassifySlice(rsList.Details, func(rs corelb.BaseTarget) string { + return rs.TargetGroupID + }) + + return relMap, tgRsMap, nil +} + +// 判断rs信息是否变化 +func isRsChange(cloud typeslb.Backend, db corelb.BaseTarget) bool { + if cvt.PtrToVal(cloud.Port) != db.Port { + return true + } + + if cvt.PtrToVal(cloud.Weight) != cvt.PtrToVal(db.Weight) { + return true + } + if cvt.PtrToVal(cloud.InstanceName) != db.InstName { + return true + } + + if !assert.IsStringSliceEqual(cvt.PtrToSlice(cloud.PrivateIpAddresses), db.PrivateIPAddress) { + return true + } + + if !assert.IsStringSliceEqual(cvt.PtrToSlice(cloud.PublicIpAddresses), db.PublicIPAddress) { + return true + } + return false +} + +func genTargetGroupNameL4(lbl *corelb.Listener[corelb.TCloudListenerExtension]) string { + return "auto-" + lbl.CloudID +} + +func genTargetGroupNameL7(rule corelb.TCloudLbUrlRule) string { + return "auto-" + rule.CloudID +} + +// SyncTargetGroupOption ... +type SyncTargetGroupOption struct { + AccountID string `json:"account_id" validate:"required"` + Region string `json:"region" validate:"required"` + BizID int64 `json:"biz_id" validate:"required"` + // 对应的负载均衡 + LBID string `json:"lbid" validate:"required"` + CloudLBID string `json:"cloud_lbid" validate:"required"` +} + +// diff 该diff 和common.Diff的区别在于该接口的delete返回本地id +func diff[CloudType common.CloudResType, DBType common.DBResType](dataFromCloud []CloudType, dataFromDB []DBType, + isChange func(CloudType, DBType) bool) (newAddData []CloudType, updateMap map[string]CloudType, + delLocalIDs []string) { + + dbMap := make(map[string]DBType, len(dataFromDB)) + for _, one := range dataFromDB { + dbMap[one.GetCloudID()] = one + } + + newAddData = make([]CloudType, 0) + updateMap = make(map[string]CloudType, 0) + for _, oneFromCloud := range dataFromCloud { + oneFromDB, exist := dbMap[oneFromCloud.GetCloudID()] + if !exist { + newAddData = append(newAddData, oneFromCloud) + continue + } + + delete(dbMap, oneFromCloud.GetCloudID()) + if isChange(oneFromCloud, oneFromDB) { + updateMap[oneFromDB.GetID()] = oneFromCloud + } + } + + // 返回本地id 而不是云上id + delLocalIDs = make([]string, 0, len(dbMap)) + for _, item := range dbMap { + delLocalIDs = append(delLocalIDs, item.GetID()) + } + + return newAddData, updateMap, delLocalIDs +} + +func genExists[T comparable]() (exists func(T) bool) { + existsMap := make(map[T]struct{}) + exists = func(k T) bool { + if _, exist := existsMap[k]; exist { + return exist + } + existsMap[k] = struct{}{} + return false + } + return exists +} diff --git a/cmd/hc-service/logics/sync/diff.go b/cmd/hc-service/logics/sync/diff.go new file mode 100644 index 0000000000..119a85c31f --- /dev/null +++ b/cmd/hc-service/logics/sync/diff.go @@ -0,0 +1,50 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sync + +// Diff 对比源数据和目标数据的增/删/改数据。 +func Diff[SourceDataType SourceData, TargetDataType TargetData]( + sourceData []SourceDataType, targetData []TargetDataType, isChange func(SourceDataType, TargetDataType) bool) ( + createData []SourceDataType, idUpdateDataMap map[string]SourceDataType, delIDs []string) { + + uuidTargetDataMap := make(map[string]TargetDataType, len(targetData)) + for _, one := range targetData { + uuidTargetDataMap[one.GetUUID()] = one + } + + for _, oneFromSource := range sourceData { + oneFromTarget, exist := uuidTargetDataMap[oneFromSource.GetUUID()] + if !exist { + createData = append(createData, oneFromSource) + continue + } + + delete(uuidTargetDataMap, oneFromSource.GetUUID()) + if isChange(oneFromSource, oneFromTarget) { + idUpdateDataMap[oneFromTarget.GetUUID()] = oneFromSource + } + } + + for _, one := range uuidTargetDataMap { + delIDs = append(delIDs, one.GetID()) + } + + return createData, idUpdateDataMap, delIDs +} diff --git a/cmd/hc-service/logics/sync/sync_handler.go b/cmd/hc-service/logics/sync/sync_handler.go new file mode 100644 index 0000000000..aa936d0597 --- /dev/null +++ b/cmd/hc-service/logics/sync/sync_handler.go @@ -0,0 +1,41 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sync + +import "hcm/pkg/kit" + +// Handler 定义批量同步所需函数。 +type Handler[ParamType any, SourceDataType SourceData, TargetDataType TargetData] interface { + // Name 返回处理器名称 + Name() HandlerName + + // QueryFromSource 从数据源查询数据 + QueryFromSource(kt *kit.Kit, params ParamType) (sourceData []SourceDataType, err error) + // QueryFromTarget 从目标源查询数据。 + QueryFromTarget(kt *kit.Kit, params ParamType) (targetData []TargetDataType, err error) + // DiffFunc 对比源数据和目标数据是否发生改变 + DiffFunc(sourceData SourceDataType, targetData TargetDataType) bool + // DeleteTargetData 删除目标源中的数据。这部分数据是从数据源中已经删除了的,但目标源中还存在的数据。 + DeleteTargetData(kt *kit.Kit, params ParamType, delIDs []string) error + // CreateTargetData 添加目标源中的数据。这部分数据是数据源中已经创建了的数据,但目标源还没有的数据。 + CreateTargetData(kt *kit.Kit, params ParamType, createData []SourceDataType) (ids []string, err error) + // UpdateTargetData 更新源数据和目标源中存在字段发生改变的数据。 + UpdateTargetData(kt *kit.Kit, params ParamType, idUpdateDataMap map[string]SourceDataType) error +} diff --git a/cmd/hc-service/logics/sync/sync_pager.go b/cmd/hc-service/logics/sync/sync_pager.go new file mode 100644 index 0000000000..66b4aee0ac --- /dev/null +++ b/cmd/hc-service/logics/sync/sync_pager.go @@ -0,0 +1,49 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sync + +import "hcm/pkg/kit" + +// Pager 同步分页器,用于。 +type Pager[ParamType any] interface { + // BuildParam 构建同步请求参数 + BuildParam(uuids []string) (params ParamType) + + // SourcePager 数据源分页遍历 + SourcePager + // TargetPager 目标源分页遍历 + TargetPager +} + +// SourcePager 数据源分页 +type SourcePager interface { + // NextFromSource 返回下一批数据 + NextFromSource(kt *kit.Kit) (uuids []string, err error) + // HasNextFromSource 是否还有下一页数据 + HasNextFromSource() (bool, error) +} + +// TargetPager 目标源分页 +type TargetPager interface { + // NextFromTarget 返回下一批数据 + NextFromTarget(kt *kit.Kit) (uuidIDMap map[string]string, err error) + // HasNextFromTarget 是否还有下一页数据 + HasNextFromTarget() (bool, error) +} diff --git a/cmd/hc-service/logics/sync/syncer.go b/cmd/hc-service/logics/sync/syncer.go new file mode 100644 index 0000000000..0b41ab639c --- /dev/null +++ b/cmd/hc-service/logics/sync/syncer.go @@ -0,0 +1,224 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sync + +import ( + "errors" + + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/maps" +) + +// Interface 同步提供的函数能力。 +type Interface[BatchSyncParamType any, SourceDataType SourceData, TargetDataType TargetData] interface { + // AllPages 分页全量同步,将数据源分页调用批量同步进行同步。 + AllPages(kt *kit.Kit) (result *Result, err error) + // BatchOrAll 批量/全量同步,取决于用户传的查询参数查询的是全量数据还是批量数据。 + BatchOrAll(kt *kit.Kit, params BatchSyncParamType) (result *Result, err error) + // RemoveDeletedFromSource 移除已经从数据源删除的数据。 + RemoveDeletedFromSource(kt *kit.Kit) (ids []string, err error) +} + +// Syncer 同步器 +type Syncer[BatchSyncParamType any, SourceDataType SourceData, TargetDataType TargetData] struct { + // Pager 分页同步处理器 + Pager Pager[BatchSyncParamType] + + // Handler 批量同步处理器 + Handler Handler[BatchSyncParamType, SourceDataType, TargetDataType] +} + +// RemoveDeletedFromSource 移除已经从数据源删除的数据。 +func (sync *Syncer[BatchSyncParamType, SourceDataType, TargetDataType]) RemoveDeletedFromSource(kt *kit.Kit) ( + ids []string, err error) { + + for { + // 从目标源查询一批数据,判断这批数据是否有已经从数据源删除的数据 + uuidIDMapFromTarget, err := sync.Pager.NextFromTarget(kt) + if err != nil { + logs.Errorf("[%s] get next from target failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return nil, err + } + + if len(uuidIDMapFromTarget) != 0 { + // 从数据源查询数据 + params := sync.Pager.BuildParam(maps.Keys(uuidIDMapFromTarget)) + sourceData, err := sync.Handler.QueryFromSource(kt, params) + if err != nil { + logs.Errorf("[%s] query from source failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return nil, err + } + + // 如果查询数据和返回数据数量不同,则证明目标源中有数据要被删除 + if len(uuidIDMapFromTarget) != len(sourceData) { + for _, one := range sourceData { + delete(uuidIDMapFromTarget, one.GetUUID()) + } + + delIDs := maps.Values(uuidIDMapFromTarget) + if err = sync.Handler.DeleteTargetData(kt, params, delIDs); err != nil { + logs.Errorf("[%s] delete target data failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return nil, err + } + + ids = append(ids, delIDs...) + } + + // 判断是否还有下一页资源需要同步 + hasNext, err := sync.Pager.HasNextFromTarget() + if err != nil { + logs.Errorf("[%s] exec has next from target failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return ids, err + } + + if !hasNext { + break + } + } + } + + return ids, nil +} + +// AllPages 分页全量同步,将数据源分页调用批量同步进行同步。 +func (sync *Syncer[BatchSyncParamType, SourceDataType, TargetDataType]) AllPages(kt *kit.Kit) ( + result *Result, err error) { + + if sync.Handler == nil { + return nil, errors.New("page sync handler is required") + } + + delIDs, err := sync.RemoveDeletedFromSource(kt) + if err != nil { + logs.Errorf("[%s] remove deleted from source failed, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + result.DeleteIDs = append(result.DeleteIDs, delIDs...) + + for { + // 获取下一页要同步资源的唯一ID列表 + uuids, err := sync.Pager.NextFromSource(kt) + if err != nil { + logs.Errorf("[%s] get next from source failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return nil, err + } + + // 执行批量同步,同步这一页的资源 + if len(uuids) != 0 { + params := sync.Pager.BuildParam(uuids) + batchSyncResult, err := sync.BatchOrAll(kt, params) + if err != nil { + logs.Errorf("[%s] batch sync failed, err: %v, uuids: %v, rid: %s", sync.Handler.Name(), + err, uuids, kt.Rid) + return nil, err + } + + result.DeleteIDs = append(result.DeleteIDs, batchSyncResult.DeleteIDs...) + result.CreateIDs = append(result.CreateIDs, batchSyncResult.CreateIDs...) + result.UpdateIDs = append(result.UpdateIDs, batchSyncResult.UpdateIDs...) + } + + // 判断是否还有下一页资源需要同步 + hasNext, err := sync.Pager.HasNextFromSource() + if err != nil { + logs.Errorf("[%s] exec has next from source failed, err: %v, rid: %s", sync.Handler.Name(), err, kt.Rid) + return nil, err + } + + if !hasNext { + break + } + } + + return result, nil +} + +// BatchOrAll 批量/全量同步,取决于用户传的查询参数查询的是全量数据还是批量数据。 +func (sync *Syncer[BatchSyncParamType, SourceDataType, TargetDataType]) BatchOrAll( + kt *kit.Kit, params BatchSyncParamType) (result *Result, err error) { + + if sync.Handler == nil { + return nil, errors.New("batch sync handler is required") + } + + // 从数据源查询数据 + sourceData, err := sync.Handler.QueryFromSource(kt, params) + if err != nil { + logs.Errorf("[%s] query from source failed, err: %v, params: %+v, rid: %s", sync.Handler.Name(), + err, params, kt.Rid) + return nil, err + } + + // 从目标源查询数据 + targetData, err := sync.Handler.QueryFromTarget(kt, params) + if err != nil { + logs.Errorf("[%s] query from target failed, err: %v, params: %+v, rid: %s", sync.Handler.Name(), + err, params, kt.Rid) + return nil, err + } + + // 没有数据需要同步 + if len(sourceData) == 0 && len(targetData) == 0 { + return new(Result), nil + } + + // 对比数据源和目标源数据,对增/删/改数据进行分类 + createData, idUpdateDataMap, delIDs := Diff(sourceData, targetData, sync.Handler.DiffFunc) + + // TODO: 添加日志和metrics数量统计,和失败请求统计 + // 删除目标源中多余的数据 + if len(delIDs) > 0 { + if err = sync.Handler.DeleteTargetData(kt, params, delIDs); err != nil { + logs.Errorf("[%s] delete target data failed, err: %v, params: %+v, delIDs: %+v, rid: %s", + sync.Handler.Name(), err, params, delIDs, kt.Rid) + return nil, err + } + } + + // 更新源数据更新,但目标源没更新的数据 + if len(idUpdateDataMap) > 0 { + if err = sync.Handler.UpdateTargetData(kt, params, idUpdateDataMap); err != nil { + logs.Errorf("[%s] update target data failed, err: %v, params: %+v, updateMap: %+v, rid: %s", + sync.Handler.Name(), err, params, idUpdateDataMap, kt.Rid) + return nil, err + } + } + + var createIDs []string + // 添加数据源多出的数据 + if len(createData) > 0 { + createIDs, err = sync.Handler.CreateTargetData(kt, params, createData) + if err != nil { + logs.Errorf("[%s] create target data failed, err: %v, params: %+v, createData: %+v, rid: %s", + sync.Handler.Name(), err, params, createData, kt.Rid) + return nil, err + } + } + + // 聚合处理结果 + result = &Result{ + DeleteIDs: delIDs, + CreateIDs: createIDs, + UpdateIDs: maps.Keys(idUpdateDataMap), + } + + return result, nil +} diff --git a/cmd/hc-service/logics/sync/types.go b/cmd/hc-service/logics/sync/types.go new file mode 100644 index 0000000000..55500d2c1c --- /dev/null +++ b/cmd/hc-service/logics/sync/types.go @@ -0,0 +1,52 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package sync + +// HandlerName define handler name. +type HandlerName string + +// SourceData define source data. +type SourceData interface { + Data + + any +} + +// TargetData define target data. +type TargetData interface { + Data + // GetID 获取目标数据的ID,因为 GetUUID() 获取的唯一标识不是目标数据的ID,更新数据时还需要转换一次。 + GetID() string + + any +} + +// Data 定义数据拥有的接口 +type Data interface { + // GetUUID 数据唯一标识。 + GetUUID() string +} + +// Result define sync result. +type Result struct { + DeleteIDs []string `json:"delete_ids"` + CreateIDs []string `json:"create_ids"` + UpdateIDs []string `json:"update_ids"` +} diff --git a/cmd/hc-service/service/account/account_type.go b/cmd/hc-service/service/account/account_type.go new file mode 100644 index 0000000000..06ac559e89 --- /dev/null +++ b/cmd/hc-service/service/account/account_type.go @@ -0,0 +1,41 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package account + +import ( + "hcm/pkg/criteria/errf" + "hcm/pkg/rest" +) + +func (svc *service) GetTCloudNetworkAccountType(cts *rest.Contexts) (any, error) { + + accountID := cts.PathParameter("account_id").String() + if len(accountID) == 0 { + return nil, errf.New(errf.InvalidParameter, "accountID is required") + } + + client, err := svc.ad.TCloud(cts.Kit, accountID) + if err != nil { + return nil, err + } + + return client.DescribeNetworkAccountType(cts.Kit) +} diff --git a/cmd/hc-service/service/account/service.go b/cmd/hc-service/service/account/service.go index b0dc0dc27d..e6a3437d0c 100644 --- a/cmd/hc-service/service/account/service.go +++ b/cmd/hc-service/service/account/service.go @@ -73,6 +73,10 @@ func InitAccountService(cap *capability.Capability) { h.Add("ListTCloudAuthPolicies", http.MethodPost, "/vendors/tcloud/accounts/auth_policies/list", svc.ListTCloudAuthPolicies) + // 获取腾讯云账号用户网络类型 + h.Add("GetTCloudNetworkAccountType", http.MethodGet, "/vendors/tcloud/accounts/{account_id}/network_type", + svc.GetTCloudNetworkAccountType) + initAccountServiceHooks(svc, h) h.Load(cap.WebService) diff --git a/cmd/hc-service/service/cert/cert.go b/cmd/hc-service/service/cert/cert.go new file mode 100644 index 0000000000..1b876d7b7b --- /dev/null +++ b/cmd/hc-service/service/cert/cert.go @@ -0,0 +1,44 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + "hcm/cmd/hc-service/logics/cloud-adaptor" + "hcm/cmd/hc-service/service/capability" + "hcm/pkg/client" + dataservice "hcm/pkg/client/data-service" +) + +// InitCertService initial cert service. +func InitCertService(cap *capability.Capability) { + svc := &certSvc{ + ad: cap.CloudAdaptor, + dataCli: cap.ClientSet.DataService(), + } + + svc.initTCloudCertService(cap) +} + +type certSvc struct { + ad *cloudadaptor.CloudAdaptorClient + dataCli *dataservice.Client + client *client.ClientSet +} diff --git a/cmd/hc-service/service/cert/tcloud.go b/cmd/hc-service/service/cert/tcloud.go new file mode 100644 index 0000000000..a6a1a4d7b8 --- /dev/null +++ b/cmd/hc-service/service/cert/tcloud.go @@ -0,0 +1,212 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package cert ... +package cert + +import ( + "net/http" + + synctcloud "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/capability" + typecert "hcm/pkg/adaptor/types/cert" + adcore "hcm/pkg/adaptor/types/core" + "hcm/pkg/api/core" + "hcm/pkg/api/core/cloud/cert" + dataproto "hcm/pkg/api/data-service/cloud" + protocert "hcm/pkg/api/hc-service/cert" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/runtime/filter" +) + +func (svc *certSvc) initTCloudCertService(cap *capability.Capability) { + h := rest.NewHandler() + + h.Add("CreateTCloudCert", http.MethodPost, "/vendors/tcloud/certs/create", svc.CreateTCloudCert) + h.Add("DeleteTCloudCert", http.MethodDelete, "/vendors/tcloud/certs", svc.DeleteTCloudCert) + h.Add("ListTCloudCert", http.MethodPost, "/vendors/tcloud/certs/list", svc.ListTCloudCert) + + h.Load(cap.WebService) +} + +// CreateTCloudCert ... +func (svc *certSvc) CreateTCloudCert(cts *rest.Contexts) (interface{}, error) { + req := new(protocert.TCloudCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloud, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + createOpt := &typecert.TCloudCreateOption{ + Name: req.Name, + CertType: string(req.CertType), + PublicKey: req.PublicKey, + PrivateKey: req.PrivateKey, + Repeatable: true, + } + result, err := tcloud.CreateCert(cts.Kit, createOpt) + if err != nil { + logs.Errorf("request adaptor tcloud upload cert error, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + respData := &cert.CertCreateResult{} + if len(result.SuccessCloudIDs) == 0 { + logs.Errorf("request adaptor tcloud upload cert failed, req: %+v, rid: %s", req, cts.Kit.Rid) + return nil, errf.Newf(errf.Aborted, "upload certificate failed") + } + + cloudIDs := result.SuccessCloudIDs + syncClient := synctcloud.NewClient(svc.dataCli, tcloud) + + params := &synctcloud.SyncBaseParams{ + AccountID: req.AccountID, + Region: "region", + CloudIDs: cloudIDs, + } + _, err = syncClient.Cert(cts.Kit, params, &synctcloud.SyncCertOption{BkBizID: req.BkBizID}) + if err != nil { + logs.Errorf("sync tcloud cert failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, err + } + + // 查询证书云ID对应的DB记录 + resp, err := svc.dataCli.Global.ListCert( + cts.Kit, + &core.ListReq{Filter: &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{ + &filter.AtomRule{ + Field: "cloud_id", + Op: filter.In.Factory(), + Value: cloudIDs, + }, &filter.AtomRule{ + Field: "vendor", + Op: filter.Equal.Factory(), + Value: req.Vendor, + }, + }, + }, Page: &core.BasePage{Limit: uint(len(cloudIDs))}, Fields: []string{"id"}}, + ) + if err != nil { + logs.Errorf("request dataservice cert list failed, cloudIDs: %v, err: %v, rid: %s", cloudIDs, err, cts.Kit.Rid) + return nil, err + } + + if len(resp.Details) == 0 { + return respData, nil + } + + return &cert.CertCreateResult{ID: resp.Details[0].ID}, nil +} + +// DeleteTCloudCert ... +func (svc *certSvc) DeleteTCloudCert(cts *rest.Contexts) (interface{}, error) { + req := new(protocert.TCloudDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + listReq := &core.ListReq{ + Fields: []string{"cloud_id"}, + Filter: tools.EqualExpression("id", req.ID), + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dataCli.Global.ListCert(cts.Kit, listReq) + if err != nil { + logs.Errorf("request dataservice list tcloud cert failed, id: %s, err: %v, rid: %s", req.ID, err, cts.Kit.Rid) + return nil, err + } + + if len(listResp.Details) == 0 { + logs.Errorf("request dataservice list tcloud cert empty, id: %s, rid: %s", err, req.ID, cts.Kit.Rid) + return nil, errf.NewFromErr(errf.Aborted, err) + } + + client, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + logs.Errorf("get adaptor to tcloud client failed, accID: %s, err: %v, rid: %s", req.AccountID, err, cts.Kit.Rid) + return nil, err + } + + opt := &typecert.TCloudDeleteOption{ + CloudID: listResp.Details[0].CloudID, + } + if err = client.DeleteCert(cts.Kit, opt); err != nil { + logs.Errorf("request adaptor to delete tcloud cert failed, err: %v, opt: %+v, rid: %s", err, opt, cts.Kit.Rid) + return nil, err + } + + delReq := &dataproto.CertBatchDeleteReq{ + Filter: tools.EqualExpression("id", req.ID), + } + if err = svc.dataCli.Global.BatchDeleteCert(cts.Kit.Ctx, cts.Kit.Header(), delReq); err != nil { + logs.Errorf("request dataservice delete tcloud cert failed, err: %v, id: %s, rid: %s", err, req.ID, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// ListTCloudCert list tcloud cert +func (svc *certSvc) ListTCloudCert(cts *rest.Contexts) (interface{}, error) { + req := new(protocert.TCloudListOption) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloud, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + opt := &typecert.TCloudListOption{ + Page: &adcore.TCloudPage{ + Offset: 0, + Limit: adcore.TCloudQueryLimit, + }, + } + result, err := tcloud.ListCert(cts.Kit, opt) + if err != nil { + logs.Errorf("[%s] list cert failed, req: %+v, err: %v, rid: %s", enumor.TCloud, req, err, cts.Kit.Rid) + return nil, err + } + + return result, nil +} diff --git a/cmd/hc-service/service/load-balancer/load_balancer.go b/cmd/hc-service/service/load-balancer/load_balancer.go new file mode 100644 index 0000000000..a0c86888f8 --- /dev/null +++ b/cmd/hc-service/service/load-balancer/load_balancer.go @@ -0,0 +1,42 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package loadbalancer ... +package loadbalancer + +import ( + "hcm/cmd/hc-service/logics/cloud-adaptor" + "hcm/cmd/hc-service/service/capability" + dataservice "hcm/pkg/client/data-service" +) + +// InitLoadBalancerService initial the clb service. +func InitLoadBalancerService(cap *capability.Capability) { + svc := &clbSvc{ + ad: cap.CloudAdaptor, + dataCli: cap.ClientSet.DataService(), + } + + svc.initTCloudClbService(cap) +} + +type clbSvc struct { + ad *cloudadaptor.CloudAdaptorClient + dataCli *dataservice.Client +} diff --git a/cmd/hc-service/service/load-balancer/tcloud.go b/cmd/hc-service/service/load-balancer/tcloud.go new file mode 100644 index 0000000000..677a1eb54c --- /dev/null +++ b/cmd/hc-service/service/load-balancer/tcloud.go @@ -0,0 +1,1084 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + "net/http" + + synctcloud "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/capability" + "hcm/pkg/adaptor/tcloud" + adcore "hcm/pkg/adaptor/types/core" + typelb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" +) + +func (svc *clbSvc) initTCloudClbService(cap *capability.Capability) { + h := rest.NewHandler() + + h.Add("BatchCreateTCloudClb", http.MethodPost, + "/vendors/tcloud/load_balancers/batch/create", svc.BatchCreateTCloudClb) + h.Add("InquiryPriceTCloudLB", http.MethodPost, + "/vendors/tcloud/load_balancers/prices/inquiry", svc.InquiryPriceTCloudLB) + h.Add("ListTCloudClb", http.MethodPost, "/vendors/tcloud/load_balancers/list", svc.ListTCloudClb) + h.Add("TCloudDescribeResources", http.MethodPost, + "/vendors/tcloud/load_balancers/resources/describe", svc.TCloudDescribeResources) + h.Add("TCloudUpdateCLB", http.MethodPatch, "/vendors/tcloud/load_balancers/{id}", svc.TCloudUpdateCLB) + h.Add("BatchDeleteTCloudLoadBalancer", http.MethodDelete, + "/vendors/tcloud/load_balancers/batch", svc.BatchDeleteTCloudLoadBalancer) + h.Add("ListQuotaTCloudLB", http.MethodPost, "/vendors/tcloud/load_balancers/quota", svc.ListTCloudLBQuota) + + h.Add("TCloudCreateUrlRule", http.MethodPost, + "/vendors/tcloud/listeners/{lbl_id}/rules/batch/create", svc.TCloudCreateUrlRule) + h.Add("TCloudUpdateUrlRule", http.MethodPatch, + "/vendors/tcloud/listeners/{lbl_id}/rules/{rule_id}", svc.TCloudUpdateUrlRule) + h.Add("TCloudBatchDeleteUrlRule", http.MethodDelete, + "/vendors/tcloud/listeners/{lbl_id}/rules/batch", svc.TCloudBatchDeleteUrlRule) + h.Add("TCloudBatchDeleteUrlRuleByDomain", http.MethodDelete, + "/vendors/tcloud/listeners/{lbl_id}/rules/by/domain/batch", svc.TCloudBatchDeleteUrlRuleByDomain) + + // 监听器 + h.Add("CreateTCloudListener", http.MethodPost, "/vendors/tcloud/listeners/create", svc.CreateTCloudListener) + h.Add("UpdateTCloudListener", http.MethodPatch, "/vendors/tcloud/listeners/{id}", svc.UpdateTCloudListener) + h.Add("UpdateTCloudListenerHealthCheck", http.MethodPatch, + "/vendors/tcloud/listeners/{lbl_id}/health_check", svc.UpdateTCloudListenerHealthCheck) + h.Add("DeleteTCloudListener", http.MethodDelete, "/vendors/tcloud/listeners/batch", svc.DeleteTCloudListener) + + // 域名、规则 + h.Add("UpdateTCloudDomainAttr", http.MethodPatch, "/vendors/tcloud/listeners/{lbl_id}/domains", + svc.UpdateTCloudDomainAttr) + + // 目标组 + h.Add("BatchCreateTCloudTargets", http.MethodPost, "/vendors/tcloud/target_groups/{target_group_id}/targets/create", + svc.BatchCreateTCloudTargets) + h.Add("BatchRemoveTCloudTargets", http.MethodDelete, + "/vendors/tcloud/target_groups/{target_group_id}/targets/batch", svc.BatchRemoveTCloudTargets) + h.Add("BatchModifyTCloudTargetsPort", http.MethodPatch, + "/vendors/tcloud/target_groups/{target_group_id}/targets/port", svc.BatchModifyTCloudTargetsPort) + h.Add("BatchModifyTCloudTargetsWeight", http.MethodPatch, + "/vendors/tcloud/target_groups/{target_group_id}/targets/weight", svc.BatchModifyTCloudTargetsWeight) + h.Add("ListTCloudTargetsHealth", http.MethodPost, "/vendors/tcloud/load_balancers/targets/health", + svc.ListTCloudTargetsHealth) + + h.Add("RegisterTargetToListenerRule", http.MethodPost, + "/vendors/tcloud/load_balancers/{lb_id}/targets/create", svc.RegisterTargetToListenerRule) + + h.Add("QueryListenerTargetsByCloudIDs", http.MethodPost, + "/vendors/tcloud/targets/query_by_cloud_ids", svc.QueryListenerTargetsByCloudIDs) + h.Load(cap.WebService) +} + +// BatchCreateTCloudClb ... +func (svc *clbSvc) BatchCreateTCloudClb(cts *rest.Contexts) (interface{}, error) { + req := new(protolb.TCloudBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + createOpt := &typelb.TCloudCreateClbOption{ + Region: req.Region, + LoadBalancerType: req.LoadBalancerType, + LoadBalancerName: req.Name, + VpcID: req.CloudVpcID, + SubnetID: req.CloudSubnetID, + Vip: req.Vip, + VipIsp: req.VipIsp, + + InternetChargeType: req.InternetChargeType, + InternetMaxBandwidthOut: req.InternetMaxBandwidthOut, + + BandwidthPackageID: req.BandwidthPackageID, + SlaType: req.SlaType, + Number: req.RequireCount, + ClientToken: cvt.StrNilPtr(cts.Kit.Rid), + } + if cvt.PtrToVal(req.CloudEipID) != "" { + createOpt.EipAddressID = req.CloudEipID + } + if req.AddressIPVersion == "" { + req.AddressIPVersion = typelb.IPV4IPVersion + } + // 负载均衡实例的网络类型-公网属性 + if req.LoadBalancerType == typelb.OpenLoadBalancerType { + // IP版本-仅适用于公网负载均衡 + createOpt.AddressIPVersion = req.AddressIPVersion + // 静态单线IP 线路类型-仅适用于公网负载均衡, 如果不指定本参数,则默认使用BGP + createOpt.VipIsp = req.VipIsp + + // 设置跨可用区容灾时的可用区ID-仅适用于公网负载均衡 + if len(req.BackupZones) > 0 && len(req.Zones) > 0 { + // 主备可用区,传递zones(单元素数组),以及backup_zones + createOpt.MasterZoneID = cvt.ValToPtr(req.Zones[0]) + createOpt.SlaveZoneID = cvt.ValToPtr(req.BackupZones[0]) + } else if len(req.Zones) > 0 { + // 单可用区 + createOpt.ZoneID = cvt.ValToPtr(req.Zones[0]) + } + } + + result, err := tcloudAdpt.CreateLoadBalancer(cts.Kit, createOpt) + if err != nil { + logs.Errorf("create tcloud clb failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + respData := &protolb.BatchCreateResult{ + UnknownCloudIDs: result.UnknownCloudIDs, + SuccessCloudIDs: result.SuccessCloudIDs, + FailedCloudIDs: result.FailedCloudIDs, + FailedMessage: result.FailedMessage, + } + + if len(result.SuccessCloudIDs) == 0 { + return respData, nil + } + + // 数据库创建失败也继续同步 + _ = svc.createTCloudDBLoadBalancer(cts, req, result.SuccessCloudIDs) + + if err := svc.lbSync(cts.Kit, tcloudAdpt, req.AccountID, req.Region, result.SuccessCloudIDs); err != nil { + return nil, err + } + return respData, nil +} + +func (svc *clbSvc) createTCloudDBLoadBalancer(cts *rest.Contexts, req *protolb.TCloudBatchCreateReq, + cloudIDs []string) (err error) { + + dataReq := &dataproto.TCloudCLBCreateReq{Lbs: make([]dataproto.TCloudCLBCreate, len(cloudIDs))} + for i, cloudID := range cloudIDs { + dataReq.Lbs[i].CloudID = cloudID + dataReq.Lbs[i].Vendor = enumor.TCloud + dataReq.Lbs[i].BkBizID = req.BkBizID + + dataReq.Lbs[i].Name = fmt.Sprintf("%s-%d", cvt.PtrToVal(req.Name), i) + dataReq.Lbs[i].AccountID = req.AccountID + dataReq.Lbs[i].Region = req.Region + dataReq.Lbs[i].LoadBalancerType = string(req.LoadBalancerType) + dataReq.Lbs[i].IPVersion = req.AddressIPVersion.Convert() + dataReq.Lbs[i].Zones = req.Zones + dataReq.Lbs[i].BackupZones = req.BackupZones + + } + // 创建本地数据,保存业务信息 + _, err = svc.dataCli.TCloud.LoadBalancer.BatchCreateTCloudClb(cts.Kit, dataReq) + if err != nil { + logs.Errorf("fail to create db load balancer after cloud create, err: %v, rid: %s", err, cts.Kit.Rid) + // 失败也继续尝试同步 + } + return err +} + +// ListTCloudClb list tcloud clb +func (svc *clbSvc) ListTCloudClb(cts *rest.Contexts) (interface{}, error) { + req := new(protolb.TCloudListOption) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + opt := &typelb.TCloudListOption{ + Region: req.Region, + CloudIDs: req.CloudIDs, + Page: &adcore.TCloudPage{ + Offset: 0, + Limit: adcore.TCloudQueryLimit, + }, + } + result, err := tcloudAdpt.ListLoadBalancer(cts.Kit, opt) + if err != nil { + logs.Errorf("[%s] list tcloud clb failed, req: %+v, err: %v, rid: %s", + enumor.TCloud, req, err, cts.Kit.Rid) + return nil, err + } + + return result, nil +} + +// TCloudDescribeResources 查询clb地域下可用资源 +func (svc *clbSvc) TCloudDescribeResources(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudDescribeResourcesOption) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + client, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + return client.DescribeResources(cts.Kit, req.TCloudDescribeResourcesOption) +} + +// TCloudUpdateCLB 更新clb属性 +func (svc *clbSvc) TCloudUpdateCLB(cts *rest.Contexts) (any, error) { + lbID := cts.PathParameter("id").String() + if len(lbID) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + req := new(protolb.TCloudLBUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 获取lb基本信息 + lb, err := svc.dataCli.TCloud.LoadBalancer.Get(cts.Kit, lbID) + if err != nil { + logs.Errorf("fail to get tcloud clb(%s), err: %v, rid: %s", lbID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云上更新接口 + client, err := svc.ad.TCloud(cts.Kit, lb.AccountID) + if err != nil { + return nil, err + } + + adtOpt := &typelb.TCloudUpdateOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + LoadBalancerName: req.Name, + InternetChargeType: req.InternetChargeType, + InternetMaxBandwidthOut: req.InternetMaxBandwidthOut, + BandwidthpkgSubType: req.BandwidthpkgSubType, + LoadBalancerPassToTarget: req.LoadBalancerPassToTarget, + SnatPro: req.SnatPro, + DeleteProtect: req.DeleteProtect, + ModifyClassicDomain: req.ModifyClassicDomain, + } + + _, err = client.UpdateLoadBalancer(cts.Kit, adtOpt) + if err != nil { + logs.Errorf("fail to call tcloud update load balancer(id:%s),err: %v, rid: %s", lbID, err, cts.Kit.Rid) + return nil, err + } + + // 同步云上变更信息 + return nil, svc.lbSync(cts.Kit, client, lb.AccountID, lb.Region, []string{lb.CloudID}) + +} + +// 同步云上资源 +func (svc *clbSvc) lbSync(kt *kit.Kit, tcloud tcloud.TCloud, accountID string, region string, lbIDs []string) error { + + syncClient := synctcloud.NewClient(svc.dataCli, tcloud) + params := &synctcloud.SyncBaseParams{ + AccountID: accountID, + Region: region, + CloudIDs: lbIDs, + } + _, err := syncClient.LoadBalancer(kt, params, &synctcloud.SyncLBOption{}) + if err != nil { + logs.Errorf("sync load balancer failed, err: %v, rid: %s", err, kt.Rid) + return err + } + return nil +} + +func (svc *clbSvc) getListenerWithLb(kt *kit.Kit, lblID string) (*corelb.BaseLoadBalancer, + *corelb.BaseListener, error) { + + // 查询监听器数据 + lblResp, err := svc.dataCli.Global.LoadBalancer.ListListener(kt, &core.ListReq{ + Filter: tools.EqualExpression("id", lblID), + Page: core.NewDefaultBasePage(), + Fields: nil, + }) + if err != nil { + logs.Errorf("fail to list tcloud listener, err: %v, id: %s, rid: %s", err, lblID, kt.Rid) + return nil, nil, err + } + if len(lblResp.Details) < 1 { + return nil, nil, errf.Newf(errf.InvalidParameter, "lbl not found") + } + listener := lblResp.Details[0] + + // 查询负载均衡 + lbResp, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(kt, &core.ListReq{ + Filter: tools.EqualExpression("id", listener.LbID), + Page: core.NewDefaultBasePage(), + Fields: nil, + }) + if err != nil { + logs.Errorf("fail to tcloud list load balancer, err: %v, id: %s, rid: %s", err, listener.LbID, kt.Rid) + return nil, nil, err + } + if len(lbResp.Details) < 1 { + return nil, nil, errf.Newf(errf.RecordNotFound, "lb not found") + } + lb := lbResp.Details[0] + return &lb, &listener, nil +} + +// CreateTCloudListener 创建监听器 +func (svc *clbSvc) CreateTCloudListener(cts *rest.Contexts) (interface{}, error) { + req := new(protolb.ListenerWithRuleCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 根据lbID,查询负载均衡信息 + lbReq := &core.ListReq{ + Filter: tools.EqualExpression("id", req.LbID), + Page: core.NewDefaultBasePage(), + } + lbList, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(cts.Kit, lbReq) + if err != nil { + logs.Errorf("list load balancer by id failed, id: %s, err: %v, rid: %s", req.LbID, err, cts.Kit.Rid) + return nil, err + } + if len(lbList.Details) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "load balancer: %s not found", req.LbID) + } + lbInfo := lbList.Details[0] + + // 查询目标组是否存在 + targetGroupList, err := svc.getTargetGroupByID(cts.Kit, req.TargetGroupID) + if err != nil { + logs.Errorf("list target group by id failed, tgID: %s, err: %v, rid: %s", req.TargetGroupID, err, cts.Kit.Rid) + return nil, err + } + if len(targetGroupList) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", req.TargetGroupID) + } + targetGroupInfo := targetGroupList[0] + + // 检查目标组是否已经绑定了其他监听器 + relOpt := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", req.TargetGroupID), + Page: core.NewDefaultBasePage(), + } + relList, err := svc.dataCli.Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, relOpt) + if err != nil { + logs.Errorf("list target listener rule rel failed, tgID: %s, err: %v, rid: %s", + req.TargetGroupID, err, cts.Kit.Rid) + return nil, err + } + if len(relList.Details) > 0 { + return nil, errf.Newf(errf.InvalidParameter, "target_group_id: %s has bound listener", req.TargetGroupID) + } + + // 创建云端监听器、规则 + cloudLblID, cloudRuleID, err := svc.createListenerWithRule(cts.Kit, req, lbInfo) + if err != nil { + return nil, err + } + + // 插入新的监听器、规则信息到DB + _, err = svc.insertListenerWithRule(cts.Kit, req, lbInfo, cloudLblID, cloudRuleID, targetGroupInfo) + if err != nil { + return nil, err + } + + return &protolb.ListenerWithRuleCreateResult{CloudLblID: cloudLblID, CloudRuleID: cloudRuleID}, nil +} + +func (svc *clbSvc) createListenerWithRule(kt *kit.Kit, req *protolb.ListenerWithRuleCreateReq, + lbInfo corelb.BaseLoadBalancer) (string, string, error) { + + tcloudAdpt, err := svc.ad.TCloud(kt, lbInfo.AccountID) + if err != nil { + return "", "", err + } + + lblOpt := &typelb.TCloudCreateListenerOption{ + Region: lbInfo.Region, + LoadBalancerId: lbInfo.CloudID, + ListenerName: req.Name, + Protocol: req.Protocol, + Port: req.Port, + SessionExpireTime: req.SessionExpire, + Scheduler: req.Scheduler, + SniSwitch: req.SniSwitch, + SessionType: cvt.ValToPtr(req.SessionType), + Certificate: req.Certificate, + } + // 7层监听器,不管SNI开启还是关闭,都需要传入证书参数 + // 7层监听器并且SNI开启时,创建监听器接口,不需要证书 + if req.Protocol.IsLayer7Protocol() { + if req.Certificate == nil { + return "", "", errf.New(errf.InvalidParameter, "certificate is required when layer 7 listener") + } + if cvt.PtrToVal(req.Certificate.CaCloudID) == "" && len(req.Certificate.CertCloudIDs) == 0 { + return "", "", errf.New(errf.InvalidParameter, + "certificate.ca_cloud_id and certificate.cert_cloud_ids is required") + } + if req.SniSwitch == enumor.SniTypeOpen { + lblOpt.Certificate = nil + } + } + result, err := tcloudAdpt.CreateListener(kt, lblOpt) + if err != nil { + logs.Errorf("create tcloud listener api failed, err: %v, lblOpt: %+v, cert: %+v, rid: %s", + err, lblOpt, cvt.PtrToVal(lblOpt.Certificate), kt.Rid) + return "", "", err + } + cloudLblID := result.SuccessCloudIDs[0] + + // 只有7层规则才走云端创建规则接口 + var cloudRuleID string + if req.Protocol.IsLayer7Protocol() { + ruleOpt := &typelb.TCloudCreateRuleOption{ + Region: lbInfo.Region, + LoadBalancerId: lbInfo.CloudID, + ListenerId: cloudLblID, + Rules: []*typelb.RuleInfo{}, + } + oneRule := &typelb.RuleInfo{ + Url: cvt.ValToPtr(req.Url), + SessionExpireTime: cvt.ValToPtr(req.SessionExpire), + DefaultServer: cvt.ValToPtr(true), + } + if len(req.Domain) > 0 { + oneRule.Domain = cvt.ValToPtr(req.Domain) + } + if len(req.Scheduler) > 0 { + oneRule.Scheduler = cvt.ValToPtr(req.Scheduler) + } + if req.Certificate != nil { + oneRule.Certificate = req.Certificate + } + ruleOpt.Rules = append(ruleOpt.Rules, oneRule) + ruleResult, err := tcloudAdpt.CreateRule(kt, ruleOpt) + if err != nil { + logs.Errorf("create tcloud listener rule api failed, err: %v, ruleOpt: %+v, cert: %+v, rid: %s", + err, ruleOpt, cvt.PtrToVal(req.Certificate), kt.Rid) + return "", "", err + } + cloudRuleID = ruleResult.SuccessCloudIDs[0] + } + + return cloudLblID, cloudRuleID, nil +} + +func (svc *clbSvc) insertListenerWithRule(kt *kit.Kit, req *protolb.ListenerWithRuleCreateReq, + lbInfo corelb.BaseLoadBalancer, cloudLblID string, cloudRuleID string, targetGroupInfo corelb.BaseTargetGroup) ( + *core.BatchCreateResult, error) { + + var domain, url string + var ruleType = enumor.Layer4RuleType + if req.Protocol.IsLayer7Protocol() { + ruleType = enumor.Layer7RuleType + // 只有7层监听器才有域名、URL + domain = req.Domain + url = req.Url + } else { + // 4层监听器对应的云端规则ID就是云监听器ID + cloudRuleID = cloudLblID + } + + lblRuleReq := &dataproto.ListenerWithRuleBatchCreateReq{ + ListenerWithRules: []dataproto.ListenerWithRuleCreateReq{ + { + CloudID: cloudLblID, + Name: req.Name, + Vendor: enumor.TCloud, + AccountID: lbInfo.AccountID, + BkBizID: req.BkBizID, + LbID: req.LbID, + CloudLbID: lbInfo.CloudID, + Protocol: req.Protocol, + Port: req.Port, + CloudRuleID: cloudRuleID, + Scheduler: req.Scheduler, + RuleType: ruleType, + SessionType: req.SessionType, + SessionExpire: req.SessionExpire, + TargetGroupID: req.TargetGroupID, + CloudTargetGroupID: targetGroupInfo.CloudID, + Domain: domain, + Url: url, + SniSwitch: req.SniSwitch, + Certificate: req.Certificate, + }, + }, + } + ids, err := svc.dataCli.TCloud.LoadBalancer.BatchCreateTCloudListenerWithRule(kt, lblRuleReq) + if err != nil { + logs.Errorf("create tcloud listener with rule failed, req: %+v, lblRuleReq: %+v, err: %v, rid: %s", + req, lblRuleReq, err, kt.Rid) + return nil, err + } + + return ids, nil +} + +func (svc *clbSvc) getTargetGroupByID(kt *kit.Kit, targetGroupID string) ([]corelb.BaseTargetGroup, error) { + tgReq := &core.ListReq{ + Filter: tools.EqualExpression("id", targetGroupID), + Page: core.NewDefaultBasePage(), + } + targetGroupInfo, err := svc.dataCli.Global.LoadBalancer.ListTargetGroup(kt, tgReq) + if err != nil { + logs.Errorf("list target group db failed, tgID: %s, err: %v, rid: %s", targetGroupID, err, kt.Rid) + return nil, err + } + + return targetGroupInfo.Details, nil +} + +// UpdateTCloudListener 更新监听器信息 +func (svc *clbSvc) UpdateTCloudListener(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + req := new(protolb.ListenerWithRuleUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 获取监听器基本信息 + lblInfo, err := svc.dataCli.TCloud.LoadBalancer.GetListener(cts.Kit, lblID) + if err != nil { + logs.Errorf("fail to get tcloud listener(%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + + // 只有HTTPS支持开启SNI开关 + if lblInfo.Protocol != enumor.HttpsProtocol && req.SniSwitch == enumor.SniTypeOpen { + return nil, errf.Newf(errf.InvalidParameter, "only https listener support sni") + } + + lbInfo, err := svc.dataCli.TCloud.LoadBalancer.Get(cts.Kit, lblInfo.LbID) + if err != nil { + logs.Errorf("fail to get tcloud load balancer(%s), err: %v, rid: %s", lblInfo.LbID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云上更新接口 + client, err := svc.ad.TCloud(cts.Kit, lblInfo.AccountID) + if err != nil { + return nil, err + } + + // 更新云端监听器信息 + lblOpt := &typelb.TCloudUpdateListenerOption{ + Region: lbInfo.Region, + LoadBalancerId: lblInfo.CloudLbID, + ListenerId: lblInfo.CloudID, + ListenerName: req.Name, + SniSwitch: req.SniSwitch, + } + if req.Extension != nil { + lblOpt.Certificate = req.Extension.Certificate + } + err = client.UpdateListener(cts.Kit, lblOpt) + if err != nil { + logs.Errorf("fail to call tcloud update listener(id:%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + if err := svc.lblSync(cts.Kit, client, &lbInfo.BaseLoadBalancer); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for update listener(%s), rid: %s", lblInfo.ID, cts.Kit.Rid) + return nil, err + } + return nil, nil +} + +// UpdateTCloudListenerHealthCheck 更新监听器信健康检查信息 +func (svc *clbSvc) UpdateTCloudListenerHealthCheck(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "id is required") + } + + req := new(protolb.HealthCheckUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 获取监听器基本信息 + lblInfo, err := svc.dataCli.TCloud.LoadBalancer.GetListener(cts.Kit, lblID) + if err != nil { + logs.Errorf("fail to get tcloud listener(%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + + // 改接口只支持修改四层监听器健康检查 + if lblInfo.Protocol.IsLayer7Protocol() { + return nil, errf.Newf(errf.InvalidParameter, "only layer 4 listener support update health check") + } + + lbInfo, err := svc.dataCli.TCloud.LoadBalancer.Get(cts.Kit, lblInfo.LbID) + if err != nil { + logs.Errorf("fail to get tcloud load balancer(%s), err: %v, rid: %s", lblInfo.LbID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云上更新接口 + client, err := svc.ad.TCloud(cts.Kit, lblInfo.AccountID) + if err != nil { + return nil, err + } + + // 更新云端监听器信息 + lblOpt := &typelb.TCloudUpdateListenerOption{ + Region: lbInfo.Region, + LoadBalancerId: lblInfo.CloudLbID, + ListenerId: lblInfo.CloudID, + HealthCheck: req.HealthCheck, + } + err = client.UpdateListener(cts.Kit, lblOpt) + if err != nil { + logs.Errorf("fail to call tcloud update listener(id:%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + if err := svc.lblSync(cts.Kit, client, &lbInfo.BaseLoadBalancer); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for update listener(%s), rid: %s", lblInfo.ID, cts.Kit.Rid) + return nil, err + } + return nil, nil +} + +// DeleteTCloudListener 删除监听器信息 +func (svc *clbSvc) DeleteTCloudListener(cts *rest.Contexts) (any, error) { + req := new(core.BatchDeleteReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + if len(req.IDs) > constant.BatchListenerMaxLimit { + return nil, fmt.Errorf("batch delete listener count should <= %d", constant.BatchListenerMaxLimit) + } + + lblIDs, lblCloudIDs, lbList, ruleMap, err := svc.getListenerWithRule(cts.Kit, req) + if err != nil { + return nil, err + } + + lbInfo := lbList.Details[0] + client, err := svc.ad.TCloud(cts.Kit, lbInfo.AccountID) + if err != nil { + return nil, err + } + + // 批量删除云端监听器规则 + for tmpCloudLblID, tmpCloudRuleIDs := range ruleMap { + ruleOpt := &typelb.TCloudDeleteRuleOption{ + Region: lbInfo.Region, + LoadBalancerId: lbInfo.CloudID, + ListenerId: tmpCloudLblID, + CloudIDs: tmpCloudRuleIDs, + } + err = client.DeleteRule(cts.Kit, ruleOpt) + if err != nil { + logs.Errorf("fail to call tcloud delete listener rule, lbID: %s, lblID: %s, ruleIDs: %+v, err: %v, rid: %s", + lbInfo.CloudID, tmpCloudLblID, tmpCloudRuleIDs, err, cts.Kit.Rid) + return nil, err + } + } + + // 批量删除云端监听器 + lblOpt := &typelb.TCloudDeleteListenerOption{ + Region: lbInfo.Region, + LoadBalancerId: lbInfo.CloudID, + CloudIDs: lblCloudIDs, + } + err = client.DeleteListener(cts.Kit, lblOpt) + if err != nil { + logs.Errorf("fail to call tcloud delete listener, lblCloudIDs: %v, err: %v, rid: %s", + lblCloudIDs, err, cts.Kit.Rid) + return nil, err + } + + // 更新DB监听器信息 + delLblReq := &dataproto.LoadBalancerBatchDeleteReq{ + Filter: tools.ContainersExpression("id", lblIDs), + } + err = svc.dataCli.Global.LoadBalancer.DeleteListener(cts.Kit, delLblReq) + if err != nil { + logs.Errorf("delete tcloud listener db failed, lblIDs: %+v, err: %v, rid: %s", lblIDs, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +func (svc *clbSvc) getListenerWithRule(kt *kit.Kit, req *core.BatchDeleteReq) ([]string, []string, + *dataproto.LbListResult, map[string][]string, error) { + + // 获取监听器列表 + lblReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", req.IDs), + Page: core.NewDefaultBasePage(), + } + lblList, err := svc.dataCli.Global.LoadBalancer.ListListener(kt, lblReq) + if err != nil { + logs.Errorf("fail to list tcloud listener, req: %+v, err: %v, rid: %s", req, err, kt.Rid) + return nil, nil, nil, nil, err + } + if len(lblList.Details) == 0 { + return nil, nil, nil, nil, errf.Newf(errf.RecordNotFound, "listeners: %v not found", req.IDs) + } + + lblIDs := make([]string, 0) + lbIDs := make([]string, 0) + lblCloudIDs := make([]string, 0) + for _, item := range lblList.Details { + lblIDs = append(lblIDs, item.ID) + lbIDs = append(lbIDs, item.LbID) + lblCloudIDs = append(lblCloudIDs, item.CloudID) + } + + // 根据lbID,查询负载均衡信息 + lbReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", lbIDs), + Page: core.NewDefaultBasePage(), + } + lbList, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(kt, lbReq) + if err != nil { + logs.Errorf("list load balancer by id failed, lbIDs: %v, err: %v, rid: %s", lbIDs, err, kt.Rid) + return nil, nil, nil, nil, err + } + if len(lbList.Details) != 1 { + return nil, nil, nil, nil, errf.Newf(errf.RecordNotFound, "load balancer: [%v] not found or "+ + "need belong to the same load balancer", lbIDs) + } + + // 查询监听器规则列表 + ruleReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleIn("lbl_id", lblIDs), + tools.RuleEqual("rule_type", enumor.Layer7RuleType), + ), + Page: core.NewDefaultBasePage(), + } + lblRuleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(kt, ruleReq) + if err != nil { + logs.Errorf("fail to list tcloud listeners url rule, lblIDs: %v, err: %v, rid: %s", lblIDs, err, kt.Rid) + return nil, nil, nil, nil, err + } + + ruleMap := make(map[string][]string) + for _, ruleItem := range lblRuleList.Details { + if _, ok := ruleMap[ruleItem.CloudLBLID]; !ok { + ruleMap[ruleItem.CloudLBLID] = make([]string, 0) + } + ruleMap[ruleItem.CloudLBLID] = append(ruleMap[ruleItem.CloudLBLID], ruleItem.CloudID) + } + + return lblIDs, lblCloudIDs, lbList, ruleMap, nil +} + +// UpdateTCloudDomainAttr 更新域名属性 +func (svc *clbSvc) UpdateTCloudDomainAttr(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "lbl_id is required") + } + + req := new(protolb.DomainAttrUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 获取监听器基本信息 + lblInfo, err := svc.dataCli.TCloud.LoadBalancer.GetListener(cts.Kit, lblID) + if err != nil || lblInfo == nil { + logs.Errorf("fail to get tcloud listener(%s), err: %v, rid: %s", lblID, err, cts.Kit.Rid) + return nil, err + } + // 只有7层监听器才能更新域名 + if !lblInfo.Protocol.IsLayer7Protocol() { + return nil, errf.Newf(errf.InvalidParameter, "only layer 7 listeners can be updated") + } + // 只有SNI开启的监听器,才能更新域名下的证书信息(非sni更新证书是在监听器上的,单个规则/域名没有单独的证书信息) + if req.Certificate != nil && lblInfo.SniSwitch == enumor.SniTypeClose { + return nil, errf.Newf(errf.InvalidParameter, "the certificate of the domain can not update when SNI closed") + } + + // 调用云上更新接口 + return nil, svc.updateTCloudDomainAttr(cts.Kit, req, lblInfo) + +} + +func (svc *clbSvc) updateTCloudDomainAttr(kt *kit.Kit, req *protolb.DomainAttrUpdateReq, + lblInfo *corelb.Listener[corelb.TCloudListenerExtension]) error { + + // 获取规则列表 + ruleOpt := &core.ListReq{ + Filter: tools.ExpressionAnd(tools.RuleEqual("lbl_id", lblInfo.ID), tools.RuleEqual("domain", req.Domain)), + Page: core.NewDefaultBasePage(), + } + ruleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(kt, ruleOpt) + if err != nil { + logs.Errorf("fail to list tcloud rule, lblID: %s, err: %v, rid: %s", lblInfo.ID, err, kt.Rid) + return err + } + + if len(ruleList.Details) == 0 { + return errf.Newf(errf.RecordNotFound, "domain: %s not found", req.Domain) + } + + // 获取负载均衡信息 + lbInfo, err := svc.dataCli.TCloud.LoadBalancer.Get(kt, lblInfo.LbID) + if err != nil { + logs.Errorf("fail to get tcloud load balancer(%s), err: %v, rid: %s", lblInfo.LbID, err, kt.Rid) + return err + } + if lbInfo == nil { + return errf.Newf(errf.RecordNotFound, "load balancer: %s not found", lblInfo.LbID) + } + + client, err := svc.ad.TCloud(kt, lbInfo.AccountID) + if err != nil { + return err + } + + // 更新云端域名属性信息 + domainOpt := &typelb.TCloudUpdateDomainAttrOption{ + Region: lbInfo.Region, + LoadBalancerId: lbInfo.CloudID, + ListenerId: lblInfo.CloudID, + Domain: req.Domain, + } + if len(req.NewDomain) > 0 { + domainOpt.NewDomain = req.NewDomain + } + if req.Certificate != nil { + domainOpt.Certificate = req.Certificate + } + if req.DefaultServer != nil { + domainOpt.DefaultServer = req.DefaultServer + } + // 只有HTTPS域名才能开启Http2、Quic + if lblInfo.Protocol == enumor.HttpsProtocol { + domainOpt.Http2 = req.Http2 + domainOpt.Quic = req.Quic + } + err = client.UpdateDomainAttr(kt, domainOpt) + if err != nil { + logs.Errorf("fail to call tcloud update domain attr, err: %v, lblID: %s, rid: %s", err, lblInfo.ID, kt.Rid) + return err + } + if err := svc.lblSync(kt, client, &lbInfo.BaseLoadBalancer); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for update domain(%s), lblID: %s, rid: %s", + domainOpt.Domain, lblInfo.ID, kt.Rid) + return err + } + return nil +} + +// BatchDeleteTCloudLoadBalancer ... +func (svc *clbSvc) BatchDeleteTCloudLoadBalancer(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudBatchDeleteLoadbalancerReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + listReq := &core.ListReq{ + Fields: []string{"cloud_id"}, + Filter: tools.ContainersExpression("id", req.IDs), + Page: core.NewDefaultBasePage(), + } + listResp, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(cts.Kit, listReq) + if err != nil { + logs.Errorf("request data service list tcloud loadBalancer failed, err: %v, ids: %v, rid: %s", + err, req.IDs, cts.Kit.Rid) + return nil, err + } + + delCloudIDs := make([]string, 0, len(listResp.Details)) + for _, one := range listResp.Details { + delCloudIDs = append(delCloudIDs, one.CloudID) + } + + client, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + opt := &typelb.TCloudDeleteOption{ + Region: req.Region, + CloudIDs: delCloudIDs, + } + if err = client.DeleteLoadBalancer(cts.Kit, opt); err != nil { + logs.Errorf("request adaptor to delete tcloud loadBalancer failed, err: %v, opt: %v, rid: %s", err, opt, + cts.Kit.Rid) + return nil, err + } + + delReq := &dataproto.LoadBalancerBatchDeleteReq{ + Filter: tools.ContainersExpression("id", req.IDs), + } + if err = svc.dataCli.Global.LoadBalancer.BatchDeleteLoadBalancer(cts.Kit, delReq); err != nil { + logs.Errorf("request data service delete tcloud loadBalancer failed, err: %v, ids: %v, rid: %s", err, req.IDs, + cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// InquiryPriceTCloudLB inquiry price tcloud clb. +func (svc *clbSvc) InquiryPriceTCloudLB(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloud, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + if req.AddressIPVersion == "" { + req.AddressIPVersion = typelb.IPV4IPVersion + } + createOpt := &typelb.TCloudCreateClbOption{ + Region: req.Region, + LoadBalancerType: req.LoadBalancerType, + LoadBalancerName: req.Name, + VpcID: req.CloudVpcID, + SubnetID: req.CloudSubnetID, + Vip: req.Vip, + VipIsp: req.VipIsp, + AddressIPVersion: req.AddressIPVersion, + + InternetChargeType: req.InternetChargeType, + InternetMaxBandwidthOut: req.InternetMaxBandwidthOut, + + BandwidthPackageID: req.BandwidthPackageID, + SlaType: req.SlaType, + Number: req.RequireCount, + ClientToken: cvt.StrNilPtr(cts.Kit.Rid), + } + if cvt.PtrToVal(req.CloudEipID) != "" { + createOpt.EipAddressID = req.CloudEipID + } + // 负载均衡实例的网络类型-公网属性 + if req.LoadBalancerType == typelb.OpenLoadBalancerType { + // 静态单线IP 线路类型-仅适用于公网负载均衡, 如果不指定本参数,则默认使用BGP + createOpt.VipIsp = req.VipIsp + + // 设置跨可用区容灾时的可用区ID-仅适用于公网负载均衡 + if len(req.BackupZones) > 0 && len(req.Zones) > 0 { + // 主备可用区,传递zones(单元素数组),以及backup_zones + createOpt.MasterZoneID = cvt.ValToPtr(req.Zones[0]) + createOpt.SlaveZoneID = cvt.ValToPtr(req.BackupZones[0]) + } else if len(req.Zones) > 0 { + // 单可用区 + createOpt.ZoneID = cvt.ValToPtr(req.Zones[0]) + } + } + result, err := tcloud.InquiryPriceLoadBalancer(cts.Kit, createOpt) + if err != nil { + logs.Errorf("inquiry load balancer price failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return result, nil +} + +// ListTCloudLBQuota list tcloud clb quota. +func (svc *clbSvc) ListTCloudLBQuota(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudListLoadBalancerQuotaReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tcloud, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + result, err := tcloud.ListLoadBalancerQuota(cts.Kit, &typelb.ListTCloudLoadBalancerQuotaOption{ + Region: req.Region, + }) + if err != nil { + logs.Errorf("list tcloud load balancer quota failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + return result, nil +} diff --git a/cmd/hc-service/service/load-balancer/tcloud_listener.go b/cmd/hc-service/service/load-balancer/tcloud_listener.go new file mode 100644 index 0000000000..dce341cfe1 --- /dev/null +++ b/cmd/hc-service/service/load-balancer/tcloud_listener.go @@ -0,0 +1,53 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + loadbalancer "hcm/pkg/adaptor/types/load-balancer" + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/errf" + "hcm/pkg/rest" +) + +// QueryListenerTargetsByCloudIDs 直接从云上查询监听器RS列表 +func (svc *clbSvc) QueryListenerTargetsByCloudIDs(cts *rest.Contexts) (any, error) { + + req := new(protolb.QueryTCloudListenerTargets) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + tcloud, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + listOpt := &loadbalancer.TCloudListTargetsOption{ + Region: req.Region, + LoadBalancerId: req.LoadBalancerCloudId, + ListenerIds: req.ListenerCloudIDs, + Protocol: req.Protocol, + Port: req.Port, + } + return tcloud.ListTargets(cts.Kit, listOpt) +} diff --git a/cmd/hc-service/service/load-balancer/tcloud_register_target.go b/cmd/hc-service/service/load-balancer/tcloud_register_target.go new file mode 100644 index 0000000000..9a1bddc0ac --- /dev/null +++ b/cmd/hc-service/service/load-balancer/tcloud_register_target.go @@ -0,0 +1,100 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + "fmt" + + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" +) + +// RegisterTargetToListenerRule 批量到云上注册RS, 对接adaptor, 无db操作 +func (svc *clbSvc) RegisterTargetToListenerRule(cts *rest.Contexts) (any, error) { + lbID := cts.PathParameter("lb_id").String() + if len(lbID) == 0 { + return nil, errf.New(errf.InvalidParameter, "lb_id is required") + } + + req := new(protolb.BatchRegisterTCloudTargetReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + // 获取负载均衡信息 + lbResp, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(cts.Kit, &core.ListReq{ + Filter: tools.EqualExpression("id", lbID), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list find load balancer, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + if len(lbResp.Details) < 1 { + return nil, errf.New(errf.RecordNotFound, "lb not found") + } + lb := lbResp.Details[0] + + adpt, err := svc.ad.TCloud(cts.Kit, lb.AccountID) + if err != nil { + return nil, err + } + + opt := &typeslb.TCloudRegisterTargetsOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + Targets: make([]*typeslb.BatchTarget, 0, len(req.Targets)), + } + for _, target := range req.Targets { + tmpRs := &typeslb.BatchTarget{ + ListenerId: cvt.ValToPtr(req.CloudListenerID), + Port: cvt.ValToPtr(target.Port), + Type: cvt.ValToPtr(target.InstType), + InstanceId: cvt.ValToPtr(target.CloudInstID), + Weight: cvt.ValToPtr(target.Weight), + } + // 只有七层规则才需要传该参数 + if req.RuleType == enumor.Layer7RuleType { + tmpRs.LocationId = cvt.ValToPtr(req.CloudRuleID) + } + opt.Targets = append(opt.Targets, tmpRs) + } + failLblIds, err := adpt.RegisterTargets(cts.Kit, opt) + if err != nil { + logs.Errorf("fail to register rs, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + if len(failLblIds) > 0 { + return nil, fmt.Errorf("some listener fail to bind: %v", failLblIds) + } + + return nil, nil +} diff --git a/cmd/hc-service/service/load-balancer/tcloud_rule.go b/cmd/hc-service/service/load-balancer/tcloud_rule.go new file mode 100644 index 0000000000..e3409b66fc --- /dev/null +++ b/cmd/hc-service/service/load-balancer/tcloud_rule.go @@ -0,0 +1,376 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + synctcloud "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/pkg/adaptor/tcloud" + typelb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + "hcm/pkg/api/data-service/cloud" + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/slice" +) + +// TCloudCreateUrlRule 创建url规则 +func (svc *clbSvc) TCloudCreateUrlRule(cts *rest.Contexts) (any, error) { + + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + req := new(protolb.TCloudRuleBatchCreateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lb, listener, err := svc.getListenerWithLb(cts.Kit, lblID) + if err != nil { + return nil, err + } + if !listener.Protocol.IsLayer7Protocol() { + return nil, errf.New(errf.InvalidParameter, + "rule creation is only supports by layer 7 listener, not "+string(listener.Protocol)) + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, listener.AccountID) + if err != nil { + return nil, err + } + + ruleOption := typelb.TCloudCreateRuleOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + ListenerId: listener.CloudID, + } + for _, item := range req.Rules { + ruleOption.Rules = append(ruleOption.Rules, convRuleCreate(item, listener.Protocol)) + } + creatResult, err := tcloudAdpt.CreateRule(cts.Kit, &ruleOption) + if err != nil { + logs.Errorf("create tcloud url rule failed, err: %v, rid: %s", err, cts.Kit.Rid) + return nil, err + } + + createReq := &cloud.TCloudUrlRuleBatchCreateReq{} + + for i, cloudID := range creatResult.SuccessCloudIDs { + createReq.UrlRules = append(createReq.UrlRules, convURLRuleCreateReq(&req.Rules[i], lb, listener, cloudID)) + } + _, err = svc.dataCli.TCloud.LoadBalancer.BatchCreateTCloudUrlRule(cts.Kit, createReq) + if err != nil { + return nil, err + } + + if err := svc.lblSync(cts.Kit, tcloudAdpt, lb); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for create rule, lblID: %s, rid: %s", lblID, cts.Kit.Rid) + return nil, err + } + + return creatResult, nil +} + +func convURLRuleCreateReq(createReq *protolb.TCloudRuleCreate, lb *corelb.BaseLoadBalancer, + listener *corelb.BaseListener, cloudID string) cloud.TCloudUrlRuleCreate { + // 7层不支持设置健康检查端口 + if createReq.HealthCheck != nil { + createReq.HealthCheck.CheckPort = nil + } + + return cloud.TCloudUrlRuleCreate{ + LbID: lb.ID, + CloudLbID: lb.CloudID, + LblID: listener.ID, + CloudLBLID: listener.CloudID, + CloudID: cloudID, + Name: createReq.Url, + RuleType: enumor.Layer7RuleType, + TargetGroupID: createReq.TargetGroupID, + CloudTargetGroupID: createReq.TargetGroupID, + Domain: createReq.Domains[0], + URL: createReq.Url, + Scheduler: cvt.PtrToVal(createReq.Scheduler), + SessionExpire: cvt.PtrToVal(createReq.SessionExpireTime), + HealthCheck: createReq.HealthCheck, + Certificate: createReq.Certificates, + Memo: createReq.Memo, + } +} + +func convRuleCreate(r protolb.TCloudRuleCreate, protocol enumor.ProtocolType) *typelb.RuleInfo { + ruleInfo := &typelb.RuleInfo{ + Url: cvt.ValToPtr(r.Url), + SessionExpireTime: r.SessionExpireTime, + HealthCheck: r.HealthCheck, + Certificate: r.Certificates, + Scheduler: r.Scheduler, + ForwardType: r.ForwardType, + DefaultServer: r.DefaultServer, + Http2: r.Http2, + TargetType: r.TargetType, + TrpcCallee: r.TrpcCallee, + TrpcFunc: r.TrpcFunc, + Quic: r.Quic, + } + // 7层不支持设置健康检查端口 + if protocol.IsLayer7Protocol() && r.HealthCheck.CheckPort != nil { + ruleInfo.HealthCheck.CheckPort = nil + } + if len(r.Domains) == 1 { + ruleInfo.Domain = cvt.ValToPtr(r.Domains[0]) + } + if len(r.Domains) > 1 { + ruleInfo.Domains = cvt.SliceToPtr(r.Domains) + } + + return ruleInfo +} + +// TCloudUpdateUrlRule 修改监听器规则 +func (svc *clbSvc) TCloudUpdateUrlRule(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + ruleID := cts.PathParameter("rule_id").String() + if len(ruleID) == 0 { + return nil, errf.New(errf.InvalidParameter, "rule id is required") + } + req := new(protolb.TCloudRuleUpdateReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lb, rules, err := svc.getL7RulesWithLb(cts.Kit, lblID, []string{ruleID}) + if err != nil { + return nil, err + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, lb.AccountID) + if err != nil { + return nil, err + } + if req.HealthCheck != nil { + // 7层不支持设置健康检查端口 + req.HealthCheck.CheckPort = nil + } + + ruleOption := typelb.TCloudUpdateRuleOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + ListenerId: rules[0].CloudLBLID, + LocationId: rules[0].CloudID, + Url: req.Url, + HealthCheck: req.HealthCheck, + Scheduler: req.Scheduler, + SessionExpireTime: req.SessionExpireTime, + ForwardType: req.ForwardType, + TrpcCallee: req.TrpcCallee, + TrpcFunc: req.TrpcFunc, + } + + if err = tcloudAdpt.UpdateRule(cts.Kit, &ruleOption); err != nil { + logs.Errorf("fail to update rule, err: %v, id: %s, rid: %s", err, ruleID, cts.Kit.Rid) + return nil, err + } + if err := svc.lblSync(cts.Kit, tcloudAdpt, lb); err != nil { + logs.Errorf("fail to sync listener for update rule(%s), rid: %s", ruleID, cts.Kit.Rid) + return nil, err + } + return nil, nil +} + +// getL7RuleWithLb 查询同一个监听器下的规则 +func (svc *clbSvc) getL7RulesWithLb(kt *kit.Kit, lblID string, ruleIDs []string) (*corelb.BaseLoadBalancer, + []corelb.TCloudLbUrlRule, error) { + + // 只能查到7层规则 + ruleResp, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(kt, &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleIn("id", ruleIDs), + tools.RuleEqual("lbl_id", lblID), + tools.RuleEqual("rule_type", enumor.Layer7RuleType), + ), + Page: core.NewDefaultBasePage(), + }) + if err != nil { + logs.Errorf("fail to list tcloud url rule, err: %v, ids: %s, rid: %s", err, ruleIDs, kt.Rid) + return nil, nil, err + } + if len(ruleResp.Details) < 1 { + return nil, nil, errf.Newf(errf.RecordNotFound, "rule not found") + } + rule := ruleResp.Details[0] + + // 查询负载均衡 + lbResp, err := svc.dataCli.Global.LoadBalancer.ListLoadBalancer(kt, &core.ListReq{ + Filter: tools.EqualExpression("id", rule.LbID), + Page: core.NewDefaultBasePage(), + Fields: nil, + }) + if err != nil { + logs.Errorf("fail to tcloud list load balancer, err: %v, id: %s, rid: %s", err, rule.LbID, kt.Rid) + return nil, nil, err + } + if len(lbResp.Details) < 1 { + return nil, nil, errf.Newf(errf.RecordNotFound, "lb not found") + } + lb := lbResp.Details[0] + return &lb, ruleResp.Details, nil +} + +// TCloudBatchDeleteUrlRule 批量删除规则 +func (svc *clbSvc) TCloudBatchDeleteUrlRule(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + req := new(protolb.TCloudRuleDeleteByIDReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 指定规则id删除 + lb, rules, err := svc.getL7RulesWithLb(cts.Kit, lblID, req.RuleIDs) + if err != nil { + logs.Errorf("fail to get lb info for rule deletion by rule ids(%v), err: %v, rid: %s", + req.RuleIDs, err, cts.Kit.Rid) + return nil, err + } + ruleOption := typelb.TCloudDeleteRuleOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + ListenerId: rules[0].CloudLBLID, + CloudIDs: slice.Map(rules, func(r corelb.TCloudLbUrlRule) string { return r.CloudID }), + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, lb.AccountID) + if err != nil { + return nil, err + } + if err = tcloudAdpt.DeleteRule(cts.Kit, &ruleOption); err != nil { + logs.Errorf("fail to delete rule, err: %v, ids: %v, rid: %s", err, req.RuleIDs, cts.Kit.Rid) + return nil, err + } + + if err := svc.lblSync(cts.Kit, tcloudAdpt, lb); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for delete rule, req: %+v, rid: %s", req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// TCloudBatchDeleteUrlRuleByDomain 按域名批量删除规则 +func (svc *clbSvc) TCloudBatchDeleteUrlRuleByDomain(cts *rest.Contexts) (any, error) { + lblID := cts.PathParameter("lbl_id").String() + if len(lblID) == 0 { + return nil, errf.New(errf.InvalidParameter, "listener id is required") + } + + req := new(protolb.TCloudRuleDeleteByDomainReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + lb, listener, err := svc.getListenerWithLb(cts.Kit, lblID) + if err != nil { + logs.Errorf("fail to get lb info for rule deletion by domains, err: %v, domain: %v, rid: %s", + err, req.Domains, cts.Kit.Rid) + return nil, err + } + if !listener.Protocol.IsLayer7Protocol() { + return nil, errf.Newf(errf.InvalidParameter, "unsupported listner protocol type: %s", listener.Protocol) + } + ruleOption := typelb.TCloudDeleteRuleOption{ + Region: lb.Region, + LoadBalancerId: lb.CloudID, + ListenerId: listener.CloudID, + NewDefaultServerDomain: req.NewDefaultDomain, + } + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, lb.AccountID) + if err != nil { + return nil, err + } + + // 遍历删除每个域名 + for _, domain := range req.Domains { + ruleOption.Domain = cvt.ValToPtr(domain) + if err = tcloudAdpt.DeleteRule(cts.Kit, &ruleOption); err != nil { + logs.Errorf("fail to delete rule, err: %v, ids: %v, rid: %s", err, domain, cts.Kit.Rid) + return nil, err + } + } + + if err := svc.lblSync(cts.Kit, tcloudAdpt, lb); err != nil { + // 调用同步的方法内会打印错误,这里只标记调用方 + logs.Errorf("fail to sync listener for delete rule, req: %+v, rid: %s", req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +func (svc *clbSvc) lblSync(kt *kit.Kit, adaptor tcloud.TCloud, lb *corelb.BaseLoadBalancer) error { + syncClient := synctcloud.NewClient(svc.dataCli, adaptor) + params := &synctcloud.SyncListenerOfSingleLBOption{ + AccountID: lb.AccountID, + Region: lb.Region, + BizID: lb.BkBizID, + LBID: lb.ID, + CloudLBID: lb.CloudID, + } + _, err := syncClient.Listener(kt, params) + if err != nil { + logs.Errorf("sync listener of lb(%s) failed, err: %v, rid: %s", lb.CloudID, err, kt.Rid) + return err + } + return nil +} diff --git a/cmd/hc-service/service/load-balancer/tcloud_target_group.go b/cmd/hc-service/service/load-balancer/tcloud_target_group.go new file mode 100644 index 0000000000..3d726b9ca3 --- /dev/null +++ b/cmd/hc-service/service/load-balancer/tcloud_target_group.go @@ -0,0 +1,630 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package loadbalancer + +import ( + typelb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + protolb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/slice" +) + +// BatchCreateTCloudTargets 批量添加RS +func (svc *clbSvc) BatchCreateTCloudTargets(cts *rest.Contexts) (any, error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(protolb.TCloudBatchOperateTargetReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tgList, err := svc.getTargetGroupByID(cts.Kit, tgID) + if err != nil { + return nil, err + } + + if len(tgList) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", tgID) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.dataCli.Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + return &protolb.BatchCreateResult{}, nil + } + + // 查询Url规则列表 + ruleIDs := slice.Map(ruleRelList.Details, func(one corelb.BaseTargetListenerRuleRel) string { + return one.ListenerRuleID + }) + urlRuleReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", ruleIDs), + Page: core.NewDefaultBasePage(), + } + urlRuleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(cts.Kit, urlRuleReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云端批量绑定虚拟主机接口 + return svc.batchAddTargetsToGroup(cts.Kit, req, tgList[0], urlRuleList) +} + +func (svc *clbSvc) batchAddTargetsToGroup(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + tgInfo corelb.BaseTargetGroup, urlRuleList *dataproto.TCloudURLRuleListResult) ( + *protolb.BatchCreateResult, error) { + + tcloudAdpt, err := svc.ad.TCloud(kt, tgInfo.AccountID) + if err != nil { + return nil, err + } + + cloudLBExists := make(map[string]struct{}, 0) + rsOpt := &typelb.TCloudRegisterTargetsOption{ + Region: tgInfo.Region, + } + for _, ruleItem := range urlRuleList.Details { + if _, ok := cloudLBExists[ruleItem.CloudLbID]; !ok { + rsOpt.LoadBalancerId = ruleItem.CloudLbID + cloudLBExists[ruleItem.CloudLbID] = struct{}{} + } + for _, rsItem := range req.RsList { + tmpRs := &typelb.BatchTarget{ + ListenerId: cvt.ValToPtr(ruleItem.CloudLBLID), + InstanceId: cvt.ValToPtr(rsItem.CloudInstID), + Port: cvt.ValToPtr(rsItem.Port), + Weight: rsItem.Weight, + } + if ruleItem.RuleType == enumor.Layer7RuleType { + tmpRs.LocationId = cvt.ValToPtr(ruleItem.CloudID) + } + rsOpt.Targets = append(rsOpt.Targets, tmpRs) + } + failIDs, err := tcloudAdpt.RegisterTargets(kt, rsOpt) + if err != nil { + logs.Errorf("register tcloud target api failed, err: %v, rsOpt: %+v, rid: %s", err, rsOpt, kt.Rid) + return nil, err + } + if len(failIDs) > 0 { + logs.Errorf("register tcloud target api partially failed, failLblIDs: %v, req: %+v, rsOpt: %+v, rid: %s", + failIDs, req, rsOpt, kt.Rid) + return nil, errf.Newf(errf.PartialFailed, "register tcloud target failed, failListenerIDs: %v", failIDs) + } + } + + rsIDs, err := svc.batchCreateTargetDb(kt, req, tgInfo.AccountID, tgInfo.ID) + if err != nil { + return nil, err + } + return &protolb.BatchCreateResult{SuccessCloudIDs: rsIDs.IDs}, nil +} + +func (svc *clbSvc) batchCreateTargetDb(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + accountID, tgID string) (*core.BatchCreateResult, error) { + + // 检查RS是否已绑定该目标组 + rsList := make([]*dataproto.TargetBaseReq, 0) + for _, item := range req.RsList { + tgReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("target_group_id", tgID), + tools.RuleEqual("cloud_inst_id", item.CloudInstID), + tools.RuleEqual("port", item.Port), + ), + Page: core.NewDefaultBasePage(), + } + tmpRsList, err := svc.dataCli.Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return nil, err + } + if len(tmpRsList.Details) == 0 { + rsList = append(rsList, item) + } + } + if len(rsList) == 0 { + return &core.BatchCreateResult{}, nil + } + + rsReq := &dataproto.TargetBatchCreateReq{} + for _, item := range rsList { + rsReq.Targets = append(rsReq.Targets, &dataproto.TargetBaseReq{ + AccountID: accountID, + TargetGroupID: tgID, + InstType: item.InstType, + CloudInstID: item.CloudInstID, + Port: item.Port, + Weight: item.Weight, + }) + } + return svc.dataCli.Global.LoadBalancer.BatchCreateTCloudTarget(kt, rsReq) +} + +// BatchRemoveTCloudTargets 批量移除RS +func (svc *clbSvc) BatchRemoveTCloudTargets(cts *rest.Contexts) (any, error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(protolb.TCloudBatchOperateTargetReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tgList, err := svc.getTargetGroupByID(cts.Kit, tgID) + if err != nil { + return nil, err + } + + if len(tgList) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", tgID) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.dataCli.Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + return &protolb.BatchCreateResult{}, nil + } + + // 查询Url规则列表 + ruleIDs := slice.Map(ruleRelList.Details, func(one corelb.BaseTargetListenerRuleRel) string { + return one.ListenerRuleID + }) + urlRuleReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", ruleIDs), + Page: core.NewDefaultBasePage(), + } + urlRuleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(cts.Kit, urlRuleReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云端批量解绑四七层后端服务接口 + return nil, svc.batchUnRegisterTargetCloud(cts.Kit, req, tgList[0], urlRuleList) +} + +func (svc *clbSvc) batchUnRegisterTargetCloud(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + tgInfo corelb.BaseTargetGroup, urlRuleList *dataproto.TCloudURLRuleListResult) error { + + tcloudAdpt, err := svc.ad.TCloud(kt, tgInfo.AccountID) + if err != nil { + return err + } + + cloudLBExists := make(map[string]struct{}, 0) + rsOpt := &typelb.TCloudRegisterTargetsOption{ + Region: tgInfo.Region, + } + for _, ruleItem := range urlRuleList.Details { + if _, ok := cloudLBExists[ruleItem.CloudLbID]; !ok { + rsOpt.LoadBalancerId = ruleItem.CloudLbID + cloudLBExists[ruleItem.CloudLbID] = struct{}{} + } + for _, rsItem := range req.RsList { + tmpRs := &typelb.BatchTarget{ + ListenerId: cvt.ValToPtr(ruleItem.CloudLBLID), + InstanceId: cvt.ValToPtr(rsItem.CloudInstID), + Port: cvt.ValToPtr(rsItem.Port), + } + if ruleItem.RuleType == enumor.Layer7RuleType { + tmpRs.LocationId = cvt.ValToPtr(ruleItem.CloudID) + } + rsOpt.Targets = append(rsOpt.Targets, tmpRs) + } + failIDs, err := tcloudAdpt.DeRegisterTargets(kt, rsOpt) + if err != nil { + logs.Errorf("unregister tcloud target api failed, err: %v, rsOpt: %+v, rid: %s", err, rsOpt, kt.Rid) + return err + } + if len(failIDs) > 0 { + logs.Errorf("unregister tcloud target api partially failed, failLblIDs: %v, req: %+v, rsOpt: %+v, rid: %s", + failIDs, req, rsOpt, kt.Rid) + return errf.Newf(errf.PartialFailed, "unregister tcloud target failed, failListenerIDs: %v", failIDs) + } + } + + err = svc.batchDeleteTargetDb(kt, req, tgInfo.AccountID, tgInfo.ID) + if err != nil { + return err + } + return nil +} + +func (svc *clbSvc) batchDeleteTargetDb(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + accountID, tgID string) error { + + // 检查RS是否已绑定该目标组 + rsID := make([]string, 0) + for _, item := range req.RsList { + tgReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("target_group_id", tgID), + tools.RuleEqual("cloud_inst_id", item.CloudInstID), + tools.RuleEqual("port", item.Port), + ), + Page: core.NewDefaultBasePage(), + } + tmpRsList, err := svc.dataCli.Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(tmpRsList.Details) > 0 { + rsID = append(rsID, tmpRsList.Details[0].ID) + } + } + if len(rsID) == 0 { + return nil + } + + delReq := &dataproto.LoadBalancerBatchDeleteReq{ + Filter: tools.ExpressionAnd( + tools.RuleIn("id", rsID), + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("target_group_id", tgID), + ), + } + return svc.dataCli.Global.LoadBalancer.BatchDeleteTarget(kt, delReq) +} + +// BatchModifyTCloudTargetsPort 批量修改RS端口 +func (svc *clbSvc) BatchModifyTCloudTargetsPort(cts *rest.Contexts) (any, error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(protolb.TCloudBatchOperateTargetReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tgList, err := svc.getTargetGroupByID(cts.Kit, tgID) + if err != nil { + return nil, err + } + + if len(tgList) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", tgID) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.dataCli.Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + return &protolb.BatchCreateResult{}, nil + } + + // 查询Url规则列表 + ruleIDs := slice.Map(ruleRelList.Details, func(one corelb.BaseTargetListenerRuleRel) string { + return one.ListenerRuleID + }) + urlRuleReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", ruleIDs), + Page: core.NewDefaultBasePage(), + } + urlRuleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(cts.Kit, urlRuleReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 调用云端批量解绑四七层后端服务接口 + return nil, svc.batchModifyTargetPortCloud(cts.Kit, req, tgList[0], urlRuleList) +} + +func (svc *clbSvc) batchModifyTargetPortCloud(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + tgInfo corelb.BaseTargetGroup, urlRuleList *dataproto.TCloudURLRuleListResult) error { + + tcloudAdpt, err := svc.ad.TCloud(kt, tgInfo.AccountID) + if err != nil { + return err + } + + rsOpt := &typelb.TCloudTargetPortUpdateOption{ + Region: tgInfo.Region, + } + for _, ruleItem := range urlRuleList.Details { + rsOpt.LoadBalancerId = ruleItem.CloudLbID + rsOpt.ListenerId = ruleItem.CloudLBLID + if ruleItem.RuleType == enumor.Layer7RuleType { + rsOpt.LocationId = cvt.ValToPtr(ruleItem.CloudID) + } + for _, rsItem := range req.RsList { + rsOpt.Targets = append(rsOpt.Targets, &typelb.BatchTarget{ + Type: cvt.ValToPtr(string(rsItem.InstType)), + InstanceId: cvt.ValToPtr(rsItem.CloudInstID), + Port: cvt.ValToPtr(rsItem.Port), + }) + } + rsOpt.NewPort = cvt.PtrToVal(req.RsList[0].NewPort) + err = tcloudAdpt.ModifyTargetPort(kt, rsOpt) + if err != nil { + logs.Errorf("batch modify tcloud target port api failed, err: %v, rsOpt: %+v, rid: %s", err, rsOpt, kt.Rid) + return errf.Newf(errf.PartialFailed, "batch modify tcloud target port api failed, err: %v", err) + } + } + + err = svc.batchUpdateTargetPortWeightDb(kt, req) + if err != nil { + return err + } + return nil +} + +func (svc *clbSvc) batchUpdateTargetPortWeightDb(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq) error { + // 检查RS是否已绑定该目标组 + updateReq := &dataproto.TargetBatchUpdateReq{} + for _, item := range req.RsList { + tgReq := &core.ListReq{ + Filter: tools.EqualExpression("id", item.ID), + Page: core.NewDefaultBasePage(), + } + tmpRsList, err := svc.dataCli.Global.LoadBalancer.ListTarget(kt, tgReq) + if err != nil { + return err + } + if len(tmpRsList.Details) > 0 { + updateReq.Targets = append(updateReq.Targets, &dataproto.TargetUpdate{ + ID: item.ID, + Port: cvt.PtrToVal(item.NewPort), + Weight: item.NewWeight, + }) + } + } + if len(updateReq.Targets) == 0 { + return nil + } + + return svc.dataCli.Global.LoadBalancer.BatchUpdateTarget(kt, updateReq) +} + +// BatchModifyTCloudTargetsWeight 批量修改RS权重 +func (svc *clbSvc) BatchModifyTCloudTargetsWeight(cts *rest.Contexts) (any, error) { + tgID := cts.PathParameter("target_group_id").String() + if len(tgID) == 0 { + return nil, errf.New(errf.InvalidParameter, "target_group_id is required") + } + + req := new(protolb.TCloudBatchOperateTargetReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + tgList, err := svc.getTargetGroupByID(cts.Kit, tgID) + if err != nil { + return nil, err + } + + if len(tgList) == 0 { + return nil, errf.Newf(errf.RecordNotFound, "target group: %s not found", tgID) + } + + // 根据目标组ID,获取目标组绑定的监听器、规则列表 + ruleRelReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + ruleRelList, err := svc.dataCli.Global.LoadBalancer.ListTargetGroupListenerRel(cts.Kit, ruleRelReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 该目标组尚未绑定监听器及规则,不需要云端操作 + if len(ruleRelList.Details) == 0 { + return &protolb.BatchCreateResult{}, nil + } + + // 查询Url规则列表 + ruleIDs := slice.Map(ruleRelList.Details, func(one corelb.BaseTargetListenerRuleRel) string { + return one.ListenerRuleID + }) + urlRuleReq := &core.ListReq{ + Filter: tools.ContainersExpression("id", ruleIDs), + Page: core.NewDefaultBasePage(), + } + urlRuleList, err := svc.dataCli.TCloud.LoadBalancer.ListUrlRule(cts.Kit, urlRuleReq) + if err != nil { + logs.Errorf("list tcloud listener url rule failed, tgID: %s, err: %v, rid: %s", tgID, err, cts.Kit.Rid) + return nil, err + } + + // 批量修改监听器绑定的后端机器的转发权重 + return nil, svc.batchModifyTargetWeightCloud(cts.Kit, req, tgList[0], urlRuleList) +} + +func (svc *clbSvc) batchModifyTargetWeightCloud(kt *kit.Kit, req *protolb.TCloudBatchOperateTargetReq, + tgInfo corelb.BaseTargetGroup, urlRuleList *dataproto.TCloudURLRuleListResult) error { + + tcloudAdpt, err := svc.ad.TCloud(kt, tgInfo.AccountID) + if err != nil { + return err + } + + rsOpt := &typelb.TCloudTargetWeightUpdateOption{ + Region: tgInfo.Region, + } + for _, ruleItem := range urlRuleList.Details { + rsOpt.LoadBalancerId = ruleItem.CloudLbID + tmpRs := &typelb.TargetWeightRule{ + ListenerId: cvt.ValToPtr(ruleItem.CloudLBLID), + } + if ruleItem.RuleType == enumor.Layer7RuleType { + tmpRs.LocationId = cvt.ValToPtr(ruleItem.CloudID) + } + for _, rsItem := range req.RsList { + tmpRs.Targets = append(tmpRs.Targets, &typelb.BatchTarget{ + Type: cvt.ValToPtr(string(rsItem.InstType)), + InstanceId: cvt.ValToPtr(rsItem.CloudInstID), + Port: cvt.ValToPtr(rsItem.Port), + Weight: rsItem.NewWeight, + }) + rsOpt.ModifyList = append(rsOpt.ModifyList, tmpRs) + } + err = tcloudAdpt.ModifyTargetWeight(kt, rsOpt) + if err != nil { + logs.Errorf("batch modify tcloud target port api failed, err: %v, rsOpt: %+v, rid: %s", err, rsOpt, kt.Rid) + return errf.Newf(errf.PartialFailed, "batch modify tcloud target port api failed, err: %v", err) + } + } + + err = svc.batchUpdateTargetPortWeightDb(kt, req) + if err != nil { + return err + } + return nil +} + +// ListTCloudTargetsHealth 查询目标组所在负载均衡的端口健康数据 +func (svc *clbSvc) ListTCloudTargetsHealth(cts *rest.Contexts) (any, error) { + req := new(protolb.TCloudTargetHealthReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + if len(req.AccountID) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "account_id is required") + } + if len(req.Region) == 0 { + return nil, errf.Newf(errf.InvalidParameter, "region is required") + } + + tcloudAdpt, err := svc.ad.TCloud(cts.Kit, req.AccountID) + if err != nil { + return nil, err + } + + opt := &typelb.TCloudListTargetHealthOption{ + Region: req.Region, + LoadBalancerIDs: req.CloudLbIDs, + } + healthList, err := tcloudAdpt.ListTargetHealth(cts.Kit, opt) + if err != nil { + logs.Errorf("list tcloud target health api failed, err: %v, cloudLbIDs: %v, rid: %s", + err, req.CloudLbIDs, cts.Kit.Rid) + return nil, err + } + + healths := &protolb.TCloudTargetHealthResp{} + for _, item := range healthList { + tmpHealthInfo := protolb.TCloudTargetHealthResult{CloudLbID: cvt.PtrToVal(item.LoadBalancerId)} + for _, lblItem := range item.Listeners { + tmpListener := &protolb.TCloudTargetHealthLblResult{ + CloudLblID: cvt.PtrToVal(lblItem.ListenerId), + Protocol: enumor.ProtocolType(cvt.PtrToVal(lblItem.Protocol)), + ListenerName: cvt.PtrToVal(lblItem.ListenerName), + } + for _, ruleItem := range lblItem.Rules { + var healthNum, unHealthNum int64 + for _, targetItem := range ruleItem.Targets { + // 当前健康状态,true:健康,false:不健康(包括尚未开始探测、探测中、状态异常等几种状态)。 + if cvt.PtrToVal(targetItem.HealthStatus) { + healthNum++ + } else { + unHealthNum++ + } + } + + if !tmpListener.Protocol.IsLayer7Protocol() { + tmpListener.HealthCheck = &corelb.TCloudHealthCheckInfo{ + HealthNum: cvt.ValToPtr(healthNum), + UnHealthNum: cvt.ValToPtr(unHealthNum), + } + break + } else { + tmpListener.Rules = append(tmpListener.Rules, &protolb.TCloudTargetHealthRuleResult{ + CloudRuleID: cvt.PtrToVal(ruleItem.LocationId), + HealthCheck: &corelb.TCloudHealthCheckInfo{ + HealthNum: cvt.ValToPtr(healthNum), + UnHealthNum: cvt.ValToPtr(unHealthNum), + }, + }) + } + } + tmpHealthInfo.Listeners = append(tmpHealthInfo.Listeners, tmpListener) + } + healths.Details = append(healths.Details, tmpHealthInfo) + } + + return healths, nil +} diff --git a/cmd/hc-service/service/security-group/logics.go b/cmd/hc-service/service/security-group/logics.go index a59a1279ec..5613060b02 100644 --- a/cmd/hc-service/service/security-group/logics.go +++ b/cmd/hc-service/service/security-group/logics.go @@ -25,6 +25,7 @@ import ( corecvm "hcm/pkg/api/core/cloud/cvm" dataproto "hcm/pkg/api/data-service" protocloud "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/enumor" "hcm/pkg/criteria/errf" "hcm/pkg/dal/dao/tools" "hcm/pkg/kit" @@ -52,6 +53,38 @@ func buildSGCvmRelDeleteReq(sgID, cvmID string) *dataproto.BatchDeleteReq { } } +func buildSGCommonRelDeleteReq(vendor enumor.Vendor, resID string, sgIDs []string, + resType enumor.CloudResourceType) *dataproto.BatchDeleteReq { + + return &dataproto.BatchDeleteReq{ + Filter: &filter.Expression{ + Op: filter.And, + Rules: []filter.RuleFactory{ + &filter.AtomRule{ + Field: "vendor", + Op: filter.Equal.Factory(), + Value: vendor, + }, + &filter.AtomRule{ + Field: "res_type", + Op: filter.Equal.Factory(), + Value: resType, + }, + &filter.AtomRule{ + Field: "res_id", + Op: filter.Equal.Factory(), + Value: resID, + }, + &filter.AtomRule{ + Field: "security_group_id", + Op: filter.In.Factory(), + Value: sgIDs, + }, + }, + }, + } +} + func (g *securityGroup) getSecurityGroupAndCvm(kt *kit.Kit, sgID, cvmID string) (*corecloud.BaseSecurityGroup, *corecvm.BaseCvm, error) { diff --git a/cmd/hc-service/service/security-group/security_group.go b/cmd/hc-service/service/security-group/security_group.go index 971763e12e..5465f05074 100644 --- a/cmd/hc-service/service/security-group/security_group.go +++ b/cmd/hc-service/service/security-group/security_group.go @@ -93,6 +93,12 @@ func InitSecurityGroupService(cap *capability.Capability) { h.Add("DeleteAzureSGRule", "DELETE", "/vendors/azure/security_groups/{security_group_id}/rules/{id}", sg.DeleteAzureSGRule) + // CLB负载均衡 + h.Add("TCloudSecurityGroupAssociateLoadBalancer", "POST", + "/vendors/tcloud/security_groups/associate/load_balancers", sg.TCloudSecurityGroupAssociateLoadBalancer) + h.Add("TCloudSecurityGroupDisassociateLoadBalancer", "POST", + "/vendors/tcloud/security_groups/disassociate/load_balancers", sg.TCloudSecurityGroupDisassociateLoadBalancer) + initSecurityGroupServiceHooks(sg, h) h.Load(cap.WebService) diff --git a/cmd/hc-service/service/security-group/tcloud_security_group.go b/cmd/hc-service/service/security-group/tcloud_security_group.go index a888041bef..bbbe650f1f 100644 --- a/cmd/hc-service/service/security-group/tcloud_security_group.go +++ b/cmd/hc-service/service/security-group/tcloud_security_group.go @@ -23,13 +23,18 @@ import ( "errors" typecvm "hcm/pkg/adaptor/types/cvm" + typelb "hcm/pkg/adaptor/types/load-balancer" securitygroup "hcm/pkg/adaptor/types/security-group" "hcm/pkg/api/core" corecloud "hcm/pkg/api/core/cloud" + corelb "hcm/pkg/api/core/cloud/load-balancer" protocloud "hcm/pkg/api/data-service/cloud" proto "hcm/pkg/api/hc-service" + hclb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/criteria/enumor" "hcm/pkg/criteria/errf" "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" "hcm/pkg/logs" "hcm/pkg/rest" ) @@ -289,7 +294,7 @@ func (g *securityGroup) UpdateTCloudSecurityGroup(cts *rest.Contexts) (interface }, }, } - if err := g.dataCli.TCloud.SecurityGroup.BatchUpdateSecurityGroup(cts.Kit.Ctx, cts.Kit.Header(), + if err = g.dataCli.TCloud.SecurityGroup.BatchUpdateSecurityGroup(cts.Kit.Ctx, cts.Kit.Header(), updateReq); err != nil { logs.Errorf("request dataservice BatchUpdateSecurityGroup failed, err: %v, id: %s, rid: %s", err, id, @@ -299,3 +304,228 @@ func (g *securityGroup) UpdateTCloudSecurityGroup(cts *rest.Contexts) (interface return nil, nil } + +// TCloudSecurityGroupAssociateLoadBalancer ... +func (g *securityGroup) TCloudSecurityGroupAssociateLoadBalancer(cts *rest.Contexts) (interface{}, error) { + req := new(hclb.TCloudSetLbSecurityGroupReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 根据LbID查询负载均衡基本信息 + lbInfo, sgComList, err := g.getLoadBalancerInfoAndSGComRels(cts.Kit, req.LbID) + if err != nil { + return nil, err + } + + sgCloudIDs, sgComReq, err := g.getUpsertSGIDsParams(cts.Kit, req, sgComList) + if err != nil { + return nil, err + } + + client, err := g.ad.TCloud(cts.Kit, lbInfo.AccountID) + if err != nil { + return nil, err + } + + opt := &typelb.TCloudSetClbSecurityGroupOption{ + Region: lbInfo.Region, + LoadBalancerID: lbInfo.CloudID, + SecurityGroups: sgCloudIDs, + } + if _, err = client.SetLoadBalancerSecurityGroups(cts.Kit, opt); err != nil { + logs.Errorf("request adaptor to tcloud security group associate lb failed, err: %v, opt: %v, rid: %s", + err, opt, cts.Kit.Rid) + return nil, err + } + + if err = g.dataCli.Global.SGCommonRel.BatchUpsert(cts.Kit, sgComReq); err != nil { + logs.Errorf("request dataservice upsert security group lb rels failed, err: %v, req: %+v, rid: %s", + err, sgComReq, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +func (g *securityGroup) getUpsertSGIDsParams(kt *kit.Kit, req *hclb.TCloudSetLbSecurityGroupReq, + sgComList *protocloud.SGCommonRelListResult) ([]string, *protocloud.SGCommonRelBatchUpsertReq, error) { + + delSGIDs := make([]string, 0) + for _, sg := range sgComList.Details { + delSGIDs = append(delSGIDs, sg.SecurityGroupID) + } + + sgComReq := &protocloud.SGCommonRelBatchUpsertReq{ + Rels: make([]protocloud.SGCommonRelCreate, 0, len(req.SecurityGroupIDs)), + } + if len(delSGIDs) > 0 { + sgComReq.DeleteReq = buildSGCommonRelDeleteReq( + enumor.TCloud, req.LbID, delSGIDs, enumor.LoadBalancerCloudResType) + } + + tmpPriority := int64(0) + for _, newSGID := range req.SecurityGroupIDs { + tmpPriority++ + sgComReq.Rels = append(sgComReq.Rels, protocloud.SGCommonRelCreate{ + SecurityGroupID: newSGID, + Vendor: enumor.TCloud, + ResID: req.LbID, + ResType: enumor.LoadBalancerCloudResType, + Priority: tmpPriority, + }) + } + + sgMap, err := g.getSecurityGroupMap(kt, req.SecurityGroupIDs) + if err != nil { + return nil, nil, err + } + + // 安全组的云端ID数组 + sgCloudIDs := make([]string, 0) + for _, sgID := range req.SecurityGroupIDs { + sg, ok := sgMap[sgID] + if !ok { + continue + } + sgCloudIDs = append(sgCloudIDs, sg.CloudID) + } + if len(sgCloudIDs) == 0 { + return nil, nil, errf.Newf(errf.RecordNotFound, "cloud security group ids is empty") + } + + return sgCloudIDs, sgComReq, nil +} + +// TCloudSecurityGroupDisassociateLoadBalancer ... +func (g *securityGroup) TCloudSecurityGroupDisassociateLoadBalancer(cts *rest.Contexts) (interface{}, error) { + req := new(hclb.TCloudDisAssociateLbSecurityGroupReq) + if err := cts.DecodeInto(req); err != nil { + return nil, errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 根据LbID查询负载均衡基本信息 + lbInfo, sgComList, err := g.getLoadBalancerInfoAndSGComRels(cts.Kit, req.LbID) + if err != nil { + return nil, err + } + + allSGIDs := make([]string, 0) + existSG := false + for _, rel := range sgComList.Details { + if rel.SecurityGroupID == req.SecurityGroupID { + existSG = true + } + allSGIDs = append(allSGIDs, rel.SecurityGroupID) + } + if !existSG { + return nil, errf.Newf(errf.RecordNotFound, "not found sg id: %s", req.SecurityGroupID) + } + + sgMap, err := g.getSecurityGroupMap(cts.Kit, allSGIDs) + if err != nil { + return nil, err + } + + // 安全组的云端ID数组 + sgCloudIDs := make([]string, 0) + for _, sgID := range allSGIDs { + sg, ok := sgMap[sgID] + if !ok { + continue + } + sgCloudIDs = append(sgCloudIDs, sg.CloudID) + } + + client, err := g.ad.TCloud(cts.Kit, lbInfo.AccountID) + if err != nil { + return nil, err + } + + opt := &typelb.TCloudSetClbSecurityGroupOption{ + Region: lbInfo.Region, + LoadBalancerID: lbInfo.CloudID, + SecurityGroups: sgCloudIDs, + } + if _, err = client.SetLoadBalancerSecurityGroups(cts.Kit, opt); err != nil { + logs.Errorf("request adaptor to tcloud security group disAssociate lb failed, err: %v, opt: %v, rid: %s", + err, opt, cts.Kit.Rid) + return nil, err + } + + deleteReq := buildSGCommonRelDeleteReq( + enumor.TCloud, req.LbID, []string{req.SecurityGroupID}, enumor.LoadBalancerCloudResType) + if err = g.dataCli.Global.SGCommonRel.BatchDelete(cts.Kit, deleteReq); err != nil { + logs.Errorf("request dataservice tcloud delete security group lb rels failed, err: %v, req: %+v, rid: %s", + err, req, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +func (g *securityGroup) getLoadBalancerInfoAndSGComRels(kt *kit.Kit, lbID string) ( + *corelb.BaseLoadBalancer, *protocloud.SGCommonRelListResult, error) { + + lbReq := &core.ListReq{ + Filter: tools.EqualExpression("id", lbID), + Page: core.NewDefaultBasePage(), + } + lbList, err := g.dataCli.Global.LoadBalancer.ListLoadBalancer(kt, lbReq) + if err != nil { + logs.Errorf("list load balancer by id failed, id: %s, err: %v, rid: %s", lbID, err, kt.Rid) + return nil, nil, err + } + + if len(lbList.Details) == 0 { + return nil, nil, errf.Newf(errf.RecordNotFound, "not found lb id: %s", lbID) + } + + lbInfo := lbList.Details[0] + // 查询目前绑定的安全组 + sgcomReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("vendor", lbInfo.Vendor), + tools.RuleEqual("res_id", lbID), + tools.RuleEqual("res_type", enumor.LoadBalancerCloudResType), + ), + Page: &core.BasePage{Start: 0, Limit: core.DefaultMaxPageLimit, Sort: "priority", Order: "ASC"}, + } + sgComList, err := g.dataCli.Global.SGCommonRel.List(kt, sgcomReq) + if err != nil { + logs.Errorf("call dataserver to list sg common failed, lbID: %s, err: %v, rid: %s", lbID, err, kt.Rid) + return nil, nil, err + } + + return &lbInfo, sgComList, nil +} + +func (g *securityGroup) getSecurityGroupMap(kt *kit.Kit, sgIDs []string) ( + map[string]corecloud.BaseSecurityGroup, error) { + + sgReq := &protocloud.SecurityGroupListReq{ + Filter: tools.ContainersExpression("id", sgIDs), + Page: core.NewDefaultBasePage(), + } + sgResult, err := g.dataCli.Global.SecurityGroup.ListSecurityGroup(kt.Ctx, kt.Header(), sgReq) + if err != nil { + logs.Errorf("request dataservice list tcloud security group failed, err: %v, ids: %v, rid: %s", + err, sgIDs, kt.Rid) + return nil, err + } + + sgMap := make(map[string]corecloud.BaseSecurityGroup, len(sgResult.Details)) + for _, sg := range sgResult.Details { + sgMap[sg.ID] = sg + } + + return sgMap, nil +} diff --git a/cmd/hc-service/service/service.go b/cmd/hc-service/service/service.go index 17dc2e8a15..251db22b3d 100644 --- a/cmd/hc-service/service/service.go +++ b/cmd/hc-service/service/service.go @@ -34,11 +34,13 @@ import ( argstpl "hcm/cmd/hc-service/service/argument-template" "hcm/cmd/hc-service/service/bill" "hcm/cmd/hc-service/service/capability" + "hcm/cmd/hc-service/service/cert" "hcm/cmd/hc-service/service/cvm" "hcm/cmd/hc-service/service/disk" "hcm/cmd/hc-service/service/eip" "hcm/cmd/hc-service/service/firewall" instancetype "hcm/cmd/hc-service/service/instance-type" + loadbalancer "hcm/cmd/hc-service/service/load-balancer" routetable "hcm/cmd/hc-service/service/route-table" securitygroup "hcm/cmd/hc-service/service/security-group" "hcm/cmd/hc-service/service/subnet" @@ -166,6 +168,8 @@ func (s *Service) apiSet() *restful.Container { sync.InitService(c) bill.InitBillService(c) argstpl.InitArgsTplService(c) + loadbalancer.InitLoadBalancerService(c) + cert.InitCertService(c) return restful.NewContainer().Add(c.WebService) } diff --git a/cmd/hc-service/service/sync/tcloud/cert.go b/cmd/hc-service/service/sync/tcloud/cert.go new file mode 100644 index 0000000000..29ed881dc2 --- /dev/null +++ b/cmd/hc-service/service/sync/tcloud/cert.go @@ -0,0 +1,123 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + ressync "hcm/cmd/hc-service/logics/res-sync" + "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/sync/handler" + "hcm/pkg/adaptor/types/cert" + typecore "hcm/pkg/adaptor/types/core" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" +) + +// SyncCert .... +func (svc *service) SyncCert(cts *rest.Contexts) (interface{}, error) { + return nil, handler.ResourceSync(cts, &certHandler{cli: svc.syncCli}) +} + +// certHandler sync handler. +type certHandler struct { + cli ressync.Interface + + // Prepare 构建参数 + request *sync.TCloudSyncReq + syncCli tcloud.Interface + offset uint64 +} + +var _ handler.Handler = new(certHandler) + +// Prepare ... +func (hd *certHandler) Prepare(cts *rest.Contexts) error { + request, syncCli, err := defaultPrepare(cts, hd.cli) + if err != nil { + return err + } + + hd.request = request + hd.syncCli = syncCli + + return nil +} + +// Next ... +func (hd *certHandler) Next(kt *kit.Kit) ([]string, error) { + listOpt := &cert.TCloudListOption{ + Page: &typecore.TCloudPage{ + Offset: hd.offset, + Limit: typecore.TCloudQueryLimit, + }, + } + result, err := hd.syncCli.CloudCli().ListCert(kt, listOpt) + if err != nil { + logs.Errorf("request adaptor list tcloud cert failed, opt: %v, err: %v, rid: %s", listOpt, err, kt.Rid) + return nil, err + } + + if len(result) == 0 { + return nil, nil + } + + cloudIDs := make([]string, 0, len(result)) + for _, one := range result { + cloudIDs = append(cloudIDs, converter.PtrToVal(one.CertificateId)) + } + + hd.offset += typecore.TCloudQueryLimit + return cloudIDs, nil +} + +// Sync ... +func (hd *certHandler) Sync(kt *kit.Kit, cloudIDs []string) error { + params := &tcloud.SyncBaseParams{ + AccountID: hd.request.AccountID, + Region: hd.request.Region, + CloudIDs: cloudIDs, + } + if _, err := hd.syncCli.Cert(kt, params, &tcloud.SyncCertOption{BkBizID: constant.UnassignedBiz}); err != nil { + logs.Errorf("sync tcloud cert failed, opt: %v, err: %v, rid: %s", params, err, kt.Rid) + return err + } + + return nil +} + +// RemoveDeleteFromCloud ... +func (hd *certHandler) RemoveDeleteFromCloud(kt *kit.Kit) error { + if err := hd.syncCli.RemoveCertDeleteFromCloud(kt, hd.request.AccountID, hd.request.Region); err != nil { + logs.Errorf("remove cert delete from cloud failed, accountID: %s, region: %s, err: %v, rid: %s", + hd.request.AccountID, hd.request.Region, err, kt.Rid) + return err + } + + return nil +} + +// Name get cloud resource type name +func (hd *certHandler) Name() enumor.CloudResourceType { + return enumor.CertCloudResType +} diff --git a/cmd/hc-service/service/sync/tcloud/load_balancer.go b/cmd/hc-service/service/sync/tcloud/load_balancer.go new file mode 100644 index 0000000000..e00022bb37 --- /dev/null +++ b/cmd/hc-service/service/sync/tcloud/load_balancer.go @@ -0,0 +1,123 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + ressync "hcm/cmd/hc-service/logics/res-sync" + "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/sync/handler" + typecore "hcm/pkg/adaptor/types/core" + typeclb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" +) + +// SyncLoadBalancer 同步负载均衡接口 +func (svc *service) SyncLoadBalancer(cts *rest.Contexts) (interface{}, error) { + return nil, handler.ResourceSync(cts, &lbHandler{cli: svc.syncCli}) +} + +// lbHandler lb sync handler. +type lbHandler struct { + cli ressync.Interface + + request *sync.TCloudSyncReq + syncCli tcloud.Interface + offset uint64 +} + +var _ handler.Handler = new(lbHandler) + +// Prepare ... +func (hd *lbHandler) Prepare(cts *rest.Contexts) error { + request, syncCli, err := defaultPrepare(cts, hd.cli) + if err != nil { + return err + } + + hd.request = request + hd.syncCli = syncCli + + return nil +} + +// Next ... +func (hd *lbHandler) Next(kt *kit.Kit) ([]string, error) { + listOpt := &typeclb.TCloudListOption{ + Region: hd.request.Region, + Page: &typecore.TCloudPage{ + Offset: hd.offset, + Limit: typecore.TCloudQueryLimit, + }, + } + + lbResult, err := hd.syncCli.CloudCli().ListLoadBalancer(kt, listOpt) + if err != nil { + logs.Errorf("request adaptor list tcloud load balancer failed, err: %v, opt: %v, rid: %s", err, listOpt, kt.Rid) + return nil, err + } + + if len(lbResult) == 0 { + return nil, nil + } + + cloudIDs := make([]string, 0, len(lbResult)) + for _, one := range lbResult { + cloudIDs = append(cloudIDs, converter.PtrToVal(one.LoadBalancerId)) + } + + hd.offset += typecore.TCloudQueryLimit + return cloudIDs, nil +} + +// Sync ... +func (hd *lbHandler) Sync(kt *kit.Kit, cloudIDs []string) error { + params := &tcloud.SyncBaseParams{ + AccountID: hd.request.AccountID, + Region: hd.request.Region, + CloudIDs: cloudIDs, + } + if _, err := hd.syncCli.LoadBalancerWithListener(kt, params, new(tcloud.SyncLBOption)); err != nil { + logs.Errorf("sync tcloud load balancer with rel failed, err: %v, opt: %v, rid: %s", err, params, kt.Rid) + return err + } + + return nil +} + +// RemoveDeleteFromCloud ... +func (hd *lbHandler) RemoveDeleteFromCloud(kt *kit.Kit) error { + if err := hd.syncCli.RemoveLoadBalancerDeleteFromCloud(kt, hd.request.AccountID, hd.request.Region); err != nil { + logs.Errorf("remove load balancer delete from cloud failed, err: %v, accountID: %s, region: %s, rid: %s", err, + hd.request.AccountID, hd.request.Region, kt.Rid) + return err + } + + return nil +} + +// Name load_balancer +func (hd *lbHandler) Name() enumor.CloudResourceType { + return enumor.LoadBalancerCloudResType +} diff --git a/cmd/hc-service/service/sync/tcloud/load_balancer_listener.go b/cmd/hc-service/service/sync/tcloud/load_balancer_listener.go new file mode 100644 index 0000000000..c28fbd38bb --- /dev/null +++ b/cmd/hc-service/service/sync/tcloud/load_balancer_listener.go @@ -0,0 +1,134 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + ressync "hcm/cmd/hc-service/logics/res-sync" + "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/sync/handler" + typecore "hcm/pkg/adaptor/types/core" + typeclb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" +) + +// SyncLoadBalancerListener 同步负载均衡监听器接口 +func (svc *service) SyncLoadBalancerListener(cts *rest.Contexts) (interface{}, error) { + return nil, handler.ResourceSync(cts, &lblHandler{cli: svc.syncCli}) +} + +// lblHandler lb listener sync handler. +type lblHandler struct { + cli ressync.Interface + + request *sync.TCloudListenerSyncReq + syncCli tcloud.Interface + offset uint64 +} + +var _ handler.Handler = new(lblHandler) + +// Prepare ... +func (hd *lblHandler) Prepare(cts *rest.Contexts) error { + req := new(sync.TCloudListenerSyncReq) + if err := cts.DecodeInto(req); err != nil { + return errf.NewFromErr(errf.DecodeRequestFailed, err) + } + + if err := req.Validate(); err != nil { + return errf.NewFromErr(errf.InvalidParameter, err) + } + + syncCli, err := hd.cli.TCloud(cts.Kit, req.AccountID) + if err != nil { + return err + } + + hd.request = req + hd.syncCli = syncCli + + return nil +} + +// Next ... +func (hd *lblHandler) Next(kt *kit.Kit) ([]string, error) { + listOpt := &typeclb.TCloudListListenersOption{ + Region: hd.request.Region, + LoadBalancerId: hd.request.LoadBalancerCloudID, + CloudIDs: hd.request.ListenerCloudIds, + Protocol: "", + Port: 0, + } + + lbResult, err := hd.syncCli.CloudCli().ListListener(kt, listOpt) + if err != nil { + logs.Errorf("request adaptor list tcloud load balancer failed, err: %v, opt: %v, rid: %s", err, listOpt, kt.Rid) + return nil, err + } + + if len(lbResult) == 0 { + return nil, nil + } + + cloudIDs := make([]string, 0, len(lbResult)) + for _, one := range lbResult { + cloudIDs = append(cloudIDs, one.GetCloudID()) + } + + hd.offset += typecore.TCloudQueryLimit + return cloudIDs, nil +} + +// Sync ... +func (hd *lblHandler) Sync(kt *kit.Kit, cloudIDs []string) error { + params := &tcloud.SyncListenerOfSingleLBOption{ + AccountID: hd.request.AccountID, + Region: hd.request.Region, + BizID: 0, + LBID: "", + CloudLBID: "", + } + if _, err := hd.syncCli.Listener(kt, params); err != nil { + logs.Errorf("sync tcloud load balancer with rel failed, err: %v, opt: %v, rid: %s", err, params, kt.Rid) + return err + } + + return nil +} + +// RemoveDeleteFromCloud ... +func (hd *lblHandler) RemoveDeleteFromCloud(kt *kit.Kit) error { + if err := hd.syncCli.RemoveLoadBalancerDeleteFromCloud(kt, hd.request.AccountID, hd.request.Region); err != nil { + logs.Errorf("remove load balancer delete from cloud failed, err: %v, accountID: %s, region: %s, rid: %s", err, + hd.request.AccountID, hd.request.Region, kt.Rid) + return err + } + + return nil +} + +// Name load_balancer +func (hd *lblHandler) Name() enumor.CloudResourceType { + return enumor.LoadBalancerCloudResType +} diff --git a/cmd/hc-service/service/sync/tcloud/service.go b/cmd/hc-service/service/sync/tcloud/service.go index 1fa5ac9937..cd39a34a3e 100644 --- a/cmd/hc-service/service/sync/tcloud/service.go +++ b/cmd/hc-service/service/sync/tcloud/service.go @@ -52,6 +52,8 @@ func InitService(cap *capability.Capability) { h.Add("SyncImage", "POST", "/images/sync", v.SyncImage) h.Add("SyncSubAccount", "POST", "/sub_accounts/sync", v.SyncSubAccount) h.Add("SyncArgsTpl", "POST", "/argument_templates/sync", v.SyncArgsTpl) + h.Add("SyncCert", "POST", "/certs/sync", v.SyncCert) + h.Add("SyncLoadBalancer", "POST", "/load_balancers/sync", v.SyncLoadBalancer) h.Load(cap.WebService) } diff --git a/cmd/task-server/logics/action/cli/init.go b/cmd/task-server/logics/action/cli/init.go index 29dd6cba6a..b6877479cc 100644 --- a/cmd/task-server/logics/action/cli/init.go +++ b/cmd/task-server/logics/action/cli/init.go @@ -23,9 +23,13 @@ import ( "hcm/pkg/client" dataservice "hcm/pkg/client/data-service" hcservice "hcm/pkg/client/hc-service" + "hcm/pkg/dal/dao" ) -var cliSet *client.ClientSet +var ( + cliSet *client.ClientSet + daoSet dao.Set +) // SetClientSet set client set. func SetClientSet(cli *client.ClientSet) { @@ -41,3 +45,13 @@ func GetHCService() *hcservice.Client { func GetDataService() *dataservice.Client { return cliSet.DataService() } + +// SetDaoSet set dao set. +func SetDaoSet(cli dao.Set) { + daoSet = cli +} + +// GetDaoSet get dao set. +func GetDaoSet() dao.Set { + return daoSet +} diff --git a/cmd/task-server/logics/action/init.go b/cmd/task-server/logics/action/init.go index b6559dddcc..29ccff1b28 100644 --- a/cmd/task-server/logics/action/init.go +++ b/cmd/task-server/logics/action/init.go @@ -24,15 +24,19 @@ import ( actioncvm "hcm/cmd/task-server/logics/action/cvm" actioneip "hcm/cmd/task-server/logics/action/eip" actionfirewall "hcm/cmd/task-server/logics/action/firewall" + actionlb "hcm/cmd/task-server/logics/action/load-balancer" actionsg "hcm/cmd/task-server/logics/action/security-group" actionsubnet "hcm/cmd/task-server/logics/action/subnet" + "hcm/cmd/task-server/logics/flow" "hcm/pkg/async/action" "hcm/pkg/client" + "hcm/pkg/dal/dao" ) // Init init action. -func Init(cli *client.ClientSet) { +func Init(cli *client.ClientSet, dao dao.Set) { actcli.SetClientSet(cli) + actcli.SetDaoSet(dao) register() } @@ -52,4 +56,13 @@ func register() { action.RegisterAction(actionsg.CreateHuaweiSGRuleAction{}) action.RegisterAction(actioneip.DeleteEIPAction{}) + action.RegisterAction(actionlb.AddTargetToGroupAction{}) + action.RegisterAction(actionflow.LoadBalancerOperateWatchAction{}) + action.RegisterTpl(actionflow.FlowLoadBalancerOperateWatchTpl) + action.RegisterAction(actionlb.RemoveTargetAction{}) + action.RegisterAction(actionlb.ModifyTargetPortAction{}) + action.RegisterAction(actionlb.ModifyTargetWeightAction{}) + + action.RegisterAction(actionlb.ListenerRuleAddTargetAction{}) + action.RegisterAction(actionlb.DeleteLoadBalancerAction{}) } diff --git a/cmd/task-server/logics/action/load-balancer/bind_targets.go b/cmd/task-server/logics/action/load-balancer/bind_targets.go new file mode 100644 index 0000000000..6cdfbaedee --- /dev/null +++ b/cmd/task-server/logics/action/load-balancer/bind_targets.go @@ -0,0 +1,86 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package actionlb + +import ( + actcli "hcm/cmd/task-server/logics/action/cli" + hclb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/async/action" + "hcm/pkg/async/action/run" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/logs" +) + +// --------------------------[将目标组中的RS应用到监听器或者规则]----------------------------- + +var _ action.Action = new(ListenerRuleAddTargetAction) +var _ action.ParameterAction = new(ListenerRuleAddTargetAction) + +// ListenerRuleAddTargetAction 将目标组中的RS应用到监听器或者规则 +type ListenerRuleAddTargetAction struct{} + +// ListenerRuleAddTargetOption ... +type ListenerRuleAddTargetOption struct { + LoadBalancerID string `json:"lb_id" validate:"required"` + *hclb.BatchRegisterTCloudTargetReq `json:",inline"` +} + +// Validate validate option. +func (opt ListenerRuleAddTargetOption) Validate() error { + + return validator.Validate.Struct(opt) +} + +// ParameterNew return request params. +func (act ListenerRuleAddTargetAction) ParameterNew() (params any) { + return new(ListenerRuleAddTargetOption) +} + +// Name return action name +func (act ListenerRuleAddTargetAction) Name() enumor.ActionName { + return enumor.ActionListenerRuleAddTarget +} + +// Run 将目标组中的RS绑定到监听器/规则中 +func (act ListenerRuleAddTargetAction) Run(kt run.ExecuteKit, params any) (any, error) { + opt, ok := params.(*ListenerRuleAddTargetOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + err := actcli.GetHCService().TCloud.Clb.BatchRegisterTargetToListenerRule( + kt.Kit(), opt.LoadBalancerID, opt.BatchRegisterTCloudTargetReq) + if err != nil { + logs.Errorf("fail to create register target to listener rule, err: %v, rid: %s", err, kt.Kit().Rid) + return nil, err + } + + return nil, err +} + +// Rollback 添加rs支持重入,无需回滚 +func (act ListenerRuleAddTargetAction) Rollback(kt run.ExecuteKit, params any) error { + logs.Infof(" ----------- ListenerRuleAddTargetAction Rollback -----------, params: %+v, rid: %s", + params, kt.Kit().Rid) + return nil +} diff --git a/cmd/task-server/logics/action/load-balancer/delete.go b/cmd/task-server/logics/action/load-balancer/delete.go new file mode 100644 index 0000000000..972579518b --- /dev/null +++ b/cmd/task-server/logics/action/load-balancer/delete.go @@ -0,0 +1,131 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package actionlb + +import ( + "fmt" + + actcli "hcm/cmd/task-server/logics/action/cli" + hcproto "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/async/action" + "hcm/pkg/async/action/run" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/logs" + "hcm/pkg/tools/json" + + "github.com/tidwall/gjson" +) + +// --------------------------[删除负载均衡]----------------------------- + +var _ action.Action = new(DeleteLoadBalancerAction) +var _ action.ParameterAction = new(DeleteLoadBalancerAction) + +// DeleteLoadBalancerAction 删除负载均衡 +type DeleteLoadBalancerAction struct{} + +// DeleteLoadBalancerOption ... +type DeleteLoadBalancerOption struct { + Vendor enumor.Vendor `json:"vendor,omitempty" validate:"required"` + hcproto.TCloudBatchDeleteLoadbalancerReq `json:",inline"` +} + +// MarshalJSON DeleteLoadBalancerOption. +func (opt DeleteLoadBalancerOption) MarshalJSON() ([]byte, error) { + + var req interface{} + switch opt.Vendor { + case enumor.TCloud: + req = struct { + Vendor enumor.Vendor `json:"vendor" validate:"required"` + hcproto.TCloudBatchDeleteLoadbalancerReq `json:",inline"` + }{ + Vendor: opt.Vendor, + TCloudBatchDeleteLoadbalancerReq: opt.TCloudBatchDeleteLoadbalancerReq, + } + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + return json.Marshal(req) +} + +// UnmarshalJSON DeleteLoadBalancerOption. +func (opt *DeleteLoadBalancerOption) UnmarshalJSON(raw []byte) (err error) { + opt.Vendor = enumor.Vendor(gjson.GetBytes(raw, "vendor").String()) + + switch opt.Vendor { + case enumor.TCloud: + err = json.Unmarshal(raw, &opt.TCloudBatchDeleteLoadbalancerReq) + default: + return fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + return err +} + +// Validate validate option. +func (opt DeleteLoadBalancerOption) Validate() error { + + return validator.Validate.Struct(opt) +} + +// ParameterNew return request params. +func (act DeleteLoadBalancerAction) ParameterNew() (params any) { + return new(DeleteLoadBalancerOption) +} + +// Name return action name +func (act DeleteLoadBalancerAction) Name() enumor.ActionName { + return enumor.ActionDeleteLoadBalancer +} + +// Run 将目标组中的RS绑定到监听器/规则中 +func (act DeleteLoadBalancerAction) Run(kt run.ExecuteKit, params any) (any, error) { + + opt, ok := params.(*DeleteLoadBalancerOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + var err error + switch opt.Vendor { + case enumor.TCloud: + err = actcli.GetHCService().TCloud.Clb.BatchDeleteLoadBalancer(kt.Kit(), &opt.TCloudBatchDeleteLoadbalancerReq) + if err != nil { + logs.Errorf("fail to delete tcloud load balancer, err: %v, opt: %+v rid: %s", + err, opt.TCloudBatchDeleteLoadbalancerReq, kt.Kit().Rid) + return nil, err + } + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + return nil, nil +} + +// Rollback 无需回滚 +func (act DeleteLoadBalancerAction) Rollback(kt run.ExecuteKit, params any) error { + logs.Infof(" ----------- DeleteLoadBalancerAction Rollback -----------, params: %+v, rid: %s", + params, kt.Kit().Rid) + return nil +} diff --git a/cmd/task-server/logics/action/load-balancer/target_group_rs.go b/cmd/task-server/logics/action/load-balancer/target_group_rs.go new file mode 100644 index 0000000000..e98204483d --- /dev/null +++ b/cmd/task-server/logics/action/load-balancer/target_group_rs.go @@ -0,0 +1,302 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package actionlb + +import ( + "fmt" + + actcli "hcm/cmd/task-server/logics/action/cli" + hclb "hcm/pkg/api/hc-service/load-balancer" + "hcm/pkg/async/action" + "hcm/pkg/async/action/run" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/logs" + "hcm/pkg/tools/json" + + "github.com/tidwall/gjson" +) + +// SaveRsCloudIDKey 批量操作RS成功的监听器ID列表 +const SaveRsCloudIDKey = "batch_rs_cloud_ids" + +// --------------------------[批量添加RS到目标组]----------------------------- + +var _ action.Action = new(AddTargetToGroupAction) +var _ action.ParameterAction = new(AddTargetToGroupAction) + +// AddTargetToGroupAction define add rs action. +type AddTargetToGroupAction struct{} + +// OperateRsOption define operate rs option. +type OperateRsOption struct { + Vendor enumor.Vendor `json:"vendor" validate:"required"` + hclb.TCloudBatchOperateTargetReq `json:",inline"` +} + +// MarshalJSON marshal json. +func (opt OperateRsOption) MarshalJSON() ([]byte, error) { + var req interface{} + switch opt.Vendor { + case enumor.TCloud: + req = struct { + Vendor enumor.Vendor `json:"vendor" validate:"required"` + hclb.TCloudBatchOperateTargetReq `json:",inline"` + }{ + Vendor: opt.Vendor, + TCloudBatchOperateTargetReq: opt.TCloudBatchOperateTargetReq, + } + + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + return json.Marshal(req) +} + +// UnmarshalJSON unmarshal json. +func (opt *OperateRsOption) UnmarshalJSON(raw []byte) (err error) { + opt.Vendor = enumor.Vendor(gjson.GetBytes(raw, "vendor").String()) + + switch opt.Vendor { + case enumor.TCloud: + err = json.Unmarshal(raw, &opt.TCloudBatchOperateTargetReq) + default: + return fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + return err +} + +// Validate validate option. +func (opt OperateRsOption) Validate() error { + if err := opt.Vendor.Validate(); err != nil { + return err + } + + var req validator.Interface + switch opt.Vendor { + case enumor.TCloud: + req = &opt.TCloudBatchOperateTargetReq + default: + return fmt.Errorf("vendor: %s not support", opt.Vendor) + } + + if err := req.Validate(); err != nil { + return err + } + + return nil +} + +// ParameterNew return request params. +func (act AddTargetToGroupAction) ParameterNew() (params interface{}) { + return new(OperateRsOption) +} + +// Name return action name +func (act AddTargetToGroupAction) Name() enumor.ActionName { + return enumor.ActionTargetGroupAddRS +} + +// Run add target. +func (act AddTargetToGroupAction) Run(kt run.ExecuteKit, params interface{}) (interface{}, error) { + opt, ok := params.(*OperateRsOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + var result *hclb.BatchCreateResult + var err error + switch opt.Vendor { + case enumor.TCloud: + result, err = actcli.GetHCService().TCloud.Clb.BatchAddRs( + kt.Kit(), opt.TargetGroupID, &opt.TCloudBatchOperateTargetReq) + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + if err != nil { + logs.Errorf("batch add rs failed, err: %v, result: %+v, rid: %s", err, result, kt.Kit().Rid) + return result, err + } + + if len(result.FailedCloudIDs) != 0 { + return result, errf.Newf(errf.PartialFailed, "batch add rs rs partially failed, failCloudIDs: %v", + result.FailedCloudIDs) + } + + if err = kt.ShareData().AppendIDs(kt.Kit(), SaveRsCloudIDKey, result.SuccessCloudIDs...); err != nil { + logs.Errorf("share data appendIDs failed, err: %v, rid: %s", err, kt.Kit().Rid) + return result, err + } + + return result, nil +} + +// Rollback 批量添加RS失败时的回滚Action,此处不需要回滚处理 +func (act AddTargetToGroupAction) Rollback(kt run.ExecuteKit, params interface{}) error { + logs.Infof(" ----------- AddTargetToGroupAction Rollback -----------, params: %s, rid: %s", params, kt.Kit().Rid) + return nil +} + +// --------------------------[批量移除RS]----------------------------- + +var _ action.Action = new(RemoveTargetAction) +var _ action.ParameterAction = new(RemoveTargetAction) + +// RemoveTargetAction define remove rs action. +type RemoveTargetAction struct{} + +// ParameterNew return request params. +func (act RemoveTargetAction) ParameterNew() (params interface{}) { + return new(OperateRsOption) +} + +// Name return action name +func (act RemoveTargetAction) Name() enumor.ActionName { + return enumor.ActionTargetGroupRemoveRS +} + +// Run remove rs. +func (act RemoveTargetAction) Run(kt run.ExecuteKit, params interface{}) (interface{}, error) { + opt, ok := params.(*OperateRsOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + var result *hclb.BatchCreateResult + var err error + switch opt.Vendor { + case enumor.TCloud: + _, err = actcli.GetHCService().TCloud.Clb.BatchRemoveTarget( + kt.Kit(), opt.TargetGroupID, &opt.TCloudBatchOperateTargetReq) + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + if err != nil { + logs.Errorf("batch remove rs failed, err: %v, rid: %s", err, kt.Kit().Rid) + return result, err + } + + return result, nil +} + +// Rollback 批量移除RS失败时的回滚Action,此处不需要回滚处理 +func (act RemoveTargetAction) Rollback(kt run.ExecuteKit, params interface{}) error { + logs.Infof(" ----------- RemoveTargetAction Rollback -----------, params: %s, rid: %s", params, kt.Kit().Rid) + return nil +} + +// --------------------------[批量修改RS端口]----------------------------- + +var _ action.Action = new(ModifyTargetPortAction) +var _ action.ParameterAction = new(ModifyTargetPortAction) + +// ModifyTargetPortAction define modify rs port action. +type ModifyTargetPortAction struct{} + +// ParameterNew return request params. +func (act ModifyTargetPortAction) ParameterNew() (params interface{}) { + return new(OperateRsOption) +} + +// Name return action name +func (act ModifyTargetPortAction) Name() enumor.ActionName { + return enumor.ActionTargetGroupModifyPort +} + +// Run modify target port. +func (act ModifyTargetPortAction) Run(kt run.ExecuteKit, params interface{}) (interface{}, error) { + opt, ok := params.(*OperateRsOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + var result *hclb.BatchCreateResult + var err error + switch opt.Vendor { + case enumor.TCloud: + err = actcli.GetHCService().TCloud.Clb.BatchModifyTargetPort( + kt.Kit(), opt.TargetGroupID, &opt.TCloudBatchOperateTargetReq) + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + if err != nil { + logs.Errorf("batch modify target port failed, err: %v, rid: %s", err, kt.Kit().Rid) + return result, err + } + + return result, nil +} + +// Rollback 批量修改RS端口失败时的回滚Action,此处不需要回滚处理 +func (act ModifyTargetPortAction) Rollback(kt run.ExecuteKit, params interface{}) error { + logs.Infof(" ----------- ModifyTargetPortAction Rollback -----------, params: %s, rid: %s", params, kt.Kit().Rid) + return nil +} + +// --------------------------[批量修改RS权重]----------------------------- + +var _ action.Action = new(ModifyTargetWeightAction) +var _ action.ParameterAction = new(ModifyTargetWeightAction) + +// ModifyTargetWeightAction define modify target weight action. +type ModifyTargetWeightAction struct{} + +// ParameterNew return request params. +func (act ModifyTargetWeightAction) ParameterNew() (params interface{}) { + return new(OperateRsOption) +} + +// Name return action name +func (act ModifyTargetWeightAction) Name() enumor.ActionName { + return enumor.ActionTargetGroupModifyWeight +} + +// Run modify target port. +func (act ModifyTargetWeightAction) Run(kt run.ExecuteKit, params interface{}) (interface{}, error) { + opt, ok := params.(*OperateRsOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + var result *hclb.BatchCreateResult + var err error + switch opt.Vendor { + case enumor.TCloud: + err = actcli.GetHCService().TCloud.Clb.BatchModifyTargetWeight( + kt.Kit(), opt.TargetGroupID, &opt.TCloudBatchOperateTargetReq) + default: + return nil, fmt.Errorf("vendor: %s not support", opt.Vendor) + } + if err != nil { + logs.Errorf("batch modify target weight failed, err: %v, rid: %s", err, kt.Kit().Rid) + return result, err + } + + return result, nil +} + +// Rollback 批量修改RS权重失败时的回滚Action,此处不需要回滚处理 +func (act ModifyTargetWeightAction) Rollback(kt run.ExecuteKit, params interface{}) error { + logs.Infof(" ----------- ModifyTargetWeightAction Rollback -----------, params: %s, rid: %s", params, kt.Kit().Rid) + return nil +} diff --git a/cmd/task-server/logics/flow/build_task.go b/cmd/task-server/logics/flow/build_task.go new file mode 100644 index 0000000000..4802c3b66d --- /dev/null +++ b/cmd/task-server/logics/flow/build_task.go @@ -0,0 +1,46 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package actionflow + +import ( + "hcm/pkg/async/action" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + tableasync "hcm/pkg/dal/table/async" +) + +// FlowLoadBalancerOperateWatchTpl define flow load balancer operate watch template. +var FlowLoadBalancerOperateWatchTpl = action.FlowTemplate{ + Name: enumor.FlowLoadBalancerOperateWatch, + ShareData: tableasync.NewShareData(nil), + Tasks: []action.TaskTemplate{ + { + ActionID: "1", + ActionName: enumor.ActionLoadBalancerOperateWatch, + Retry: &tableasync.Retry{ + Enable: true, + Policy: &tableasync.RetryPolicy{ + Count: constant.FlowRetryMaxLimit, + SleepRangeMS: [2]uint{100, 200}, + }, + }, + }, + }, +} diff --git a/cmd/task-server/logics/flow/load_balancer_operate_watch.go b/cmd/task-server/logics/flow/load_balancer_operate_watch.go new file mode 100644 index 0000000000..02e26e906c --- /dev/null +++ b/cmd/task-server/logics/flow/load_balancer_operate_watch.go @@ -0,0 +1,277 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2022 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package actionflow + +import ( + "fmt" + "time" + + actcli "hcm/cmd/task-server/logics/action/cli" + "hcm/pkg/api/core" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/async/action" + "hcm/pkg/async/action/run" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/dal/dao/orm" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/dal/dao/types" + typesasync "hcm/pkg/dal/dao/types/async" + tableasync "hcm/pkg/dal/table/async" + tablelb "hcm/pkg/dal/table/cloud/load-balancer" + "hcm/pkg/kit" + "hcm/pkg/logs" + + "github.com/jmoiron/sqlx" +) + +var _ action.Action = new(LoadBalancerOperateWatchAction) +var _ action.ParameterAction = new(LoadBalancerOperateWatchAction) + +// LoadBalancerOperateWatchAction define load balancer operate watch. +type LoadBalancerOperateWatchAction struct{} + +// LoadBalancerOperateWatchOption define load balancer operate watch option. +type LoadBalancerOperateWatchOption struct { + FlowID string `json:"flow_id" validate:"required"` + // 资源ID,比如负载均衡ID + ResID string `json:"res_id" validate:"required"` + // 资源类型 + ResType enumor.CloudResourceType `json:"res_type" validate:"required"` + // 子资源ID数组,比如目标组ID + SubResIDs []string `json:"sub_res_ids" validate:"required"` + // 子资源类型 + SubResType enumor.CloudResourceType `json:"sub_res_type" validate:"required"` + // 任务类型 + TaskType enumor.TaskType `json:"task_type" validate:"required"` +} + +// Validate LoadBalancerOperateWatchOption. +func (opt LoadBalancerOperateWatchOption) Validate() error { + return opt.Validate() +} + +// ParameterNew return request params. +func (act LoadBalancerOperateWatchAction) ParameterNew() (params interface{}) { + return new(LoadBalancerOperateWatchOption) +} + +// Name return action name +func (act LoadBalancerOperateWatchAction) Name() enumor.ActionName { + return enumor.ActionLoadBalancerOperateWatch +} + +// Run flow watch. +func (act LoadBalancerOperateWatchAction) Run(kt run.ExecuteKit, params interface{}) (interface{}, error) { + opt, ok := params.(*LoadBalancerOperateWatchOption) + if !ok { + return nil, errf.New(errf.InvalidParameter, "params type mismatch") + } + + end := time.Now().Add(5 * time.Minute) + for { + if time.Now().After(end) { + return nil, fmt.Errorf("wait timeout, async task flow: %s is running", opt.FlowID) + } + + req := &types.ListOption{ + Filter: tools.EqualExpression("id", opt.FlowID), + Page: core.NewDefaultBasePage(), + } + flowList, err := actcli.GetDaoSet().AsyncFlow().List(kt.Kit(), req) + if err != nil { + logs.Errorf("list query flow failed, err: %v, flowID: %s, rid: %s", err, opt.FlowID, kt.Kit().Rid) + return nil, err + } + + if len(flowList.Details) == 0 { + logs.Infof("list query flow not found, flowID: %s, rid: %s", opt.FlowID, kt.Kit().Rid) + return nil, nil + } + + isSkip, err := act.processResFlow(kt, opt, flowList.Details[0]) + if err != nil { + return nil, err + } + // 任务已终态,无需继续处理 + if isSkip { + break + } + time.Sleep(500 * time.Millisecond) + } + + return nil, nil +} + +// processResFlow 检查Flow是否终态状态、解锁资源跟Flow的状态 +func (act LoadBalancerOperateWatchAction) processResFlow(kt run.ExecuteKit, opt *LoadBalancerOperateWatchOption, + flowInfo tableasync.AsyncFlowTable) (bool, error) { + + switch flowInfo.State { + case enumor.FlowSuccess, enumor.FlowCancel: + var resStatus enumor.ResFlowStatus + if flowInfo.State == enumor.FlowSuccess { + resStatus = enumor.SuccessResFlowStatus + } + if flowInfo.State == enumor.FlowCancel { + resStatus = enumor.CancelResFlowStatus + } + + if err := act.updateTargetGroupListenerRuleRelBindStatus(kt.Kit(), opt, flowInfo.State); err != nil { + return false, err + } + + // 解锁资源 + unlockReq := &dataproto.ResFlowLockReq{ + ResID: opt.ResID, + ResType: opt.ResType, + FlowID: opt.FlowID, + Status: resStatus, + } + return true, actcli.GetDataService().Global.LoadBalancer.ResFlowUnLock(kt.Kit(), unlockReq) + case enumor.FlowFailed: + // 当Flow失败时,检查资源锁定是否超时 + resFlowLockList, err := act.queryResFlowLock(kt, opt) + if err != nil { + return false, err + } + if len(resFlowLockList) == 0 { + return true, nil + } + + // 更新目标组与监听器的绑定状态 + if err = act.updateTargetGroupListenerRuleRelBindStatus(kt.Kit(), opt, flowInfo.State); err != nil { + return false, err + } + + createTime, err := time.Parse(constant.TimeStdFormat, string(resFlowLockList[0].CreatedAt)) + if err != nil { + return false, err + } + + nowTime := time.Now() + if nowTime.Sub(createTime).Hours() > constant.ResFlowLockExpireDays*24 { + timeoutReq := &dataproto.ResFlowLockReq{ + ResID: opt.ResID, + ResType: opt.ResType, + FlowID: opt.FlowID, + Status: enumor.TimeoutResFlowStatus, + } + return true, actcli.GetDataService().Global.LoadBalancer.ResFlowUnLock(kt.Kit(), timeoutReq) + } + return false, nil + case enumor.FlowInit: + // 需要检查资源是否已锁定 + resFlowLockList, err := act.queryResFlowLock(kt, opt) + if err != nil { + return false, err + } + if len(resFlowLockList) == 0 { + return true, nil + } + + // 如已锁定资源,则需要更新Flow状态为Pending + err = act.updateFlowStateByCAS(kt.Kit(), opt.FlowID, enumor.FlowInit, enumor.FlowPending) + if err != nil { + logs.Errorf("call taskserver to update flow state failed, err: %v, flowID: %s", err, opt.FlowID) + return false, err + } + return false, nil + default: + return false, nil + } +} + +func (act LoadBalancerOperateWatchAction) queryResFlowLock(kt run.ExecuteKit, opt *LoadBalancerOperateWatchOption) ( + []tablelb.ResourceFlowLockTable, error) { + + // 当Flow失败时,检查资源锁定是否超时 + lockReq := &types.ListOption{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("res_id", opt.ResID), + tools.RuleEqual("res_type", opt.ResType), + tools.RuleEqual("owner", opt.FlowID), + ), + Page: core.NewDefaultBasePage(), + } + resFlowLockList, err := actcli.GetDaoSet().ResourceFlowLock().List(kt.Kit(), lockReq) + if err != nil { + logs.Errorf("list query flow lock failed, err: %v, flowID: %s, rid: %s", err, opt.FlowID, kt.Kit().Rid) + return nil, err + } + return resFlowLockList.Details, nil +} + +func (act LoadBalancerOperateWatchAction) updateFlowStateByCAS(kt *kit.Kit, flowID string, + source, target enumor.FlowState) error { + + _, err := actcli.GetDaoSet().Txn().AutoTxn(kt, func(txn *sqlx.Tx, opt *orm.TxnOption) (interface{}, error) { + info := &typesasync.UpdateFlowInfo{ + ID: flowID, + Source: source, + Target: target, + } + if err := actcli.GetDaoSet().AsyncFlow().UpdateStateByCAS(kt, txn, info); err != nil { + return nil, err + } + return nil, nil + }) + if err != nil { + logs.Errorf("call taskserver to update flow watch pending state failed, err: %v, flowID: %s, "+ + "source: %s, target: %s, rid: %s", err, flowID, source, target, kt.Rid) + return err + } + return nil +} + +// updateTargetGroupListenerRuleRelBindStatus 更新目标组与监听器的绑定状态 +func (act LoadBalancerOperateWatchAction) updateTargetGroupListenerRuleRelBindStatus(kt *kit.Kit, + opt *LoadBalancerOperateWatchOption, flowState enumor.FlowState) error { + + if opt == nil || opt.TaskType != enumor.ApplyTargetGroupType || opt.SubResType != enumor.TargetGroupCloudResType { + return nil + } + + var bindStatus enumor.BindingStatus + switch flowState { + case enumor.FlowSuccess: + bindStatus = enumor.SuccessBindingStatus + case enumor.FlowCancel, enumor.FlowFailed: + bindStatus = enumor.FailedBindingStatus + default: + return nil + } + + for _, targetGroupID := range opt.SubResIDs { + if err := actcli.GetDataService().Global.LoadBalancer.BatchUpdateListenerRuleRelStatusByTGID(kt, targetGroupID, + &dataproto.TGListenerRelStatusUpdateReq{BindingStatus: bindStatus}); err != nil { + return err + } + } + return nil +} + +// Rollback Flow查询状态失败时的回滚Action,此处不需要回滚处理 +func (act LoadBalancerOperateWatchAction) Rollback(kt run.ExecuteKit, params interface{}) error { + logs.Infof(" ----------- LoadBalancerOperateWatchAction Rollback -----------, params: %s, rid: %s", + params, kt.Kit().Rid) + return nil +} diff --git a/cmd/task-server/service/controller/controller.go b/cmd/task-server/service/controller/controller.go new file mode 100644 index 0000000000..70ae290d44 --- /dev/null +++ b/cmd/task-server/service/controller/controller.go @@ -0,0 +1,110 @@ +/* + * + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +// Package controller 对异步任务的控制 +package controller + +import ( + "hcm/cmd/task-server/service/capability" + "hcm/pkg/async/consumer" + "hcm/pkg/async/producer" + "hcm/pkg/criteria/errf" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/retry" +) + +// Init initial the async service +func Init(cap *capability.Capability) { + svc := &service{ + pro: cap.Async.GetProducer(), + csm: cap.Async.GetConsumer(), + } + + h := rest.NewHandler() + + h.Add("UpdateCustomFlowState", "PATCH", "/custom_flows/state/update", svc.UpdateCustomFlowState) + h.Add("RetryFlowTask", "PATCH", "/flows/{flow_id}/tasks/{task_id}/retry", svc.RetryFlowTask) + h.Add("CancelFlow", "POST", "/flows/{flow_id}/cancel", svc.CancelFlow) + + h.Load(cap.WebService) +} + +type service struct { + pro producer.Producer + csm consumer.Consumer +} + +// UpdateCustomFlowState update custom flow state +func (p service) UpdateCustomFlowState(cts *rest.Contexts) (interface{}, error) { + opt := new(producer.UpdateCustomFlowStateOption) + if err := cts.DecodeInto(opt); err != nil { + return nil, err + } + + if err := opt.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + rty := retry.NewRetryPolicy(consumer.DefRetryCount, consumer.DefRetryRangeMS) + err := rty.BaseExec(cts.Kit, func() error { + return p.pro.BatchUpdateCustomFlowState(cts.Kit, opt) + }) + if err != nil { + logs.Errorf("taskserver batch update flow state failed, err: %v, opt: %+v, rid: %s", err, opt, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// RetryFlowTask retry flow task, requirements: given flow and task must be `failed` state +func (p service) RetryFlowTask(cts *rest.Contexts) (any, error) { + flowId := cts.PathParameter("flow_id").String() + if len(flowId) == 0 { + return nil, errf.New(errf.InvalidParameter, "flow_id is required") + } + taskId := cts.PathParameter("task_id").String() + if len(taskId) == 0 { + return nil, errf.New(errf.InvalidParameter, "task_id is required") + } + + if err := p.pro.RetryFlowTask(cts.Kit, flowId, taskId); err != nil { + logs.Errorf("task server retry task(%s) failed, err: %v, opt: %+v, rid: %s", taskId, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} + +// CancelFlow 取消任务,无条件终止 +func (p service) CancelFlow(cts *rest.Contexts) (any, error) { + // 终止任务 + flowId := cts.PathParameter("flow_id").String() + if len(flowId) == 0 { + return nil, errf.New(errf.InvalidParameter, "flow_id is required") + } + if err := p.csm.CancelFlow(cts.Kit, flowId); err != nil { + logs.Errorf("task server terminate flow(%s) failed, err: %v, opt: %+v, rid: %s", flowId, err, cts.Kit.Rid) + return nil, err + } + + return nil, nil +} diff --git a/cmd/task-server/service/producer/producer.go b/cmd/task-server/service/producer/producer.go index 58843cab3f..7ff333143e 100644 --- a/cmd/task-server/service/producer/producer.go +++ b/cmd/task-server/service/producer/producer.go @@ -41,6 +41,7 @@ func Init(cap *capability.Capability) { h.Add("CreateTemplateFlow", "POST", "/template_flows/create", svc.CreateTemplateFlow) h.Add("CreateCustomFlow", "POST", "/custom_flows/create", svc.CreateCustomFlow) + h.Add("CloneFlow", "POST", "/flows/{flow_id}/clone", svc.CloneFlow) h.Load(cap.WebService) } @@ -99,3 +100,29 @@ func (p service) CreateCustomFlow(cts *rest.Contexts) (interface{}, error) { return &core.CreateResult{ID: id}, nil } + +// CloneFlow 按原参数重新发起一次任务 +func (p service) CloneFlow(cts *rest.Contexts) (any, error) { + flowId := cts.PathParameter("flow_id").String() + if len(flowId) == 0 { + return nil, errf.New(errf.InvalidParameter, "flow_id is required") + } + + opt := new(producer.CloneFlowOption) + if err := cts.DecodeInto(opt); err != nil { + return nil, err + } + + if err := opt.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + // 复制一份flow 和task + id, err := p.pro.CloneFlow(cts.Kit, flowId, opt) + if err != nil { + logs.Errorf("fail to clone flow(%s), err: %v, rid: %s", flowId, err, cts.Kit.Rid) + return nil, err + } + + return &core.CreateResult{ID: id}, nil +} diff --git a/cmd/task-server/service/service.go b/cmd/task-server/service/service.go index 1e3db0d6a4..75e46510ca 100644 --- a/cmd/task-server/service/service.go +++ b/cmd/task-server/service/service.go @@ -30,6 +30,7 @@ import ( logicsaction "hcm/cmd/task-server/logics/action" "hcm/cmd/task-server/service/capability" + "hcm/cmd/task-server/service/controller" "hcm/cmd/task-server/service/producer" "hcm/cmd/task-server/service/viewer" "hcm/pkg/async" @@ -89,7 +90,7 @@ func NewService(sd serviced.ServiceDiscover, shutdownWaitTimeSec int) (*Service, return nil, err } - logicsaction.Init(apiClientSet) + logicsaction.Init(apiClientSet, dao) async, err := createAndStartAsync(sd, dao, shutdownWaitTimeSec) if err != nil { return nil, err @@ -227,6 +228,7 @@ func (s *Service) apiSet() *restful.Container { producer.Init(c) viewer.Init(c) + controller.Init(c) return restful.NewContainer().Add(c.WebService) } diff --git a/cmd/web-server/service/cloud/vpc/service.go b/cmd/web-server/service/cloud/vpc/service.go index 64272904c7..b8a548148d 100644 --- a/cmd/web-server/service/cloud/vpc/service.go +++ b/cmd/web-server/service/cloud/vpc/service.go @@ -42,8 +42,8 @@ func InitVpcService(c *capability.Capability) { "/bizs/{bk_biz_id}/vendors/{vendor}/vpcs/with/subnet_count/list", svc.ListVpcWithSubnetCountInBiz) // 资源下 - h.Add("ListVpcWithSubnetCountInRes", http.MethodPost, "/vendors/{vendor}/vpcs/with/subnet_count/list", - svc.ListVpcWithSubnetCountInRes) + h.Add("ListVpcWithSubnetCountInRes", http.MethodPost, + "/vendors/{vendor}/vpcs/with/subnet_count/list", svc.ListVpcWithSubnetCountInRes) h.Load(c.WebService) } diff --git a/cmd/web-server/service/cloud/vpc/vpc.go b/cmd/web-server/service/cloud/vpc/vpc.go index 4b267cceab..e4a9048229 100644 --- a/cmd/web-server/service/cloud/vpc/vpc.go +++ b/cmd/web-server/service/cloud/vpc/vpc.go @@ -382,7 +382,10 @@ func (svc *service) getVpcSubnetCount(kt *kit.Kit, vpcID, zone string, bizID int logs.Errorf("list vpc failed, err: %v, rid: %svc", err, kt.Rid) return 0, 0, err } - + // 没有指定zone的时候,当前zone下的子网为0 + if len(zone) == 0 { + return vpcResult.Count, 0, nil + } req = &core.ListReq{ Filter: &filter.Expression{ Op: filter.And, diff --git a/docs/api-docs/web-server/docs/biz/cert/create_cert.md b/docs/api-docs/web-server/docs/biz/cert/create_cert.md new file mode 100644 index 0000000000..e892f702ff --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/cert/create_cert.md @@ -0,0 +1,71 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:证书创建。 +- 该接口功能描述:业务下上传证书。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/certs/create + +### 输入参数 + +输入参数由接口通用参数和vendor对应的云厂商差异参数组成。 + +#### 接口通用参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------|------|----------------------------------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| vendor | string | 是 | 云厂商(枚举值:tcloud、aws、gcp、azure、huawei) | +| account_id | string | 是 | 账号ID | +| name | string | 是 | 证书名称 | +| memo | string | 否 | 备注 | + +#### 云厂商差异参数[tcloud] + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------|------|----------------------------------------------| +| cert_type | string | 是 | 证书类型(CA:客户端证书,SVR:服务器证书) | +| public_key | string | 是 | 证书内容,需要做base64编码 | +| private_key | string | 否 | 私钥内容,需要做base64编码,CA证书可不传该参数 | + +### 腾讯云调用示例 + +```json +{ + "vendor": "tcloud", + "account_id": "00000001", + "name": "test-cert", + "memo": "test cert", + "cert_type": "CA", + "public_key": "xxxxxx", + "private_key": "xxxxxx" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 调用数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------------| +| id | string | 证书ID | diff --git a/docs/api-docs/web-server/docs/biz/cert/delete_cert.md b/docs/api-docs/web-server/docs/biz/cert/delete_cert.md new file mode 100644 index 0000000000..8debc3191b --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/cert/delete_cert.md @@ -0,0 +1,37 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:证书删除。 +- 该接口功能描述:业务下删除证书。 + +### URL + +DELETE /api/v1/cloud/bizs/{bk_biz_id}/certs/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|-------|---------| +| bk_biz_id | int64 | 是 | 业务ID | +| id | string | 是 | 证书的ID | + +### 调用示例 + +```json +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/cert/list_cert.md b/docs/api-docs/web-server/docs/biz/cert/list_cert.md new file mode 100644 index 0000000000..cc1d1c0629 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/cert/list_cert.md @@ -0,0 +1,244 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:业务下查询证书列表。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/certs/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|------|------------| +| bk_biz_id | int | 是 | 业务ID | +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|-------------|------|----------------------------------------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|-------------|------|------------------------------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-------|--------------------------------------------------|--------------------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|--------|------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | int | 否 | 记录开始位置,start 起始值为0 | +| limit | int | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|----------------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 证书名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| account_id | string | 账号ID | +| cert_type | string | 证书类型(CA:客户端证书,SVR:服务器证书) | +| cert_status | string | 证书状态 | +| cloud_created_time | string | 上传时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | 过期时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询证书名称是Cert的列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Cert" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询证书名称是Cert的数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Cert" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "cert-123", + "name": "cert-test", + "vendor": "tcloud", + "account_id": "0000001", + "domain": [ + "xxxx.com" + ], + "cert_type": "CA", + "cert_status": "1", + "cloud_created_time": "2023-02-12 14:47:39", + "cloud_expired_time": "2022-02-22 14:47:39", + "memo": "xxxx", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|-------------------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------------|----------------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| account_id | string | 账号ID | +| domain | string array | 证书域名 | +| cert_type | string | 证书类型(CA:客户端证书,SVR:服务器证书) | +| cert_status | string | 证书状态 | +| cloud_created_time | string | 上传时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | 过期时间,标准格式:2006-01-02T15:04:05Z | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +说明: + +- 证书状态字段 cert_status ,不同云厂商的状态值不同,需要根据vendor的值,显示不同的状态 +- tcloud 的状态枚举(1:已通过 3:已过期) diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/associate_listener_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/associate_listener_target_group.md new file mode 100644 index 0000000000..fbc7476118 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/associate_listener_target_group.md @@ -0,0 +1,44 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:监听器操作。 +- 该接口功能描述:给指定的监听器,关联目标组(仅支持:tcloud)。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/listeners/associate/target_group + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------------|---------|------|--------------| +| bk_biz_id | int | 是 | 业务ID | +| listener_id | string | 是 | 监听器的ID | +| listener_rule_id | string | 是 | 监听器规则的ID | +| target_group_id | string | 是 | 目标组的ID | + +### 调用示例 + +```json +{ + "listener_id": "00000001", + "listener_rule_id": "00000002", + "target_group_id": "00001112" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/associate_load_balancer_security_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/associate_load_balancer_security_group.md new file mode 100644 index 0000000000..5d4627f4cd --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/associate_load_balancer_security_group.md @@ -0,0 +1,45 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:给指定的负载均衡,批量关联安全组(仅支持:tcloud)。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/security_groups/associate/load_balancers + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------------|--------------|----|---------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡的ID | +| security_group_ids | string array | 是 | 安全组的ID数组,最多支持50个安全组 | + +### 调用示例 + +```json +{ + "lb_id": "00001112", + "security_group_ids": [ + "00000002", + "00000003" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_clone.md b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_clone.md new file mode 100644 index 0000000000..62b878ec49 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_clone.md @@ -0,0 +1,52 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:复制flow参数重新执行(仅支持:tcloud)。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/async_flows/clone + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|---------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡id | +| flow_id | string | 是 | flow id | + +### 调用示例 + +```json +{ + "flow_id": "00001112" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "flow_id": "cccddd" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 结果信息 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|-------------| +| flow_id | string | 新生成的flow_id | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_get_result_after_terminate.md b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_get_result_after_terminate.md new file mode 100644 index 0000000000..b72df23f3d --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_get_result_after_terminate.md @@ -0,0 +1,76 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:获取任务终止后rs的状态,仅支持任务终止后五分钟内 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/async_tasks/result + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|---------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡id | +| flow_id | string | 是 | flow id | + +### 调用示例 + +```json +{ + "flow_id": "00001112" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "task_id": "0000001", + "target_group_id": "xxxxx", + "target_list": [ + { + "account_id": "xx", + "inst_type": "CVM", + "cloud_inst_id": "yyy", + "port": 80, + "weight": 10 + } + ] + } + ] +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | detail array | 结果信息 | + +#### detail + +| 参数名称 | 参数类型 | 描述 | +|-----------------|--------------|-------------------| +| task_id | string | task id | +| status | string | 结果 succeed/failed | +| target_group_id | string | 目标组ID | +| target_list | target array | 目标详情 | + +#### target + +| 参数名称 | 参数类型 | 描述 | +|---------------|--------|-------| +| account_id | string | 账号ID | +| inst_type | string | 实例类型 | +| cloud_inst_id | string | 云实例ID | +| port | int | 端口 | +| weight | int | 权重 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_terminate.md b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_terminate.md new file mode 100644 index 0000000000..a662defdea --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/async_flow_terminate.md @@ -0,0 +1,41 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:终止指定异步任务操作 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/async_flows/terminate + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|---------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡id | +| flow_id | string | 是 | flow id | + +### 调用示例 + +```json +{ + "flow_id":"xxxxx" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/async_task_retry.md b/docs/api-docs/web-server/docs/biz/load-balancer/async_task_retry.md new file mode 100644 index 0000000000..54dc724a3b --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/async_task_retry.md @@ -0,0 +1,44 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:重试异步任务操作 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/async_tasks/retry + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|---------------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡id | +| flow_id | string | 是 | flow id | +| task_id | string | 是 | 待重新执行的task id | + +### 调用示例 + +```json +{ + "flow_id": "xxxxx", + "task_id": "aaabbb" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" + +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_target_group_rs.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_target_group_rs.md new file mode 100644 index 0000000000..097e36a843 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_target_group_rs.md @@ -0,0 +1,80 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下给指定目标组批量添加RS。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/target_groups/targets/create + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| target_groups | object array | 是 | 目标组列表,单次最多10个 | + +#### target_groups + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------| +| target_group_id | string | 是 | 目标组ID | +| targets | object array | 是 | RS实例列表,单次最多100个 | + +#### targets + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------------------| +| inst_type | string | 是 | 实例类型(CVM:云服务器) | +| cloud_inst_id | string | 是 | 云实例ID | +| port | int | 是 | 端口 | +| weight | int | 是 | 权重,取值范围:[0, 100] | + +### 调用示例 + +```json +{ + "account_id": "00000001", + "target_groups": [ + { + "target_group_id": "0000000g", + "targets": [ + { + "inst_type": "CVM", + "cloud_inst_id": "cvm-xxxxxx", + "port": 8000, + "weight": 10 + } + ] + } + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "flow_id": "xxxxxxxx" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| flow_id | string | 任务id | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_tcloud_rules.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_tcloud_rules.md new file mode 100644 index 0000000000..565aa35b88 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_create_tcloud_rules.md @@ -0,0 +1,92 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下创建腾讯云规则。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/listeners/{lbl_id}/rules/create + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------------------|--------------|----|-------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | +| url | string | 是 | 监听的url | +| target_group_id | string | 是 | 目标组id | +| domains | string array | 否 | 域名 | +| session_expire_time | int | 否 | 会话过期时间 | +| scheduler | string | 否 | 均衡方式 | +| forward_type | string | 否 | 转发类型 | +| default_server | bool | 否 | 默认服务 | +| http2 | bool | 否 | http2 | +| target_type | string | 否 | 目标类型 | +| quic | bool | 否 | quic 开关 | +| trpc_func | string | 否 | trpc函数 | +| trpc_callee | string | 否 | trpc 调用者 | +| certificate | object | 否 | 证书信息,当协议为HTTPS时必传 | + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|--------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | ca证书的云ID | +| cert_cloud_ids | string array | 服务端证书的云ID | + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的监听器列表。 + +```json +{ + "rules": [ + { + "url": "/testcreate0", + "domains": [ + "domain1.com" + ], + "session_expire_time": 30, + "scheduler": "WRR", + "forward_type": "HTTP", + "default_server": true, + "http2": true + }, + { + "url": "/testcreate1", + "domains": [ + "domain1.com" + ], + "session_expire_time": 30, + "scheduler": "WRR", + "forward_type": "HTTP", + "default_server": true, + "http2": true + } + ] +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": null +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_listener.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_listener.md new file mode 100644 index 0000000000..ff9d37fa6e --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_listener.md @@ -0,0 +1,43 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:监听器删除。 +- 该接口功能描述:业务下删除监听器。 + +### URL + +DELETE /api/v1/cloud/bizs/{bk_biz_id}/listeners/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------------|------|------------------------| +| bk_biz_id | int | 是 | 业务ID | +| ids | string array | 是 | 监听器ID数组,最大支持20个 | + +### 调用示例 + +```json +{ + "ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_load_balancer.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_load_balancer.md new file mode 100644 index 0000000000..bc594a2af6 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_load_balancer.md @@ -0,0 +1,43 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡删除。 +- 该接口功能描述:业务下批量删除负载均衡。有监听器的负载均衡不能删除。 + +### URL + +DELETE /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------------|----|-----------| +| bk_biz_id | int64 | 是 | 业务ID | +| ids | string array | 是 | 负载均衡的ID列表 | + +### 调用示例 + +```json +{ + "ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group.md new file mode 100644 index 0000000000..6041803afc --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group.md @@ -0,0 +1,43 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:目标组删除。 +- 该接口功能描述:业务下删除目标组。 + +### URL + +DELETE /api/v1/cloud/bizs/{bk_biz_id}/target_groups/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------------|------|------------| +| bk_biz_id | int | 是 | 业务ID | +| ids | string array | 是 | 目标组ID数组 | + +### 调用示例 + +```json +{ + "ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group_rs.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group_rs.md new file mode 100644 index 0000000000..fe44555c12 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_target_group_rs.md @@ -0,0 +1,65 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下给指定目标组批量移除RS + +### URL + +DELETE /api/v1/cloud/bizs/{bk_biz_id}/target_groups/targets/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| target_groups | object array | 是 | 目标组列表,单次最多10个 | + +#### target_groups + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------| +| target_group_id | string | 是 | 目标组ID | +| target_ids | string array | 是 | 目标ID数组,单次最多100个 | + +### 调用示例 + +```json +{ + "account_id": "00000001", + "target_groups": [ + { + "target_group_id": "0000000g", + "target_ids": ["00000001"] + } + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "flow_id": "xxxxxxxx" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| flow_id | string | 任务id | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule.md new file mode 100644 index 0000000000..7c926d7dae --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule.md @@ -0,0 +1,45 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下删除腾讯云URL规则 + +### URL + +DELETE /api/v1/cloud/vendors/tcloud/listeners/{lbl_id}/rules/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------------|--------------|----|-------------------------| +| bk_biz_id | int | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | +| rule_ids | string array | 否 | URL规则ID数组 | + + +### 调用示例 + +```json +{ + "rule_ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule_by_domain.md b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule_by_domain.md new file mode 100644 index 0000000000..f5252e8fe1 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/batch_delete_tcloud_url_rule_by_domain.md @@ -0,0 +1,45 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下按域名删除腾讯云URL规则 + +### URL + +DELETE /api/v1/cloud/vendors/tcloud/listeners/{lbl_id}/rules/by/domains/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------------|--------------|----|-------------------------| +| bk_biz_id | int | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | +| domains | string array | 是 | 按域名删除数组 | +| new_default_domain | string | 否 | 新默认域名,删除的域名是默认域名的时候需要指定 | + +### 调用示例 + +```json +{ + "domains": [ + "qweqwe.com", + "kkkk.com" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/create_listener.md b/docs/api-docs/web-server/docs/biz/load-balancer/create_listener.md new file mode 100644 index 0000000000..43d371c897 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/create_listener.md @@ -0,0 +1,79 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:监听器创建。 +- 该接口功能描述:业务下创建监听器。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/listeners/create + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|------------------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| lb_id | string | 是 | 负载均衡ID | +| name | string | 是 | 名称 | +| protocol | string | 是 | 协议 | +| port | int | 是 | 端口 | +| scheduler | string | 是 | 均衡方式(WRR:按权重轮询 LEAST_CONN:最小连接数、IP_HASH:IP Hash) | +| session_type | string | 是 | 会话保持类型(NORMAL表示默认会话保持类型。QUIC_CID表示根据Quic Connection ID做会话保持) | +| session_expire | int | 是 | 会话保持时间,最小值30秒 | +| target_group_id | string | 是 | 目标组ID | +| domain | string | 否 | 默认域名,当协议为HTTP、HTTPS时必传 | +| url | string | 否 | URL路径,当协议为HTTP、HTTPS时必传 | +| sni_switch | int | 否 | 是否开启SNI特性(0:关闭 1:开启),当协议为HTTPS时必传 | +| certificate | object | 否 | 证书信息,当协议为HTTPS时必传 | + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|------------------|--------------|--------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | CA证书的ID | +| cert_cloud_ids | string array | 服务端证书的ID数组 | + +### 调用示例 + +```json +{ + "account_id": "0000001", + "name": "xxx", + "protocol": "TCP", + "port": 22, + "scheduler": "WRR", + "session_type": "NORMAL", + "session_expire": 30, + "target_group_id": "00000001", + "domain": "www.xxxx.com", + "url": "/api/url" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| id | string | 监听器id | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/create_load_balancer.md b/docs/api-docs/web-server/docs/biz/load-balancer/create_load_balancer.md new file mode 100644 index 0000000000..69fbf2a9ea --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/create_load_balancer.md @@ -0,0 +1,107 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡创建。 +- 该接口功能描述:业务下创建负载均衡。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/create + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|----------------------------|--------------|----|---------------------------------------------------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| region | string | 是 | 地域 | +| load_balancer_type | string | 是 | 网络类型 公网 OPEN,内网 INTERNAL | +| name | string | 是 | 名称 | +| zones | string array | 否 | 主可用区,仅限公网型 | +| backup_zones | string array | 否 | 备可用区,目前仅广州、上海、南京、北京、中国香港、首尔地域的 IPv4 版本的 CLB 支持主备可用区。 | +| address_ip_version | string | 否 | ip版本,IPV4,IPV6(ipv6 nat64),IPv6FullChain(ipv6) | +| cloud_vpc_id | string | 是 | 云VpcID | +| cloud_subnet_id | string | 否 | 云子网ID ,内网型必填 | +| vip | string | 否 | 绑定已有eip的ip地址,,ipv6 nat64 不支持 | +| cloud_eip_id | string | 否 | 绑定eip id | +| vip_isp | string | 否 | 运营商类型仅公网,枚举值:CMCC,CUCC,CTCC,BGP。通过TCloudDescribeResource 接口确定 | +| internet_charge_type | string | 否 | 网络计费模式 | +| internet_max_bandwidth_out | int64 | 否 | 最大出带宽,单位Mbps | +| bandwidth_package_id | string | 否 | 带宽包id,计费模式为带宽包计费时必填 | +| sla_type | string | 否 | 性能容量型规格, 留空为共享型 | +| auto_renew | boolean | 否 | 按月付费自动续费 | +| require_count | int | 是 | 购买数量 | +| memo | string | 否 | 备注 | + +#### 网络计费模式取值范围: + +- `TRAFFIC_POSTPAID_BY_HOUR` 按流量按小时后计费 +- `BANDWIDTH_POSTPAID_BY_HOUR` 按带宽按小时后计费 +- `BANDWIDTH_PACKAGE` 带宽包计费 + +#### sla_type 性能容量型规格取值范围: + +- `clb.c2.medium` 标准型规格 +- `clb.c3.small` 高阶型1规格 +- `clb.c3.medium` 高阶型2规格 +- `clb.c4.small` 超强型1规格 +- `clb.c4.medium` 超强型2规格 +- `clb.c4.large` 超强型3规格 +- `clb.c4.xlarge` 超强型4规格 + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "region": "ap-hk", + "zone": "ap-hk-1", + "backup_zones": [], + "name": "xxx", + "load_balance_type": "INTERNAL", + "cloud_vpc_id": "vpc-123", + "cloud_subnet_id": "subnet-123", + "address_ip_version": "IPV4", + "vip": "1.2.3.4", + "vip_isp": "BGP", + "charge_type": "TRAFFIC_POSTPAID_BY_HOUR", + "sla_type": "clb.c2.medium", + "internet_max_bandwidth_out": 10, + "auto_renew": true, + "required_count": 1, + "memo": "" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|--------------| +| unknown_cloud_ids | string | 未知创建状态的lb id | +| success_cloud_ids | string | 成功创建的lb id | +| failed_cloud_ids | string | 创建失败的lb id | +| failed_message | string | 失败原因 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/create_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/create_target_group.md new file mode 100644 index 0000000000..17c8fdb0a5 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/create_target_group.md @@ -0,0 +1,84 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:目标组创建。 +- 该接口功能描述:业务下创建目标组。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/target_groups/create + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| name | string | 是 | 名称 | +| protocol | string | 是 | 协议 | +| port | int | 是 | 端口 | +| region | string | 是 | 地域 | +| cloud_vpc_id | string array | 是 | 云端vpc的ID数组 | +| memo | string | 否 | 备注 | +| rs_list | object array | 否 | RS列表 | + +#### rs_list + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|-----------------------------------| +| inst_type | string | 是 | 实例类型(CVM:云服务器) | +| cloud_inst_id | string | 是 | 云实例ID | +| port | int | 是 | 端口 | +| weight | string | 是 | 权重 | + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "name": "xxx", + "protocol": "TCP", + "port": 22, + "region": "ap-hk", + "cloud_vpc_id": ["xxxx", "xxxx"] + "memo": "", + "rs_list": [ + { + "inst_type": "CVM", + "cloud_inst_id": "cvm-xxxxxx", + "port": 8000, + "weight": 10 + } + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| id | string | 目标组id | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/disassociate_load_balancer_security_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/disassociate_load_balancer_security_group.md new file mode 100644 index 0000000000..16e6f01fc0 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/disassociate_load_balancer_security_group.md @@ -0,0 +1,42 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限: 负载均衡操作。 +- 该接口功能描述:给指定的负载均衡,取消与安全组的关联(仅支持:tcloud)。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/security_groups/disassociate/load_balancers + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------------------|--------|----|--------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡ID | +| security_group_id | string | 是 | 安全组ID | + +### 调用示例 + +```json +{ + "lb_id": "00001112", + "security_group_id": "00001111" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_async_task_flow.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_async_task_flow.md new file mode 100644 index 0000000000..c4e8185dd6 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_async_task_flow.md @@ -0,0 +1,57 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询异步任务Flow详情。 + +### URL + +GET /api/v1/cloud/async_task/flows/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|--------|------|----------| +| id | string | 是 | 异步任务ID | + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001", + "name": "xxxxxx", + "state": "failed", + "reason": null, + "creator": "admin", + "reviser": "admin", + "created_at": "2024-01-01T19:31:58Z", + "updated_at": "2024-01-01T19:32:40Z" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|------------|----------|----------------------------------------| +| id | string | 异步任务ID | +| name | string | 异步任务名称 | +| state | string | 任务状态(初始状态:init 等待中:pending 待调度:scheduled 执行中:running 已取消:canceled 成功:success 失败:failed) | +| reason | string | 任务失败原因 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_biz_tcloud_url_rule.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_biz_tcloud_url_rule.md new file mode 100644 index 0000000000..11414f8248 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_biz_tcloud_url_rule.md @@ -0,0 +1,84 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:获取业务下腾讯云规则详情。 + +### URL + +GET /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/listeners/{lbl_id}/rules/{rule_id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int64 | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | + +### 调用示例 +```json +{} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000005", + "cloud_id": "loc-abcde", + "name": "loc-005", + "rule_type": "layer_7", + "lb_id": "00000001", + "cloud_lb_id": "lb-123456", + "lbl_id": "00000002", + "cloud_lbl_id": "lbl-xyz", + "target_group_id": "00000003", + "cloud_target_group_id": "lbtg-xxxx", + "domain": "www.qq.com", + "url": "/test", + "scheduler": "WRR", + "sni_switch": 0, + "session_type": "NORMAL", + "session_expire": 0, + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| lb_id | string | 负载均衡id | +| cloud_lb_id | string | 云上负载均衡id | +| lbl_id | string | 所属监听器id | +| cloud_lbl_id | string | 所属监听器云上id | +| domain | string | 监听的域名 | +| url | string | 监听的url | +| scheduler | string | 调度器 | +| sni_switch | int | sni开关 | +| session_type | string | 会话保持类型 | +| session_expire | string | 会话过期时间 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_listener.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_listener.md new file mode 100644 index 0000000000..3ae20b5a83 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_listener.md @@ -0,0 +1,144 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询监听器详情。 + +### URL + +GET /api/v1/cloud/bizs/{bk_biz_id}/listeners/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|-------| +| bk_biz_id | int64 | 是 | 业务ID | +| id | string | 是 | 监听器ID | + +### 调用示例 + +#### 获取详细信息请求参数示例 + +```json +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001", + "name": "listener-name", + "cloud_id": "listener-123", + "vendor": "tcloud", + "account_id": "0000001", + "bk_biz_id": -1, + "lb_id": "xxxx", + "cloud_lb_id": "lb-xxxx", + "protocol": "HTTP", + "port": 8080, + "target_group_id": "tg-001", + "target_group_name": "tg-name", + "cloud_target_group_id": "cloud-tg-001", + "scheduler": "WRR", + "session_type": "NORMAL", + "session_expire": 0, + "health_check": { + "health_switch": 1, + "time_out": 2, + "interval_time": 5, + "health_num": 3, + "un_health_num": 3, + "check_port": 80, + "check_type": "HTTP", + "http_version": "HTTP/1.0", + "http_check_path": "/", + "http_check_domain": "www.weixin.com", + "http_check_method": "GET", + "source_ip_type": 1 + }, + "certificate": { + "ssl_mode": "MUTUAL", + "cert_id": "cert-001", + "cert_ca_id": "ca-001", + "ext_cert_ids": [ + "ext-001" + ] + }, + "domain_num": 50, + "url_num": 100, + "memo": "memo-test", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|-----------------------|--------|--------------------------------| +| id | int | 监听器ID | +| name | string | 监听器名称 | +| cloud_id | string | 云监听器ID | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| bk_biz_id | int64 | 业务ID | +| lb_id | string | 负载均衡ID | +| cloud_lb_id | string | 云负载均衡ID | +| protocol | string | 协议 | +| port | int | 端口 | +| target_group_id | string | 目标组ID | +| target_group_name | string | 目标组名称 | +| cloud_target_group_id | string | 云目标组ID | +| scheduler | string | 负载均衡方式 | +| session_type | string | 会话保持类型 | +| session_expire | int | 会话保持时间,0为关闭 | +| health_check | object | 健康检查 | +| certificate | object | 证书信息 | +| domain_num | int | 域名数量 | +| url_num | int | URL数量 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +### health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-------------------------------------------------------------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP/HTTP/HTTPS/GRPC/PING/CUSTOM | +| http_version | string | HTTP版本 | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|--------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | CA证书的云ID | +| cert_cloud_ids | string array | 服务端证书的云ID | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer.md new file mode 100644 index 0000000000..1fa16e3511 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer.md @@ -0,0 +1,134 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询负载均衡详情。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | string | 是 | 业务id | +| id | string | 是 | 负载均衡id | + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001", + "cloud_id": "lb-asdfefe", + "name": "test", + "vendor": "tcloud", + "account_id": "00000001", + "bk_biz_id": 1234, + "ip_version": "ipv4", + "lb_type": "OPEN", + "region": "ap-guangzhou", + "zones": [ + "ap-guangzhou-1" + ], + "backup_zones": [], + "vpc_id": "00000001", + "cloud_vpc_id": "vpc-abcdef", + "subnet_id": "", + "cloud_subnet_id": "", + "private_ipv4_addresses": [], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "1.1.1.1" + ], + "public_ipv6_addresses": [], + "domain": "", + "status": "1", + "cloud_created_time": "2024-01-02 15:04:05", + "cloud_status_time": "2024-01-02 15:04:05", + "cloud_expired_time": "", + "memo": null, + "creator": "admin", + "reviser": "admin", + "created_at": "2024-01-02T15:04:05Z", + "updated_at": "2024-01-02T15:04:05Z", + "extension": { + "vip_isp": "BGP" + } + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| private_ipv4_addresses | string array | 内网ipv4地址 | +| private_ipv6_addresses | string array | 内网ipv6地址 | +| public_ipv4_addresses | string array | 外网ipv4地址 | +| public_ipv6_addresses | string array | 外网ipv6地址 | +| cloud_created_time | string | lb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | lb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | lb过期时间,标准格式:2006-01-02T15:04:05Z | +| extension | object | 拓展 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +##### TCloud status 状态含义: + +| 状态值 | 含义 | +|-----|------| +| 0 | 创建中 | +| 1 | 正常运行 | + +#### data.extension[tcloud] + +腾讯云拓展字段 + +| 参数名称 | 参数类型 | 描述 | +|------------------------------|--------|---------------------------------------------| +| sla_type | string | 性能容量型规格。 | +| vip_isp | string | 运营商类型。 | +| load_balancer_pass_to_target | string | Target是否放通来自CLB的流量。 | +| internet_max_bandwidth_out | string | 最大出带宽,单位Mbps, | +| internet_charge_type | string | 计费模式 | +| bandwidthpkg_sub_type | string | 带宽包的类型 | +| bandwidth_package_id | string | 带宽包ID | +| ipv6_mode | string | IP地址版本为ipv6时此字段有意义, IPv6Nat64/IPv6FullChain | +| snat | string | snat | +| snat_pro | string | 是否开启SnatPro。 | +| snat_ips | string | 开启SnatPro负载均衡后,SnatIp列表。 | +| delete_protect | string | 删除保护 | +| egress | string | 网络出口 | +| mix_ip_target | string | 双栈混绑 | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer_lock_status.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer_lock_status.md new file mode 100644 index 0000000000..3ab9694811 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_load_balancer_lock_status.md @@ -0,0 +1,50 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询负载均衡状态锁定详情。 + +### URL + +GET /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{id}/lock/status + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|------|-----------| +| bk_biz_id | string | 是 | 业务id | +| id | string | 是 | 负载均衡id | + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "res_id": "00000001", + "res_type": "xxxxxx", + "flow_id": "xxxxxx", + "status": "executing" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|----------|-----------------------------------------| +| res_id | string | 当前锁定的资源ID | +| res_type | string | 当前锁定的资源类型(load_balancer:负载均衡) | +| flow_id | string | 当前锁定的任务ID | +| status | string | 锁定状态(锁定中:executing 未锁定:success) | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_flow.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_flow.md new file mode 100644 index 0000000000..9e6a2a048b --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_flow.md @@ -0,0 +1,121 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询异步任务的操作记录详情。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/audits/async_flow/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|------|----------------| +| bk_biz_id | int | 是 | 业务ID | +| audit_id | int | 是 | 操作记录ID | +| flow_id | string | 是 | 任务ID | + +### 调用示例 + +```json +{ + "audit_id": 1001, + "flow_id": "00000001" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "flow": { + "id": "00000001", + "name": "add_rs", + "state": "success", + "reason": { + "message": "some tasks failed to be executed" + }, + "share_data": { + "lb_id": "00000001" + }, + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + }, + "tasks": [ + { + "id": "00000004", + "action_id": "00000001", + "action_name": "add_rs", + "state": "failed", + "reason": { + "message": "some tasks failed to be executed" + }, + "creator": "sync-timing-admin", + "reviser": "sync-timing-admin", + "created_at": "2023-02-25T18:28:46Z", + "updated_at": "2023-02-27T19:14:33Z" + } + ] + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|--------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data.flow + +| 参数名称 | 参数类型 | 描述 | +|----------------|---------|----------------------------------------------------| +| id | string | 任务ID | +| name | string | 任务名称 | +| state | string | 任务状态 | +| reason | json | 任务失败原因 | +| share_data | json | 任务共享数据 | +| creator | string | 任务创建者 | +| reviser | string | 任务最后一次修改的修改者 | +| created_at | string | 任务创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 任务最后一次修改时间,标准格式:2006-01-02T15:04:05Z | + +#### data.tasks + +| 参数名称 | 参数类型 | 描述 | +|----------------|---------|------------------------------------------------------| +| id | string | 子任务自增ID | +| action_id | string | 子任务ID | +| action_name | string | 子任务名称 | +| state | string | 子任务状态 | +| reason | json | 子任务失败原因 | +| creator | string | 子任务创建者 | +| reviser | string | 子任务最后一次修改的修改者 | +| created_at | string | 子任务创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 子任务最后一次修改时间,标准格式:2006-01-02T15:04:05Z | + +#### data.flow.reason + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|---------| +| message | string | 任务失败原因 | + +#### data.flow.share_data + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|-----------| +| lb_id | string | 负载均衡ID | + +#### data.tasks[n].reason + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|--------------| +| message | string | 子任务失败原因 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_task.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_task.md new file mode 100644 index 0000000000..9b114276c7 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_record_async_task.md @@ -0,0 +1,156 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询异步任务的操作记录指定子任务的详情。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/audits/async_task/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|------|----------------| +| bk_biz_id | int | 是 | 业务ID | +| audit_id | int | 是 | 操作记录ID | +| flow_id | string | 是 | 任务ID | +| action_id | string | 是 | 子任务ID | + +### 调用示例 + +```json +{ + "audit_id": 1001, + "flow_id": "00000001", + "action_id": 1 +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "flow": { + "id": "00000001", + "name": "add_rs", + "state": "success", + "reason": { + "message": "some tasks failed to be executed" + }, + "share_data": { + "lb_id": "00000001" + }, + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + }, + "tasks": [ + { + "id": "00000004", + "flow_id": "00000001", + "flow_name": "add_rs", + "action_name": "add_rs", + "params": { + "vendor": "tcloud", + "targets": [] + }, + "state": "failed", + "reason": { + "message": "some tasks failed to be executed" + }, + "creator": "sync-timing-admin", + "reviser": "sync-timing-admin", + "created_at": "2023-02-25T18:28:46Z", + "updated_at": "2023-02-27T19:14:33Z" + } + ] + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|--------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data.flow + +| 参数名称 | 参数类型 | 描述 | +|----------------|---------|----------------------------------------------------| +| id | string | 任务ID | +| name | string | 任务名称 | +| state | string | 任务状态 | +| reason | json | 任务失败原因 | +| creator | string | 任务创建者 | +| reviser | string | 任务最后一次修改的修改者 | +| created_at | string | 任务创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 任务最后一次修改时间,标准格式:2006-01-02T15:04:05Z | + +#### data.tasks + +| 参数名称 | 参数类型 | 描述 | +|----------------|---------|------------------------------------------------------| +| id | string | 子任务ID | +| action_name | string | 子任务名称 | +| flow_id | string | 任务ID | +| flow_name | string | 任务名称 | +| params | json | 子任务参数 | +| state | string | 子任务状态 | +| reason | json | 子任务失败原因 | +| creator | string | 子任务创建者 | +| reviser | string | 子任务最后一次修改的修改者 | +| created_at | string | 子任务创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 子任务最后一次修改时间,标准格式:2006-01-02T15:04:05Z | + +#### data.flow.reason + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|---------| +| message | string | 任务失败原因 | + +#### data.flow.share_data + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|-----------| +| lb_id | string | 负载均衡ID | + +#### data.tasks[n].reason + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|--------------| +| message | string | 子任务失败原因 | + + +#### data.tasks[n].params(针对负载均衡操作的参数) + +| 参数名称 | 参数类型 | 描述 | +|---------|----------------|-------| +| vendor | string | 云厂商 | +| targets | object array | RS列表 | + + +#### data.tasks[n].params[n].targets(针对负载均衡操作的参数) + +| 参数名称 | 参数类型 | 描述 | +|----------------------|--------------|--------------------------| +| account_id | string | 账号ID | +| inst_type | string | 实例类型 | +| inst_name | string | 实例名称 | +| cloud_inst_id | string | 云实例ID | +| port | int | 端口 | +| weight | int | 权重 | +| new_port | int | 新端口,仅限批量修改端口时有值 | +| new_weight | int | 新权重,仅限批量修改权重时有值 | +| target_group_id | string | 目标组ID | +| private_ip_addresses | string array | 内网IP地址 | +| public_ip_addresses | string array | 外网IP地址 | +| cloud_vpc_ids | string array | 云端VpcID | +| zone | string | 可用区 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/get_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/get_target_group.md new file mode 100644 index 0000000000..74df59703c --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/get_target_group.md @@ -0,0 +1,150 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询目标组详情。 + +### URL + +GET /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|-----|---------| +| bk_biz_id | int | 是 | 业务ID | +| id | string | 是 | 目标组ID | + +### 调用示例 + +#### 获取详细信息请求参数示例 + +```json +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001", + "cloud_id": "clb-123", + "name": "clb-test", + "vendor": "tcloud", + "account_id": "0000001", + "bk_biz_id": -1, + "target_group_type": "local", + "region": "ap-hk", + "protocol": "TCP", + "port": 22, + "weight": 22, + "health_check": { + "health_switch": 1, + "time_out": 2, + "interval_time": 5, + "health_num": 3, + "un_health_num": 3, + "check_port": 80, + "check_type": "HTTP", + "http_version": "HTTP/1.0", + "http_check_path": "/", + "http_check_domain": "www.weixin.com", + "http_check_method": "GET", + "source_ip_type": 1 + }, + "target_list": [ + { + "id": "tg-xxxx", + "account_id": "0000001", + "inst_id": "inst-0000001", + "inst_name": "inst-xxxx", + "cloud_inst_id": "cloud-inst-0000001", + "inst_type": "cvm", + "target_group_id": "0000001", + "cloud_target_group_id": "cloud-tg-0000001", + "port": 80, + "weight": 80, + "private_ip_address": [], + "public_ip_address": [], + "zone": "" + } + ], + "memo": "memo-test", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|------------------------|----------------|---------------------------------------| +| id | int | 目标组ID | +| cloud_id | string | 云目标组ID | +| name | string | 目标组名称 | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| bk_biz_id | int | 业务ID | +| target_group_type | string | 目标组类型 | +| region | string | 地域 | +| protocol | string | 协议 | +| port | int | 端口 | +| weight | int | 权重 | +| vpc_id | string array | vpcID数组 | +| health_check | object | 健康检查 | +| target_list | object array | 目标列表 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +### data.health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP | HTTP | HTTPS | GRPC | PING | CUSTOM | +| http_version | string | HTTP版本 | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | + +### data.target_list + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|-----------| +| id | string | 目标ID | +| account_id | string | 账号ID | +| inst_id | string | 实例ID | +| inst_name | string | 实例名称 | +| cloud_inst_id | string | 云实例ID | +| inst_type | string | 实例类型 | +| target_group_id | string | 目标组ID | +| cloud_target_group_id | string | 云目标组ID | +| port | int | 端口 | +| weight | int | 权重 | +| private_ip_address | string array | 私有IP数组 | +| public_ip_address | string array | 公有IP数组 | +| zone | string | 可用区 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_load_balancer.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_load_balancer.md new file mode 100644 index 0000000000..e1fa73d67e --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_load_balancer.md @@ -0,0 +1,289 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:列出业务下的负载均衡。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int64 | 是 | 业务ID | +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| cloud_created_time | string | lb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | lb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | lb过期时间,标准格式:2006-01-02T15:04:05Z | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的Cvm列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的Cvm数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "lb-123", + "name": "lb-test", + "vendor": "tcloud", + "bk_biz_id": -1, + "account_id": "0000001", + "region": "ap-hk", + "main_zones": [ + "ap-hk-1" + ], + "backup_zones": [ + "ap-hk-2", + "ap-hk-3" + ], + "cloud_vpc_id": "vpc-123", + "vpc_id": "00000002", + "network_type": "ipv4", + "domain": "", + "memo": "lb test", + "status": "init", + "private_ipv4_addresses": [ + "127.0.0.1" + ], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "127.0.0.2" + ], + "public_ipv6_addresses": [], + "cloud_created_time": "2023-02-12T14:47:39Z", + "cloud_status_time": "2023-02-12T14:47:39Z", + "cloud_expired_time": "2023-02-12T14:47:39Z", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|----------------| +| count | uint64 | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|---------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| private_ipv4_addresses | string array | 内网ipv4地址 | +| private_ipv6_addresses | string array | 内网ipv6地址 | +| public_ipv4_addresses | string array | 外网ipv4地址 | +| public_ipv6_addresses | string array | 外网ipv6地址 | +| cloud_created_time | string | 负载均衡在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | 负载均衡状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | 负载均衡过期时间,标准格式:2006-01-02T15:04:05Z | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +##### TCloud status 状态含义: + +| 状态值 | 含义 | +|-----|------| +| 0 | 创建中 | +| 1 | 正常运行 | + + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group.md new file mode 100644 index 0000000000..ae405527fd --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group.md @@ -0,0 +1,296 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:业务下查询目标组。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/target_groups/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int | 是 | 业务ID | +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| target_group_type | string | 目标组类型 | +| region | string | 地域 | +| protocol | string | 协议 | +| port | int | 端口 | +| weight | int | 权重 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的目标组列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的目标组数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "clb-123", + "name": "clb-test", + "vendor": "tcloud", + "account_id": "0000001", + "bk_biz_id": -1, + "target_group_type": "local", + "region": "ap-hk", + "protocol": "TCP", + "port": 22, + "weight": 22, + "memo": "memo-test", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z", + "lb_id": "xxxx", + "lb_name": "xxxx", + "cloud_lb_id": "lb-xxxx", + "private_ipv4_addresses": [ + "127.0.0.1" + ], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "127.0.0.1" + ], + "public_ipv6_addresses": [], + "listener_num": 10, + "health_check": { + "health_switch": 1, + "time_out": 2, + "interval_time": 5, + "health_num": 3, + "un_health_num": 3, + "http_code": null, + "check_type": "TCP", + "source_ip_type": 0 + } + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|-------|----------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| bk_biz_id | int | 业务ID | +| target_group_type | string | 目标组类型 | +| region | string | 地域 | +| protocol | string | 协议 | +| port | int | 端口 | +| weight | int | 权重 | +| vpc_id | string array | vpcID数组 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | +| lb_id | string | 负载均衡ID | +| lb_name | string | 云负载均衡名称 | +| cloud_lb_id | string | 云负载均衡ID | +| health_check | health_check | 健康检查 | +| private_ipv4_addresses | string array | 负载均衡的内网IPv4地址 | +| private_ipv6_addresses | string array | 负载均衡的内网IPv6地址 | +| public_ipv4_addresses | string array | 负载均衡的外网IPv4地址 | +| public_ipv6_addresses | string array | 负载均衡的外网IPv6地址 | +| listener_num | int | 绑定的监听器数量 | + +### health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-------------------------------------------------------------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP/HTTP/HTTPS/GRPC/PING/CUSTOM | +| http_version | string | HTTP版本 | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group_targets.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group_targets.md new file mode 100644 index 0000000000..4b02436cbf --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_biz_target_group_targets.md @@ -0,0 +1,245 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:业务下查询目标组中的RS列表。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{tg_id}/targets/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int | 是 | 业务ID | +| tg_id | string | 是 | 目标组ID | +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|----------------------|--------------|--------------------------------| +| id | string | | +| inst_name | string | 绑定的实例名称 | +| inst_type | string | 实例类型 | +| account_id | string | 账号ID | +| port | int | 端口 | +| weight | int | 权重 | +| private_ip_addresses | string array | 内网IP地址 | +| public_ip_addresses | string array | 外网IP地址 | +| zone | string | 可用区 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的RS列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的RS数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000003", + "account_id": "00002024", + "inst_type": "cvm", + "cloud_inst_id": "ins-abcdefg", + "inst_name": "server1", + "port": 81, + "weight": 10, + "private_ip_address": [ + "10.10.10.10" + ], + "public_ip_address": ["1.2.3.4"], + "zone": "ap-guangzhou", + "memo": "", + "creator": "admin", + "reviser": "admin", + "created_at": "2024-02-29T11:52:42Z", + "updated_at": "2024-02-29T15:26:33Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|-------|----------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|----------------------|--------------|--------------------------------| +| id | string | | +| inst_name | string | 绑定的实例名称 | +| inst_type | string | 实例类型 | +| account_id | string | 账号ID | +| port | int | 端口 | +| weight | int | 权重 | +| private_ip_addresses | string array | 内网IP地址 | +| public_ip_addresses | string array | 外网IP地址 | +| zone | string | 可用区 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_listener_count_by_load_balancer_id.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_listener_count_by_load_balancer_id.md new file mode 100644 index 0000000000..e5ba4e4039 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_listener_count_by_load_balancer_id.md @@ -0,0 +1,69 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询负载均衡下的监听器数量。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/listeners/count + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------------|------|--------------| +| bk_biz_id | int64 | 是 | 业务ID | +| lb_ids | string array | 是 | 负载均衡ID数组 | + +### 调用示例 + +```json +{ + "lb_ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "lb_id": "00000001", + "num": 10 + }, + { + "lb_id": "00000002", + "num": 2 + } + ] + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object array | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|--------------| +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|----------|---------|------------| +| lb_id | string | 负载均衡ID | +| num | int | 监听器数量 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_by_load_balancer_id.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_by_load_balancer_id.md new file mode 100644 index 0000000000..0ac34ee956 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_by_load_balancer_id.md @@ -0,0 +1,265 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询指定的负载均衡绑定的监听器列表。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{lb_id}/listeners/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int | 是 | 业务ID | +| lb_id | string | 是 | 负载均衡ID | +| filter | object | 否 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint | 否 | 记录开始位置,start 起始值为0 | +| limit | uint | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| bk_biz_id | int | 业务ID | +| lb_id | string | 负载均衡ID | +| cloud_lb_id | string | 云负载均衡ID | +| protocol | string | 协议 | +| port | int | 端口 | +| default_domain | string | 默认域名 | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的监听器列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 10 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的监听器数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "lbl-123", + "name": "listener-test", + "vendor": "tcloud", + "account_id": "0000001", + "bk_biz_id": -1, + "lb_id": "xxxx", + "cloud_lb_id": "xxxx", + "protocol": "https", + "port": 80, + "default_domain": "www.qq.com", + "zones": [ + "ap-xxx" + ], + "target_group_id": "tg-00000001", + "scheduler": "IP_HASH", + "domain_num": 50, + "url_num": 100, + "rs_weight_zero_num": 2, + "rs_weight_non_zero_num": 8, + "binding_status": "binding", + "memo": "cvm test", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|-------|----------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud) | +| account_id | string | 账号ID | +| bk_biz_id | int | 业务ID | +| lb_id | string | 负载均衡ID | +| cloud_lb_id | string | 云负载均衡ID | +| protocol | string | 协议 | +| port | string | 端口 | +| default_domain | string | 默认域名 | +| zones | string array | 可用区数组 | +| target_group_id | string | 目标组ID | +| scheduler | string array | 负载均衡方式数组 | +| domain_num | int | 域名数量 | +| url_num | int | URL数量 | +| rs_weight_zero_num | int | 权重为0的RS数量 | +| rs_weight_non_zero_num | int | 权重不为0的RS数量 | +| binding_status | string | 绑定状态(success:成功 failed:失败 binding:绑定中 partial_failed:部分失败) | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_rules_by_target_group_id.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_rules_by_target_group_id.md new file mode 100644 index 0000000000..2c25c8e2ff --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_listeners_rules_by_target_group_id.md @@ -0,0 +1,343 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询指定的目标组绑定的监听器/规则 列表。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/target_groups/{target_group_id}/rules/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------------|--------|----|--------| +| bk_biz_id | int | 是 | 业务ID | +| target_group_id | string | 是 | 目标组ID | +| filter | object | 否 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint | 否 | 记录开始位置,start 起始值为0 | +| limit | uint | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------|--------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 资源名称 | +| lbl_id | string | 监听器ID | +| cloud_lbl_id | string | 云监听器ID | +| lb_id | string | 负载均衡ID | +| cloud_lb_id | string | 云负载均衡ID | +| url | string | 关联的URL | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的监听器列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 10 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的监听器数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "loc-123", + "name": "loc-test", + "lbl_id": "xxxx", + "lbl_name": "xxxx", + "cloud_lbl_id": "lbl-xxxx", + "lb_id": "xxxx", + "lb_name": "xxxx", + "cloud_lb_id": "lb-xxxx", + "target_group_id": "xxxx", + "cloud_target_group_id": "cloud-tg-xxxx", + "private_ipv4_addresses": [ + "127.0.0.1" + ], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "127.0.0.1" + ], + "public_ipv6_addresses": [], + "protocol": "https", + "port": 80, + "domain": "www.qq.com", + "url": "/", + "scheduler": "WRR", + "sni_switch": 0, + "session_type": "NORMAL", + "session_expire": 0, + "health_check": { + "health_switch": 1, + "time_out": 2, + "interval_time": 5, + "health_num": 3, + "un_health_num": 3, + "check_port": 80, + "check_type": "HTTP", + "http_version": "HTTP/1.0", + "http_check_path": "/", + "http_check_domain": "www.weixin.com", + "http_check_method": "GET", + "source_ip_type": 1 + }, + "certificate": { + "ssl_mode": "MUTUAL", + "ca_cloud_id": "ca-001", + "cert_cloud_ids": [ + "cert-001" + ] + }, + "inst_type": "cvm", + "vpc_id": "vpc-123", + "vpc_name": "vpc-name", + "cloud_vpc_id": "cloud-vpc-123", + "memo": "listener test", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|-------|----------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 资源名称 | +| rule_type | string | URL规则类型 | +| lbl_id | string | 监听器ID | +| lbl_name | string | 监听器名称 | +| cloud_lbl_id | string | 云监听器ID | +| lb_id | string | 负载均衡ID | +| lb_name | string | 云负载均衡名称 | +| cloud_lb_id | string | 云负载均衡ID | +| target_group_id | string | 目标组ID | +| cloud_target_group_id | string | 云目标组ID | +| private_ipv4_addresses | string array | 负载均衡的内网IPv4地址 | +| private_ipv6_addresses | string array | 负载均衡的内网IPv6地址 | +| public_ipv4_addresses | string array | 负载均衡的外网IPv4地址 | +| public_ipv6_addresses | string array | 负载均衡的外网IPv6地址 | +| protocol | string | 协议 | +| port | string | 端口 | +| domain | string | 关联的域名 | +| url | string | 关联的URL | +| scheduler | string | 均衡方式 | +| sni_switch | int | 是否开启SNI特性,此参数仅适用于HTTPS监听器 | +| session_type | string | 会话保持类型 | +| session_expire | int | 会话保持时间,0为关闭 | +| health_check | object | 健康检查 | +| certificate | object | 证书信息 | +| inst_type | string | 资源实例类型 | +| vpc_id | string | VPCID | +| vpc_name | string | VPC名称 | +| cloud_vpc_id | string | 云VPCID | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +### health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-------------------------------------------------------------------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP/HTTP/HTTPS/GRPC/PING/CUSTOM | +| http_code | string | 健康检查类型 | +| http_version | string | HTTP版本 | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | +| context_type | string | 健康检查的输入格式,可取值:HEX或TEXT; | + +### http_code 取值说明 + +| 值 | 说明 | +|----|-------------------| +| 1 | 表示探测后返回值 1xx 代表健康 | +| 2 | 表示返回 2xx 代表健康 | +| 4 | 表示返回 3xx 代表健康 | +| 8 | 表示返回 4xx 代表健康, | +| 16 | 表示返回 5xx 代表健康。 | + +若希望多种返回码都可代表健康,则将相应的值相加。 + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|------------------|--------------|-------------------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | CA证书的云ID | +| cert_cloud_ids | string array | 服务端证书的云ID | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_security_group_by_res_id.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_security_group_by_res_id.md new file mode 100644 index 0000000000..7d6999cb09 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_security_group_by_res_id.md @@ -0,0 +1,82 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询负载均衡绑定安全组列表。 + +### URL + +GET /api/v1/cloud/bizs/{bk_biz_id}/security_groups/res/{res_type}/{res_id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|------| +| bk_biz_id | int64 | 是 | 业务ID | +| res_id | string | 是 | 资源ID | +| res_type | string | 是 | 资源类型 | + +### 调用示例 + +```json +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": [ + { + "id": "00000004", + "vendor": "tcloud", + "cloud_id": "sg-xxxxx", + "region": "ap-guangzhou", + "name": "security-group", + "memo": "security group", + "account_id": "00000003", + "bk_biz_id": -1, + "creator": "sync-timing-admin", + "reviser": "sync-timing-admin", + "created_at": "2023-02-25T18:28:46Z", + "updated_at": "2023-02-27T19:14:33Z", + "res_id": "0000000x", + "res_type": "load_balancer", + "priority": 1, + "rel_creator": "Jim", + "rel_created_at": "2023-02-27T19:31:33Z" + } + ] +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object array | 响应数据 | + +#### data[n] + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|----------------------------------------------------| +| id | string | 安全组ID | +| vendor | string | 云厂商 | +| cloud_id | string | 安全组云ID | +| bk_biz_id | int64 | 业务ID, -1代表未分配业务 | +| region | string | 地域 | +| name | string | 安全组名称 | +| memo | string | 安全组备注 | +| account_id | string | 安全组账号ID | +| creator | string | 安全组创建者 | +| reviser | string | 安全组最后一次修改的修改者 | +| created_at | string | 安全组创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 安全组最后一次修改时间,标准格式:2006-01-02T15:04:05Z | +| res_id | string | 资源ID | +| res_type | string | 资源类型 | +| priority | int64 | 安全组排序ID | +| rel_creator | string | 负载均衡和安全组绑定操作人(azure该字段为空) | +| rel_created_at | string | 负载均衡和安全组绑定时间(azure该字段为空,标准格式:2006-01-02T15:04:05Z) | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_target_health_by_target_group_id.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_target_health_by_target_group_id.md new file mode 100644 index 0000000000..37a7946c4d --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_target_health_by_target_group_id.md @@ -0,0 +1,105 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询指定的目标组绑定的负载均衡下的端口健康信息。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{target_group_id}/targets/health + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------------|--------------|-----|---------------| +| bk_biz_id | int | 是 | 业务ID | +| target_group_id | string | 是 | 目标组ID | +| cloud_lb_ids | string array | 是 | 云负载均衡ID数组 | + +### 调用示例 + +```json +{ + "cloud_lb_ids": ["lb-xxxxxx"] +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "cloud_lb_id": "lb-prcn8tfk", + "listeners": [ + { + "cloud_lbl_id": "lbl-eu8ct24u", + "protocol": "HTTP", + "listener_name": "lt-test-001", + "rules": [ + { + "cloud_rule_id": "loc-g06bng5g", + "health_check": { + "health_num": 0, + "un_health_num": 4, + } + } + ] + } + ] + } + ] + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|-------------------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### details + +| 参数名称 | 参数类型 | 描述 | +|-------------|--------|--------------------| +| cloud_lb_id | string | 云负载均衡ID | +| listeners | array | 查询返回的监听器数组 | + +#### listeners + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|-------------------| +| cloud_lbl_id | string | 云监听器ID | +| listener_name | string | 云监听器名称 | +| protocol | string | 云监听器协议 | +| health_check | object | 4层监听器的健康检查 | +| rules | array | 7层规则的数组 | + +#### rules + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|-----------------| +| cloud_rule_id | string | 云规则ID | +| health_check | object | 7层规则的健康检查 | + +#### health_check + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|-----------| +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_domains.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_domains.md new file mode 100644 index 0000000000..4e10f2a57b --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_domains.md @@ -0,0 +1,69 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:业务下腾讯云监听器域名列表。注意:域名非实体,查询条件是规则的。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/listeners/{lbl_id}/domains/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|-------| +| bk_biz_id | int64 | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | + + +### 调用示例 +```json +{} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "default_domain": "www.qq.com.cn", + "domain_list": [ + { + "domain": "www.qq.com.cn", + "url_count": 3 + }, + { + "domain": "www.weixin.com", + "url_count": 1 + } + ] + } +} +``` + + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|--------| +| default_domain | string | 默认域名 | +| domain_list | array | 域名信息列表 | + +#### data.domain_list[n] + +| 参数名称 | 参数类型 | 描述 | +|-----------|--------|-------| +| domain | string | 监听的域名 | +| url_count | int | url数量 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_rules.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_rules.md new file mode 100644 index 0000000000..87c885d660 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_biz_listener_rules.md @@ -0,0 +1,253 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:业务下腾讯云监听器规则列表。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/listeners/{lbl_id}/rules/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int64 | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| lb_id | string | 负载均衡id | +| cloud_lb_id | string | 云上负载均衡id | +| lbl_id | string | 所属监听器id | +| cloud_lbl_id | string | 所属监听器云上id | +| domain | string | 监听的域名 | +| url | string | 监听的url | +| scheduler | string | 均衡方式 | +| sni_switch | int | sni开关 | +| session_type | string | 会话保持类型 | +| session_expire | string | 会话过期时间 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的监听器列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "domain", + "op": "eq", + "value": "www.qq.com" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的监听器数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "domain", + "op": "eq", + "value": "www.qq.com" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000005", + "cloud_id": "loc-abcde", + "name": "loc-005", + "rule_type": "layer_7", + "lb_id": "00000001", + "cloud_lb_id": "lb-123456", + "lbl_id": "00000002", + "cloud_lbl_id": "lbl-xyz", + "target_group_id": "00000003", + "cloud_target_group_id": "lbtg-xxxx", + "domain": "www.qq.com", + "url": "/test-target-group", + "scheduler": "WRR", + "sni_switch": 0, + "session_type": "NORMAL", + "session_expire": 0, + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|----------------| +| count | uint64 | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|--------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| lb_id | string | 负载均衡id | +| cloud_lb_id | string | 云上负载均衡id | +| lbl_id | string | 所属监听器id | +| cloud_lbl_id | string | 所属监听器云上id | +| domain | string | 监听的域名 | +| url | string | 监听的url | +| scheduler | string | 调度器 | +| sni_switch | int | sni开关 | +| session_type | string | 会话保持类型 | +| session_expire | string | 会话过期时间 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_load_balancer_account_quota.md b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_load_balancer_account_quota.md new file mode 100644 index 0000000000..d0aae0b736 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/list_tcloud_load_balancer_account_quota.md @@ -0,0 +1,70 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:获取腾讯云账号负载均衡的配额。 + +### URL + +POST /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/load_balancers/accounts/{account_id}/quotas + +### 请求参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------|--------|------|-------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| region | string | 是 | 地域 | + +### 调用示例 + +#### 请求参数示例 + +查询腾讯云账号的负载均衡配额。 +```json +{ + "region": "ap-guangzhou" +} +``` + +#### 返回参数示例 + +查询腾讯云账号配额响应。 +```json +{ + "code": 0, + "message": "", + "data": [ + { + "quota_id": "TOTAL_OPEN_CLB_QUOTA", + "quota_current": null, + "quota_limit": 10 + } + ] +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | array object | 响应数据 | + +#### data[tcloud] + +| 参数名称 | 参数类型 | 描述 | +|----------------|---------|--------------------------------| +| quota_id | string | 配额名称 | +| quota_current | int | 当前使用数量,为 null 时表示无意义 | +| quota_limit | int | 配额数量 | + +#### 配额名称,取值范围: + - TOTAL_OPEN_CLB_QUOTA:用户当前地域下的公网CLB配额 + - TOTAL_INTERNAL_CLB_QUOTA:用户当前地域下的内网CLB配额 + - TOTAL_LISTENER_QUOTA:一个CLB下的监听器配额 + - TOTAL_LISTENER_RULE_QUOTA:一个监听器下的转发规则配额 + - TOTAL_TARGET_BIND_QUOTA:一条转发规则下可绑定设备的配额 + - TOTAL_SNAP_IP_QUOTA: 一个CLB实例下跨地域2.0的SNAT IP配额 + - TOTAL_ISP_CLB_QUOTA:用户当前地域下的三网CLB配额 diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/tcloud_update_load_balancer.md b/docs/api-docs/web-server/docs/biz/load-balancer/tcloud_update_load_balancer.md new file mode 100644 index 0000000000..715184ff8b --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/tcloud_update_load_balancer.md @@ -0,0 +1,48 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:更新腾讯云负载均衡云上属性。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/load_balancers/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------------------|---------|----|---------------------------------------------------------------------------------------------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| id | string | 是 | 负载均衡ID | +| name | string | 否 | 名字 | +| internet_charge_type | string | 否 | 计费模式 TRAFFIC_POSTPAID_BY_HOUR 按流量按小时后计费 ; BANDWIDTH_POSTPAID_BY_HOUR 按带宽按小时后计费; BANDWIDTH_PACKAGE 带宽包计费 | +| internet_max_bandwidth_out | int64 | 否 | 最大出带宽,单位Mbps | +| delete_protect | boolean | 否 | 删除保护 | +| load_balancer_pass_to_target | boolean | 否 | Target是否放通来自CLB的流量。开启放通(true):只验证CLB上的安全组;不开启放通(false):需同时验证CLB和后端实例上的安全组。 | +| memo | string | 否 | 备注 | + +接口调用者可以根据以上参数自行根据更新场景设置更新的字段,除了ID之外的更新字段至少需要填写一个。 + +### 调用示例 + +```json +{ + "memo": "default clb" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_domain.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_domain.md new file mode 100644 index 0000000000..16b368a2e5 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_domain.md @@ -0,0 +1,60 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:域名更新。 +- 该接口功能描述:业务下更新域名。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/listeners/{lbl_id}/domains + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|----------------|--------|----|----------------------------------------------------------| +| bk_biz_id | int | 是 | 业务ID | +| lbl_id | string | 是 | 监听器ID | +| domain | string | 是 | 域名 | +| new_domain | string | 否 | 新域名, 需要修改域名时填此参数 | +| default_server | bool | 否 | 是否设为默认域名,一个监听器下只能设置一个默认域名。如果不想修改请不要传改参数,传false可以取消当前默认域名 | +| certificate | object | 否 | 证书信息 | + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|--------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | CA证书的云ID | +| cert_cloud_ids | string array | 服务端证书的云ID | + +### 调用示例 + +```json +{ + "domain": "www.old.com", + "new_domain": "www.new.com", + "certificate": { + "ssl_mode": "MUTUAL", + "ca_cloud_id": "ca-001", + "cert_cloud_ids": [ + "cert-001" + ] + } +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_listener.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_listener.md new file mode 100644 index 0000000000..d2428ad386 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_listener.md @@ -0,0 +1,58 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:监听器更新。 +- 该接口功能描述:业务下更新监听器。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/listeners/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------|--------|----|------| +| bk_biz_id | int | 是 | 业务ID | +| account_id | string | 是 | 账号ID | +| name | string | 是 | 名称 | +| extension | object | 否 | 拓展信息 | + +#### extension + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------------|--------|----|--------------------------| +| certificate | object | 否 | 证书信息, 非SNI类型HTTPS监听器可以修改 | + +### certificate + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------------|--------------------------------------| +| ssl_mode | string | 认证类型,UNIDIRECTIONAL:单向认证,MUTUAL:双向认证 | +| ca_cloud_id | string | CA证书的云ID | +| cert_cloud_ids | string array | 服务端证书的云ID | + +### 调用示例 + +```json +{ + "account_id": "0000001", + "name": "xxx" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_load_balancer_basic.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_load_balancer_basic.md new file mode 100644 index 0000000000..31447a57ae --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_load_balancer_basic.md @@ -0,0 +1,43 @@ +### 描述 + +- 该接口提供版本:v1.0.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:更新负载均衡。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/load_balancers/{id}/basic + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|----|--------| +| bk_biz_id | int64 | 是 | 业务ID | +| id | string | 是 | 负载均衡ID | +| memo | string | 否 | 备注 | + +接口调用者可以根据以上参数自行根据更新场景设置更新的字段,除了ID之外的更新字段至少需要填写一个。 + +### 调用示例 + +```json +{ + "memo": "default clb" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group.md new file mode 100644 index 0000000000..1e83c3ef65 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group.md @@ -0,0 +1,59 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:目标组更新。 +- 该接口功能描述:业务下更新目标组基本信息。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{id} + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------------|----|------------| +| id | string | 是 | 目标组ID | +| name | string | 是 | 名称 | +| protocol | string | 是 | 协议 | +| port | int | 是 | 端口 | +| region | string | 是 | 地域 | +| cloud_vpc_id | string array | 是 | 云端vpc的ID数组 | +| memo | string | 否 | 备注 | + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "name": "xxx", + "protocol": "TCP", + "port": 22, + "region": "ap-hk", + "cloud_vpc_id": [ + "xxxx", + "xxxx" + ], + "memo": "" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_health_check.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_health_check.md new file mode 100644 index 0000000000..ac42c1dd50 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_health_check.md @@ -0,0 +1,88 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:目标组更新。 +- 该接口功能描述:业务下更新目标组健康检查。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{id}/health_check + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------------|----|--------| +| id | string | 是 | 目标组ID | +| health_check | health_check | 是 | 健康检查信息 | + +### health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-----------------------------------------------------------------------------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP/HTTP/HTTPS/GRPC/PING/CUSTOM | +| http_code | int | 健康检查类型 (仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_version | string | HTTP版本 健康检查协议CheckType的值取HTTP时,必传此字段,代表后端服务的HTTP版本:HTTP/1.0、HTTP/1.1;(仅适用于TCP监听器) | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | +| context_type | string | 健康检查的输入格式,可取值:HEX或TEXT; | +| send_context | string | 自定义探测相关参数。健康检查协议CheckType的值取CUSTOM时,必填此字段,代表健康检查返回的结果,只允许ASCII可见字符,最大长度限制500。 | +| recv_context | string | 自定义探测相关参数。健康检查协议CheckType的值取CUSTOM时,必填此字段,代表健康检查返回的结果,只允许ASCII可见字符,最大长度限制500 | +| extended_code | string | GRPC健康检查状态码(仅适用于后端转发协议为GRPC的规则).默认值为 12,可输入值为数值、多个数值, 或者范围,例如 20 或 20,25 或 0-99 | + +#### http_code 取值范围 + +可选值:1~31,默认 31。 + +1 表示探测后返回值 1xx 代表健康 +2 表示返回 2xx 代表健康 +4 表示返回 3xx 代表健康 +8 表示返回 4xx 代表健康 +16 表示返回 5xx 代表健康。 + +若希望多种返回码都可代表健康,则将相应的值相加。 + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "name": "xxx", + "protocol": "TCP", + "port": 22, + "region": "ap-hk", + "cloud_vpc_id": [ + "xxxx", + "xxxx" + ], + "memo": "" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_port.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_port.md new file mode 100644 index 0000000000..7483f50300 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_port.md @@ -0,0 +1,54 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下批量修改RS端口。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{target_group_id}/targets/port + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|------------------------| +| bk_biz_id | int | 是 | 业务ID | +| target_group_id | string | 是 | 目标组ID | +| target_ids | string array | 是 | 目标ID数组,单次最多20个 | +| new_port | int | 是 | 新端口 | + +### 调用示例 + +```json +{ + "target_ids": ["00000001"], + "new_port": 8081 +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "flow_id": "xxxxxxxx" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| flow_id | string | 任务id | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_weight.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_weight.md new file mode 100644 index 0000000000..34245cd9a7 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_target_group_rs_weight.md @@ -0,0 +1,54 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下批量修改RS权重。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/target_groups/{target_group_id}/targets/weight + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------------|--------------|------|------------------------| +| bk_biz_id | int | 是 | 业务ID | +| target_group_id | string | 是 | 目标组ID | +| target_ids | string array | 是 | 目标ID数组,单次最多100个 | +| new_weight | int | 是 | 新权重,取值范围:[0, 100] | + +### 调用示例 + +```json +{ + "target_ids": ["00000001"], + "new_weight": 10 +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "flow_id": "xxxxxxxx" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|----------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|----------|--------|---------| +| flow_id | string | 任务id | + diff --git a/docs/api-docs/web-server/docs/biz/load-balancer/update_tcloud_rules.md b/docs/api-docs/web-server/docs/biz/load-balancer/update_tcloud_rules.md new file mode 100644 index 0000000000..411ee1f675 --- /dev/null +++ b/docs/api-docs/web-server/docs/biz/load-balancer/update_tcloud_rules.md @@ -0,0 +1,95 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:业务下腾讯云规则更新。 + +### URL + +PATCH /api/v1/cloud/bizs/{bk_biz_id}/vendors/tcloud/listeners/{lbl_id}/rules/{rule_id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------------------|--------|----|-------------------| +| bk_biz_id | int64 | 是 | 业务ID | +| lbl_id | string | 是 | 监听器id | +| rule_id | string | 是 | 规则id | +| url | string | 否 | 监听的url | +| session_expire_time | int | 否 | 会话过期时间 | +| scheduler | string | 否 | 均衡方式 | +| forward_type | string | 否 | 转发类型 | +| default_server | bool | 否 | 默认服务 | +| http2 | bool | 否 | http2 | +| target_type | string | 否 | 目标类型 | +| quic | bool | 否 | quic 开关 | +| trpc_func | string | 否 | trpc函数 | +| trpc_callee | string | 否 | trpc 调用者 | +| health_check | object | 否 | 健康检查信息 | +| certificate | object | 否 | 证书信息,当协议为HTTPS时必传 | + +### health_check + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|-------------------------------------------------------------------| +| health_switch | int | 是否开启健康检查:1(开启)、0(关闭) | +| time_out | int | 健康检查的响应超时时间,可选值:2~60,单位:秒 | +| interval_time | int | 健康检查探测间隔时间 | +| health_num | int | 健康阈值 | +| un_health_num | int | 不健康阈值 | +| check_port | int | 自定义探测相关参数。健康检查端口,默认为后端服务的端口 | +| check_type | string | 健康检查使用的协议。取值 TCP/HTTP/HTTPS/GRPC/PING/CUSTOM | +| http_code | string | 健康检查类型 | +| http_version | string | HTTP版本 | +| http_check_path | string | 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) | +| http_check_domain | string | 健康检查域名 | +| http_check_method | string | 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET | +| source_ip_type | string | 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) | +| context_type | string | 健康检查的输入格式,可取值:HEX或TEXT; | + +### http_code 取值说明 + +| 值 | 说明 | +|----|-------------------| +| 1 | 表示探测后返回值 1xx 代表健康 | +| 2 | 表示返回 2xx 代表健康 | +| 4 | 表示返回 3xx 代表健康 | +| 8 | 表示返回 4xx 代表健康, | +| 16 | 表示返回 5xx 代表健康。 | + +若希望多种返回码都可代表健康,则将相应的值相加。 + +### 调用示例 + +```json +{ + "url": "/new/url", + "scheduler": "IP_HASH", + "session_type": "NORMAL", + "session_expire": 300, + "health_check": { + "http_check_path": "/healthz", + "http_check_domain": "www.updatedomain.com", + "http_check_method": "HEAD", + "source_ip_type": 1 + } +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": null +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | diff --git a/docs/api-docs/web-server/docs/resource/cert/assign_cert_to_biz.md b/docs/api-docs/web-server/docs/resource/cert/assign_cert_to_biz.md new file mode 100644 index 0000000000..5cee73a20f --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/cert/assign_cert_to_biz.md @@ -0,0 +1,44 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:资源分配。 +- 该接口功能描述:分配证书到业务下。 + +### URL + +POST /api/v1/cloud/certs/assign/bizs + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------|--------------|-------|------------| +| cert_ids | string array | 是 | 证书的ID列表 | +| bk_biz_id | int | 是 | 业务的ID | + +### 调用示例 + +```json +{ + "cert_ids": [ + "00000001", + "00000002" + ], + "bk_biz_id": 3 +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------|--------| +| code | int | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/cert/create_cert.md b/docs/api-docs/web-server/docs/resource/cert/create_cert.md new file mode 100644 index 0000000000..8fae71fd22 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/cert/create_cert.md @@ -0,0 +1,70 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:证书创建。 +- 该接口功能描述:资源下上传证书。 + +### URL + +POST /api/v1/cloud/certs/create + +### 输入参数 + +输入参数由接口通用参数和vendor对应的云厂商差异参数组成。 + +#### 接口通用参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------|------|----------------------------------------------| +| vendor | string | 是 | 云厂商(枚举值:tcloud、aws、gcp、azure、huawei) | +| account_id | string | 是 | 账号ID | +| name | string | 是 | 证书名称 | +| memo | string | 否 | 备注 | + +#### 云厂商差异参数[tcloud] + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------|------|----------------------------------------------| +| cert_type | string | 是 | 证书类型(CA:客户端证书,SVR:服务器证书) | +| public_key | string | 是 | 证书内容,需要做base64编码 | +| private_key | string | 否 | 私钥内容,需要做base64编码,CA证书可不传该参数 | + +### 腾讯云调用示例 + +```json +{ + "vendor": "tcloud", + "account_id": "00000001", + "name": "test-cert", + "memo": "test cert", + "cert_type": "CA", + "public_key": "xxxxxx", + "private_key": "xxxxxx" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 调用数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------------| +| id | string | 证书ID | diff --git a/docs/api-docs/web-server/docs/resource/cert/delete_cert.md b/docs/api-docs/web-server/docs/resource/cert/delete_cert.md new file mode 100644 index 0000000000..fd27354ef8 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/cert/delete_cert.md @@ -0,0 +1,36 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:证书删除。 +- 该接口功能描述:资源下删除证书。 + +### URL + +DELETE /api/v1/cloud/certs/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|-------|---------| +| id | string | 是 | 证书的ID | + +### 调用示例 + +```json +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/cert/list_cert.md b/docs/api-docs/web-server/docs/resource/cert/list_cert.md new file mode 100644 index 0000000000..0a41e2e95c --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/cert/list_cert.md @@ -0,0 +1,243 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询证书列表。 + +### URL + +POST /api/v1/cloud/certs/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------|------|------------| +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|-------------|------|----------------------------------------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|-------------|------|------------------------------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-------|--------------------------------------------------|--------------------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z")| +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|---------|--------|------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | int | 否 | 记录开始位置,start 起始值为0 | +| limit | int | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|----------------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 证书名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| account_id | string | 账号ID | +| cert_type | string | 证书类型(CA:客户端证书,SVR:服务器证书) | +| cert_status | string | 证书状态 | +| cloud_created_time | string | 上传时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | 过期时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询证书名称是Cert的列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Cert" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询证书名称是Cert的数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Cert" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "cert-123", + "name": "cert-test", + "vendor": "tcloud", + "account_id": "0000001", + "domain": [ + "xxxx.com" + ], + "cert_type": "CA", + "cert_status": "1", + "cloud_created_time": "2023-02-12 14:47:39", + "cloud_expired_time": "2022-02-22 14:47:39", + "memo": "xxxx", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|-------------------------| +| count | int | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------------|----------------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| account_id | string | 账号ID | +| domain | string array | 证书域名 | +| cert_type | string | 证书类型(CA:客户端证书,SVR:服务器证书) | +| cert_status | string | 证书状态 | +| cloud_created_time | string | 上传时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | 过期时间,标准格式:2006-01-02T15:04:05Z | +| memo | string | 备注 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +说明: + +- 证书状态字段 cert_status ,不同云厂商的状态值不同,需要根据vendor的值,显示不同的状态 +- tcloud 的状态枚举(1:已通过 3:已过期) diff --git a/docs/api-docs/web-server/docs/resource/list_security_group_by_res_id.md b/docs/api-docs/web-server/docs/resource/list_security_group_by_res_id.md new file mode 100644 index 0000000000..58cedbca09 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/list_security_group_by_res_id.md @@ -0,0 +1,81 @@ +### 描述 + +- 该接口提供版本:v1.5.0。 +- 该接口所需权限:资源查看。 +- 该接口功能描述:查询负载均衡绑定的安全组列表。 + +### URL + +GET /api/v1/cloud/security_groups/res/{res_type}/{res_id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|----------|--------|-------|----------| +| res_id | string | 是 | 负载均衡ID | +| res_type | string | 是 | 资源类型 | + +### 调用示例 + +```json +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": [ + { + "id": "00000004", + "vendor": "tcloud", + "cloud_id": "sg-xxxxx", + "region": "ap-guangzhou", + "name": "security-group", + "memo": "security group", + "account_id": "00000003", + "bk_biz_id": -1, + "creator": "sync-timing-admin", + "reviser": "sync-timing-admin", + "created_at": "2023-02-25T18:28:46Z", + "updated_at": "2023-02-27T19:14:33Z", + "res_id": "0000000x", + "res_type": "clb", + "priority": 1, + "rel_creator": "Jim", + "rel_created_at": "2023-02-27T19:31:33Z" + } + ] +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------------|---------| +| code | int | 状态码 | +| message | string | 请求信息 | +| data | object array | 响应数据 | + +#### data[n] + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|----------------------------| +| id | string | 安全组ID | +| vendor | string | 云厂商 | +| cloud_id | string | 安全组云ID | +| bk_biz_id | int64 | 业务ID, -1代表未分配业务 | +| region | string | 地域 | +| name | string | 安全组名称 | +| memo | string | 安全组备注 | +| account_id | string | 安全组账号ID | +| creator | string | 安全组创建者 | +| reviser | string | 安全组最后一次修改的修改者 | +| created_at | string | 安全组创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 安全组最后一次修改时间,标准格式:2006-01-02T15:04:05Z | +| res_id | string | 资源ID | +| res_type | string | 资源类型 | +| priority | int64 | 安全组排序ID | +| rel_creator | string | 负载均衡和安全组绑定操作人 | +| rel_created_at | string | 负载均衡和安全组绑定时间(标准格式:2006-01-02T15:04:05Z) | diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/assign_load_balancer_to_biz.md b/docs/api-docs/web-server/docs/resource/load-balancer/assign_load_balancer_to_biz.md new file mode 100644 index 0000000000..2aadc50e38 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/assign_load_balancer_to_biz.md @@ -0,0 +1,44 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡操作。 +- 该接口功能描述:分配负载均衡到业务下。 + +### URL + +POST /api/v1/cloud/load_balancers/assign/bizs + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|--------------|----|-----------| +| lb_ids | string array | 是 | 负载均衡的ID列表 | +| bk_biz_id | int64 | 是 | 业务的ID | + +### 调用示例 + +```json +{ + "lb_ids": [ + "00000001", + "00000002" + ], + "bk_biz_id": 3 +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/associate_load_balancer_security_group.md b/docs/api-docs/web-server/docs/resource/load-balancer/associate_load_balancer_security_group.md new file mode 100644 index 0000000000..6c6196b20c --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/associate_load_balancer_security_group.md @@ -0,0 +1,44 @@ +### 描述 + +- 该接口提供版本:v1.5.0。 +- 该接口所需权限:资源分配。 +- 该接口功能描述:给指定的负载均衡器,批量绑定安全组。 + +### URL + +POST /api/v1/cloud/load_balancers/bind/security_groups + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------|--------------|----|---------------------| +| lb_id | string | 是 | 负载均衡的ID | +| sg_ids | string array | 是 | 安全组的ID数组,最多支持50个安全组 | + +### 调用示例 + +```json +{ + "lb_id": "00000001", + "sg_ids": [ + "00000002", + "00000003" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/batch_count_load_balancer_listener.md b/docs/api-docs/web-server/docs/resource/load-balancer/batch_count_load_balancer_listener.md new file mode 100644 index 0000000000..73861b24c5 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/batch_count_load_balancer_listener.md @@ -0,0 +1,256 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:批量查询负载均衡下监听器数量。 + +### URL + +POST /api/v1/cloud/load_balancers/listeners/count/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------|--------|----|--------| +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| cloud_created_time | string | lb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | lb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | lb过期时间,标准格式:2006-01-02T15:04:05Z | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的Cvm列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的Cvm数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "clb-123", + "name": "clb-test", + "vendor": "tcloud", + "bk_biz_id": -1, + "account_id": "0000001", + "region": "ap-hk", + "main_zones": [ + "ap-hk-1" + ], + "backup_zones": [ + "ap-hk-2", + "ap-hk-3" + ], + "cloud_vpc_id": "vpc-123", + "vpc_id": "00000002", + "network_type": "ipv4", + "domain": "", + "memo": "clb test", + "status": "init", + "private_ipv4_addresses": [ + "127.0.0.1" + ], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "127.0.0.2" + ], + "public_ipv6_addresses": [], + "cloud_created_time": "2023-02-12T14:47:39Z", + "cloud_status_time": "2023-02-12T14:47:39Z", + "cloud_expired_time": "2023-02-12T14:47:39Z", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|----------------| +| count | uint64 | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|----------------|--------|---------| +| id | string | 负载均衡 ID | +| listener_total | int64 | 监听器数量 | diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/batch_delete_load_balancer.md b/docs/api-docs/web-server/docs/resource/load-balancer/batch_delete_load_balancer.md new file mode 100644 index 0000000000..5077865dc3 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/batch_delete_load_balancer.md @@ -0,0 +1,42 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡删除。 +- 该接口功能描述:批量删除负载均衡。有监听器的负载均衡不能删除。 + +### URL + +DELETE /api/v1/cloud/load_balancers/batch + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------|--------------|----|-----------| +| ids | string array | 是 | 负载均衡的ID列表 | + +### 调用示例 + +```json +{ + "ids": [ + "00000001", + "00000002" + ] +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/create_load_balancer.md b/docs/api-docs/web-server/docs/resource/load-balancer/create_load_balancer.md new file mode 100644 index 0000000000..5c987db8f6 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/create_load_balancer.md @@ -0,0 +1,106 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡创建。 +- 该接口功能描述:创建负载均衡。 + +### URL + +POST /api/v1/cloud/vendors/tcloud/load_balancers/create + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|----------------------------|--------------|----|---------------------------------------------------------------| +| account_id | string | 是 | 账号ID | +| region | string | 是 | 地域 | +| load_balancer_type | string | 是 | 网络类型 公网 OPEN,内网 INTERNAL | +| name | string | 是 | 名称 | +| zones | string array | 否 | 主可用区,,仅限公网型 | +| backup_zones | string array | 否 | 备可用区,目前仅广州、上海、南京、北京、中国香港、首尔地域的 IPv4 版本的 CLB 支持主备可用区。 | +| address_ip_version | string | 否 | ip版本,IPV4,IPV6(ipv6 nat64),IPv6FullChain(ipv6) | +| cloud_vpc_id | string | 是 | 云VpcID | +| cloud_subnet_id | string | 否 | 云子网ID ,内网型必填 | +| vip | string | 否 | 绑定已有eip的ip地址,,ipv6 nat64 不支持 | +| cloud_eip_id | string | 否 | 绑定eip id | +| vip_isp | string | 否 | 运营商类型仅公网,枚举值:CMCC,CUCC,CTCC,BGP。通过TCloudDescribeResource 接口确定 | +| internet_charge_type | string | 否 | 网络计费模式 | +| internet_max_bandwidth_out | int64 | 否 | 最大出带宽,单位Mbps | +| bandwidth_package_id | string | 否 | 带宽包id,计费模式为带宽包计费时必填 | +| sla_type | string | 否 | 性能容量型规格, 留空为共享型 | +| auto_renew | boolean | 否 | 按月付费自动续费 | +| require_count | int | 是 | 购买数量 | +| memo | string | 否 | 备注 | + +#### 网络计费模式取值范围: + +- `TRAFFIC_POSTPAID_BY_HOUR` 按流量按小时后计费 +- `BANDWIDTH_POSTPAID_BY_HOUR` 按带宽按小时后计费 +- `BANDWIDTH_PACKAGE` 带宽包计费 + +#### sla_type 性能容量型规格取值范围: + +- `clb.c2.medium` 标准型规格 +- `clb.c3.small` 高阶型1规格 +- `clb.c3.medium` 高阶型2规格 +- `clb.c4.small` 超强型1规格 +- `clb.c4.medium` 超强型2规格 +- `clb.c4.large` 超强型3规格 +- `clb.c4.xlarge` 超强型4规格 + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "region": "ap-hk", + "zone": "ap-hk-1", + "backup_zones": [], + "name": "xxx", + "load_balance_type": "INTERNAL", + "cloud_vpc_id": "vpc-123", + "cloud_subnet_id": "subnet-123", + "address_ip_version": "IPV4", + "vip": "1.2.3.4", + "vip_isp": "BGP", + "charge_type": "TRAFFIC_POSTPAID_BY_HOUR", + "sla_type": "clb.c2.medium", + "internet_max_bandwidth_out": 10, + "auto_renew": true, + "required_count": 1, + "memo": "" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001" + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|-------------------|--------|----------------| +| unknown_cloud_ids | string | 未知创建状态的负载均衡 id | +| success_cloud_ids | string | 成功创建的负载均衡 id | +| failed_cloud_ids | string | 创建失败的负载均衡 id | +| failed_message | string | 失败原因 | diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/disassociate_load_balancer_security_group.md b/docs/api-docs/web-server/docs/resource/load-balancer/disassociate_load_balancer_security_group.md new file mode 100644 index 0000000000..f127ffecf3 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/disassociate_load_balancer_security_group.md @@ -0,0 +1,41 @@ +### 描述 + +- 该接口提供版本:v1.5.0。 +- 该接口所需权限:资源分配。 +- 该接口功能描述:给指定的负载均衡,解绑安全组。 + +### URL + +POST /api/v1/cloud/load_balancers/unbind/security_group + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|---------| +| lb_id | string | 是 | 负载均衡的ID | +| sg_id | string | 是 | 安全组的ID | + +### 调用示例 + +```json +{ + "lb_id": "00000001", + "sg_id": "00000002" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "ok" +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int | 状态码 | +| message | string | 请求信息 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/get_load_balancer.md b/docs/api-docs/web-server/docs/resource/load-balancer/get_load_balancer.md new file mode 100644 index 0000000000..6e2aaed5a5 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/get_load_balancer.md @@ -0,0 +1,133 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:资源查看。 +- 该接口功能描述:查询负载均衡详情。 + +### URL + +POST /api/v1/cloud/load_balancers/{id} + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------|--------|----|--------| +| id | string | 是 | 负载均衡id | + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "id": "00000001", + "cloud_id": "lb-asdfefe", + "name": "test", + "vendor": "tcloud", + "account_id": "00000001", + "bk_biz_id": 1234, + "ip_version": "ipv4", + "lb_type": "OPEN", + "region": "ap-guangzhou", + "zones": [ + "ap-guangzhou-1" + ], + "backup_zones": [], + "vpc_id": "00000001", + "cloud_vpc_id": "vpc-abcdef", + "subnet_id": "", + "cloud_subnet_id": "", + "private_ipv4_addresses": [], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "1.1.1.1" + ], + "public_ipv6_addresses": [], + "domain": "", + "status": "1", + "cloud_created_time": "2024-01-02 15:04:05", + "cloud_status_time": "2024-01-02 15:04:05", + "cloud_expired_time": "", + "memo": null, + "creator": "admin", + "reviser": "admin", + "created_at": "2024-01-02T15:04:05Z", + "updated_at": "2024-01-02T15:04:05Z", + "extension": { + "vip_isp": "BGP" + } + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| private_ipv4_addresses | string array | 内网ipv4地址 | +| private_ipv6_addresses | string array | 内网ipv6地址 | +| public_ipv4_addresses | string array | 外网ipv4地址 | +| public_ipv6_addresses | string array | 外网ipv6地址 | +| cloud_created_time | string | clb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | clb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | clb过期时间,标准格式:2006-01-02T15:04:05Z | +| extension | object | 拓展 | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +#### data.extension[tcloud] + +status 状态含义: + +| 状态值 | 含义 | +|-----|------| +| 0 | 创建中 | +| 1 | 正常运行 | + +腾讯云拓展字段 + +| 参数名称 | 参数类型 | 描述 | +|------------------------------|--------|---------------------------------------------| +| sla_type | string | 性能容量型规格。 | +| vip_isp | string | 运营商类型。 | +| load_balancer_pass_to_target | string | Target是否放通来自CLB的流量。 | +| internet_max_bandwidth_out | string | 最大出带宽,单位Mbps, | +| internet_charge_type | string | 计费模式 | +| bandwidthpkg_sub_type | string | 带宽包的类型 | +| bandwidth_package_id | string | 带宽包ID | +| ipv6_mode | string | IP地址版本为ipv6时此字段有意义, IPv6Nat64/IPv6FullChain | +| snat | string | snat | +| snat_pro | string | 是否开启SnatPro。 | +| snat_ips | string | 开启SnatPro负载均衡后,SnatIp列表。 | +| delete_protect | string | 删除保护 | +| egress | string | 网络出口 | +| mix_ip_target | string | 双栈混绑 | + diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/inquiry_load_balancer.md b/docs/api-docs/web-server/docs/resource/load-balancer/inquiry_load_balancer.md new file mode 100644 index 0000000000..ef4fd886ae --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/inquiry_load_balancer.md @@ -0,0 +1,131 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:负载均衡创建。 +- 该接口功能描述:查询负载均衡价格。 + +### URL + +POST /api/v1/cloud/load_balancer/prices/inquiry + +### 输入参数 + +#### tcloud + +| 参数名称 | 参数类型 | 必选 | 描述 | +|----------------------------|--------------|----|---------------------------------------------------------------| +| account_id | string | 是 | 账号ID | +| region | string | 是 | 地域 | +| load_balancer_type | string | 是 | 网络类型 公网 OPEN,内网 INTERNAL | +| name | string | 是 | 名称 | +| zones | string array | 否 | 主可用区,,仅限公网型 | +| backup_zones | string array | 否 | 备可用区,目前仅广州、上海、南京、北京、中国香港、首尔地域的 IPv4 版本的 CLB 支持主备可用区。 | +| address_ip_version | string | 否 | ip版本,IPV4,IPV6(ipv6 nat64),IPv6FullChain(ipv6) | +| cloud_vpc_id | string | 是 | 云VpcID | +| cloud_subnet_id | string | 否 | 云子网ID ,内网型必填 | +| vip | string | 否 | 绑定已有eip的ip地址,,ipv6 nat64 不支持 | +| cloud_eip_id | string | 否 | 绑定eip id | +| vip_isp | string | 否 | 运营商类型仅公网,枚举值:CMCC,CUCC,CTCC,BGP。通过TCloudDescribeResource 接口确定 | +| internet_charge_type | string | 否 | 网络计费模式 | +| internet_max_bandwidth_out | int64 | 否 | 最大出带宽,单位Mbps | +| bandwidth_package_id | string | 否 | 带宽包id,计费模式为带宽包计费时必填 | +| sla_type | string | 否 | 性能容量型规格, 留空为共享型 | +| require_count | int | 是 | 购买数量 | +| memo | string | 否 | 备注 | + +#### 网络计费模式取值范围: + +- `TRAFFIC_POSTPAID_BY_HOUR` 按流量按小时后计费 +- `BANDWIDTH_POSTPAID_BY_HOUR` 按带宽按小时后计费 +- `BANDWIDTH_PACKAGE` 带宽包计费 + +#### sla_type 性能容量型规格取值范围: + +- `clb.c2.medium` 标准型规格 +- `clb.c3.small` 高阶型1规格 +- `clb.c3.medium` 高阶型2规格 +- `clb.c4.small` 超强型1规格 +- `clb.c4.medium` 超强型2规格 +- `clb.c4.large` 超强型3规格 +- `clb.c4.xlarge` 超强型4规格 + +### 调用示例 + +#### tcloud + +```json +{ + "account_id": "0000001", + "region": "ap-hk", + "zone": "ap-hk-1", + "backup_zones": [], + "name": "xxx", + "load_balance_type": "INTERNAL", + "cloud_vpc_id": "vpc-123", + "cloud_subnet_id": "subnet-123", + "address_ip_version": "IPV4", + "vip": "1.2.3.4", + "vip_isp": "BGP", + "charge_type": "TRAFFIC_POSTPAID_BY_HOUR", + "sla_type": "clb.c2.medium", + "internet_max_bandwidth_out": 10, + "auto_renew": true, + "required_count": 1, + "memo": "" +} +``` + +### 响应示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "bandwidth_price": null, + "instance_price": { + "charge_unit": "HOUR", + "discount": 1.2, + "discount_price": null, + "original_price": null, + "unit_price": 3.4, + "unit_price_discount": 5.6 + }, + "lcu_price": null + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data[tcloud] + +| 参数名称 | 参数类型 | 描述 | +|-----------------|------------|-------------------------------| +| bandwidth_price | price_item | 网络价格信息,对于标准账户,网络在cvm上计费,该选项为空 | +| instance_price | price_item | 实例价格信息 | +| lcu_price | price_item | lcu 价格信息 | + +#### price_item + +| 参数名称 | 参数类型 | 描述 | +|---------------------|--------|----------------| +| charge_unit | string | 后续计价单元,HOUR、GB | +| discount | float | 折扣 ,如20.0代表2折 | +| discount_price | float | 预支费用的折扣价,单位:元 | +| original_price | float | 预支费用的原价,单位:元 | +| unit_price | float | 后付费单价,单位:元 | +| unit_price_discount | float | 后付费的折扣单价,单位:元 | + +##### 后续计价单元 charge_unit,取值范围: +- HOUR:表示计价单元是按每小时来计算。当前涉及该计价单元的场景有: + - 实例按小时后付费(POSTPAID_BY_HOUR) 、 + - 带宽按小时后付费(BANDWIDTH_POSTPAID_BY_HOUR); +- GB:表示计价单元是按每GB来计算。当前涉及该计价单元的场景有: + - 流量按小时后付费(TRAFFIC_POSTPAID_BY_HOUR)。 \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/list_load_balancer.md b/docs/api-docs/web-server/docs/resource/load-balancer/list_load_balancer.md new file mode 100644 index 0000000000..3d23b43429 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/list_load_balancer.md @@ -0,0 +1,288 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:资源查看。 +- 该接口功能描述:查询负载均衡列表。 + +### URL + +POST /api/v1/cloud/load_balancers/list + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------|--------|----|--------| +| filter | object | 是 | 查询过滤条件 | +| page | object | 是 | 分页设置 | + +#### filter + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|-----------------------------------------------------------------| +| op | enum string | 是 | 操作符(枚举值:and、or)。如果是and,则表示多个rule之间是且的关系;如果是or,则表示多个rule之间是或的关系。 | +| rules | array | 是 | 过滤规则,最多设置5个rules。如果rules为空数组,op(操作符)将没有作用,代表查询全部数据。 | + +#### rules[n] (详情请看 rules 表达式说明) + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|-------------|----|---------------------------------------------| +| field | string | 是 | 查询条件Field名称,具体可使用的用于查询的字段及其说明请看下面 - 查询参数介绍 | +| op | enum string | 是 | 操作符(枚举值:eq、neq、gt、gte、le、lte、in、nin、cs、cis) | +| value | 可变类型 | 是 | 查询条件Value值 | + +##### rules 表达式说明: + +##### 1. 操作符 + +| 操作符 | 描述 | 操作符的value支持的数据类型 | +|-----|-------------------------------------------|-----------------------------------------------| +| eq | 等于。不能为空字符串 | boolean, numeric, string | +| neq | 不等。不能为空字符串 | boolean, numeric, string | +| gt | 大于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| gte | 大于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lt | 小于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| lte | 小于等于 | numeric,时间类型为字符串(标准格式:"2006-01-02T15:04:05Z") | +| in | 在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| nin | 不在给定的数组范围中。value数组中的元素最多设置100个,数组中至少有一个元素 | boolean, numeric, string | +| cs | 模糊查询,区分大小写 | string | +| cis | 模糊查询,不区分大小写 | string | + +##### 2. 协议示例 + +查询 name 是 "Jim" 且 age 大于18小于30 且 servers 类型是 "api" 或者是 "web" 的数据。 + +```json +{ + "op": "and", + "rules": [ + { + "field": "name", + "op": "eq", + "value": "Jim" + }, + { + "field": "age", + "op": "gt", + "value": 18 + }, + { + "field": "age", + "op": "lt", + "value": 30 + }, + { + "field": "servers", + "op": "in", + "value": [ + "api", + "web" + ] + } + ] +} +``` + +#### page + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------|--------|----|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| count | bool | 是 | 是否返回总记录条数。 如果为true,查询结果返回总记录条数 count,但查询结果详情数据 details 为空数组,此时 start 和 limit 参数将无效,且必需设置为0。如果为false,则根据 start 和 limit 参数,返回查询结果详情数据,但总记录条数 count 为0 | +| start | uint32 | 否 | 记录开始位置,start 起始值为0 | +| limit | uint32 | 否 | 每页限制条数,最大500,不能为0 | +| sort | string | 否 | 排序字段,返回数据将按该字段进行排序 | +| order | string | 否 | 排序顺序(枚举值:ASC、DESC) | + +#### 查询参数介绍: + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| cloud_created_time | string | lb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | lb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | lb过期时间,标准格式:2006-01-02T15:04:05Z | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +接口调用者可以根据以上参数自行根据查询场景设置查询规则。 + +### 调用示例 + +#### 获取详细信息请求参数示例 + +查询创建者是Jim的Cvm列表。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": false, + "start": 0, + "limit": 500 + } +} +``` + +#### 获取数量请求参数示例 + +查询创建者是Jim的Cvm数量。 + +```json +{ + "filter": { + "op": "and", + "rules": [ + { + "field": "created_at", + "op": "eq", + "value": "Jim" + } + ] + }, + "page": { + "count": true + } +} +``` + +### 响应示例 + +#### 获取详细信息返回结果示例 + +```json +{ + "code": 0, + "message": "", + "data": { + "details": [ + { + "id": "00000001", + "cloud_id": "lb-123", + "name": "lb-test", + "vendor": "tcloud", + "bk_biz_id": -1, + "account_id": "0000001", + "region": "ap-hk", + "main_zones": [ + "ap-hk-1" + ], + "backup_zones": [ + "ap-hk-2", + "ap-hk-3" + ], + "cloud_vpc_id": "vpc-123", + "vpc_id": "00000002", + "network_type": "ipv4", + "domain": "", + "memo": "lb test", + "status": "init", + "private_ipv4_addresses": [ + "127.0.0.1" + ], + "private_ipv6_addresses": [], + "public_ipv4_addresses": [ + "127.0.0.2" + ], + "public_ipv6_addresses": [], + "cloud_created_time": "2023-02-12T14:47:39Z", + "cloud_status_time": "2023-02-12T14:47:39Z", + "cloud_expired_time": "2023-02-12T14:47:39Z", + "creator": "Jim", + "reviser": "Jim", + "created_at": "2023-02-12T14:47:39Z", + "updated_at": "2023-02-12T14:55:40Z" + } + ] + } +} +``` + +#### 获取数量返回结果示例 + +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 1 + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | object | 响应数据 | + +#### data + +| 参数名称 | 参数类型 | 描述 | +|---------|--------|----------------| +| count | uint64 | 当前规则能匹配到的总记录条数 | +| details | array | 查询返回的数据 | + +#### data.details[n] + +| 参数名称 | 参数类型 | 描述 | +|------------------------|--------------|--------------------------------------| +| id | string | 资源ID | +| cloud_id | string | 云资源ID | +| name | string | 名称 | +| vendor | string | 供应商(枚举值:tcloud、aws、azure、gcp、huawei) | +| bk_biz_id | int64 | 业务ID | +| account_id | string | 账号ID | +| region | string | 地域 | +| main_zones | string | 主可用区 | +| backup_zones | string | 备可用区 | +| cloud_vpc_id | string | 云vpcID | +| vpc_id | string | vpcID | +| network_type | string | 网络类型 | +| memo | string | 备注 | +| status | string | 状态 | +| domain | string | 域名 | +| private_ipv4_addresses | string array | 内网ipv4地址 | +| private_ipv6_addresses | string array | 内网ipv6地址 | +| public_ipv4_addresses | string array | 外网ipv4地址 | +| public_ipv6_addresses | string array | 外网ipv6地址 | +| cloud_created_time | string | lb在云上创建时间,标准格式:2006-01-02T15:04:05Z | +| cloud_status_time | string | lb状态变更时间,标准格式:2006-01-02T15:04:05Z | +| cloud_expired_time | string | lb过期时间,标准格式:2006-01-02T15:04:05Z | +| creator | string | 创建者 | +| reviser | string | 修改者 | +| created_at | string | 创建时间,标准格式:2006-01-02T15:04:05Z | +| updated_at | string | 修改时间,标准格式:2006-01-02T15:04:05Z | + +##### TCloud status 状态含义: + +| 状态值 | 含义 | +|-----|------| +| 0 | 创建中 | +| 1 | 正常运行 | + diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_network_account_type.md b/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_network_account_type.md new file mode 100644 index 0000000000..31f138232b --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_network_account_type.md @@ -0,0 +1,29 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询用户网络类型。腾讯云代理接口 DescribeNetworkAccountType + +### URL + +GET /api/v1/cloud/vendors/tcloud/accounts/{account_id}/network_type + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|------------|--------|----|-------| +| account_id | string | 是 | 云账户id | + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|-----------------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | AccountTypeInfo | | + +### AccountTypeResp + +| 参数名称 | 参数类型 | 描述 | +|--------------------|--------|-------------------------------------| +| NetworkAccountType | string | 用户账号的网络类型,STANDARD为标准用户,LEGACY为传统用户 | \ No newline at end of file diff --git a/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_resources.md b/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_resources.md new file mode 100644 index 0000000000..31543fd2a5 --- /dev/null +++ b/docs/api-docs/web-server/docs/resource/load-balancer/tcloud_describe_resources.md @@ -0,0 +1,92 @@ +### 描述 + +- 该接口提供版本:v1.5.0+。 +- 该接口所需权限:业务访问。 +- 该接口功能描述:查询用户在当前地域支持可用区列表和资源列表。腾讯云代理接口 DescribeResources + +### URL + +POST /api/v1/cloud/vendors/tcloud/load_balancers/resources/describe + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-------------|--------------|----|--------------------------------------| +| account_id | string | 是 | 云账户id | +| region | string | 是 | 地域 | +| master_zone | string array | 否 | 指定可用区 | +| ip_version | string array | 否 | 指定IP版本,如"IPv4"、"IPv6"、"IPv6_Nat" | +| isp | string array | 否 | 指定运营商类型,如:"BGP","CMCC","CUCC","CTCC" | +| limit | int | 否 | 返回可用区资源列表数目,默认20,最大值100。 | +| offset | int | 否 | 返回可用区资源列表起始偏移量,默认0。 | + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|---------|---------------------------|------| +| code | int32 | 状态码 | +| message | string | 请求信息 | +| data | DescribeResourcesResponse | 响应数据 | + +#### DescribeResourcesResponse + +| 参数名称 | 参数类型 | 描述 | +|-----------------|-----------------------|------------| +| ZoneResourceSet | array of ZoneResource | 响应数据 | +| TotalCount | int | 符合条件的总记录条数 | + +#### ZoneResource + +可用区资源 + +| 参数名称 | 参数类型 | 描述 | +|--------------------|-------------------|---------------------------------------| +| MasterZone | string | 主可用区 | +| SlaveZone | string | 备可用区 | +| ResourceSet | array of Resource | 资源列表 | +| IPVersion | string | ip版本(枚举值:IPv4,IPv6,IPv6_Nat) | +| LocalZone | bool | 是否本地可用区 | +| zone_resource_type | string | 可用区资源的类型,SHARED表示共享资源,EXCLUSIVE表示独占资源 | +| ZoneRegion | string | 所属地域 | +| EdgeZone | bool | 可用区是否是EdgeZone可用区 | +| Egress | string | 网络出口 | + +#### Resource + +资源 + +| 参数名称 | 参数类型 | 描述 | +|------------------|-------------------------------|-------------------------------------------------------| +| Isp | string | 运营商信息,如"CMCC", "CUCC", "CTCC", "BGP", "INTERNAL" | +| Type | array of string | 运营商内具体资源信息,如"CMCC", "CUCC", "CTCC", "BGP", "INTERNAL" | +| AvailabilitySet | array of ResourceAvailability | 可用资源。 | +| TypeSet | array of TypeInfo | 运营商类型信息。 | + +#### ResourceAvailability + +资源可用性 + +| 参数名称 | 参数类型 | 描述 | +|--------------|--------|-------------------------------------------------------| +| Availability | string | 资源可用性,"Available":可用,"Unavailable":不可用 | +| Type | string | 运营商内具体资源信息,如"CMCC", "CUCC", "CTCC", "BGP", "INTERNAL" | + +#### TypeInfo + +运营商类型信息 + +| 参数名称 | 参数类型 | 描述 | +|---------------------|---------------------------|--------------| +| SpecAvailabilitySet | array of SpecAvailability | 规格可用性 | +| Type | string | 运营商类型,如"BGP" | + +#### SpecAvailability + +规格可用性 + +| 参数名称 | 参数类型 | 描述 | +|--------------|--------|----------------------------------------| +| Availability | string | 规格可用性,"Available":可用,"Unavailable":不可用 | +| SpecType | string | 规格类型, 如 "shared" | + + diff --git a/docs/support-file/changelog/ch/v1.5.0-2024-05-21.md b/docs/support-file/changelog/ch/v1.5.0-2024-05-21.md new file mode 100644 index 0000000000..ccd21b6596 --- /dev/null +++ b/docs/support-file/changelog/ch/v1.5.0-2024-05-21.md @@ -0,0 +1,16 @@ +**V1.5.0 版本更新日志** + +# 新增 +- [新增] 腾讯云-负载均衡的管理,如购买,管理规则,绑定、解绑RS等功能 +- [新增] 腾讯云-负载均衡的目标组的管理,批量变更RS,修改权重等功能 +- [新增] 证书托管,支持上传证书,删除证书等功能 +- [新增] 负载均衡的规则变更,支持异步任务 +- [新增] 负载均衡、安全组的操作记录 +- [新增] filter 支持json_neq 操作符 + +# 优化 +- [优化] Golang运行时升级到v1.21版本,腾讯云SDK升级到v1.0.908版本,以及其他基础依赖升级 + +# 修复 +- [修复] make all 重复编译cmd目录 + diff --git a/docs/support-file/changelog/en/v1.5.0-2024-05-21.md b/docs/support-file/changelog/en/v1.5.0-2024-05-21.md new file mode 100644 index 0000000000..1ec1ea7049 --- /dev/null +++ b/docs/support-file/changelog/en/v1.5.0-2024-05-21.md @@ -0,0 +1,15 @@ +**Version 1.5.0 Release Notes** + +# Feature +- [Feature] Tencent Cloud - Load Balancer management, including purchasing, managing rules, binding/unbinding RS, and other functions +- [Feature] Tencent Cloud - Target group management for Load Balancer, including batch modification of RS, weight modification, and other functions +- [Feature] Certificate hosting, supporting uploading and deleting certificates +- [Feature] Load Balancer rule changes, supporting asynchronous tasks +- [Feature] Operation records for Load Balancer and Security Group +- [Feature] Filter supports json_neq operator + +# Upgrade +- [Upgrade] Upgraded Golang runtime to v1.21, Tencent Cloud SDK to v1.0.908, and other basic dependencies + +# Bugfix +- [Bugfix] make all compiles cmd directory repeatedly diff --git a/front/.eslintrc.js b/front/.eslintrc.js index 90ed10a97e..797d917701 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { root: true, - extends: ['@blueking/eslint-config-bk/tsvue3'], + extends: ['@blueking/eslint-config-bk/tsvue3', 'plugin:prettier/recommended'], parserOptions: { project: './tsconfig.eslint.json', tsconfigRootDir: __dirname, @@ -12,6 +12,8 @@ module.exports = { '@typescript-eslint/naming-convention': 0, '@typescript-eslint/no-misused-promises': 0, 'prefer-spread': 'off', + 'no-console': 'error', + 'no-debugger': 'error', 'linebreak-style': 0, }, }; diff --git a/front/.husky/pre-commit b/front/.husky/pre-commit index 888ce71266..0f017a882e 100755 --- a/front/.husky/pre-commit +++ b/front/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" cd front && echo ~+ && echo CODE LINT START -yarn lint-staged \ No newline at end of file +yarn lint-staged diff --git a/front/.husky/pre-push b/front/.husky/pre-push index 4d4603c704..d5d19e18e5 100755 --- a/front/.husky/pre-push +++ b/front/.husky/pre-push @@ -21,4 +21,4 @@ do fi done -exit 0 \ No newline at end of file +exit 0 diff --git a/front/.prettierrc.js b/front/.prettierrc.js index bfa633a46a..e58028b4d0 100644 --- a/front/.prettierrc.js +++ b/front/.prettierrc.js @@ -15,7 +15,7 @@ module.exports = { proseWrap: 'preserve', htmlWhitespaceSensitivity: 'ignore', vueIndentScriptAndStyle: false, - endOfLine: 'lf', + endOfLine: 'auto', embeddedLanguageFormatting: 'auto', }; \ No newline at end of file diff --git a/front/package-lock.json b/front/package-lock.json index 09e752c799..59faf264b0 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -22,7 +22,7 @@ "@wangeditor/editor-for-vue": "^5.1.12", "art-template": "^4.13.2", "axios": "^1.6.0", - "bkui-vue": "0.0.2-beta.103", + "bkui-vue": "^1.0.3-beta.67.dialog.3", "classnames": "^2.3.1", "cookie": "^0.5.0", "cookie-parser": "^1.4.6", @@ -77,10 +77,12 @@ "autoprefixer": "^10.4.7", "bk-vision-cli": "^4.1.5", "cross-env": "^7.0.3", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "lint-staged": "^13.0.3", "postcss-import": "^14.1.0", - "prettier": "2.8.8", + "prettier": "^2.8.8", "sass": "^1.52.3", "sass-loader": "^13.0.0", "typescript": "^4.7.4", @@ -101,9 +103,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "peer": true }, "node_modules/@amap/amap-jsapi-loader": { @@ -112,12 +114,12 @@ "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==" }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -141,9 +143,9 @@ } }, "node_modules/@antv/g-device-api": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@antv/g-device-api/-/g-device-api-1.4.1.tgz", - "integrity": "sha512-oVRf809zmpF4F3oS//tC7FTY4AgCOQ1/MqIyE+Q43A7yHqqygDIodVNoLCfYx3XZEu46iT0Nf1scUXKq7CauHg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@antv/g-device-api/-/g-device-api-1.6.8.tgz", + "integrity": "sha512-WL/bkr7BLKTp/F43U/s0FTSPTR1QqTOqNxvTaoO7QUe24HTa/A9kabCF/3dFzmw+ix5GF8wwNi8QLmDKd852Kw==", "dependencies": { "@antv/util": "^3.3.4", "@webgpu/types": "^0.1.34", @@ -158,66 +160,61 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/@antv/l7": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7/-/l7-2.20.5.tgz", - "integrity": "sha512-3r9lQQHhhE8GIFbiDhSZmptq4m0BCfE9kHIH/eTPoatuOowIuZGfZ57utUUU73zYRWLf6SUBS3JP+/mLYSWGjA==", - "dependencies": { - "@antv/l7-component": "2.20.5", - "@antv/l7-core": "2.20.5", - "@antv/l7-layers": "2.20.5", - "@antv/l7-maps": "2.20.5", - "@antv/l7-scene": "2.20.5", - "@antv/l7-source": "2.20.5", - "@antv/l7-utils": "2.20.5", - "@babel/runtime": "^7.7.7", - "webpack-bundle-analyzer": "^4.9.1" + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7/-/l7-2.21.3.tgz", + "integrity": "sha512-GDl0rTRQtvtes9znopJxADFjaUPSiBzxNTbq3MgiBwgh00TWsKK9frQWa6RjvVB4R8/wu5zw/yKkcSVoZC/KIA==", + "dependencies": { + "@antv/l7-component": "^2.21.3", + "@antv/l7-core": "^2.21.3", + "@antv/l7-layers": "^2.21.3", + "@antv/l7-maps": "^2.21.3", + "@antv/l7-scene": "^2.21.3", + "@antv/l7-source": "^2.21.3", + "@antv/l7-utils": "^2.21.3", + "@babel/runtime": "^7.7.7" } }, "node_modules/@antv/l7-component": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-component/-/l7-component-2.20.5.tgz", - "integrity": "sha512-dXB8ROK5TrAB+HgjBQnG4oN6AgAcOVR0dTiW2ZnT2R7Xw7U+425rY6bz0ULpaBpUABQZGm+aYHsiGAVdDLQp6w==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-component/-/l7-component-2.21.3.tgz", + "integrity": "sha512-SGZpyjJHDyMc5sTIkOeNmOG3sIiIAmCrssQ/PFi0uJXUnKyC/AnwDSiqTGphWTI/FPbMeQ2gdDblILaqRuRVWg==", "dependencies": { - "@antv/l7-core": "2.20.5", - "@antv/l7-utils": "2.20.5", + "@antv/l7-core": "^2.21.3", + "@antv/l7-layers": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", "eventemitter3": "^4.0.0", - "inversify": "^5.0.1", - "reflect-metadata": "^0.1.13", "supercluster": "^7.0.0" } }, "node_modules/@antv/l7-core": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-core/-/l7-core-2.20.5.tgz", - "integrity": "sha512-szeERmOqW+GV6yY0QNd6A8f/s6IFI38WlnXRHOMMdtLu13zt/LRYaNOoZMWW0wAKa/icgTjy/fzqkuC83Sfrjg==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-core/-/l7-core-2.21.3.tgz", + "integrity": "sha512-TA1QjkbhAUwH3X+RiF6WQg3rqT70T/pcpnZJLtO2MCFPdv4KZINF08TXAU92Jd1zQ5lGiYVyN7+1w6Vpcx3PBA==", "dependencies": { "@antv/async-hook": "^2.2.9", - "@antv/l7-utils": "2.20.5", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", "@mapbox/tiny-sdf": "^1.2.5", "@turf/helpers": "^6.1.4", "ajv": "^6.10.2", - "element-resize-event": "^3.0.3", + "element-resize-detector": "^1.2.4", "eventemitter3": "^4.0.0", "gl-matrix": "^3.1.0", "hammerjs": "^2.0.8", - "inversify": "^5.0.1", - "inversify-inject-decorators": "^3.1.0", - "reflect-metadata": "^0.1.13", "viewport-mercator-project": "^6.2.1" } }, "node_modules/@antv/l7-layers": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-layers/-/l7-layers-2.20.5.tgz", - "integrity": "sha512-kz0fFDOylSK4msWrocVsffpuot5T0XElxvXomcnSLq9jRH5x0GHxKzrIOlQ6iUusaU3w5ZxJ4H6Nhu5DNvzs8g==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-layers/-/l7-layers-2.21.3.tgz", + "integrity": "sha512-Vu3wxPkakUQwizM1ud3+xplc4oU6N18YaU2WdSvLHXZk1G7RTCIsiDuYGYfR5eGCzL0DCXlNMl8AIi488wEDyg==", "dependencies": { "@antv/async-hook": "^2.2.9", - "@antv/l7-core": "2.20.5", - "@antv/l7-maps": "2.20.5", - "@antv/l7-source": "2.20.5", - "@antv/l7-utils": "2.20.5", + "@antv/l7-core": "^2.21.3", + "@antv/l7-maps": "^2.21.3", + "@antv/l7-source": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", "@mapbox/martini": "^0.2.0", "@turf/clone": "^6.5.0", @@ -234,17 +231,15 @@ "extrude-polyline": "^1.0.6", "gl-matrix": "^3.1.0", "gl-vec2": "^1.3.0", - "inversify": "^5.0.1", - "polyline-miter-util": "^1.0.1", - "reflect-metadata": "^0.1.13" + "polyline-miter-util": "^1.0.1" } }, "node_modules/@antv/l7-map": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-map/-/l7-map-2.20.5.tgz", - "integrity": "sha512-9KoGIadepSBqzmZRbcbrSCtffCmFwUPfGliQqBSYNn/ZWcqyBKCJCyvQqu2P6q/HcWF/deg8G33+Nmnpra93Vg==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-map/-/l7-map-2.21.3.tgz", + "integrity": "sha512-ZSwqF9gGXaSarHPWGhcJs79bP3HVm/A8zYA/ogatNsD1/+K9FnaExJnor/huNRYhbN0D+uXeXjvfpwgVQXk7bg==", "dependencies": { - "@antv/l7-utils": "2.20.5", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", "@mapbox/point-geometry": "^0.1.0", "@mapbox/unitbezier": "^0.0.0", @@ -254,63 +249,57 @@ } }, "node_modules/@antv/l7-maps": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-maps/-/l7-maps-2.20.5.tgz", - "integrity": "sha512-Lv2PtADM2RP+IBFoWm81yrwfZE4Hvuj15rbeSBXISd+9vZdVaxYceFxClvEtDJq4x1bj0+cjQLCtMr4pltDb1g==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-maps/-/l7-maps-2.21.3.tgz", + "integrity": "sha512-6y7o+CC/luWQNfseCSsw5icqr9QcuxoYXNi4YTa85mzmNjqQUUnGefUcKwjCAyHwDvjDZw2g4yc9QtXs2hQ4JA==", "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", - "@antv/l7-core": "2.20.5", - "@antv/l7-map": "2.20.5", - "@antv/l7-utils": "2.20.5", + "@antv/l7-core": "^2.21.3", + "@antv/l7-map": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", + "eventemitter3": "^4.0.0", "gl-matrix": "^3.1.0", - "inversify": "^5.0.1", "mapbox-gl": "^1.2.1", "maplibre-gl": "^3.5.2", - "reflect-metadata": "^0.1.13", "viewport-mercator-project": "^6.2.1" } }, "node_modules/@antv/l7-renderer": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-renderer/-/l7-renderer-2.20.5.tgz", - "integrity": "sha512-0WONfggP9zsPVwts6oNSv3gpDdQbx2hDhjrjbI7wRgbCM7Ib4Q7RMbHQ5lgyq43H5GYeduZqehNbV4zcPLiw9A==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-renderer/-/l7-renderer-2.21.3.tgz", + "integrity": "sha512-yC8DZRq4zPyGVL+lLEZ/DXKid9+qmcqjwf/KVDRRrfzE+e5kfg5jlHR/elhAJsC69Sv+oAPKMQrIH8LpDo50DA==", "dependencies": { - "@antv/g-device-api": "^1.3.6", - "@antv/l7-core": "2.20.5", - "@antv/l7-utils": "2.20.5", + "@antv/g-device-api": "^1.6.4", + "@antv/l7-core": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", - "inversify": "^5.0.1", - "reflect-metadata": "^0.1.13", "regl": "1.6.1" } }, "node_modules/@antv/l7-scene": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-scene/-/l7-scene-2.20.5.tgz", - "integrity": "sha512-lgxgu94rrEfaxDxwDCayTjhPKnEoW2yMzpocXR++oROrryvxq4GKr5JawNe86MxTKJGrpzZTQH/oTrICBSxeHw==", - "dependencies": { - "@antv/l7-component": "2.20.5", - "@antv/l7-core": "2.20.5", - "@antv/l7-layers": "2.20.5", - "@antv/l7-maps": "2.20.5", - "@antv/l7-renderer": "2.20.5", - "@antv/l7-utils": "2.20.5", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-scene/-/l7-scene-2.21.3.tgz", + "integrity": "sha512-sIBDtj6SvuN2TDtiRNoh741p4+F9Krs3xXBxEwphJURbtEoIJlvcZwhMFHl/Qop+mixp1OHFZSYxl9e/YZE8WQ==", + "dependencies": { + "@antv/l7-component": "^2.21.3", + "@antv/l7-core": "^2.21.3", + "@antv/l7-layers": "^2.21.3", + "@antv/l7-maps": "^2.21.3", + "@antv/l7-renderer": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", - "eventemitter3": "^4.0.7", - "inversify": "^5.0.1", - "mapbox-gl": "^1.2.1", - "reflect-metadata": "^0.1.13" + "eventemitter3": "^4.0.7" } }, "node_modules/@antv/l7-source": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-source/-/l7-source-2.20.5.tgz", - "integrity": "sha512-c05NQgq8H5JM8nAcCuD+nFnn1RZV/MMKUdWYuyiRGvmKqpObKVs50jY1NXUCpB3xqhksvnne4QFkm3mDFcKS8Q==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-source/-/l7-source-2.21.3.tgz", + "integrity": "sha512-ricKvuPT2AO/Hu96749/RLiLz7h1iv+IEHrkvvEy+gZKQIAYsq8vaRgl+xvfsocxJ1pm7ubGrZejxREcQuBfJg==", "dependencies": { "@antv/async-hook": "^2.2.9", - "@antv/l7-core": "2.20.5", - "@antv/l7-utils": "2.20.5", + "@antv/l7-core": "^2.21.3", + "@antv/l7-utils": "^2.21.3", "@babel/runtime": "^7.7.7", "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/vector-tile": "^1.3.1", @@ -321,16 +310,14 @@ "d3-hexbin": "^0.2.2", "eventemitter3": "^4.0.0", "geojson-vt": "^3.2.1", - "inversify": "^5.0.1", "pbf": "^3.2.1", - "reflect-metadata": "^0.1.13", "supercluster": "^7.0.0" } }, "node_modules/@antv/l7-utils": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/@antv/l7-utils/-/l7-utils-2.20.5.tgz", - "integrity": "sha512-7gTv8IapVJ9o8N2tyoAOqOEt2YOhK5wubXGLJp2/5z1aMURoo3JlnjA13TFgMkf4lD4ggOMKKjZLlo5Fvxb4fg==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@antv/l7-utils/-/l7-utils-2.21.3.tgz", + "integrity": "sha512-j0JcLkLSYklIK2T0R1OSdT5HLjwVe3pjVxtTOQHo+VsS47WX1wfHNhLz6+srFkIhwO0sSr2Xtn/8MKjv9+4L/g==", "dependencies": { "@babel/runtime": "^7.7.7", "@turf/bbox": "^6.5.0", @@ -345,51 +332,50 @@ } }, "node_modules/@antv/util": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.5.tgz", - "integrity": "sha512-bVv1loamL/MgUEN9dNt7VKAsghO4Wgb+kzr8B9TgkM5tHgKk++xiTwi3pejIdgU8DDkzcyaRsO+VTOXJt8jLng==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.7.tgz", + "integrity": "sha512-qqPg7rIPCsJyl7N56jAC25v/99mJ3ApVkgBsGijhiWrEeKvzXBPk1r5P77Pm9nCljpnn+hH8Z3t5AivbEoTJMg==", "dependencies": { "fast-deep-equal": "^3.1.3", - "flru": "^1.0.2", "gl-matrix": "^3.3.0", "tslib": "^2.3.1" } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", - "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.5", - "@babel/parser": "^7.23.5", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -405,9 +391,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.23.3.tgz", - "integrity": "sha512-9bTuNlyx7oSstodm1cR1bECj4fkiknsDa1YniISkJemMY3DGhJNYBECbe6QD/q54mp2J8VO66jW3/7uP//iFCw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz", + "integrity": "sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ==", "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -422,13 +408,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", - "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", "dependencies": { - "@babel/types": "^7.23.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -458,13 +444,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -473,16 +459,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", - "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -511,9 +497,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", + "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -568,11 +554,11 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -608,9 +594,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "engines": { "node": ">=6.9.0" } @@ -632,12 +618,12 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -681,9 +667,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "engines": { "node": ">=6.9.0" } @@ -718,35 +704,36 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", - "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -754,12 +741,27 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz", + "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -769,13 +771,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/plugin-transform-optional-chaining": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -785,12 +787,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -901,11 +903,11 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -915,11 +917,11 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -951,11 +953,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1074,11 +1076,11 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1088,12 +1090,12 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-remap-async-to-generator": "^7.22.20", "@babel/plugin-syntax-async-generators": "^7.8.4" }, @@ -1105,12 +1107,12 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { @@ -1121,11 +1123,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1135,11 +1137,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", + "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1149,12 +1151,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1164,12 +1166,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1180,17 +1182,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -1202,12 +1203,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1217,11 +1218,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1231,12 +1232,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1246,11 +1247,11 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1260,11 +1261,11 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1275,12 +1276,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", "dependencies": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1290,11 +1291,11 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1305,11 +1306,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1319,13 +1321,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1335,11 +1337,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1350,11 +1352,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1364,11 +1366,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1379,11 +1381,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1393,12 +1395,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1408,12 +1410,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -1424,13 +1426,13 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { @@ -1441,12 +1443,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1471,11 +1473,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1485,11 +1487,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1500,11 +1502,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1515,15 +1517,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/plugin-transform-parameters": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1533,12 +1534,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1548,11 +1549,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1563,11 +1564,11 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -1579,11 +1580,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1593,12 +1594,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1608,13 +1609,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1625,11 +1626,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1639,11 +1640,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1654,11 +1655,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1668,15 +1669,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz", - "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==", - "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", + "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -1687,11 +1688,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1701,11 +1702,11 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { @@ -1716,11 +1717,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1730,11 +1731,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1744,11 +1745,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1758,11 +1759,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1772,12 +1773,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1787,12 +1788,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1802,12 +1803,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1817,25 +1818,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", - "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz", + "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/compat-data": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -1847,58 +1849,58 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.3", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.4", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.1", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.1", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.1", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.1", + "@babel/plugin-transform-parameters": "^7.24.1", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.1", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.1", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1928,9 +1930,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1956,32 +1958,32 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", - "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.5", - "@babel/types": "^7.23.5", - "debug": "^4.1.0", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1989,9 +1991,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -2002,9 +2004,9 @@ } }, "node_modules/@blueking/app-select": { - "version": "0.0.1-beta.6", - "resolved": "https://registry.npmjs.org/@blueking/app-select/-/app-select-0.0.1-beta.6.tgz", - "integrity": "sha512-CufAaBX4Z25eyrTGp9wazrYAYK+XxDb+ON2ZlJs0fFAJueld+BO2jlVpaltAC7XIE2I/KV8hbqSHKRJs9FRkQw==", + "version": "0.0.1-beta.8", + "resolved": "https://registry.npmjs.org/@blueking/app-select/-/app-select-0.0.1-beta.8.tgz", + "integrity": "sha512-/IFTLFStH4xG3d9PKHTRkQoY+k8C8ulaguDvzu7GBOjZibLfzEsAqJZLrK/0bncokNjlkthAbwvNsvmB0ZMS8Q==", "engines": { "node": ">= 16.16.0", "npm": "6.14.15" @@ -2043,9 +2045,9 @@ } }, "node_modules/@blueking/babel-preset-bk/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2117,9 +2119,9 @@ } }, "node_modules/@blueking/cli-service-webpack/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2229,9 +2231,9 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2252,13 +2254,13 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2266,9 +2268,9 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@blueking/eslint-config-bk/node_modules/@vue/eslint-config-typescript": { @@ -2290,9 +2292,9 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2372,16 +2374,16 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -2513,9 +2515,9 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2566,9 +2568,9 @@ } }, "node_modules/@blueking/eslint-config-bk/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3318,9 +3320,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "peer": true, "dependencies": { "type-fest": "^0.20.2" @@ -3367,34 +3369,34 @@ } }, "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "dependencies": { - "@floating-ui/utils": "^0.1.3" + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", + "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@floating-ui/core": "^1.5.3", + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, "node_modules/@hapi/hoek": { "version": "9.3.0", @@ -3442,17 +3444,17 @@ "peer": true }, "node_modules/@interactjs/types": { - "version": "1.10.23", - "resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.23.tgz", - "integrity": "sha512-8/s1gFVNW60SqFLiFQDsvJuuzICthzyOu52bu8MhLFsxFnhVfng1xzjxi2+UokQULsp0WgBsctIS9bF7se9nJQ==" + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz", + "integrity": "sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA==" }, "node_modules/@intlify/core-base": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.8.0.tgz", - "integrity": "sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.11.0.tgz", + "integrity": "sha512-cveOqAstjLZIiyatcP/HrzrQ87cZI8ScPQna3yvoM8zjcjcIRK1MRvmxUNlPdg0rTNJMZw7rixPVM58O5aHVPA==", "dependencies": { - "@intlify/message-compiler": "9.8.0", - "@intlify/shared": "9.8.0" + "@intlify/message-compiler": "9.11.0", + "@intlify/shared": "9.11.0" }, "engines": { "node": ">= 16" @@ -3462,11 +3464,11 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.8.0.tgz", - "integrity": "sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.11.0.tgz", + "integrity": "sha512-x31Gl7cscnoI4UUY1yaIy8e7vVMVW1VVlTXZz4SIHKqoSEUkfmgqK8NAx1e7RcoHEbICR7uyCbud0ZL1s4OGXQ==", "dependencies": { - "@intlify/shared": "9.8.0", + "@intlify/shared": "9.11.0", "source-map-js": "^1.0.2" }, "engines": { @@ -3477,9 +3479,9 @@ } }, "node_modules/@intlify/shared": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.8.0.tgz", - "integrity": "sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.11.0.tgz", + "integrity": "sha512-KHSNgi7sRjmSm7aD8QH8WFt9VfKaekJuJ473opbJlkGY3EDnDUU8ikIhG8PbasQbgNvbY3m3tWNGqk2omIdwMA==", "engines": { "node": ">= 16" }, @@ -3488,41 +3490,41 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -3531,18 +3533,18 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", @@ -3682,9 +3684,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.23", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", - "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" }, "node_modules/@popperjs/core": { "version": "2.11.8", @@ -3709,9 +3711,9 @@ } }, "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -3767,9 +3769,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true }, "node_modules/@tsconfig/node12": { @@ -3920,9 +3922,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "8.44.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", - "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "version": "8.56.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", + "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3959,9 +3961,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -3970,9 +3972,9 @@ } }, "node_modules/@types/geojson": { - "version": "7946.0.13", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", - "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" }, "node_modules/@types/glob": { "version": "7.2.0", @@ -4013,9 +4015,9 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==" }, "node_modules/@types/lodash-es": { "version": "4.17.12", @@ -4065,17 +4067,17 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==" }, "node_modules/@types/node": { - "version": "18.18.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.14.tgz", - "integrity": "sha512-iSOeNeXYNYNLLOMDSVPvIFojclvMZ/HDY2dU17kUlcsOsSQETbWIslJbYLZgA+ox8g2XQwSHKTkght1a5X26lQ==", + "version": "18.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.30.tgz", + "integrity": "sha512-453z1zPuJLVDbyahaa1sSD5C2sht6ZpHp5rgJNs+H8YGqhluCXcuOUmBYsAo0Tos0cHySJ3lVUGbGgLlqIkpyg==", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/node-forge": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", - "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dependencies": { "@types/node": "*" } @@ -4096,9 +4098,9 @@ "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" }, "node_modules/@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==" }, "node_modules/@types/range-parser": { "version": "1.2.7", @@ -4111,9 +4113,9 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" }, "node_modules/@types/send": { "version": "0.17.4", @@ -4133,13 +4135,13 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { @@ -4151,9 +4153,9 @@ } }, "node_modules/@types/sortablejs": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.7.tgz", - "integrity": "sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", + "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", "peer": true }, "node_modules/@types/supercluster": { @@ -4233,9 +4235,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4376,9 +4378,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4510,27 +4512,71 @@ "integrity": "sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==" }, "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.1.5.tgz", - "integrity": "sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz", + "integrity": "sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==" }, "node_modules/@vue/babel-plugin-jsx": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.5.tgz", - "integrity": "sha512-nKs1/Bg9U1n3qSWnsHhCVQtAzI6aQXqua8j/bZrau8ywT1ilXQbK4FwEJGmU8fV7tcpuFvWmmN7TMmV1OBma1g==", - "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "@vue/babel-helper-vue-transform-on": "^1.1.5", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz", + "integrity": "sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==", + "dependencies": { + "@babel/helper-module-imports": "~7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "@vue/babel-helper-vue-transform-on": "1.2.2", + "@vue/babel-plugin-resolve-type": "1.2.2", "camelcase": "^6.3.0", "html-tags": "^3.3.1", "svg-tags": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-jsx/node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz", + "integrity": "sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/helper-module-imports": "~7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/parser": "^7.23.9", + "@vue/compiler-sfc": "^3.4.15" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@vue/babel-plugin-transform-vue-jsx": { @@ -4679,49 +4725,49 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.9.tgz", - "integrity": "sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", + "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/shared": "3.3.9", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.21", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.9.tgz", - "integrity": "sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", + "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", "dependencies": { - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-core": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.9.tgz", - "integrity": "sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==", - "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-ssr": "3.3.9", - "@vue/reactivity-transform": "3.3.9", - "@vue/shared": "3.3.9", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", + "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.21", + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.31", + "magic-string": "^0.30.7", + "postcss": "^8.4.35", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.9.tgz", - "integrity": "sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", + "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", "dependencies": { - "@vue/compiler-dom": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-dom": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/component-compiler-utils": { @@ -4791,9 +4837,9 @@ "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" }, "node_modules/@vue/eslint-config-standard": { "version": "6.1.0", @@ -4820,60 +4866,48 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.9.tgz", - "integrity": "sha512-VmpIqlNp+aYDg2X0xQhJqHx9YguOmz2UxuUJDckBdQCNkipJvfk9yA75woLWElCa0Jtyec3lAAt49GO0izsphw==", - "dependencies": { - "@vue/shared": "3.3.9" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.9.tgz", - "integrity": "sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", + "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5" + "@vue/shared": "3.4.21" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.9.tgz", - "integrity": "sha512-xxaG9KvPm3GTRuM4ZyU8Tc+pMVzcu6eeoSRQJ9IE7NmCcClW6z4B3Ij6L4EDl80sxe/arTtQ6YmgiO4UZqRc+w==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", + "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", "dependencies": { - "@vue/reactivity": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/reactivity": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.9.tgz", - "integrity": "sha512-e7LIfcxYSWbV6BK1wQv9qJyxprC75EvSqF/kQKe6bdZEDNValzeRXEVgiX7AHI6hZ59HA4h7WT5CGvm69vzJTQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", + "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", "dependencies": { - "@vue/runtime-core": "3.3.9", - "@vue/shared": "3.3.9", - "csstype": "^3.1.2" + "@vue/runtime-core": "3.4.21", + "@vue/shared": "3.4.21", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.9.tgz", - "integrity": "sha512-w0zT/s5l3Oa3ZjtLW88eO4uV6AQFqU8X5GOgzq7SkQQu6vVr+8tfm+OI2kDBplS/W/XgCBuFXiPw6T5EdwXP0A==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", + "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", "dependencies": { - "@vue/compiler-ssr": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21" }, "peerDependencies": { - "vue": "3.3.9" + "vue": "3.4.21" } }, "node_modules/@vue/shared": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz", - "integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==" + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" }, "node_modules/@vueuse/core": { "version": "8.9.4", @@ -4925,9 +4959,9 @@ } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -4996,9 +5030,9 @@ } }, "node_modules/@vueuse/router/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -5171,9 +5205,9 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -5190,9 +5224,9 @@ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" }, "node_modules/@webassemblyjs/helper-code-frame": { "version": "1.9.0", @@ -5271,14 +5305,14 @@ "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -5303,26 +5337,26 @@ "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -5330,22 +5364,22 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -5392,11 +5426,11 @@ "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -5475,9 +5509,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "engines": { "node": ">=0.4.0" } @@ -5656,21 +5690,24 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/array-ify": { "version": "1.0.0", @@ -5679,14 +5716,15 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -5722,30 +5760,35 @@ } }, "node_modules/array.prototype.find": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.2.tgz", - "integrity": "sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.3.tgz", + "integrity": "sha512-fO/ORdOELvjbbeIfZfzrXFMhYHGofRGqd+am9zm3tZ4GlJINj/pA2eITyfd65Vg6+ZbHd/Cys7stpoRSWtQFdA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5789,16 +5832,17 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -5845,14 +5889,13 @@ "integrity": "sha512-HkI/zLo2AbSRO4fqVkmyf3hms0bJDs3iboHqTrNuwTiCRvdYXM7HFhfhB6Dk51anV2LM/IMB83mtK9mHw4FlAg==" }, "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "minimalistic-assert": "^1.0.0" } }, "node_modules/asn1.js/node_modules/bn.js": { @@ -5930,9 +5973,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -5949,9 +5992,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -5967,9 +6010,12 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -5978,11 +6024,11 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dependencies": { - "follow-redirects": "^1.15.6", + "follow-redirects": "^1.15.0", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -6006,12 +6052,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { @@ -6019,23 +6065,23 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", - "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.6.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6119,6 +6165,11 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" }, + "node_modules/batch-processor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", + "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==" + }, "node_modules/bezier-easing": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", @@ -6133,11 +6184,14 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bindings": { @@ -7168,9 +7222,9 @@ } }, "node_modules/bkui-vue": { - "version": "0.0.2-beta.103", - "resolved": "https://registry.npmjs.org/bkui-vue/-/bkui-vue-0.0.2-beta.103.tgz", - "integrity": "sha512-0JZphxCwYLRYDZYUcK5J01FStiIjgTph9COHoGCxdcYFkmxa1LDFfr8At/bMaOUoK3lryraWnVa4Z0NMoVOw6Q==", + "version": "1.0.3-beta.67.dialog.3", + "resolved": "https://registry.npmjs.org/bkui-vue/-/bkui-vue-1.0.3-beta.67.dialog.3.tgz", + "integrity": "sha512-Lso1k2qfsrLB1YKXNBFFZ/4SdkOJuV9zuCWNh8wlDsX06EVM+W7Bt9mFd0+bTGqo1gpK01Ve+c8K18VG9surdA==", "dependencies": { "@floating-ui/dom": "~1.5.0", "@popperjs/core": "~2.11.8", @@ -7210,12 +7264,12 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -7223,7 +7277,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -7282,16 +7336,20 @@ } }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, + "node_modules/bonjour/node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, "node_modules/bonjour/node_modules/dns-packet": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", @@ -7389,24 +7447,62 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", - "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.4", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.6", - "readable-stream": "^3.6.2", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 4" + "safe-buffer": "~5.1.0" } }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", @@ -7416,9 +7512,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -7434,9 +7530,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -7491,9 +7587,9 @@ "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" }, "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dependencies": { "semver": "^7.0.0" } @@ -7510,9 +7606,9 @@ } }, "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7611,13 +7707,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7687,9 +7788,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001565", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", - "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "version": "1.0.30001607", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz", + "integrity": "sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==", "funding": [ { "type": "opencollective", @@ -7754,15 +7855,9 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -7775,6 +7870,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -7850,9 +7948,9 @@ } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-css": { "version": "4.2.4", @@ -8513,9 +8611,9 @@ } }, "node_modules/core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz", + "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -8523,11 +8621,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz", - "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.23.0" }, "funding": { "type": "opencollective", @@ -8535,9 +8633,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.33.3.tgz", - "integrity": "sha512-taJ00IDOP+XYQEA2dAe4ESkmHt1fL8wzYDo3mRWQey8uO9UojlBFMneA65kMyxfYP7106c6LzWaq7/haDT6BCQ==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz", + "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -8698,18 +8796,18 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -8719,7 +8817,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -8734,9 +8841,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8996,9 +9103,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cyclist": { "version": "1.0.2", @@ -9006,12 +9113,15 @@ "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==" }, "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/d3-array": { @@ -9108,6 +9218,54 @@ "node": ">=8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -9244,16 +9402,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -9444,7 +9605,8 @@ "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true }, "node_modules/dns-packet": { "version": "5.6.1", @@ -9498,6 +9660,14 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/dom7": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz", @@ -9592,14 +9762,14 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -9670,12 +9840,12 @@ "dev": true }, "node_modules/echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz", + "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "5.5.0" } }, "node_modules/echarts-wordcloud": { @@ -9697,9 +9867,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.597", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.597.tgz", - "integrity": "sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==" + "version": "1.4.729", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz", + "integrity": "sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA==" }, "node_modules/element-plus": { "version": "2.2.11", @@ -9739,15 +9909,18 @@ "@floating-ui/core": "^0.7.3" } }, - "node_modules/element-resize-event": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-3.0.6.tgz", - "integrity": "sha512-sSeXY9rNDp86bJODW68pxLcy3A5FrPZfIgOrJHzqgYzX513Zq6/ytdBigp7KeJEpZZopBBSiO1cVuiRkZpNxLw==" + "node_modules/element-resize-detector": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz", + "integrity": "sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==", + "dependencies": { + "batch-processor": "1.0.0" + } }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -9821,17 +9994,20 @@ } }, "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/envinfo": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", - "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz", + "integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -9860,49 +10036,56 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -9911,19 +10094,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==" + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -9954,13 +10167,14 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -9978,12 +10192,15 @@ } }, "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/esbuild": { @@ -10323,9 +10540,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -10430,6 +10647,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-config-standard": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", @@ -10605,9 +10834,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dependencies": { "debug": "^3.2.7" }, @@ -10754,9 +10983,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -10774,7 +11003,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -10878,9 +11107,9 @@ } }, "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -10942,7 +11171,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, - "peer": true, "dependencies": { "prettier-linter-helpers": "^1.0.0" }, @@ -11300,9 +11528,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "peer": true, "dependencies": { "type-fest": "^0.20.2" @@ -11375,9 +11603,9 @@ } }, "node_modules/eslint/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11419,6 +11647,20 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "peer": true }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -11646,16 +11888,16 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -11697,10 +11939,13 @@ "art-template": ">=4.1.0" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } }, "node_modules/express/node_modules/debug": { "version": "2.6.9", @@ -11737,11 +11982,6 @@ "type": "^2.7.2" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11818,8 +12058,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fast-glob": { "version": "3.3.2", @@ -11866,9 +12105,9 @@ } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -11887,7 +12126,8 @@ "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "deprecated": "This module is no longer supported." }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -12007,17 +12247,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" - }, - "node_modules/flru": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", - "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", - "engines": { - "node": ">=6" - } + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, "node_modules/flush-write-stream": { "version": "1.1.1", @@ -12238,9 +12470,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -12510,15 +12742,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12546,12 +12782,13 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -12817,20 +13054,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -12850,11 +13087,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -12927,13 +13164,12 @@ } }, "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" }, "engines": { "node": ">=4" @@ -12954,9 +13190,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -13074,9 +13310,9 @@ } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "funding": [ { "type": "github", @@ -13143,9 +13379,9 @@ } }, "node_modules/html-minifier-terser/node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "dependencies": { "source-map": "~0.6.0" }, @@ -13204,9 +13440,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -13222,7 +13458,16 @@ "url": "https://opencollective.com/html-webpack-plugin" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/html-webpack-plugin/node_modules/tapable": { @@ -13251,6 +13496,14 @@ "entities": "^2.0.0" } }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -13415,9 +13668,9 @@ "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==" }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "engines": { "node": ">= 4" } @@ -13445,9 +13698,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "devOptional": true }, "node_modules/import-fresh": { @@ -13541,11 +13794,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/interactjs": { - "version": "1.10.23", - "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.23.tgz", - "integrity": "sha512-ZnxfYh4QBnWnnCXVOVHEU4r2w01EQMTsLCd71n0mpsItFhV7S/jXycvzgsNvf5I99trBRRwP8RJXU8oy4hRFEw==", + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz", + "integrity": "sha512-y/8RcCftGAF24gSp76X2JS3XpHiUvDQyhF8i7ujemBz77hwiHDuJzftHx7thY8cxGogwGiPJ+o97kWB6eAXnsA==", "dependencies": { - "@interactjs/types": "1.10.23" + "@interactjs/types": "1.10.27" } }, "node_modules/internal-ip": { @@ -13702,11 +13955,11 @@ } }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -13723,20 +13976,10 @@ "node": ">= 0.10" } }, - "node_modules/inversify": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.1.1.tgz", - "integrity": "sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==" - }, - "node_modules/inversify-inject-decorators": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/inversify-inject-decorators/-/inversify-inject-decorators-3.1.0.tgz", - "integrity": "sha512-/seBlVp5bXrLQS3DpKEmlgeZL6C7Tf/QITd+IMQrbBBGuCbxb7k3hRAWu9XSreNpFzLgSboz3sClLSEmGwHphw==" - }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "node_modules/ip-regex": { @@ -13824,13 +14067,15 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13933,6 +14178,20 @@ "node": ">= 0.4" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -14052,9 +14311,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "engines": { "node": ">= 0.4" }, @@ -14174,11 +14433,14 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14236,11 +14498,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -14373,13 +14635,13 @@ } }, "node_modules/joi": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", - "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "version": "17.12.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", + "integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==", "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } @@ -14476,9 +14738,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -14585,9 +14847,9 @@ } }, "node_modules/less-loader": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.3.tgz", - "integrity": "sha512-A5b7O8dH9xpxvkosNrP0dFp2i/dISOJa9WwGF3WJflfqIERE2ybxh1BFDj5CovC2+jCE4M354mk90hN6ziXlVw==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.4.tgz", + "integrity": "sha512-6/GrYaB6QcW6Vj+/9ZPgKKs6G10YZai/l/eJ4SLwbzqNTBsAqt5hSLVF47TgsiBxV1P6eAU0GYRH3YRuQU9V3A==", "engines": { "node": ">= 14.15.0" }, @@ -14774,9 +15036,9 @@ } }, "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -15166,9 +15428,9 @@ } }, "node_modules/loglevel": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", - "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", "dev": true, "engines": { "node": ">= 0.6.0" @@ -15201,9 +15463,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -15633,11 +15895,12 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -15699,6 +15962,14 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/mini-css-extract-plugin/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -15865,9 +16136,9 @@ } }, "node_modules/mlly/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -15877,15 +16148,15 @@ } }, "node_modules/mlly/node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } @@ -15957,9 +16228,9 @@ } }, "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "engines": { "node": ">=10" } @@ -15998,9 +16269,9 @@ "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" }, "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", "optional": true }, "node_modules/nanoid": { @@ -16105,13 +16376,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/needle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", - "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "optional": true, "peer": true, "dependencies": { - "debug": "^3.2.6", "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, @@ -16122,16 +16392,6 @@ "node": ">= 4.4.x" } }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "peer": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/needle/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -16286,9 +16546,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-package-data": { "version": "3.0.3", @@ -16316,9 +16576,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -19011,13 +19271,13 @@ } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -19046,12 +19306,12 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -19063,13 +19323,14 @@ } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -19079,14 +19340,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/object.pick": { @@ -19101,13 +19364,13 @@ } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -19501,15 +19764,19 @@ } }, "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, "node_modules/parse-entities": { @@ -19761,9 +20028,9 @@ } }, "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -19877,9 +20144,9 @@ } }, "node_modules/pkg-types/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -19889,21 +20156,21 @@ } }, "node_modules/pkg-types/node_modules/mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", "dev": true, "dependencies": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", + "acorn": "^8.11.3", + "pathe": "^1.1.2", "pkg-types": "^1.0.3", - "ufo": "^1.3.0" + "ufo": "^1.3.2" } }, "node_modules/pkg-types/node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true }, "node_modules/plur": { @@ -19922,10 +20189,11 @@ } }, "node_modules/polygon-clipping": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", - "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.7.tgz", + "integrity": "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA==", "dependencies": { + "robust-predicates": "^3.0.2", "splaytree": "^3.1.0" } }, @@ -19977,10 +20245,18 @@ "node": ">=0.10.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -19996,9 +20272,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -20230,13 +20506,13 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { "node": ">= 14.15.0" @@ -20262,9 +20538,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -20378,9 +20654,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -20389,9 +20665,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -20405,9 +20681,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -20725,9 +21001,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -20825,9 +21101,9 @@ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" }, "node_modules/preact": { - "version": "10.19.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz", - "integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==", + "version": "10.20.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", + "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -20861,7 +21137,6 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, - "peer": true, "dependencies": { "fast-diff": "^1.1.2" }, @@ -21087,11 +21362,11 @@ } }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -21172,9 +21447,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -21368,11 +21643,6 @@ "node": ">=8" } }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -21390,9 +21660,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -21449,13 +21719,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -21727,9 +21998,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", "dev": true }, "node_modules/rimraf": { @@ -21755,6 +22026,11 @@ "inherits": "^2.0.1" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -21791,12 +22067,12 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -21835,14 +22111,17 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21853,9 +22132,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.74.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz", + "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==", "devOptional": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -21870,9 +22149,9 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", "dependencies": { "neo-async": "^2.6.2" }, @@ -22020,9 +22299,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dependencies": { "randombytes": "^2.1.0" } @@ -22118,27 +22397,30 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -22231,13 +22513,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22249,12 +22535,12 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sirv": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", - "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", "totalist": "^3.0.0" }, "engines": { @@ -22319,11 +22605,11 @@ } }, "node_modules/snabbdom": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.1.tgz", - "integrity": "sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.6.2.tgz", + "integrity": "sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==", "engines": { - "node": ">=8.3.0" + "node": ">=12.17.0" } }, "node_modules/snapdragon": { @@ -22517,9 +22803,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -22577,9 +22863,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", @@ -22591,9 +22877,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==" + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" }, "node_modules/spdy": { "version": "4.0.2", @@ -22830,9 +23116,9 @@ } }, "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -22896,13 +23182,14 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -22912,26 +23199,29 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -23008,9 +23298,9 @@ } }, "node_modules/strip-literal/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -23020,9 +23310,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "engines": { "node": ">= 12.13.0" @@ -23575,12 +23865,12 @@ } }, "node_modules/stylus": { - "version": "0.62.0", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.62.0.tgz", - "integrity": "sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==", + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.63.0.tgz", + "integrity": "sha512-OMlgrTCPzE/ibtRMoeLVhOY0RcNuNWh0rhAVqeKnk/QwcuUKQbnqhZ1kg2vzD8VU/6h3FoPTq4RJPHgLBvX6Bw==", "peer": true, "dependencies": { - "@adobe/css-tools": "~4.3.1", + "@adobe/css-tools": "~4.3.3", "debug": "^4.3.2", "glob": "^7.1.6", "sax": "~1.3.0", @@ -23765,9 +24055,9 @@ } }, "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -23881,9 +24171,9 @@ } }, "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -23898,15 +24188,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -23948,9 +24238,9 @@ } }, "node_modules/terser/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -24224,11 +24514,11 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -24299,9 +24589,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/ts-loader/node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -24330,9 +24620,9 @@ } }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -24376,9 +24666,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -24419,9 +24709,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -24431,9 +24721,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -24500,9 +24790,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/tsconfig-paths-webpack-plugin/node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -24605,9 +24895,9 @@ } }, "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/type-check": { "version": "0.4.0", @@ -24645,27 +24935,28 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -24675,15 +24966,16 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -24693,13 +24985,19 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -24744,9 +25042,9 @@ "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, "node_modules/uglify-js": { @@ -25117,9 +25415,9 @@ } }, "node_modules/unplugin/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -25391,15 +25689,15 @@ } }, "node_modules/vue": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.9.tgz", - "integrity": "sha512-sy5sLCTR8m6tvUk1/ijri3Yqzgpdsmxgj6n6yl7GXXCXqVbmW2RCXe9atE4cEI6Iv7L89v5f35fZRRr5dChP9w==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", + "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", "dependencies": { - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-sfc": "3.3.9", - "@vue/runtime-dom": "3.3.9", - "@vue/server-renderer": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-sfc": "3.4.21", + "@vue/runtime-dom": "3.4.21", + "@vue/server-renderer": "3.4.21", + "@vue/shared": "3.4.21" }, "peerDependencies": { "typescript": "*" @@ -25518,28 +25816,29 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@blueking/eslint-config-bk": { - "version": "2.1.0-beta.16", - "resolved": "https://registry.npmjs.org/@blueking/eslint-config-bk/-/eslint-config-bk-2.1.0-beta.16.tgz", - "integrity": "sha512-8N0ufQAOb2J9wIBEE7zmYgQ//O+7pJzVKJg4PWAnGUmBZpdCdzsr0pPY6uYTxpAXmXyoS/uUGyZ6XqmwbCvmdA==", + "version": "2.1.0-beta.19", + "resolved": "https://registry.npmjs.org/@blueking/eslint-config-bk/-/eslint-config-bk-2.1.0-beta.19.tgz", + "integrity": "sha512-NZ3DbYNHJ+pu+lfYUc90qzEiEOpwEVKTR03D+DkuynLyv+qu5iCnXQo86ca88OtOYow3weOtlxPaqDJnEL32Jg==", "dependencies": { - "@babel/eslint-parser": "^7.22.9", - "@typescript-eslint/eslint-plugin": "^6.2.1", - "@typescript-eslint/parser": "^6.2.1", + "@babel/eslint-parser": "^7.23.10", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "@vue/eslint-config-standard": "^8.0.1", - "@vue/eslint-config-typescript": "^12.0.0", - "eslint": "^8.46.0", - "eslint-plugin-import": "^2.28.0", + "@vue/eslint-config-typescript": "^13.0.0", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "~11.1.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-standard": "~5.0.0", - "eslint-plugin-vue": "^9.16.1", - "typescript": "^5.1.6" + "eslint-plugin-vue": "^9.23.0", + "typescript": "^5.4.2", + "vue-eslint-parser": "^9.4.2" } }, "node_modules/vue-grid-layout-next/node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -25559,12 +25858,12 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -25572,20 +25871,20 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", + "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/type-utils": "7.5.0", + "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -25594,15 +25893,15 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -25611,25 +25910,25 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", - "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "dependencies": { + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -25638,15 +25937,15 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", + "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -25654,24 +25953,24 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", + "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/utils": "7.5.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -25680,11 +25979,11 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", + "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -25692,20 +25991,21 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", + "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -25717,40 +26017,54 @@ } } }, + "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", + "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", "semver": "^7.5.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/vue-grid-layout-next/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", + "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "7.5.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -25775,21 +26089,21 @@ } }, "node_modules/vue-grid-layout-next/node_modules/@vue/eslint-config-typescript": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", - "integrity": "sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz", + "integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==", "dependencies": { - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", "vue-eslint-parser": "^9.3.1" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", + "eslint": "^8.56.0", "eslint-plugin-vue": "^9.0.0", - "typescript": "*" + "typescript": ">=4.7.4" }, "peerDependenciesMeta": { "typescript": { @@ -25916,9 +26230,9 @@ } }, "node_modules/vue-grid-layout-next/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -25948,6 +26262,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/vue-grid-layout-next/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/vue-grid-layout-next/node_modules/braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", @@ -26053,15 +26375,15 @@ } }, "node_modules/vue-grid-layout-next/node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -26146,16 +26468,17 @@ } }, "node_modules/vue-grid-layout-next/node_modules/eslint-plugin-vue": { - "version": "9.18.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz", - "integrity": "sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz", + "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.13", - "semver": "^7.5.4", - "vue-eslint-parser": "^9.3.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.0", + "vue-eslint-parser": "^9.4.2", "xml-name-validator": "^4.0.0" }, "engines": { @@ -26243,9 +26566,9 @@ } }, "node_modules/vue-grid-layout-next/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -26569,9 +26892,9 @@ } }, "node_modules/vue-grid-layout-next/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -26687,9 +27010,9 @@ } }, "node_modules/vue-grid-layout-next/node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26699,9 +27022,9 @@ } }, "node_modules/vue-grid-layout-next/node_modules/vue-eslint-parser": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz", - "integrity": "sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", + "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", @@ -26833,12 +27156,12 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==" }, "node_modules/vue-i18n": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.8.0.tgz", - "integrity": "sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.11.0.tgz", + "integrity": "sha512-vU4gY6lu8Pdfs9BgKGiDAJmFDf88cceR47KcSB0VW4xJzUrXR/7qwqM7A8dQ2nedhoIDxoOm5Ro4pFd2KvJqbA==", "dependencies": { - "@intlify/core-base": "9.8.0", - "@intlify/shared": "9.8.0", + "@intlify/core-base": "9.11.0", + "@intlify/shared": "9.11.0", "@vue/devtools-api": "^6.5.0" }, "engines": { @@ -26852,9 +27175,9 @@ } }, "node_modules/vue-loader": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.3.1.tgz", - "integrity": "sha512-nmVu7KU8geOyzsStyyaxID/uBGDMS8BkPXb6Lu2SNkMawriIbb+hYrNtgftHMKxOSkjjjTF5OSSwPo3KP59egg==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", "dependencies": { "chalk": "^4.1.0", "hash-sum": "^2.0.0", @@ -27001,11 +27324,11 @@ } }, "node_modules/vue-router": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", - "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz", + "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==", "dependencies": { - "@vue/devtools-api": "^6.5.0" + "@vue/devtools-api": "^6.5.1" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -27129,9 +27452,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -27493,33 +27816,33 @@ "integrity": "sha512-/TllNPjGenDwjE67M16TD9ALwuY847/zIoH7r+e5rSeG4kEa3HiMTAsUDj80yzIzhtshkv215KfsnQ/RXR3nVA==" }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -27565,9 +27888,9 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -27598,6 +27921,7 @@ "version": "6.5.1", "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dependencies": { "deepmerge": "^1.5.2", "javascript-stringify": "^2.0.1" @@ -27680,9 +28004,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -27751,9 +28075,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -27783,7 +28107,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -27858,9 +28182,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, @@ -27944,9 +28268,9 @@ "dev": true }, "node_modules/webpack/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -27963,9 +28287,9 @@ } }, "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -28056,15 +28380,15 @@ "dev": true }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -28309,9 +28633,9 @@ } }, "node_modules/zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz", + "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==", "dependencies": { "tslib": "2.3.0" } diff --git a/front/package.json b/front/package.json index 6b48392812..f66c32f1e8 100644 --- a/front/package.json +++ b/front/package.json @@ -31,7 +31,7 @@ "build:open": "cross-env version=open webpack5-cli-service build", "build:vue": "cross-env version=sdk-dashboard-vue webpack5-cli-service build", "build:app": "cross-env version=sdk-dashboard-app webpack5-cli-service build", - "lint": "eslint --fix src", + "lint": "eslint --fix src/**/*.{js,jsx,ts,tsx,vue}", "lint:style": "stylelint --fix ./**/*.{scss,css,vue} --custom-syntax", "prepare": "cd .. && husky install front/.husky" }, @@ -50,7 +50,7 @@ "@wangeditor/editor-for-vue": "^5.1.12", "art-template": "^4.13.2", "axios": "^1.6.0", - "bkui-vue": "0.0.2-beta.103", + "bkui-vue": "^1.0.3-beta.67.dialog.3", "classnames": "^2.3.1", "cookie": "^0.5.0", "cookie-parser": "^1.4.6", @@ -105,10 +105,12 @@ "autoprefixer": "^10.4.7", "bk-vision-cli": "^4.1.5", "cross-env": "^7.0.3", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "lint-staged": "^13.0.3", "postcss-import": "^14.1.0", - "prettier": "2.8.8", + "prettier": "^2.8.8", "sass": "^1.52.3", "sass-loader": "^13.0.0", "typescript": "^4.7.4", diff --git a/front/src/api/load_balancers/apply-clb/index.ts b/front/src/api/load_balancers/apply-clb/index.ts new file mode 100644 index 0000000000..b65237bcad --- /dev/null +++ b/front/src/api/load_balancers/apply-clb/index.ts @@ -0,0 +1,23 @@ +/** + * 负载均衡 - 购买 + */ +// import http +import http from '@/http'; +// import types +import { NetworkAccountTypeResp, ResourceOfCurrentRegionReqData, ResourceOfCurrentRegionResp } from './types'; + +const { BK_HCM_AJAX_URL_PREFIX } = window.PROJECT_CONFIG; + +// 查询用户网络类型 +// https://github.com/TencentBlueKing/bk-hcm/blob/feature-loadbalancer/docs/api-docs/web-server/docs/resource/clb/tcloud_describe_network_account_type.md +export const reqAccountNetworkType = async (account_id: string): Promise => { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/vendors/tcloud/accounts/${account_id}/network_type`); +}; + +// 查询用户在当前地域支持可用区列表和资源列表 +// https://github.com/TencentBlueKing/bk-hcm/blob/feature-loadbalancer/docs/api-docs/web-server/docs/resource/clb/tcloud_describe_resources.md +export const reqResourceListOfCurrentRegion = async ( + data: ResourceOfCurrentRegionReqData, +): Promise => { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/vendors/tcloud/load_balancers/resources/describe`, data); +}; diff --git a/front/src/api/load_balancers/apply-clb/types.ts b/front/src/api/load_balancers/apply-clb/types.ts new file mode 100644 index 0000000000..ff6eaa446c --- /dev/null +++ b/front/src/api/load_balancers/apply-clb/types.ts @@ -0,0 +1,136 @@ +import { VendorEnum } from '@/common/constant'; +import { IQueryResData } from '@/typings'; + +// 用户网络类型 +export type NetworkAccountType = 'STANDARD' | 'LEGACY'; +interface AccountTypeInfo { + NetworkAccountType: NetworkAccountType; // 用户账号的网络类型 + RequestId: string; // 请求id +} +// 用户网络类型 resp +export type NetworkAccountTypeResp = IQueryResData; + +// 查询当前地域下可用区列表和资源列表 - 输入参数 +export interface ResourceOfCurrentRegionReqData { + // 云账号id + account_id: string; + // 地域 + region: string; + // 指定可用区 + master_zone?: string[]; + // 指定IP版本,如"IPv4"、"IPv6"、"IPv6_Nat" + ip_version?: string[]; + // 指定运营商类型,如:"BGP","CMCC","CUCC","CTCC" + isp?: string[]; + // 返回可用区资源列表数目, 默认20, 最大值100 + limit?: number; + // 返回可用区资源列表起始偏移量, 默认0 + offset?: number; +} +// 查询当前地域下可用区列表和资源列表 - 响应结果 +export type ResourceOfCurrentRegionResp = IQueryResData; +interface DescribeResourcesResponse { + // 响应数据 + ZoneResourceSet: ZoneResource[]; + // 符合条件的总记录条数 + TotalCount: number; + RequestId: string; +} +interface ZoneResource { + // 主可用区 + MasterZone: string; + // 资源列表 + ResourceSet: Resource[]; + // 备可用区 + SlaveZone?: string; + // ip版本(枚举值:IPv4,IPv6,IPv6_Nat) + IPVersion: string; + // 所属地域 + ZoneRegion: string; + // 是否本地可用区 + LocalZone: boolean; + // 可用区资源的类型,SHARED表示共享资源,EXCLUSIVE表示独占资源 + ZoneResourceType: string; + // 可用区是否是EdgeZone可用区 + EdgeZone: boolean; + // 网络出口 + Egress: string; +} +interface Resource { + // 运营商内具体资源信息,如"CMCC", "CUCC", "CTCC", "BGP", "INTERNAL" + Type: string[]; + // 运营商信息,如"CMCC", "CUCC", "CTCC", "BGP", "INTERNAL" + Isp: string; + // 可用资源 + AvailabilitySet?: ResourceAvailability[]; + // 运营商类型信息 + TypeSet?: TypeInfo[]; +} +interface ResourceAvailability { + // 运营商内具体资源信息,如"CMCC", "CUCC", "CTCC", "BGP" + Type: string; + // 资源可用性,"Available":可用,"Unavailable":不可用 + Availability: string; +} +interface TypeInfo { + // 运营商类型 + Type: string; + // 规格可用性 + SpecAvailabilitySet?: SpecAvailability[]; +} +export interface SpecAvailability { + // 规格类型 clb.c2.medium(标准型)clb.c3.small(高阶型1)clb.c3.medium(高阶型2)clb.c4.small(超强型1)clb.c4.medium(超强型2)clb.c4.large(超强型3)clb.c4.xlarge(超强型4)shared(共享型) + SpecType?: string; + // 规格可用性。资源可用性,"Available":可用,"Unavailable":不可用 + Availability?: string; +} + +// 申请负载均衡 - 输入参数 +export interface ApplyClbModel { + // 业务ID + bk_biz_id: number; + // 账号ID + account_id: string; + // 地域 + region: string; + // 网络类型: 公网 OPEN, 内网 INTERNAL + load_balancer_type: 'OPEN' | 'INTERNAL'; + // 名称 + name: string; + // 主可用区, 仅限公网型 + zones: string; + // 备可用区, 目前仅广州、上海、南京、北京、中国香港、首尔地域的 IPv4 版本的 CLB 支持主备可用区。 + backup_zones?: string; + // ip版本: IPV4, IPV6(ipv6 nat64), IPv6FullChain(ipv6) + address_ip_version?: 'IPV4' | 'IPv6FullChain' | 'IPV6'; + // 云VpcID + cloud_vpc_id: string; + // 云子网ID, 内网型必填 + cloud_subnet_id?: string; + // 绑定已有eip的ip地址, ipv6 nat64 不支持 + vip?: string; + // 绑定eip id + cloud_eip_id?: string; + // 运营商类型(仅公网), 枚举值: CMCC, CUCC, CTCC, BGP。通过 TCloudDescribeResource 接口确定 + vip_isp?: string; + // 网络计费模式(暂不支持包月) + internet_charge_type?: 'TRAFFIC_POSTPAID_BY_HOUR' | 'BANDWIDTH_POSTPAID_BY_HOUR' | 'BANDWIDTH_PACKAGE'; + // 最大出带宽,单位Mbps + internet_max_bandwidth_out?: number; + // // 带宽包id,计费模式为带宽包计费时必填(暂不支持) + // bandwidth_package_id?: string; + // 负载均衡规格类型: 性能容量型规格, 留空为共享型 + sla_type?: string; + // // 按月付费自动续费(暂不支持包月) + // auto_renew?: boolean; + // 购买数量 + require_count: number; + // 备注 + memo?: string; + // 可用区类型, 仅前端使用 + zoneType: 'single' | 'primaryStand'; + // 云厂商, 前端组件使用 + vendor: VendorEnum; + // 用户网络类型 + account_type: NetworkAccountType; +} diff --git a/front/src/app.tsx b/front/src/app.tsx index 7767dca787..793becc5da 100644 --- a/front/src/app.tsx +++ b/front/src/app.tsx @@ -26,8 +26,6 @@ export default defineComponent({ onUnmounted(() => { window.removeEventListener('resize', calcRem, false); }); - return () => ( - - ); + return () => ; }, }); diff --git a/front/src/assets/iconfont/demo.html b/front/src/assets/iconfont/demo.html index a05120114b..c2cb8b30d7 100644 --- a/front/src/assets/iconfont/demo.html +++ b/front/src/assets/iconfont/demo.html @@ -437,6 +437,18 @@

automatic-typesetting

+
  • + +

    alert

    +
  • +
  • + +

    jiebang

    +
  • +
  • + +

    mubiao

    +
  • 为什么使用

      @@ -934,6 +946,24 @@

      如何使用

      automatic-typesetting

      +
    • + + + +

      alert

      +
    • +
    • + + + +

      jiebang

      +
    • +
    • + + + +

      mubiao

      +

    为什么使用

      diff --git a/front/src/assets/iconfont/fonts/iconcool.eot b/front/src/assets/iconfont/fonts/iconcool.eot index 3c5b412d92..9edee297c2 100644 Binary files a/front/src/assets/iconfont/fonts/iconcool.eot and b/front/src/assets/iconfont/fonts/iconcool.eot differ diff --git a/front/src/assets/iconfont/fonts/iconcool.svg b/front/src/assets/iconfont/fonts/iconcool.svg index 7cafd6f160..e94d6e9cfa 100644 --- a/front/src/assets/iconfont/fonts/iconcool.svg +++ b/front/src/assets/iconfont/fonts/iconcool.svg @@ -269,6 +269,15 @@ + + + + + + + + + diff --git a/front/src/assets/iconfont/fonts/iconcool.ttf b/front/src/assets/iconfont/fonts/iconcool.ttf index 828de963e6..428eb721b1 100644 Binary files a/front/src/assets/iconfont/fonts/iconcool.ttf and b/front/src/assets/iconfont/fonts/iconcool.ttf differ diff --git a/front/src/assets/iconfont/fonts/iconcool.woff b/front/src/assets/iconfont/fonts/iconcool.woff index 22a20f1e79..e4a1f9d534 100644 Binary files a/front/src/assets/iconfont/fonts/iconcool.woff and b/front/src/assets/iconfont/fonts/iconcool.woff differ diff --git a/front/src/assets/iconfont/iconcool.js b/front/src/assets/iconfont/iconcool.js index 8256ee4e63..c637dbc5ee 100644 --- a/front/src/assets/iconfont/iconcool.js +++ b/front/src/assets/iconfont/iconcool.js @@ -1,5 +1,5 @@ !(function () { - var svgCode = '' + var svgCode = '' if (document.body) { document.body.insertAdjacentHTML('afterbegin', svgCode) } else { diff --git a/front/src/assets/iconfont/iconcool.json b/front/src/assets/iconfont/iconcool.json index 46ecab7563..e36416a618 100644 --- a/front/src/assets/iconfont/iconcool.json +++ b/front/src/assets/iconfont/iconcool.json @@ -1 +1 @@ -{"iconName":"hcm","icons":[{"name":"minus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e14a"},{"name":"plus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e10c"},{"name":"template-orchestration","svgCode":"","codepoint":"\\e154"},{"name":"check-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e128"},{"name":"close-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e127"},{"name":"prompt","svgCode":"","codepoint":"\\e125"},{"name":"question-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e126"},{"name":"zoomout","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e12b"},{"name":"link","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e122"},{"name":"fullscreen","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e12a"},{"name":"set-fill","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e14e"},{"name":"info-line","svgCode":"\n\n\n\n\n\t\n\t\n\t\n\n\n","codepoint":"\\e149"},{"name":"arrows-up","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e148"},{"name":"7chenggong-01","svgCode":"","codepoint":"\\e11f"},{"name":"38moxingshibai-01","svgCode":"","codepoint":"\\e120"},{"name":"batch-edit","svgCode":"","codepoint":"\\e14d"},{"name":"file","svgCode":"","codepoint":"\\e147"},{"name":"down-shape","svgCode":"","codepoint":"\\e101"},{"name":"minus-circle-shape","svgCode":"","codepoint":"\\e124"},{"name":"plus-circle-shape","svgCode":"","codepoint":"\\e129"},{"name":"right-shape","svgCode":"","codepoint":"\\e107"},{"name":"loading-circle","svgCode":"","codepoint":"\\e109"},{"name":"bianji","svgCode":"","codepoint":"\\e11d"},{"name":"fenxiang","svgCode":"","codepoint":"\\e11e"},{"name":"jiantou_fanhui","svgCode":"","codepoint":"\\e14c"},{"name":"arrows--right--line","svgCode":"","codepoint":"\\e106"},{"name":"arrows--left-line","svgCode":"","codepoint":"\\e103"},{"name":"angle-up-fill","svgCode":"","codepoint":"\\e108"},{"name":"more-fill","svgCode":"","codepoint":"\\e14b"},{"name":"grag-fill","svgCode":"","codepoint":"\\e11c"},{"name":"operation-record","svgCode":"","codepoint":"\\e155"},{"name":"jump-fill","svgCode":"","codepoint":"\\e123"},{"name":"exchange-line","svgCode":"","codepoint":"\\e151"},{"name":"invisible1","svgCode":"","codepoint":"\\e104"},{"name":"edit","svgCode":"","codepoint":"\\e105"},{"name":"yuyanqiehuanzhongwen","svgCode":"","codepoint":"\\e10d"},{"name":"yuyanqiehuanyingwen","svgCode":"","codepoint":"\\e10e"},{"name":"yuyanqiehuanriwen","svgCode":"","codepoint":"\\e10f"},{"name":"not-favorited","svgCode":"","codepoint":"\\e10b"},{"name":"collect","svgCode":"","codepoint":"\\e10a"},{"name":"shezhi","svgCode":"","codepoint":"\\e121"},{"name":"cuo","svgCode":"","codepoint":"\\e119"},{"name":"dui","svgCode":"","codepoint":"\\e118"},{"name":"tishi","svgCode":"","codepoint":"\\e117"},{"name":"jiazai","svgCode":"","codepoint":"\\e116"},{"name":"abnormal","svgCode":"","codepoint":"\\e115"},{"name":"normal","svgCode":"","codepoint":"\\e114"},{"name":"default","svgCode":"","codepoint":"\\e113"},{"name":"success","svgCode":"","codepoint":"\\e110"},{"name":"failed","svgCode":"","codepoint":"\\e111"},{"name":"waiting","svgCode":"","codepoint":"\\e112"},{"name":"warning-2","svgCode":"","codepoint":"\\e11a"},{"name":"unknown","svgCode":"","codepoint":"\\e11b"},{"name":"jiangxu","svgCode":"","codepoint":"\\e12c"},{"name":"paiming","svgCode":"","codepoint":"\\e12d"},{"name":"shengxu","svgCode":"","codepoint":"\\e12e"},{"name":"shouqi","svgCode":"","codepoint":"\\e12f"},{"name":"xuanze","svgCode":"","codepoint":"\\e130"},{"name":"zhankai","svgCode":"","codepoint":"\\e131"},{"name":"bushu","svgCode":"","codepoint":"\\e132"},{"name":"gugeyun","svgCode":"","codepoint":"\\e133"},{"name":"yamaxunyun","svgCode":"","codepoint":"\\e136"},{"name":"tengxunyun","svgCode":"","codepoint":"\\e135"},{"name":"weiruanyun","svgCode":"","codepoint":"\\e134"},{"name":"aliyun","svgCode":"","codepoint":"\\e137"},{"name":"huaweiyun","svgCode":"","codepoint":"\\e138"},{"name":"home","svgCode":"","codepoint":"\\e139"},{"name":"security-group","svgCode":"","codepoint":"\\e13a"},{"name":"eip","svgCode":"","codepoint":"\\e13b"},{"name":"loadbalancer","svgCode":"","codepoint":"\\e13e"},{"name":"recyclebin","svgCode":"","codepoint":"\\e13c"},{"name":"image","svgCode":"","codepoint":"\\e13d"},{"name":"route-table","svgCode":"","codepoint":"\\e13f"},{"name":"network-interface","svgCode":"","codepoint":"\\e140"},{"name":"disk","svgCode":"","codepoint":"\\e141"},{"name":"cert","svgCode":"","codepoint":"\\e143"},{"name":"host","svgCode":"","codepoint":"\\e142"},{"name":"subnet","svgCode":"","codepoint":"\\e144"},{"name":"vpc","svgCode":"","codepoint":"\\e145"},{"name":"automatic-typesetting","svgCode":"","codepoint":"\\e146"}]} \ No newline at end of file +{"iconName":"hcm","icons":[{"name":"minus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e14a"},{"name":"plus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e10c"},{"name":"template-orchestration","svgCode":"","codepoint":"\\e154"},{"name":"check-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e128"},{"name":"close-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e127"},{"name":"prompt","svgCode":"","codepoint":"\\e125"},{"name":"question-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e126"},{"name":"zoomout","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e12b"},{"name":"link","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e122"},{"name":"fullscreen","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e12a"},{"name":"set-fill","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e14e"},{"name":"info-line","svgCode":"\n\n\n\n\n\t\n\t\n\t\n\n\n","codepoint":"\\e149"},{"name":"arrows-up","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e148"},{"name":"7chenggong-01","svgCode":"","codepoint":"\\e11f"},{"name":"38moxingshibai-01","svgCode":"","codepoint":"\\e120"},{"name":"batch-edit","svgCode":"","codepoint":"\\e14d"},{"name":"file","svgCode":"","codepoint":"\\e147"},{"name":"down-shape","svgCode":"","codepoint":"\\e101"},{"name":"minus-circle-shape","svgCode":"","codepoint":"\\e124"},{"name":"plus-circle-shape","svgCode":"","codepoint":"\\e129"},{"name":"right-shape","svgCode":"","codepoint":"\\e107"},{"name":"loading-circle","svgCode":"","codepoint":"\\e109"},{"name":"bianji","svgCode":"","codepoint":"\\e11d"},{"name":"fenxiang","svgCode":"","codepoint":"\\e11e"},{"name":"jiantou_fanhui","svgCode":"","codepoint":"\\e14c"},{"name":"arrows--right--line","svgCode":"","codepoint":"\\e106"},{"name":"arrows--left-line","svgCode":"","codepoint":"\\e103"},{"name":"angle-up-fill","svgCode":"","codepoint":"\\e108"},{"name":"more-fill","svgCode":"","codepoint":"\\e14b"},{"name":"grag-fill","svgCode":"","codepoint":"\\e11c"},{"name":"operation-record","svgCode":"","codepoint":"\\e155"},{"name":"jump-fill","svgCode":"","codepoint":"\\e123"},{"name":"exchange-line","svgCode":"","codepoint":"\\e151"},{"name":"invisible1","svgCode":"","codepoint":"\\e104"},{"name":"edit","svgCode":"","codepoint":"\\e105"},{"name":"yuyanqiehuanzhongwen","svgCode":"","codepoint":"\\e10d"},{"name":"yuyanqiehuanyingwen","svgCode":"","codepoint":"\\e10e"},{"name":"yuyanqiehuanriwen","svgCode":"","codepoint":"\\e10f"},{"name":"not-favorited","svgCode":"","codepoint":"\\e10b"},{"name":"collect","svgCode":"","codepoint":"\\e10a"},{"name":"shezhi","svgCode":"","codepoint":"\\e121"},{"name":"cuo","svgCode":"","codepoint":"\\e119"},{"name":"dui","svgCode":"","codepoint":"\\e118"},{"name":"tishi","svgCode":"","codepoint":"\\e117"},{"name":"jiazai","svgCode":"","codepoint":"\\e116"},{"name":"abnormal","svgCode":"","codepoint":"\\e115"},{"name":"normal","svgCode":"","codepoint":"\\e114"},{"name":"default","svgCode":"","codepoint":"\\e113"},{"name":"success","svgCode":"","codepoint":"\\e110"},{"name":"failed","svgCode":"","codepoint":"\\e111"},{"name":"waiting","svgCode":"","codepoint":"\\e112"},{"name":"warning-2","svgCode":"","codepoint":"\\e11a"},{"name":"unknown","svgCode":"","codepoint":"\\e11b"},{"name":"jiangxu","svgCode":"","codepoint":"\\e12c"},{"name":"paiming","svgCode":"","codepoint":"\\e12d"},{"name":"shengxu","svgCode":"","codepoint":"\\e12e"},{"name":"shouqi","svgCode":"","codepoint":"\\e12f"},{"name":"xuanze","svgCode":"","codepoint":"\\e130"},{"name":"zhankai","svgCode":"","codepoint":"\\e131"},{"name":"bushu","svgCode":"","codepoint":"\\e132"},{"name":"gugeyun","svgCode":"","codepoint":"\\e133"},{"name":"yamaxunyun","svgCode":"","codepoint":"\\e136"},{"name":"tengxunyun","svgCode":"","codepoint":"\\e135"},{"name":"weiruanyun","svgCode":"","codepoint":"\\e134"},{"name":"aliyun","svgCode":"","codepoint":"\\e137"},{"name":"huaweiyun","svgCode":"","codepoint":"\\e138"},{"name":"home","svgCode":"","codepoint":"\\e139"},{"name":"security-group","svgCode":"","codepoint":"\\e13a"},{"name":"eip","svgCode":"","codepoint":"\\e13b"},{"name":"loadbalancer","svgCode":"","codepoint":"\\e13e"},{"name":"recyclebin","svgCode":"","codepoint":"\\e13c"},{"name":"image","svgCode":"","codepoint":"\\e13d"},{"name":"route-table","svgCode":"","codepoint":"\\e13f"},{"name":"network-interface","svgCode":"","codepoint":"\\e140"},{"name":"disk","svgCode":"","codepoint":"\\e141"},{"name":"cert","svgCode":"","codepoint":"\\e143"},{"name":"host","svgCode":"","codepoint":"\\e142"},{"name":"subnet","svgCode":"","codepoint":"\\e144"},{"name":"vpc","svgCode":"","codepoint":"\\e145"},{"name":"automatic-typesetting","svgCode":"","codepoint":"\\e146"},{"name":"alert","svgCode":"","codepoint":"\\e159"},{"name":"jiebang","svgCode":"","codepoint":"\\e156"},{"name":"mubiao","svgCode":"","codepoint":"\\e158"}]} \ No newline at end of file diff --git a/front/src/assets/iconfont/style.css b/front/src/assets/iconfont/style.css index 1b0ccd7fb8..ecdfbf5248 100644 --- a/front/src/assets/iconfont/style.css +++ b/front/src/assets/iconfont/style.css @@ -263,3 +263,12 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bkhcm-icon-automatic-typesetting:before { content: "\e146"; } +.bkhcm-icon-alert:before { + content: "\e159"; +} +.bkhcm-icon-jiebang:before { + content: "\e156"; +} +.bkhcm-icon-mubiao:before { + content: "\e158"; +} diff --git a/front/src/assets/image/all-lb.svg b/front/src/assets/image/all-lb.svg new file mode 100644 index 0000000000..5a9464ab3a --- /dev/null +++ b/front/src/assets/image/all-lb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/front/src/assets/image/clb.png b/front/src/assets/image/clb.png deleted file mode 100644 index 86a97ac261..0000000000 Binary files a/front/src/assets/image/clb.png and /dev/null differ diff --git a/front/src/assets/image/domain.png b/front/src/assets/image/domain.png deleted file mode 100644 index ecf9803849..0000000000 Binary files a/front/src/assets/image/domain.png and /dev/null differ diff --git a/front/src/assets/image/domain.svg b/front/src/assets/image/domain.svg new file mode 100644 index 0000000000..96b5b558e3 --- /dev/null +++ b/front/src/assets/image/domain.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/front/src/assets/image/listener.png b/front/src/assets/image/listener.png deleted file mode 100644 index dc5a429827..0000000000 Binary files a/front/src/assets/image/listener.png and /dev/null differ diff --git a/front/src/assets/image/listener.svg b/front/src/assets/image/listener.svg new file mode 100644 index 0000000000..c268b244fd --- /dev/null +++ b/front/src/assets/image/listener.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/front/src/assets/image/loadbalancer.svg b/front/src/assets/image/loadbalancer.svg new file mode 100644 index 0000000000..df5441d1db --- /dev/null +++ b/front/src/assets/image/loadbalancer.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/front/src/assets/image/mubiao.svg b/front/src/assets/image/mubiao.svg new file mode 100644 index 0000000000..a8df70cfa2 --- /dev/null +++ b/front/src/assets/image/mubiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/common/auth-model.ts b/front/src/common/auth-model.ts index 8774ce765b..fb8971746f 100644 --- a/front/src/common/auth-model.ts +++ b/front/src/common/auth-model.ts @@ -1,6 +1,6 @@ interface ILoginData { - login_url?: string, - target?: string + login_url?: string; + target?: string; } const getLoginUrl = () => { @@ -38,7 +38,10 @@ class AuthModel { const width = 700; const height = 510; const { availHeight, availWidth } = window.screen; - this.loginWindow = window.open(url, '_blank', ` + this.loginWindow = window.open( + url, + '_blank', + ` width=${width}, height=${height}, left=${(availWidth - width) / 2}, @@ -54,7 +57,8 @@ class AuthModel { titlebar=0, toolbar=0, close=0 - `); + `, + ); this.checkWinClose(); } checkWinClose() { diff --git a/front/src/common/bus.ts b/front/src/common/bus.ts index 08627a39f0..9eff0cd18c 100644 --- a/front/src/common/bus.ts +++ b/front/src/common/bus.ts @@ -8,9 +8,9 @@ type Events = { }; interface IBus { - $on: Emitter['on'], - $off: Emitter['off'], - $emit: Emitter['emit'], + $on: Emitter['on']; + $off: Emitter['off']; + $emit: Emitter['emit']; } const emitter = mitt(); diff --git a/front/src/common/constant.ts b/front/src/common/constant.ts index 37e6f73c8c..5a97543087 100644 --- a/front/src/common/constant.ts +++ b/front/src/common/constant.ts @@ -11,6 +11,7 @@ export enum ResourceTypeEnum { VPC = 'vpc', DISK = 'disk', SUBNET = 'subnet', + CLB = 'clb', } // 资源类型 @@ -51,6 +52,14 @@ export const RESOURCE_TYPES = [ name: '镜像', type: 'image', }, + { + name: '负载均衡', + type: 'clb', + }, + { + name: '证书管理', + type: 'certs', + }, ]; // 云厂商 @@ -153,7 +162,6 @@ export const AUDIT_RESOURCE_TYPES = [ }, ]; - export const FILTER_DATA = [ // 移除 ID 搜索条件 // { @@ -199,19 +207,19 @@ export const CIDRLIST = [ }, ]; -export const CIDRDATARANGE = { +export const CIDRDATARANGE = { 10: { min: 0, max: 255 }, 172: { min: 16, max: 31 }, 192: { min: 168, max: 168 }, }; -export const TCLOUDCIDRMASKRANGE = { +export const TCLOUDCIDRMASKRANGE = { 10: { min: 12, max: 28 }, 172: { min: 12, max: 28 }, 192: { min: 16, max: 28 }, }; -export const CIDRMASKRANGE = { +export const CIDRMASKRANGE = { 10: { min: 8, max: 28 }, 172: { min: 12, max: 28 }, 192: { min: 16, max: 28 }, @@ -460,8 +468,7 @@ export const RESOURCE_TABS = [ }, ]; - -export const RESOURCE_DETAIL_TABS = [ +export const RESOURCE_DETAIL_TABS = [ { key: '/resource/resource/account/detail', label: '基本信息', @@ -494,6 +501,8 @@ export const RESOURCE_TYPES_MAP = { zone: '可用区', azure_resource_group: '微软云资源组', argument_template: '参数模板', + cert: '证书', + load_balancer: '负载均衡', }; export const RESOURCES_SYNC_STATUS_MAP = { @@ -505,7 +514,7 @@ export const RESOURCES_SYNC_STATUS_MAP = { export enum SECURITY_GROUP_RULE_TYPE { INGRESS = 'ingress', EGRESS = 'egress', -}; +} export const VendorMap = { [VendorEnum.AWS]: '亚马逊云', @@ -515,9 +524,54 @@ export const VendorMap = { [VendorEnum.TCLOUD]: '腾讯云', }; +export const VendorReverseMap = { + 亚马逊云: VendorEnum.AWS, + 微软云: VendorEnum.AZURE, + 谷歌云: VendorEnum.GCP, + 华为云: VendorEnum.HUAWEI, + 腾讯云: VendorEnum.TCLOUD, +}; + export const SYNC_STAUS_MAP = { a: '绑定中', b: '成功', c: '失败', d: '部分成功', }; + +export const TARGET_GROUP_PROTOCOLS = ['TCP', 'UDP', 'HTTP', 'HTTPS']; + +export const LB_TYPE_MAP = { + OPEN: '公网', + INTERNAL: '内网', +}; + +export const CHARGE_TYPE = { + PREPAID: '包年包月', + POSTPAID_BY_HOUR: '按量计费', +}; + +export const LB_ISP = { + CMCC: '中国移动', + CUCC: '中国联通', + CTCC: '中国电信', + BGP: 'BGP', + INTERNAL: '内网流量', +}; + +export const CLB_SPECS = { + 'clb.c1.small': '简约型', + 'clb.c2.medium': '标准型规格', + 'clb.c3.small': '高阶型1规格', + 'clb.c3.medium': '高阶型2规格', + 'clb.c4.small': '超强型1规格', + 'clb.c4.medium': '超强型2规格', + 'clb.c4.large': '超强型3规格', + 'clb.c4.xlarge': '超强型4规格', +}; + +export const CLB_BINDING_STATUS = { + binding: '绑定中', + success: '已绑定', + failed: '未绑定', +}; diff --git a/front/src/common/util.ts b/front/src/common/util.ts index cdb5c272d5..b22dc98860 100644 --- a/front/src/common/util.ts +++ b/front/src/common/util.ts @@ -1,10 +1,10 @@ -import dayjs from 'dayjs'; +import dayjs, { OpUnitType, QUnitType } from 'dayjs'; // 获取 cookie object export function getCookies(strCookie = document.cookie): any { if (!strCookie) { return {}; } - const arrCookie = strCookie.split('; ');// 分割 + const arrCookie = strCookie.split('; '); // 分割 const cookiesObj = {}; arrCookie.forEach((cookieStr) => { const arr = cookieStr.split('='); @@ -22,10 +22,9 @@ export function getCookies(strCookie = document.cookie): any { * @returns {boolean} */ export function isObject(item: any) { - return (item && Object.prototype.toString.apply(item) === '[object Object]'); + return item && Object.prototype.toString.apply(item) === '[object Object]'; } - /** * 深度合并多个对象 * @param objectArray 待合并列表 @@ -58,12 +57,34 @@ export function timeFormatter(val: any, format = 'YYYY-MM-DD HH:mm:ss') { return val ? dayjs(val).format(format) : '--'; } +/** + * 相对当前的时间 + * @param val 待比较的时间 + * @returns 相对的时间字符串 + */ +export function timeFromNow(val: string, unit: QUnitType | OpUnitType = 'minute') { + return dayjs().diff(val, unit); +} + +/** + * 为表格设置new标识(配合useTable使用) + * @returns 'row-class': ({ created_at }: { created_at: string }) => string + */ +export function getTableNewRowClass() { + return ({ created_at }: { created_at: string }) => { + if (timeFromNow(created_at) <= 5) { + return 'table-new-row'; + } + }; +} + export function classes(dynamicCls: object, constCls = ''): string { - return Object.entries(dynamicCls).filter(entry => entry[1]) - .map(entry => entry[0]) + return Object.entries(dynamicCls) + .filter((entry) => entry[1]) + .map((entry) => entry[0]) .join(' ') .concat(constCls ? ` ${constCls}` : ''); -}; +} /** * 获取Cookie @@ -99,12 +120,12 @@ export function json2Query(param: any, key?: any) { const separator = '&'; let paramStr = ''; if ( - param instanceof String - || typeof param === 'string' - || param instanceof Number - || typeof param === 'number' - || param instanceof Boolean - || typeof param === 'boolean' + param instanceof String || + typeof param === 'string' || + param instanceof Number || + typeof param === 'number' || + param instanceof Boolean || + typeof param === 'boolean' ) { // @ts-ignore paramStr += separator + key + mappingOperator + encodeURIComponent(param); @@ -112,9 +133,8 @@ export function json2Query(param: any, key?: any) { if (param) { Object.keys(param).forEach((p) => { const value = param[p]; - const k = key === null || key === '' || key === undefined - ? p - : key + (param instanceof Array ? `[${p}]` : `.${p}`); + const k = + key === null || key === '' || key === undefined ? p : key + (param instanceof Array ? `[${p}]` : `.${p}`); paramStr += separator + json2Query(value, k); }); } @@ -128,9 +148,8 @@ export function json2Query(param: any, key?: any) { * @return {number} 浏览器视口的高度 */ export function getWindowHeight() { - const windowHeight = document.compatMode === 'CSS1Compat' - ? document.documentElement.clientHeight - : document.body.clientHeight; + const windowHeight = + document.compatMode === 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight; return windowHeight; } @@ -144,7 +163,7 @@ export function getWindowHeight() { export function formatStorageSize(value: number, digits = 0) { const uints = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const index = Math.floor(Math.log(value) / Math.log(1024)); - const size = value / (1024 ** index); + const size = value / 1024 ** index; return `${size.toFixed(digits)}${uints[index]}`; } @@ -155,9 +174,11 @@ export function formatStorageSize(value: number, digits = 0) { export function getScoreColor(score: number) { if (score > 0 && score < 180) { return '#00A62B'; - } if (score >= 180 && score <= 360) { + } + if (score >= 180 && score <= 360) { return '#FF9D00'; - } if (score > 360) { + } + if (score > 360) { return '#EA3636'; } return '#63656E'; @@ -184,4 +205,55 @@ export function getDifferenceSet(origin: Array, compare: Array) } }); return Array.from(set); +} + +// localStorage 操作类 +export const localStorageActions = { + set(key: string, value: any) { + if (typeof value === 'object') { + value = JSON.stringify(value); + } + localStorage.setItem(key, value); + }, + get(key: string) { + const value = localStorage.getItem(key); + if (value) { + return JSON.parse(value); + } + return null; + }, + remove(key: string) { + localStorage.removeItem(key); + }, + clear() { + localStorage.clear(); + }, +}; + +/** + * 获取指定的 URL 查询参数值 + * @param {string} param 要获取的查询参数名 + * @param {string} url 可选,指定的 URL,默认为当前浏览器地址 + * @returns {string | null} 查询参数值,如果不存在则返回 null + */ +export const getQueryStringParams = (param: string, url = window.location.href) => { + let queryParams; + + if (url.includes('#')) { + // 如果 URL 包含 #,假定是 hash 路由,需要从 hash 中解析查询参数 + const hash = url.split('#')[1]; // 获取 hash 部分 + if (hash.includes('?')) { + const search = hash.split('?')[1]; // 从 hash 中分离查询字符串 + queryParams = new URLSearchParams(search); + } else { + // 如果 hash 中没有查询字符串,提前返回 null + return null; + } + } else { + // 如果是常规路由,直接从 URL 对象解析查询字符串 + const urlObj = new URL(url); + queryParams = new URLSearchParams(urlObj.search); + } + + return queryParams.get(param); }; diff --git a/front/src/components/CommonCard/index.tsx b/front/src/components/CommonCard/index.tsx index 51912a8a77..fd844ea66b 100644 --- a/front/src/components/CommonCard/index.tsx +++ b/front/src/components/CommonCard/index.tsx @@ -15,21 +15,10 @@ export default defineComponent({ }, setup(props, { slots }) { return () => ( - -

      - { - props.title?.() - } -

      + +

      {props.title?.()}

      - { - slots.default() - } + {slots.default()}
      ); diff --git a/front/src/components/CommonLocalTable/index.scss b/front/src/components/CommonLocalTable/index.scss new file mode 100644 index 0000000000..996b8253e5 --- /dev/null +++ b/front/src/components/CommonLocalTable/index.scss @@ -0,0 +1,40 @@ +.local-table-container { + height: 100%; + overflow: hidden; + + .top-bar { + display: flex; + justify-content: space-between; + + .operation-area { + margin-bottom: 16px; + display: flex; + align-items: center; + + .mr12 { + margin-right: 12px; + } + + .f20 { + font-size: 20px; + } + } + + .table-search-selector { + margin-bottom: 16px; + width: 500px; + } + } + + .loading-container { + height: 100%; + + .table-container { + max-height: 100%; + } + } + + &.has-top-bar .loading-container { + max-height: calc(100% - 48px); + } +} diff --git a/front/src/components/CommonLocalTable/index.tsx b/front/src/components/CommonLocalTable/index.tsx new file mode 100644 index 0000000000..2620bd6bc1 --- /dev/null +++ b/front/src/components/CommonLocalTable/index.tsx @@ -0,0 +1,121 @@ +import { PropType, computed, defineComponent, reactive, ref, watch } from 'vue'; +// import components +import { Loading, SearchSelect, Table } from 'bkui-vue'; +import Empty from '@/components/empty'; +// import types +import { ISearchItem } from 'bkui-vue/lib/search-select/utils'; +import { CLB_SPECS_REVERSE_MAP } from '@/constants'; +import { Column } from 'bkui-vue/lib/table/props'; +import { getLocalFilterConditions } from '@/utils'; +import './index.scss'; + +/** + * 本地搜索. 本地分页 Table 组件 + */ +export default defineComponent({ + name: 'CommonLocalTable', + props: { + loading: Boolean, + // 是否显示操作栏 + hasOperation: { + type: Boolean, + default: true, + }, + // 是否显示搜索栏 + hasSearch: { + type: Boolean, + default: true, + }, + // 搜索栏配置项 + searchOptions: { + type: Object as PropType<{ + searchData: Array; + extra?: Object; + }>, + }, + // 表格配置项 + tableOptions: { + type: Object as PropType<{ + rowKey: string | Function; + columns: Array; + extra?: Object; + }>, + }, + // 表格数据 + tableData: Array, + }, + setup(props, { slots }) { + // 搜索相关 + const searchValue = ref(); + // 表格相关 + const tableData = ref(props.tableData); + const pagination = reactive({ limit: 10, count: props.tableData.length }); + const hasTopBar = computed(() => props.hasOperation && props.hasSearch); + + // 监听 searchValue 的变化,根据过滤条件过滤得到 实际用于渲染的数据 + const renderTableData = computed(() => { + const filterConditions = getLocalFilterConditions(searchValue.value, (rule) => { + switch (rule.id) { + // 负载均衡规格类型需要映射 + case 'SpecType': + return CLB_SPECS_REVERSE_MAP[rule.values[0].id]; + default: + return rule.values[0].id; + } + }); + const resultData = props.tableData.filter((item) => + Object.keys(filterConditions).every((key) => filterConditions[key].includes(`${item[key]}`)), + ); + // 更新分页器 + pagination.count = resultData.length; + return resultData; + }); + + watch( + () => props.tableData, + (val) => { + // 解决异步函数 tableData 数据返回不及时的问题 + tableData.value = val; + }, + { deep: true }, + ); + + return () => ( +
      + {/* top-bar */} +
      + {/* 操作栏 */} + {props.hasOperation &&
      {slots.operation?.()}
      } + {/* 搜索栏 */} + {props.hasSearch && ( + + )} +
      + {/* 表格 */} + + + {{ + empty: () => { + if (props.loading) return null; + return ; + }, + }} +
      +
      +
      + ); + }, +}); diff --git a/front/src/components/MemberSelect/Tpl.tsx b/front/src/components/MemberSelect/Tpl.tsx index d72fd688d4..d0cc9b9ec0 100644 --- a/front/src/components/MemberSelect/Tpl.tsx +++ b/front/src/components/MemberSelect/Tpl.tsx @@ -13,7 +13,7 @@ export default defineComponent({ }, setup(props) { return () => ( -
      +
      {/* {props.englishName} ({props.chineseName}) */} {props.englishName}
      diff --git a/front/src/components/MemberSelect/index.tsx b/front/src/components/MemberSelect/index.tsx index 7c94067671..861fa4d9af 100644 --- a/front/src/components/MemberSelect/index.tsx +++ b/front/src/components/MemberSelect/index.tsx @@ -42,9 +42,13 @@ export default defineComponent({ const staffStore = useStaffStore(); const searchKey = ['username']; const userList: any = ref(props.defaultUserlist); - const maxData = computed(() => (!props.multiple ? { - maxData: 1, - } : {})); + const maxData = computed(() => + !props.multiple + ? { + maxData: 1, + } + : {}, + ); const popoverProps = { boundary: document.body, fixOnBoundary: true, @@ -57,15 +61,10 @@ export default defineComponent({ }); function tpl(node: Staff) { - return ( - - ); + return ; } function handleChange(val: Staff[]) { - userList.value = val.map(name => ({ + userList.value = val.map((name) => ({ username: name, display_name: name, })); @@ -93,7 +92,7 @@ export default defineComponent({ nextTick(() => { const arr = [...userList.value, ...list]; const set = new Set(arr.map(({ username }) => username)); - userList.value = Array.from(set).map(name => ({ + userList.value = Array.from(set).map((name) => ({ username: name, display_name: name, })); @@ -111,8 +110,8 @@ export default defineComponent({ // disabled={props.disabled || staffStore.fetching} list={userList} ref={tagInputRef} - displayKey="display_name" - saveKey="username" + displayKey='display_name' + saveKey='username' is-async-list searchKey={searchKey} // filterCallback={handleSearch} @@ -124,19 +123,11 @@ export default defineComponent({ tagTpl={tpl} clearable={props.clearable} allowCreate={props.allowCreate} - popoverProps={popoverProps} - > - {{ - suffix: () => staffStore.fetching && ( - - ), - }} + popoverProps={popoverProps}> + {{ + suffix: () => + staffStore.fetching && , + }} ); }, diff --git a/front/src/components/OrganizationSelect/index.tsx b/front/src/components/OrganizationSelect/index.tsx index 8e47af2517..ab489f4e72 100644 --- a/front/src/components/OrganizationSelect/index.tsx +++ b/front/src/components/OrganizationSelect/index.tsx @@ -27,7 +27,7 @@ export default defineComponent({ updateDepartment, checkedDept, } = useDepartment(); - const isLoading = computed(() => !props.modelValue.every(id => isAllLoaded(id))); + const isLoading = computed(() => !props.modelValue.every((id) => isAllLoaded(id))); const dispalyValue = computed(() => { if (!isLoading.value) { props.modelValue.forEach((id) => { @@ -37,22 +37,23 @@ export default defineComponent({ } }); } - const nameValues = props.modelValue.map(id => departmentMap.value.get(id)?.full_name ?? id); + const nameValues = props.modelValue.map((id) => departmentMap.value.get(id)?.full_name ?? id); return isLoading.value ? [] : nameValues; }); - - watch(() => isLoading.value, async (loading) => { - if (!loading) { - props.modelValue.forEach((id) => { - const dept = departmentMap.value.get(id); - if (!dept.checked) { - handleCheck(true, dept, false); - } - }); - } - }); - + watch( + () => isLoading.value, + async (loading) => { + if (!loading) { + props.modelValue.forEach((id) => { + const dept = departmentMap.value.get(id); + if (!dept.checked) { + handleCheck(true, dept, false); + } + }); + } + }, + ); function isAllLoaded(id: number): boolean { if (!id) return true; @@ -75,7 +76,8 @@ export default defineComponent({ // } function handleCheck(checked: boolean, department: Department, update = true) { - Array.from(departmentMap.value.values()).forEach((e) => { // 只能选中一条 + Array.from(departmentMap.value.values()).forEach((e) => { + // 只能选中一条 e.checked = e.id === department.id; e.indeterminate = e.id === department.id; }); @@ -86,16 +88,10 @@ export default defineComponent({ indeterminate: false, }); if (has_children && loaded) { - recursionCheckChildDept( - children, - !!checked, - ); + recursionCheckChildDept(children, !!checked); } if (parent) { - recursionCheckParentDept( - department.id, - !!checked, - ); + recursionCheckParentDept(department.id, !!checked); } if (update) { @@ -139,41 +135,35 @@ export default defineComponent({ disabled={props.disabled} customContent modelValue={dispalyValue.value} - multipleMode="tag" + multipleMode='tag' multiple={false} loading={isLoading.value} onChange={handleChange} onToggle={handleToggle} - clearable={false} - > - + clearable={false}> + {{ nodeAction: ({ __attr__, loading, has_children }: any) => ( - - { - has_children && ( - // eslint-disable-next-line no-nested-ternary - loading ? - : (__attr__.isOpen - ? - : ) - ) - } - + + {has_children && + // eslint-disable-next-line no-nested-ternary + (loading ? ( + + ) : __attr__.isOpen ? ( + + ) : ( + + ))} + ), node: (department: Department) => ( - e.stopPropagation()}> + e.stopPropagation()}> handleCheck(checked, department)} + onChange={(checked) => handleCheck(checked, department)} /> - { department.name } + {department.name} ), }} diff --git a/front/src/components/RenderDetailEdit/index.tsx b/front/src/components/RenderDetailEdit/index.tsx index 02887295d9..6c59dff642 100644 --- a/front/src/components/RenderDetailEdit/index.tsx +++ b/front/src/components/RenderDetailEdit/index.tsx @@ -19,6 +19,10 @@ export default defineComponent({ type: Boolean, default: false, }, + isLoading: { + type: Boolean, + default: false, + }, fromKey: { type: String, default: '', @@ -50,7 +54,6 @@ export default defineComponent({ const handleEdit = () => { // @ts-ignore renderEdit.value = true; - console.log('props.modelValue', props.modelValue); nextTick(() => { // @ts-ignore inputRef.value?.focus(); @@ -58,11 +61,9 @@ export default defineComponent({ }); }; - watch( () => props.isEdit, () => { - console.log('props.isEdit', props.isEdit); renderEdit.value = props.isEdit; }, ); @@ -87,6 +88,11 @@ export default defineComponent({ } }; + const handleKeyUpEnter = (e: KeyboardEvent) => { + if (e.key !== 'Enter') return; + handleBlur(props.fromKey); + }; + const computedDefaultUserlist = computed(() => { let res = props.modelValue; if (props.fromType === 'member') { @@ -101,36 +107,70 @@ export default defineComponent({ const renderComponentsContent = (type: string) => { switch (type) { case 'input': - return handleBlur(props.fromKey)} />; + return ( + handleBlur(props.fromKey)} + onKeyup={(_, e) => handleKeyUpEnter(e)} + /> + ); case 'member': - return handleBlur(props.fromKey)}/>; + return ( + handleBlur(props.fromKey)} + /> + ); case 'department': - return ; + return ; case 'textarea': - return handleBlur(props.fromKey)} />; + return ( + handleBlur(props.fromKey)} + /> + ); case 'select': - return ; + return ( + + ); default: - return handleBlur(props.fromKey)} />; + return ( + handleBlur(props.fromKey)} + /> + ); } }; @@ -139,9 +179,7 @@ export default defineComponent({ case 'input': return {props.modelValue}; case 'member': - return props.modelValue.length - ? {props.modelValue.join(',')} - : '暂无'; + return props.modelValue.length ? {props.modelValue.join(',')} : '暂无'; case 'select': // eslint-disable-next-line no-case-declarations let selectModelValue; @@ -154,25 +192,30 @@ export default defineComponent({ selectModelValue = selectModelValue.map((e: any) => e.name); } // eslint-disable-next-line no-nested-ternary - return selectModelValue.length - ? {selectModelValue.join(',')} - : props.modelValue.join(',') === '-1' ? '未分配' : '暂无'; + return selectModelValue.length ? ( + {selectModelValue.join(',')} + ) : props.modelValue.join(',') === '-1' ? ( + '未分配' + ) : ( + '暂无' + ); default: return {props.modelValue}; } }; return () => ( -
      - {renderEdit.value ? ( - renderComponentsContent(props.fromType) - ) : renderTextContent(props.fromType)} - {renderEdit.value || props.hideEdit - ? '' - : - } -
      +
      + {renderEdit.value ? renderComponentsContent(props.fromType) : renderTextContent(props.fromType)} + {renderEdit.value || props.hideEdit ? ( + '' + ) : ( + + )} +
      ); }, - - }); diff --git a/front/src/components/account-selector/index.vue b/front/src/components/account-selector/index.vue index ad11908c57..58846bce9d 100644 --- a/front/src/components/account-selector/index.vue +++ b/front/src/components/account-selector/index.vue @@ -1,5 +1,6 @@ diff --git a/front/src/components/common-dialog/index.scss b/front/src/components/common-dialog/index.scss index f6907fd903..ac893613c1 100644 --- a/front/src/components/common-dialog/index.scss +++ b/front/src/components/common-dialog/index.scss @@ -12,7 +12,7 @@ } .bk-modal-close { - top: 0 !important; + top: 4px !important; right: 4px !important; font-size: 28px !important; } diff --git a/front/src/components/common-dialog/index.tsx b/front/src/components/common-dialog/index.tsx index 3c067876da..050962d00e 100644 --- a/front/src/components/common-dialog/index.tsx +++ b/front/src/components/common-dialog/index.tsx @@ -1,5 +1,6 @@ import { defineComponent } from 'vue'; import { Dialog } from 'bkui-vue'; +import { useI18n } from 'vue-i18n'; import './index.scss'; export default defineComponent({ @@ -14,6 +15,7 @@ export default defineComponent({ }, emits: ['update:isShow', 'handleConfirm'], setup(props, { emit, slots }) { + const { t } = useI18n(); const triggerShow = (isShow: boolean) => { emit('update:isShow', isShow); }; @@ -25,13 +27,14 @@ export default defineComponent({ triggerShow(false)}> {{ default: () => slots.default?.(), tools: () => slots.tools?.(), + footer: slots.footer ? () => slots.footer?.() : undefined, }} ); diff --git a/front/src/components/common-sideslider/index.scss b/front/src/components/common-sideslider/index.scss index 0885bfd56e..13e39a5b7b 100644 --- a/front/src/components/common-sideslider/index.scss +++ b/front/src/components/common-sideslider/index.scss @@ -5,8 +5,11 @@ .bk-modal-header { } - .bk-modal-content { - padding: 28px 40px; + .bk-modal-content .bk-sideslider-content { + .common-sideslider-content { + padding: 28px 40px 48px; + overflow: auto; + } } .bk-modal-footer .bk-sideslider-footer { diff --git a/front/src/components/common-sideslider/index.tsx b/front/src/components/common-sideslider/index.tsx index d1155844d2..3cbdee2c79 100644 --- a/front/src/components/common-sideslider/index.tsx +++ b/front/src/components/common-sideslider/index.tsx @@ -1,5 +1,6 @@ import { defineComponent } from 'vue'; import { Sideslider, Button } from 'bkui-vue'; +import { useI18n } from 'vue-i18n'; import './index.scss'; export default defineComponent({ @@ -17,9 +18,21 @@ export default defineComponent({ type: [Number, String], default: 400, }, + isSubmitDisabled: { + type: Boolean, + default: false, + }, + isSubmitLoading: { + type: Boolean, + default: false, + }, + handleClose: Function, }, emits: ['update:isShow', 'handleSubmit'], setup(props, ctx) { + // use hooks + const { t } = useI18n(); + const triggerShow = (isShow: boolean) => { ctx.emit('update:isShow', isShow); }; @@ -33,16 +46,23 @@ export default defineComponent({ class='common-sideslider' width={props.width} isShow={props.isShow} - title={props.title} - onClosed={() => triggerShow(false)}> + title={t(props.title)} + onClosed={() => { + triggerShow(false); + props.handleClose?.(); + }}> {{ - default: () => ctx.slots.default?.(), + default: () =>
      {ctx.slots.default?.()}
      , footer: () => ( <> - - + ), }} diff --git a/front/src/components/loading/index.tsx b/front/src/components/loading/index.tsx index c63aa616b1..4963104cbf 100644 --- a/front/src/components/loading/index.tsx +++ b/front/src/components/loading/index.tsx @@ -15,7 +15,7 @@ export default defineComponent({ }, render() { return ( -
      +
      ); diff --git a/front/src/components/permission-dialog/index.tsx b/front/src/components/permission-dialog/index.tsx index a2e733613e..eb8097c896 100644 --- a/front/src/components/permission-dialog/index.tsx +++ b/front/src/components/permission-dialog/index.tsx @@ -1,26 +1,18 @@ /* eslint-disable no-nested-ternary */ -import { - defineComponent, - PropType, - watch, - ref, - h, -} from 'vue'; +import { defineComponent, PropType, watch, ref, h } from 'vue'; import permissions from '@/assets/image/403.png'; import { useVerify } from '@/hooks'; import './index.scss'; -import { - useI18n, -} from 'vue-i18n'; +import { useI18n } from 'vue-i18n'; import { Senarios, useWhereAmI } from '@/hooks/useWhereAmI'; import { useResourceAccountStore } from '@/store/useResourceAccountStore'; import { useAccountStore } from '@/store'; import { useBusinessMapStore } from '@/store/useBusinessMap'; - type permissionType = { - system_id: string; - actions: any; - }; +type permissionType = { + system_id: string; + actions: any; +}; export default defineComponent({ name: 'PermissionDialog', @@ -48,34 +40,33 @@ export default defineComponent({ emits: ['confirm', 'cancel'], setup(_, { emit }) { - const { - t, - } = useI18n(); + const { t } = useI18n(); const { whereAmI } = useWhereAmI(); const resourceAccountStore = useResourceAccountStore(); const accountStore = useAccountStore(); const businessMapStore = useBusinessMapStore(); - const columns = [{ - label: '需要申请的权限', - field: 'name', - }, - { - label: '关联的资源实例', - field: 'memo', - render({ data }: any) { - return h( - 'span', - {}, - [ + const columns = [ + { + label: '需要申请的权限', + field: 'name', + }, + { + label: '关联的资源实例', + field: 'memo', + render({ data }: any) { + return h('span', {}, [ `${data?.related_resource_types[0]?.type_name || '--'}${ - whereAmI.value === Senarios.resource ? ( - resourceAccountStore.resourceAccount?.name ? `: ${resourceAccountStore.resourceAccount?.name}` : '' - ) : ` ${businessMapStore.getNameFromBusinessMap(accountStore.bizs)}`}`, - ], - ); + whereAmI.value === Senarios.resource + ? resourceAccountStore.resourceAccount?.name + ? `: ${resourceAccountStore.resourceAccount?.name}` + : '' + : ` ${businessMapStore.getNameFromBusinessMap(accountStore.bizs)}` + }`, + ]); + }, }, - }]; + ]; const tableData = ref([]); const url = ref(''); @@ -114,49 +105,53 @@ export default defineComponent({ }, render() { - return <> + return ( + <> + onClosed={this.handleClose}> {{ default: () => { - return <> - 403 -
      {this.t('没有权限访问或操作此资源')}
      - - ; + return ( + <> + 403 +
      {this.t('没有权限访问或操作此资源')}
      + + + ); }, footer: () => { - return <> - + {this.t('去申请')} - {this.t('取消')} - ; + onClick={this.handleConfirm}> + {this.t('去申请')} + + + {this.t('取消')} + + + ); }, }}
      - ; + + ); }, }); - diff --git a/front/src/components/permission-dialog/install-permission.ts b/front/src/components/permission-dialog/install-permission.ts index bedb8c00d0..42f79e138c 100644 --- a/front/src/components/permission-dialog/install-permission.ts +++ b/front/src/components/permission-dialog/install-permission.ts @@ -4,7 +4,7 @@ import { App } from 'vue'; export default { install(app: App) { - // 此处形参为main.js文件中use()方法自动传进来的Vue实例 + // 此处形参为main.js文件中use()方法自动传进来的Vue实例 app.component(permissionDialog.name, permissionDialog); }, }; diff --git a/front/src/components/permission/index.ts b/front/src/components/permission/index.ts index bedb8c00d0..42f79e138c 100644 --- a/front/src/components/permission/index.ts +++ b/front/src/components/permission/index.ts @@ -4,7 +4,7 @@ import { App } from 'vue'; export default { install(app: App) { - // 此处形参为main.js文件中use()方法自动传进来的Vue实例 + // 此处形参为main.js文件中use()方法自动传进来的Vue实例 app.component(permissionDialog.name, permissionDialog); }, }; diff --git a/front/src/components/resource-group/index.vue b/front/src/components/resource-group/index.vue index 38c3908833..0a205ed718 100644 --- a/front/src/components/resource-group/index.vue +++ b/front/src/components/resource-group/index.vue @@ -1,8 +1,6 @@ diff --git a/front/src/components/route-table-selector/index.vue b/front/src/components/route-table-selector/index.vue index 96486b7dec..9eb0f7f5da 100644 --- a/front/src/components/route-table-selector/index.vue +++ b/front/src/components/route-table-selector/index.vue @@ -1,9 +1,6 @@ diff --git a/front/src/components/step-dialog/step-dialog.tsx b/front/src/components/step-dialog/step-dialog.tsx index b6d5907d4f..59940e6575 100644 --- a/front/src/components/step-dialog/step-dialog.tsx +++ b/front/src/components/step-dialog/step-dialog.tsx @@ -1,12 +1,5 @@ -import { - defineComponent, - ref, - VNode, - PropType, -} from 'vue'; -import { - useI18n, -} from 'vue-i18n'; +import { defineComponent, ref, VNode, PropType } from 'vue'; +import { useI18n } from 'vue-i18n'; import './step.dialog.scss'; type StepType = { @@ -56,9 +49,7 @@ export default defineComponent({ emits: ['confirm', 'cancel', 'next'], setup(_, { emit }) { - const { - t, - } = useI18n(); + const { t } = useI18n(); const curStep = ref(1); @@ -92,76 +83,73 @@ export default defineComponent({ }, render() { - return <> - - {{ - default: () => { - return <> - { - this.steps.length > 1 - ? - : '' - } - { - this.steps[this.curStep - 1].component() - } - ; - }, - footer: () => { - return <> - { - this.steps[this.curStep - 1].footer?.() - } - { - this.curStep > 1 - ? {this.t('上一步')} - : '' - } - { - this.curStep < this.steps.length - ? + + {{ + default: () => { + return ( + <> + {this.steps.length > 1 ? ( + + ) : ( + '' + )} + {this.steps[this.curStep - 1].component()} + + ); + }, + footer: () => { + return ( + <> + {this.steps[this.curStep - 1].footer?.()} + {this.curStep > 1 ? ( + + {this.t('上一步')} + + ) : ( + '' + )} + {this.curStep < this.steps.length ? ( + 1 ? !this.business : false)} - onClick={this.handleNextStep} - >{this.t('下一步')} - : '' - } - { - this.curStep >= this.steps.length - ? + {this.t('下一步')} + + ) : ( + '' + )} + {this.curStep >= this.steps.length ? ( + {this.t('确认')} - : '' - } - {this.t('取消')} - ; - }, - }} - - ; + onClick={this.handleConfirm}> + {this.t('确认')} + + ) : ( + '' + )} + + {this.t('取消')} + + + ); + }, + }} + + + ); }, }); diff --git a/front/src/components/vpc-selector/index.vue b/front/src/components/vpc-selector/index.vue index 6fd72bd23f..4b97ddb33f 100644 --- a/front/src/components/vpc-selector/index.vue +++ b/front/src/components/vpc-selector/index.vue @@ -1,8 +1,6 @@ diff --git a/front/src/constants/account.ts b/front/src/constants/account.ts index ed2fe45ec4..dbddfe6e57 100644 --- a/front/src/constants/account.ts +++ b/front/src/constants/account.ts @@ -2,16 +2,20 @@ export const CLOUD_TYPE = [ { id: 'tcloud', name: '腾讯云', - }, { + }, + { id: 'aws', name: '亚马逊', - }, { + }, + { id: 'azure', name: '微软云', - }, { + }, + { id: 'gcp', name: '谷歌云', - }, { + }, + { id: 'huawei', name: '华为云', }, @@ -21,10 +25,12 @@ export const BUSINESS_TYPE = [ { id: 1, name: 'cmdb', - }, { + }, + { id: 2, name: 'lesscode', - }, { + }, + { id: 3, name: '开发者中心', }, @@ -49,7 +55,8 @@ export const SITE_TYPE = [ { label: '中国站', value: 'china', - }, { + }, + { label: '国际站', value: 'international', }, @@ -58,25 +65,45 @@ export const SITE_TYPE = [ export const DESC_ACCOUNT = { tcloud: { vendor: '

      腾讯云支持中国站 cloud.tencent.com

      ', - accountInfo: '主账号ID,子账号ID如何查看?登陆控制台,鼠标停留在右上角的账号图标上。第一行个人账号@后的数字为主账号ID。第二行账号ID后的数字为子账号ID', - apiSecret: '在腾讯云控制台-访问管理-访问密钥-API密钥管理中查看 https://console.cloud.tencent.com/cam/capi' }, - aws: { vendor: '亚马逊云支持中国站(amazonaws.cn)和国际站(aws.amazon.com)', + accountInfo: + '主账号ID,子账号ID如何查看?登陆控制台,鼠标停留在右上角的账号图标上。第一行个人账号@后的数字为主账号ID。第二行账号ID后的数字为子账号ID', + apiSecret: + '在腾讯云控制台-访问管理-访问密钥-API密钥管理中查看 https://console.cloud.tencent.com/cam/capi', + }, + aws: { + vendor: + '亚马逊云支持中国站(amazonaws.cn)和国际站(aws.amazon.com)', accountInfo: '账号ID和IAM用户如何查看?进入AWS控制台,点击右上角用户名,可见“账户ID”和“IAM用户名称”', - apiSecret: '进入AWS控制台,点击右上角用户名,点击“安全凭证”(https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/security_credentials)在“访问密钥”栏下可查看访问密钥ID。' }, - azure: { vendor: '微软云支持中国站(azure.cn)和国际站(azure.microsoft.com)', - accountInfo: '租户ID,在Azure portal里的“Azure Active Directory”里查看,链接:portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview' - + '订阅ID和订阅名称,在“订阅”里查看,链接:https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade', - apiSecret: '在“应用注册”里(链接:https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade )获取应用程序名称和应用程序(客户端)ID。' }, - gcp: { vendor: '谷歌云国际站(console.cloud.google.com)', - accountInfo: '项目ID和项目名称进入GCP控制台->IAM和管理->设置,或直接点击链接:' - + 'https://console.cloud.google.com/iam-admin/settings?orgonly=true&supportedpurview=organizationId可找到项目名称和项目ID。', - apiSecret: '进入GCP控制台=>IAM和管理=>服务账号,或直接点击链接:' - + 'https://console.cloud.google.com/iam-admin/serviceaccounts?orgonly=true&supportedpurview=organizationId' - + '可找到服务账号名称和密钥ID' }, - huawei: { vendor: '华为支持中国站(huaweicloud.com)和国际站(huaweicloud.com/intl/)', - accountInfo: '主账号ID进入华为云控制台,点击右上方账号浮窗下的基本信息,在账号中心点击我的主账号,可找到主账号名。' - + 'https://account-intl.huaweicloud.com/usercenter/?region=ap-southeast-1&locale=zh-cn#/accountindex/associatedAccount' - + '

      IAM用户名、IAM用户ID、账号名和账号ID。进入华为云控制台,点击右上方账号浮窗下的“我的凭证”,在“API凭证”

      ' - + 'https://console-intl.huaweicloud.com/iam/?region=ap-southeast-1&locale=zh-cn#/mine/apiCredential', - apiSecret: '在“我的凭证”里,点击“访问密钥”,点击“新增访问密钥”。下载后打开对应csv文件获取。' }, + apiSecret: + '进入AWS控制台,点击右上角用户名,点击“安全凭证”(https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/security_credentials)在“访问密钥”栏下可查看访问密钥ID。', + }, + azure: { + vendor: + '微软云支持中国站(azure.cn)和国际站(azure.microsoft.com)', + accountInfo: + '租户ID,在Azure portal里的“Azure Active Directory”里查看,链接:portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview' + + '订阅ID和订阅名称,在“订阅”里查看,链接:https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade', + apiSecret: + '在“应用注册”里(链接:https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade )获取应用程序名称和应用程序(客户端)ID。', + }, + gcp: { + vendor: '谷歌云国际站(console.cloud.google.com)', + accountInfo: + '项目ID和项目名称进入GCP控制台->IAM和管理->设置,或直接点击链接:' + + 'https://console.cloud.google.com/iam-admin/settings?orgonly=true&supportedpurview=organizationId可找到项目名称和项目ID。', + apiSecret: + '进入GCP控制台=>IAM和管理=>服务账号,或直接点击链接:' + + 'https://console.cloud.google.com/iam-admin/serviceaccounts?orgonly=true&supportedpurview=organizationId' + + '可找到服务账号名称和密钥ID', + }, + huawei: { + vendor: + '华为支持中国站(huaweicloud.com)和国际站(huaweicloud.com/intl/)', + accountInfo: + '主账号ID进入华为云控制台,点击右上方账号浮窗下的基本信息,在账号中心点击我的主账号,可找到主账号名。' + + 'https://account-intl.huaweicloud.com/usercenter/?region=ap-southeast-1&locale=zh-cn#/accountindex/associatedAccount' + + '

      IAM用户名、IAM用户ID、账号名和账号ID。进入华为云控制台,点击右上方账号浮窗下的“我的凭证”,在“API凭证”

      ' + + 'https://console-intl.huaweicloud.com/iam/?region=ap-southeast-1&locale=zh-cn#/mine/apiCredential', + apiSecret: '在“我的凭证”里,点击“访问密钥”,点击“新增访问密钥”。下载后打开对应csv文件获取。', + }, }; diff --git a/front/src/constants/clb.ts b/front/src/constants/clb.ts new file mode 100644 index 0000000000..ab2e46c38a --- /dev/null +++ b/front/src/constants/clb.ts @@ -0,0 +1,228 @@ +import { NetworkAccountType } from '@/api/load_balancers/apply-clb/types'; + +// 负载均衡-路由组件名称 +export enum LBRouteName { + allLbs = 'all-lbs-manager', + lb = 'specific-lb-manager', + listener = 'specific-listener-manager', + domain = 'specific-domain-manager', + allTgs = 'all-tgs-manager', + tg = 'specific-tg-manager', +} +// 负载均衡-路由组件名称映射 +export const LB_ROUTE_NAME_MAP = { + all: 'all-lbs-manager', + lb: 'specific-lb-manager', + listener: 'specific-listener-manager', + domain: 'specific-domain-manager', +}; + +// 网络类型 +export const LOAD_BALANCER_TYPE = [ + { + label: '公网', + value: 'OPEN', + }, + { + label: '内网', + value: 'INTERNAL', + }, +]; +// IP版本 +export const ADDRESS_IP_VERSION = [ + { + label: 'IPv4', + value: 'IPV4', + }, + { + label: 'IPv6', + value: 'IPv6FullChain', + }, + { + label: 'IPv6 NAT64', + value: 'IPV6', + isDisabled: (region: string) => !WHITE_LIST_REGION_IPV6_NAT64.includes(region), + }, +]; +// 可用区类型 +export const ZONE_TYPE = [ + { + label: '单可用区', + value: 'single', + }, + { + label: '主备可用区', + value: 'primaryStand', + isDisabled: (region: string, accountType: NetworkAccountType) => + !WHITE_LIST_REGION_PRIMARY_STAND_ZONE.includes(region) || accountType !== 'STANDARD', + }, +]; +// 网络计费模式 +export const INTERNET_CHARGE_TYPE = [ + { + label: '包月', + value: undefined, + }, + { + label: '按流量', + value: 'TRAFFIC_POSTPAID_BY_HOUR', + }, + { + label: '按带宽', + value: 'BANDWIDTH_POSTPAID_BY_HOUR', + }, + // { + // label: '共享带宽包', + // value: 'BANDWIDTH_PACKAGE', + // }, +]; + +// 支持IPv6 NAT64的地域 +export const WHITE_LIST_REGION_IPV6_NAT64 = ['ap-beijing', 'ap-shanghai', 'ap-guangzhou']; +// 支持主备可用区的地域 +export const WHITE_LIST_REGION_PRIMARY_STAND_ZONE = [ + 'ap-guangzhou', + 'ap-shanghai', + 'ap-nanjing', + 'ap-beijing', + 'ap-hongkong', + 'ap-seoul', +]; + +// 会话类型映射 +export const SESSION_TYPE_MAP = { + NORMAL: '基于源 IP ', + QUIC_CID: '基于源端口', +}; + +// 证书认证方式映射 +export const SSL_MODE_MAP = { + UNIDIRECTIONAL: '单向认证', + MUTUAL: '双向认证', +}; + +// 均衡方式映射 +export const SCHEDULER_MAP = { + WRR: '按权重轮询', + LEAST_CONN: '最小连接数', + IP_HASH: 'IP Hash', +}; +// 均衡方式映射 - 反向映射 +export const SCHEDULER_REVERSE_MAP = { + 按权重轮询: 'WRR', + 最小连接数: 'LEAST_CONN', + IP_HASH: 'IP_HASH', +}; + +// 传输层协议, 如 TCP, UDP +export const TRANSPORT_LAYER_LIST = ['TCP', 'UDP']; +// 应用层协议, 如 HTTP, HTTPS +export const APPLICATION_LAYER_LIST = ['HTTP', 'HTTPS']; + +// 负载均衡网络类型映射 +export const LB_NETWORK_TYPE_MAP = { + OPEN: '公网', + INTERNAL: '内网', +}; + +// 负载均衡网络类型映射 - 反向映射 +export const LB_NETWORK_TYPE_REVERSE_MAP = { + 公网: 'OPEN', + 内网: 'INTERNAL', +}; + +// 腾讯云负载均衡状态映射 +export const CLB_STATUS_MAP = { + '1': '正常运行', + '0': '创建中', +}; + +// 负载均衡规格映射 - 反向映射 +export const CLB_SPECS_REVERSE_MAP = { + 简约型: 'clb.c1.small', + 标准型规格: 'clb.c2.medium', + 高阶型1规格: 'clb.c3.small', + 高阶型2规格: 'clb.c3.medium', + 超强型1规格: 'clb.c4.small', + 超强型2规格: 'clb.c4.medium', + 超强型3规格: 'clb.c4.large', + 超强型4规格: 'clb.c4.xlarge', +}; + +// 腾讯云CLB规格列表映射 +export const CLB_SPEC_TYPE_COLUMNS_MAP = { + 'clb.c1.small': { + connectionsPerMinute: '100,000', + newConnectionsPerSecond: '10,000', + queriesPerSecond: '10,000', + bandwidthLimit: '1Gbps', + }, + 'clb.c2.medium': { + connectionsPerMinute: '100,000', + newConnectionsPerSecond: '10,000', + queriesPerSecond: '10,000', + bandwidthLimit: '2Gbps', + }, + 'clb.c3.small': { + connectionsPerMinute: '200,000', + newConnectionsPerSecond: '20,000', + queriesPerSecond: '20,000', + bandwidthLimit: '4Gbps', + }, + 'clb.c3.medium': { + connectionsPerMinute: '500,000', + newConnectionsPerSecond: '50,000', + queriesPerSecond: '30,000', + bandwidthLimit: '6Gbps', + }, + 'clb.c4.small': { + connectionsPerMinute: '1,000,000', + newConnectionsPerSecond: '100,000', + queriesPerSecond: '50,000', + bandwidthLimit: '10Gbps', + }, + 'clb.c4.medium': { + connectionsPerMinute: '2,000,000', + newConnectionsPerSecond: '200,000', + queriesPerSecond: '100,000', + bandwidthLimit: '20Gbps', + }, + 'clb.c4.large': { + connectionsPerMinute: '5,000,000', + newConnectionsPerSecond: '500,000', + queriesPerSecond: '200,000', + bandwidthLimit: '40Gbps', + }, + 'clb.c4.xlarge': { + connectionsPerMinute: '10,000,000', + newConnectionsPerSecond: '1,000,000', + queriesPerSecond: '300,000', + bandwidthLimit: '60Gbps', + }, +}; + +// 监听器同步状态映射 - 反向映射 +export const LISTENER_BINDING_STATUS_REVERSE_MAP = { + 绑定中: 'binding', + 已绑定: 'success', +}; + +// 编辑目标组操作场景映射 +export const TG_OPERATION_SCENE_MAP = { + add: '新增目标组', + edit: '编辑目标组基本信息', + BatchDelete: '批量删除目标组', + AddRs: '添加RS', + BatchAddRs: '批量添加RS', + BatchDeleteRs: '批量删除RS', + port: '批量修改端口', + weight: '批量修改权重', +}; + +// IP版本映射 - 前端展示使用 +export const IP_VERSION_MAP = { + ipv4: 'IPv4', + ipv6: 'IPv6', + ipv6_nat64: 'IPv6_NAT', + ipv6_dual_stack: 'IPv6双栈', +}; diff --git a/front/src/constants/geo-data.ts b/front/src/constants/geo-data.ts index a86ce7f36b..dd23e1142d 100644 --- a/front/src/constants/geo-data.ts +++ b/front/src/constants/geo-data.ts @@ -1,245 +1,16352 @@ export default { - "type":"FeatureCollection", - "features":[ - {"type":"Feature","properties":{"name":"津巴布韦","full_name":"津巴布韦共和国","iso_a2":"ZW","iso_a3":"ZWE","iso_n3":"716"},"geometry":{"type":"Polygon","coordinates":[[[31.287891,-22.402051],[32.429785,-21.29707],[32.492383,-20.659766],[32.992773,-19.984863],[32.699707,-18.940918],[32.993066,-18.35957],[32.948047,-16.712305],[31.23623,-16.023633],[30.437793,-15.995313],[30.396094,-15.643066],[29.487305,-15.696777],[28.913086,-15.987793],[28.760547,-16.532129],[27.932227,-16.896191],[27.020801,-17.958398],[25.258789,-17.793555],[26.168066,-19.538281],[27.178223,-20.100977],[27.280762,-20.478711],[27.679297,-20.503027],[27.669434,-21.064258],[28.014063,-21.554199],[29.025586,-21.796875],[29.364844,-22.193945],[31.287891,-22.402051]]]}}, - {"type":"Feature","properties":{"name":"赞比亚","full_name":"赞比亚共和国","iso_a2":"ZM","iso_a3":"ZMB","iso_n3":"894"},"geometry":{"type":"Polygon","coordinates":[[[30.396094,-15.643066],[30.231836,-14.990332],[33.201758,-14.013379],[32.67041,-13.59043],[33.021582,-12.630469],[33.512305,-12.347754],[33.252344,-12.112598],[33.261328,-10.893359],[33.661523,-10.553125],[32.919922,-9.407422],[31.033398,-8.597656],[30.751172,-8.193652],[28.898145,-8.485449],[28.400684,-9.224805],[28.645508,-10.550195],[28.383398,-11.566699],[29.064355,-12.348828],[29.485547,-12.418457],[29.795117,-12.155469],[29.775195,-13.438086],[29.554199,-13.248926],[29.014258,-13.368848],[28.412891,-12.518066],[27.573828,-12.227051],[27.15918,-11.579199],[26.824023,-11.965234],[26.025977,-11.890137],[25.349414,-11.623047],[25.28877,-11.212402],[24.37793,-11.41709],[24.365723,-11.129883],[23.966504,-10.871777],[23.962988,-12.988477],[21.978906,-13.000977],[22.040234,-16.262793],[23.380664,-17.640625],[24.73291,-17.517773],[25.258789,-17.793555],[27.020801,-17.958398],[27.932227,-16.896191],[28.760547,-16.532129],[28.913086,-15.987793],[29.487305,-15.696777],[30.396094,-15.643066]]]}}, - {"type":"Feature","properties":{"name":"也门","full_name":"也门共和国","iso_a2":"YE","iso_a3":"YEM","iso_n3":"887"},"geometry":{"type":"MultiPolygon","coordinates":[[[[53.085645,16.648389],[51.977637,18.996143],[49.041992,18.581787],[48.172168,18.156934],[47.143555,16.94668],[46.727637,17.265576],[43.916992,17.324707],[43.417969,17.51626],[43.190918,17.359375],[43.165039,16.689404],[42.799316,16.371777],[42.657812,15.232812],[42.936426,14.938574],[43.231934,13.26709],[43.487598,12.698828],[44.005859,12.607666],[45.038672,12.815869],[45.657324,13.338721],[46.788867,13.465576],[47.989941,14.048096],[48.668359,14.050146],[49.349902,14.637793],[52.21748,15.655518],[52.327734,16.293555],[53.085645,16.648389]]],[[[53.763184,12.636816],[53.31582,12.533154],[53.718848,12.318994],[54.511133,12.552783],[53.763184,12.636816]]]]}}, - {"type":"Feature","properties":{"name":"越南","full_name":"越南社会主义共和国","iso_a2":"VN","iso_a3":"VNM","iso_n3":"704"},"geometry":{"type":"MultiPolygon","coordinates":[[[[104.063965,10.39082],[103.849512,10.371094],[104.018457,10.029199],[104.063965,10.39082]]],[[[107.972656,21.507959],[106.663574,21.978906],[106.550391,22.501367],[106.780273,22.778906],[105.842969,22.922803],[105.275391,23.345215],[103.941504,22.540088],[103.32666,22.769775],[102.981934,22.448242],[102.470898,22.750928],[102.127441,22.379199],[102.662012,21.676025],[102.949609,21.681348],[102.851172,21.265918],[103.104492,20.89165],[103.635059,20.69707],[104.101367,20.945508],[104.583203,20.64668],[104.367773,20.441406],[104.92793,20.018115],[104.587891,19.61875],[104.032031,19.675146],[103.891602,19.30498],[105.146484,18.650977],[105.114551,18.405273],[106.502246,16.954102],[106.656445,16.492627],[107.396387,16.043018],[107.165918,15.80249],[107.653125,15.255225],[107.519434,14.705078],[107.331445,14.126611],[107.605469,13.437793],[107.506445,12.364551],[106.413867,11.948438],[106.399219,11.687012],[105.851465,11.63501],[106.163965,10.794922],[105.755078,10.98999],[105.045703,10.911377],[104.850586,10.534473],[104.426367,10.41123],[105.084473,9.995703],[104.77041,8.597656],[105.114355,8.629199],[106.168359,9.396729],[105.830957,10.000732],[106.484082,9.559424],[106.136426,10.22168],[106.595605,9.859863],[106.785254,10.116455],[106.464063,10.298291],[106.757422,10.295801],[106.605859,10.464941],[106.947461,10.400342],[107.006641,10.660547],[107.261523,10.398389],[108.001367,10.720361],[108.986719,11.336377],[109.198633,11.724854],[109.423926,12.955957],[108.821289,15.37793],[106.370508,17.746875],[106.499023,17.946436],[105.621777,18.966309],[105.984082,19.939062],[106.572852,20.392188],[106.683398,21.000293],[107.164746,20.94873],[107.972656,21.507959]]]]}}, - {"type":"Feature","properties":{"name":"委内瑞拉","full_name":"委内瑞拉玻利瓦尔共和国","iso_a2":"VE","iso_a3":"VEN","iso_n3":"862"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-63.849365,11.131006],[-64.402344,10.981592],[-63.917627,10.887549],[-63.849365,11.131006]]],[[[-60.017529,8.549316],[-61.304004,8.4104],[-61.618701,8.597461],[-61.247266,8.600342],[-60.79248,9.360742],[-61.588867,9.894531],[-61.735938,9.631201],[-62.0771,9.975049],[-62.32041,9.783057],[-62.550342,10.200439],[-62.740576,10.056152],[-62.913574,10.531494],[-61.879492,10.741016],[-64.298193,10.635156],[-63.731885,10.503418],[-65.129102,10.070068],[-65.851758,10.257764],[-66.247217,10.632227],[-68.139941,10.492725],[-68.398633,11.160986],[-68.827979,11.431738],[-69.631592,11.479932],[-70.003955,12.177881],[-70.286523,11.886035],[-69.804785,11.474219],[-71.469531,10.96416],[-71.494238,10.533203],[-71.052686,9.705811],[-71.241406,9.160449],[-71.619531,9.047949],[-72.112842,9.815576],[-71.594336,10.657373],[-71.956934,11.569922],[-71.319727,11.861914],[-71.958105,11.666406],[-72.690088,10.83584],[-73.366211,9.194141],[-72.796387,9.108984],[-72.390332,8.287061],[-72.471973,7.524268],[-72.006641,7.032617],[-70.129199,6.953613],[-69.427148,6.123975],[-67.481982,6.180273],[-67.855273,4.506885],[-67.311133,3.415869],[-67.859082,2.793604],[-67.21084,2.390137],[-66.876025,1.223047],[-66.347119,0.767188],[-65.681445,0.983447],[-65.473389,0.69126],[-64.205029,1.529492],[-64.008496,1.931592],[-63.43252,2.155566],[-63.389258,2.411914],[-64.046582,2.502393],[-64.221094,3.587402],[-64.788672,4.276025],[-64.021484,3.929102],[-63.338672,3.943896],[-62.856982,3.593457],[-62.712109,4.01792],[-61.002832,4.535254],[-60.603857,4.949365],[-60.742139,5.202051],[-61.39082,5.93877],[-61.128711,6.214307],[-61.145605,6.694531],[-60.3521,7.002881],[-60.718652,7.535937],[-59.849072,8.248682],[-60.017529,8.549316]]]]}}, - {"type":"Feature","properties":{"name":"梵蒂冈","full_name":"梵蒂冈城国","iso_a2":"VA","iso_a3":"VAT","iso_n3":"336"},"geometry":{"type":"Polygon","coordinates":[[[12.43916,41.898389],[12.430566,41.905469],[12.430566,41.897559],[12.43916,41.898389]]]}}, - {"type":"Feature","properties":{"name":"瓦努阿图","full_name":"瓦努阿图共和国","iso_a2":"VU","iso_a3":"VUT","iso_n3":"548"},"geometry":{"type":"MultiPolygon","coordinates":[[[[166.745801,-14.826855],[166.567383,-14.641797],[166.631055,-15.406055],[166.758301,-15.631152],[167.093945,-15.580859],[167.075586,-14.935645],[166.810156,-15.157422],[166.745801,-14.826855]]],[[[167.4125,-16.095898],[167.199512,-15.885059],[167.449316,-16.55498],[167.836621,-16.449707],[167.4125,-16.095898]]],[[[168.29668,-16.336523],[168.163867,-16.081641],[167.929004,-16.228711],[168.29668,-16.336523]]],[[[168.445801,-17.542188],[168.158203,-17.710547],[168.524609,-17.798047],[168.445801,-17.542188]]],[[[167.911328,-15.435938],[168.002539,-15.283203],[167.674219,-15.451562],[167.911328,-15.435938]]],[[[169.491309,-19.540137],[169.247461,-19.344727],[169.347266,-19.623535],[169.491309,-19.540137]]],[[[169.334375,-18.940234],[169.01582,-18.64375],[168.986914,-18.871289],[169.334375,-18.940234]]]]}}, - {"type":"Feature","properties":{"name":"乌兹别克斯坦","full_name":"乌兹别克斯坦共和国","iso_a2":"UZ","iso_a3":"UZB","iso_n3":"860"},"geometry":{"type":"Polygon","coordinates":[[[70.946777,42.248682],[69.153613,41.425244],[68.572656,40.622656],[68.291895,40.656104],[67.935742,41.196582],[66.709668,41.17915],[66.498633,41.994873],[66.00957,42.004883],[66.100293,42.99082],[65.803027,42.876953],[64.905469,43.714697],[61.990234,43.492139],[61.00791,44.393799],[58.555273,45.555371],[55.975684,44.994922],[55.977441,41.322217],[57.017969,41.263477],[56.964063,41.856543],[58.028906,42.487646],[58.474414,42.299365],[58.151563,42.628076],[58.589063,42.778467],[59.985156,42.211719],[60.089648,41.399414],[61.902832,41.093701],[62.483203,39.975635],[63.763672,39.160547],[65.612891,38.238574],[66.60625,37.986719],[66.522266,37.348486],[67.758984,37.172217],[68.350293,38.211035],[68.13252,38.927637],[67.357617,39.216699],[67.426172,39.465576],[68.463281,39.536719],[68.97207,40.089941],[68.630664,40.16709],[69.274902,40.198096],[69.357227,40.767383],[69.712891,40.656982],[70.401953,41.035107],[70.751074,40.721777],[70.371582,40.384131],[70.958008,40.238867],[71.69248,40.152344],[73.136914,40.810645],[72.187305,41.025928],[71.664941,41.541211],[71.393066,41.123389],[70.200879,41.514453],[71.228516,42.162891],[70.946777,42.248682]]]}}, - {"type":"Feature","properties":{"name":"乌拉圭","full_name":"乌拉圭东岸共和国","iso_a2":"UY","iso_a3":"URY","iso_n3":"858"},"geometry":{"type":"Polygon","coordinates":[[[-53.370605,-33.742188],[-53.531348,-33.170898],[-53.125586,-32.736719],[-53.761719,-32.056836],[-55.603027,-30.850781],[-56.004687,-31.079199],[-56.044824,-30.777637],[-56.832715,-30.107227],[-57.608887,-30.187793],[-58.201172,-32.47168],[-58.092676,-32.967383],[-58.363525,-33.182324],[-58.438135,-33.719141],[-57.829102,-34.477344],[-57.170703,-34.452344],[-56.249951,-34.90127],[-54.902295,-34.932813],[-53.785303,-34.380371],[-53.370605,-33.742188]]]}}, - {"type":"Feature","properties":{"name":"密克罗尼西亚","full_name":"密克罗尼西亚联邦","iso_a2":"FM","iso_a3":"FSM","iso_n3":"583"},"geometry":{"type":"Polygon","coordinates":[[[158.314844,6.813672],[158.294629,6.951074],[158.134766,6.944824],[158.314844,6.813672]]]}}, - {"type":"Feature","properties":{"name":"马绍尔群岛","full_name":"马绍尔群岛共和国","iso_a2":"MH","iso_a3":"MHL","iso_n3":"584"},"geometry":{"type":"Polygon","coordinates":[[[171.101953,7.138232],[171.035742,7.156104],[171.235352,7.06875],[171.39375,7.110938],[171.101953,7.138232]]]}}, - {"type":"Feature","properties":{"name":"北马里亚纳群岛","full_name":"北马里亚纳群岛联邦(美国)","iso_a2":"MP","iso_a3":"MNP","iso_n3":"580"},"geometry":{"type":"Polygon","coordinates":[[[145.751953,15.133154],[145.821875,15.265381],[145.713184,15.215283],[145.751953,15.133154]]]}}, - {"type":"Feature","properties":{"name":"美属维尔京群岛","full_name":"美属维尔京群岛","iso_a2":"VI","iso_a3":"VIR","iso_n3":"850"},"geometry":{"type":"Polygon","coordinates":[[[-64.765625,17.794336],[-64.889111,17.701709],[-64.580469,17.750195],[-64.765625,17.794336]]]}}, - {"type":"Feature","properties":{"name":"关岛","full_name":"关岛","iso_a2":"GU","iso_a3":"GUM","iso_n3":"316"},"geometry":{"type":"Polygon","coordinates":[[[144.741797,13.259277],[144.875391,13.614648],[144.649316,13.428711],[144.741797,13.259277]]]}}, - {"type":"Feature","properties":{"name":"美属萨摩亚","full_name":"美属萨摩亚","iso_a2":"AS","iso_a3":"ASM","iso_n3":"016"},"geometry":{"type":"Polygon","coordinates":[[[-170.72627,-14.351172],[-170.568115,-14.266797],[-170.820508,-14.312109],[-170.72627,-14.351172]]]}}, - {"type":"Feature","properties":{"name":"波多黎各","full_name":"波多黎各自由邦","iso_a2":"PR","iso_a3":"PRI","iso_n3":"630"},"geometry":{"type":"Polygon","coordinates":[[[-66.129395,18.444922],[-67.158643,18.499219],[-67.196875,17.994189],[-65.970801,17.974365],[-65.62085,18.242334],[-66.129395,18.444922]]]}}, - {"type":"Feature","properties":{"name":"美国","full_name":"美利坚合众国","iso_a2":"US","iso_a3":"USA","iso_n3":"840"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-171.463037,63.640039],[-171.631836,63.351221],[-170.848389,63.444385],[-169.676367,62.956104],[-168.761328,63.21377],[-170.299365,63.680615],[-171.463037,63.640039]]],[[[-166.135449,60.383545],[-167.436426,60.206641],[-166.14873,59.764111],[-165.591797,59.913135],[-166.135449,60.383545]]],[[[-152.898047,57.823926],[-153.841553,57.862842],[-153.687695,57.305127],[-154.116162,57.651221],[-154.673193,57.446094],[-154.338965,56.920898],[-154.24375,57.143018],[-153.793213,56.989502],[-154.027344,56.777979],[-152.679053,57.345117],[-152.940771,57.498096],[-152.216211,57.577002],[-152.898047,57.823926]]],[[[-130.025098,55.888232],[-130.097852,56.109277],[-131.824268,56.58999],[-133.401123,58.410889],[-135.475928,59.793262],[-137.438574,58.903125],[-139.185156,60.083594],[-139.079248,60.343701],[-141.002148,60.300244],[-141.002148,64.975537],[-141.002148,69.650781],[-143.218311,70.11626],[-145.197363,70.008691],[-149.269434,70.500781],[-151.944678,70.4521],[-152.491211,70.880957],[-154.195215,70.801123],[-155.166846,71.099219],[-155.973535,70.841992],[-155.579443,71.121094],[-156.470215,71.407666],[-157.909375,70.860107],[-159.680908,70.786768],[-159.865674,70.278857],[-160.117139,70.591211],[-162.073877,70.161963],[-161.880957,70.331738],[-163.86792,69.03667],[-166.209082,68.885352],[-166.786279,68.359619],[-164.125195,67.606738],[-163.531836,67.102588],[-161.719922,67.020557],[-161.856689,66.700342],[-160.360889,66.6125],[-160.231689,66.420264],[-161.591016,66.459521],[-162.361621,66.947314],[-161.916895,66.411816],[-161.034277,66.188818],[-163.695361,66.083838],[-163.638232,66.574658],[-164.460498,66.588428],[-168.088379,65.657764],[-166.157031,65.28584],[-166.928418,65.15708],[-166.142773,64.582764],[-163.144336,64.423828],[-163.203906,64.652002],[-162.807031,64.374219],[-161.759375,64.81626],[-160.855908,64.755615],[-161.490723,64.433789],[-160.778564,63.818945],[-161.099707,63.55791],[-162.282812,63.529199],[-163.287842,63.046436],[-164.409033,63.215039],[-164.757861,62.496729],[-166.078809,61.803125],[-165.565869,61.102344],[-165.273633,61.274854],[-164.868994,61.111768],[-165.114844,60.932812],[-163.749023,60.969727],[-163.420947,60.757422],[-164.372363,60.591846],[-164.805176,60.892041],[-165.353809,60.541211],[-163.680371,59.801514],[-162.570752,59.989746],[-162.684961,60.268945],[-161.962012,60.695361],[-162.421338,60.283984],[-161.828711,59.588623],[-161.981055,59.146143],[-161.644385,59.109668],[-162.144922,58.644238],[-160.363135,59.051172],[-158.950684,58.404541],[-158.809473,58.973877],[-158.080518,58.977441],[-158.503174,58.850342],[-158.190918,58.614258],[-156.808887,59.134277],[-157.523633,58.421338],[-157.193701,58.194189],[-157.610889,58.05083],[-157.461914,57.506201],[-158.320947,57.2979],[-158.675146,56.794873],[-160.302051,56.314111],[-160.291699,55.805078],[-161.697314,55.907227],[-163.278809,55.121826],[-163.335303,54.83916],[-163.131104,54.916553],[-163.119629,55.064697],[-162.674365,54.996582],[-162.630371,55.24668],[-162.073975,55.139307],[-161.516943,55.618408],[-161.381934,55.371289],[-159.771387,55.841113],[-159.659668,55.625928],[-158.275635,56.19624],[-158.414404,56.43584],[-156.629004,57.009961],[-156.435889,57.359961],[-154.247021,58.159424],[-153.437598,58.754834],[-154.17832,59.155566],[-152.660107,59.997217],[-153.025,60.295654],[-152.540918,60.26543],[-151.593506,60.979639],[-149.433545,61.500781],[-150.053271,61.171094],[-149.075098,60.876416],[-150.44126,61.023584],[-151.356445,60.722949],[-151.853223,59.78208],[-151.046484,59.771826],[-151.884619,59.386328],[-151.738184,59.188525],[-150.934521,59.249121],[-149.713867,59.91958],[-149.598047,59.770459],[-149.395264,60.105762],[-148.430713,59.989111],[-147.964111,60.484863],[-148.640137,60.489453],[-148.256738,60.675293],[-148.556152,60.827002],[-147.751855,61.218945],[-147.891113,60.889893],[-146.284912,61.112646],[-146.570459,60.72915],[-145.674902,60.651123],[-145.898877,60.478174],[-145.248291,60.380127],[-144.691113,60.669092],[-144.901318,60.335156],[-144.147217,60.016406],[-141.40874,60.117676],[-141.408301,59.902783],[-140.419824,59.710742],[-139.431445,60.012256],[-138.988086,59.83501],[-139.286719,59.610938],[-139.512305,59.953564],[-139.773291,59.527295],[-136.607422,58.243994],[-136.061475,58.452734],[-136.989014,59.034473],[-136.22583,58.765479],[-136.150049,59.048096],[-135.897559,58.400195],[-135.090234,58.24585],[-135.363672,59.419434],[-134.776123,58.453857],[-134.208838,58.232959],[-133.876758,58.518164],[-134.031104,58.072168],[-133.194336,57.877686],[-133.535205,57.832959],[-133.117041,57.566211],[-133.64873,57.642285],[-133.465869,57.172168],[-131.551367,56.206787],[-132.15542,55.599561],[-131.032764,56.088086],[-130.748193,55.318018],[-131.047852,55.157666],[-130.575342,54.769678],[-130.214062,55.025879],[-130.025098,55.888232]]],[[[-163.476025,54.980713],[-164.478613,54.906836],[-164.823438,54.419092],[-163.083252,54.668994],[-163.378955,54.815527],[-163.476025,54.980713]]],[[[-133.305078,55.54375],[-133.737109,55.496924],[-133.650195,55.269287],[-133.305078,55.54375]]],[[[-131.339746,55.079834],[-131.56543,55.264111],[-131.329541,54.887744],[-131.339746,55.079834]]],[[[-132.112354,56.109375],[-132.379834,56.498779],[-132.659912,56.078174],[-132.287305,55.929395],[-132.112354,56.109375]]],[[[-130.97915,55.48916],[-131.269238,55.955371],[-131.7625,55.16582],[-131.447559,55.408789],[-131.187891,55.206299],[-130.97915,55.48916]]],[[[-133.366211,57.003516],[-133.979443,57.00957],[-133.48418,56.451758],[-133.158154,56.495166],[-133.328955,56.830078],[-132.95918,56.677051],[-133.366211,57.003516]]],[[[-132.862256,54.894434],[-133.429053,55.303809],[-132.705811,54.68418],[-132.862256,54.894434]]],[[[-160.684912,55.314795],[-160.795068,55.145215],[-160.487549,55.184863],[-160.684912,55.314795]]],[[[-177.148193,51.716748],[-177.110059,51.92876],[-177.670215,51.701074],[-177.148193,51.716748]]],[[[-134.969775,57.351416],[-135.448682,57.534375],[-135.812305,57.009521],[-135.454932,57.249414],[-134.681885,56.216162],[-134.969775,57.351416]]],[[[-134.680273,58.16167],[-134.923486,58.354639],[-134.516016,57.042578],[-133.911133,57.352539],[-134.292334,58.044727],[-133.822754,57.628662],[-134.240088,58.143994],[-134.680273,58.16167]]],[[[-135.730371,58.244238],[-136.568604,57.972168],[-135.910791,57.446582],[-135.564209,57.666406],[-134.931494,57.481152],[-135.338477,57.768652],[-134.954688,58.015332],[-135.613232,57.991846],[-135.730371,58.244238]]],[[[-133.566113,56.339209],[-133.742529,55.964844],[-133.241504,55.920801],[-133.680176,55.785156],[-133.033398,55.589697],[-133.118555,55.327637],[-132.064746,54.713135],[-131.976416,55.208594],[-132.631299,55.473193],[-132.172705,55.480615],[-133.566113,56.339209]]],[[[-133.9896,56.844971],[-134.373682,56.838672],[-134.1896,56.076953],[-133.738379,56.650439],[-133.9896,56.844971]]],[[[-152.416943,58.360205],[-152.841113,58.416406],[-153.381348,58.087207],[-152.068896,58.17793],[-152.416943,58.360205]]],[[[-167.964355,53.345117],[-167.828076,53.507959],[-168.287695,53.500146],[-169.088916,52.832031],[-167.964355,53.345117]]],[[[-166.615332,53.900928],[-167.038086,53.942188],[-166.808984,53.646143],[-167.780859,53.300244],[-166.354541,53.673535],[-166.230859,53.932617],[-166.615332,53.900928]]],[[[-173.55332,52.136279],[-173.99248,52.12334],[-173.0229,52.07915],[-173.55332,52.136279]]],[[[-174.677393,52.03501],[-174.045605,52.367236],[-175.295557,52.022168],[-174.677393,52.03501]]],[[[-176.593311,51.866699],[-176.961621,51.603662],[-176.452344,51.735693],[-176.593311,51.866699]]],[[[-177.879053,51.649707],[-177.644482,51.82627],[-178.168262,51.903027],[-177.879053,51.649707]]],[[[172.811816,53.012988],[172.494824,52.937891],[172.935156,52.7521],[173.436035,52.852051],[172.811816,53.012988]]],[[[-155.581348,19.012012],[-154.804199,19.524463],[-155.831641,20.27583],[-156.048682,19.749951],[-155.881299,19.070508],[-155.581348,19.012012]]],[[[-156.486816,20.932568],[-156.697754,20.949072],[-156.408789,20.605176],[-155.989844,20.757129],[-156.486816,20.932568]]],[[[-157.799365,21.456641],[-157.9625,21.701367],[-158.273145,21.585254],[-158.110352,21.318604],[-157.6354,21.307617],[-157.799365,21.456641]]],[[[-159.372754,21.932373],[-159.352051,22.21958],[-159.78916,22.041797],[-159.372754,21.932373]]],[[[-74.708887,45.003857],[-76.151172,44.303955],[-76.819971,43.628809],[-78.72041,43.624951],[-79.171875,43.466553],[-79.036719,42.802344],[-82.690039,41.675195],[-83.149658,42.141943],[-82.545312,42.624707],[-82.137842,43.570898],[-82.551074,45.347363],[-83.592676,45.817139],[-83.615967,46.116846],[-83.977783,46.084912],[-84.149463,46.542773],[-84.561768,46.457373],[-84.875977,46.899902],[-88.378174,48.303076],[-89.455664,47.99624],[-90.916064,48.209131],[-91.518311,48.058301],[-92.99624,48.611816],[-94.620898,48.742627],[-95.155273,49.369678],[-95.162061,48.991748],[-97.529834,48.993164],[-106.483838,48.993115],[-114.585107,48.993066],[-122.78877,48.993018],[-122.241992,48.010742],[-122.353809,47.371582],[-122.701953,47.110889],[-123.027588,47.138916],[-122.577881,47.293164],[-122.532812,47.919727],[-123.139062,47.386084],[-122.656641,47.881152],[-122.778613,48.137598],[-124.709961,48.380371],[-124.139258,46.954688],[-123.842871,46.963184],[-124.112549,46.862695],[-124.072754,46.279443],[-123.220605,46.153613],[-123.989307,46.219385],[-124.14873,43.691748],[-124.539648,42.812891],[-124.071924,41.459521],[-124.324023,40.251953],[-122.998779,37.988623],[-122.521338,37.826416],[-122.393359,38.144824],[-121.525342,38.055908],[-122.314258,38.007324],[-122.070508,37.478271],[-122.445605,37.797998],[-122.499219,37.542627],[-121.807422,36.851221],[-121.877393,36.331055],[-120.659082,35.122412],[-120.644678,34.57998],[-118.506201,34.017383],[-118.410449,33.743945],[-117.467432,33.295508],[-117.128271,32.53335],[-114.724756,32.715332],[-114.835938,32.508301],[-111.041992,31.324219],[-108.214453,31.329443],[-108.211816,31.779346],[-106.44541,31.768408],[-104.978809,30.645947],[-104.110596,29.386133],[-103.168311,28.998193],[-102.614941,29.752344],[-101.440381,29.776855],[-99.505322,27.54834],[-99.107764,26.446924],[-97.14624,25.961475],[-97.485107,27.237402],[-97.768457,27.45752],[-97.439111,27.328271],[-97.156494,28.144336],[-96.421094,28.457324],[-96.640039,28.708789],[-96.011035,28.631934],[-96.234521,28.488965],[-95.273486,28.963867],[-94.888281,29.370557],[-95.022852,29.702344],[-94.52627,29.547949],[-94.759619,29.384277],[-93.890479,29.689355],[-93.841455,29.979736],[-93.826465,29.725146],[-92.26084,29.556836],[-91.893164,29.836035],[-91.248828,29.564209],[-91.290137,29.288965],[-90.751025,29.130859],[-90.379199,29.295117],[-90.212793,29.104932],[-90.159082,29.537158],[-89.376123,28.981348],[-89.015723,29.202881],[-89.720898,29.619287],[-89.354443,29.820215],[-89.400732,30.046045],[-90.175342,30.029102],[-90.331982,30.277588],[-88.135449,30.366602],[-88.011328,30.694189],[-87.790283,30.291797],[-88.005957,30.230908],[-87.281055,30.339258],[-86.997559,30.570312],[-87.201172,30.339258],[-86.257373,30.493018],[-86.454443,30.399121],[-85.603516,30.286768],[-85.318945,29.680225],[-84.309668,30.064746],[-83.694385,29.925977],[-82.651465,28.8875],[-82.843506,27.845996],[-82.405762,27.862891],[-82.7146,27.499609],[-82.441357,27.059668],[-82.242871,26.848877],[-82.013281,26.961572],[-81.715479,25.983154],[-80.94043,25.264209],[-81.110498,25.138037],[-80.484668,25.229834],[-80.126367,25.833496],[-80.050049,26.807715],[-80.838184,28.757666],[-80.456885,27.900684],[-80.524121,28.486084],[-81.249512,29.793799],[-81.516211,30.801807],[-81.380957,31.353271],[-80.694238,32.215723],[-80.802539,32.448047],[-80.579346,32.287305],[-80.63418,32.511719],[-79.940723,32.667139],[-78.841455,33.724072],[-78.01333,33.911816],[-77.953271,34.168994],[-77.927832,33.939746],[-77.412256,34.730811],[-76.439795,34.84292],[-76.974951,35.025195],[-77.070264,35.154639],[-76.77915,34.990332],[-76.512939,35.27041],[-77.03999,35.527393],[-76.173828,35.35415],[-75.758838,35.843262],[-76.083594,35.690527],[-76.069775,35.970312],[-76.726221,35.957617],[-76.733643,36.22915],[-76.559375,36.015332],[-76.147852,36.279297],[-75.820068,36.112842],[-75.946484,36.659082],[-75.53418,35.819092],[-75.999414,36.912646],[-76.487842,36.897021],[-77.250879,37.329199],[-76.283301,37.052686],[-76.757715,37.50542],[-76.453906,37.273535],[-76.305566,37.571484],[-76.549463,37.669141],[-77.111084,38.165674],[-76.49248,37.682227],[-76.344141,37.675684],[-76.264258,37.893555],[-77.273242,38.351758],[-77.2604,38.6],[-77.030371,38.889258],[-77.23252,38.407715],[-76.341162,38.087012],[-76.668555,38.5375],[-76.394092,38.368994],[-76.57041,39.269336],[-75.958936,39.585059],[-76.341162,38.709668],[-76.016943,38.625098],[-76.264648,38.436426],[-75.858691,38.362061],[-75.659277,37.953955],[-75.934375,37.151904],[-75.03877,38.426367],[-75.187109,38.591113],[-75.088672,38.777539],[-75.392188,39.092773],[-75.587598,39.640771],[-75.07417,39.983496],[-75.524219,39.490186],[-74.897021,39.145459],[-74.954297,38.949951],[-74.794482,39.001904],[-74.0646,39.993115],[-74.079932,39.788135],[-73.972266,40.400342],[-74.264209,40.528613],[-73.969922,41.249707],[-73.987109,40.751367],[-72.924707,41.285156],[-71.522852,41.378955],[-71.390137,41.795312],[-71.168555,41.489404],[-69.948633,41.677148],[-69.977881,41.961279],[-70.108936,42.07832],[-70.001416,41.826172],[-70.42666,41.757275],[-71.046191,42.331104],[-70.612939,42.623242],[-70.733105,43.07002],[-70.178809,43.766357],[-69.226074,43.986475],[-68.762695,44.570752],[-68.53252,44.258643],[-68.450586,44.507617],[-68.056641,44.384326],[-67.19126,44.675586],[-67.124854,45.169434],[-67.802246,45.727539],[-67.806787,47.082812],[-68.235498,47.345947],[-68.937207,47.21123],[-69.242871,47.462988],[-70.865039,45.270703],[-71.327295,45.290088],[-71.517529,45.007568],[-74.708887,45.003857]],[[-122.572754,48.156641],[-122.628613,48.384229],[-122.383154,47.923193],[-122.572754,48.156641]]],[[[-72.509766,40.986035],[-72.274121,41.153027],[-73.573828,40.919629],[-74.014893,40.581201],[-71.903223,41.060693],[-72.509766,40.986035]]],[[[-80.381836,25.142285],[-80.25708,25.347607],[-80.580566,24.954248],[-80.381836,25.142285]]],[[[-84.90791,29.642627],[-84.737158,29.732422],[-85.116748,29.632812],[-84.90791,29.642627]]]]}}, - {"type":"Feature","properties":{"name":"南乔治亚和南桑威奇群岛","full_name":"南乔治亚和南桑威奇群岛(英国)","iso_a2":"GS","iso_a3":"SGS","iso_n3":"239"},"geometry":{"type":"Polygon","coordinates":[[[-37.10332,-54.065625],[-38.017432,-54.008008],[-36.085498,-54.866797],[-35.798584,-54.763477],[-36.326465,-54.251172],[-37.10332,-54.065625]]]}}, - {"type":"Feature","properties":{"name":"英属印度洋领地","full_name":"英属印度洋领地","iso_a2":"IO","iso_a3":"IOT","iso_n3":"086"},"geometry":{"type":"Polygon","coordinates":[[[72.491992,-7.377441],[72.445605,-7.22041],[72.447266,-7.395703],[72.349707,-7.263379],[72.429102,-7.435352],[72.491992,-7.377441]]]}}, - {"type":"Feature","properties":{"name":"圣赫勒拿","full_name":"圣赫勒拿岛(英国)","iso_a2":"SH","iso_a3":"SHN","iso_n3":"654"},"geometry":{"type":"Polygon","coordinates":[[[-5.692139,-15.997754],[-5.707861,-15.906152],[-5.78252,-16.004004],[-5.692139,-15.997754]]]}}, - {"type":"Feature","properties":{"name":"皮特凯恩群岛","full_name":"皮特凯恩群岛(英国)","iso_a2":"PN","iso_a3":"PCN","iso_n3":"612"},"geometry":{"type":"Polygon","coordinates":[[[-128.290088,-24.397363],[-128.330127,-24.323242],[-128.342188,-24.370703],[-128.290088,-24.397363]]]}}, - {"type":"Feature","properties":{"name":"安圭拉","full_name":"安圭拉","iso_a2":"AI","iso_a3":"AIA","iso_n3":"660"},"geometry":{"type":"Polygon","coordinates":[[[-63.001221,18.221777],[-63.026025,18.269727],[-63.16001,18.171387],[-63.001221,18.221777]]]}}, - {"type":"Feature","properties":{"name":"马尔维纳斯群岛(福克兰)","full_name":"马尔维纳斯群岛(福克兰)","iso_a2":"FK","iso_a3":"FLK","iso_n3":"238"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-58.850195,-51.269922],[-59.570801,-51.925391],[-59.395654,-52.308008],[-59.19585,-52.017676],[-58.652783,-52.099219],[-57.791797,-51.636133],[-57.976514,-51.384375],[-58.271582,-51.574707],[-58.850195,-51.269922]]],[[[-60.28623,-51.461914],[-60.568457,-51.357812],[-60.245166,-51.638867],[-60.58252,-51.712695],[-60.238477,-51.771973],[-60.961426,-52.057324],[-60.686377,-52.188379],[-59.268066,-51.427539],[-60.28623,-51.461914]]]]}}, - {"type":"Feature","properties":{"name":"开曼群岛","full_name":"开曼群岛","iso_a2":"KY","iso_a3":"CYM","iso_n3":"136"},"geometry":{"type":"Polygon","coordinates":[[[-81.369531,19.348877],[-81.404785,19.278418],[-81.107129,19.305176],[-81.369531,19.348877]]]}}, - {"type":"Feature","properties":{"name":"百慕大","full_name":"百慕大","iso_a2":"BM","iso_a3":"BMU","iso_n3":"060"},"geometry":{"type":"Polygon","coordinates":[[[-64.730273,32.293457],[-64.668311,32.381934],[-64.862842,32.273877],[-64.730273,32.293457]]]}}, - {"type":"Feature","properties":{"name":"英属维尔京群岛","full_name":"英属维尔京群岛","iso_a2":"VG","iso_a3":"VGB","iso_n3":"092"},"geometry":{"type":"Polygon","coordinates":[[[-64.395215,18.4646],[-64.324658,18.51748],[-64.426074,18.513086],[-64.395215,18.4646]]]}}, - {"type":"Feature","properties":{"name":"特克斯和凯科斯群岛","full_name":"特克斯和凯科斯群岛","iso_a2":"TC","iso_a3":"TCA","iso_n3":"796"},"geometry":{"type":"Polygon","coordinates":[[[-71.661426,21.765234],[-71.668359,21.833447],[-71.847656,21.843457],[-71.661426,21.765234]]]}}, - {"type":"Feature","properties":{"name":"蒙特塞拉特","full_name":"蒙特塞拉特","iso_a2":"MS","iso_a3":"MSR","iso_n3":"500"},"geometry":{"type":"Polygon","coordinates":[[[-62.148438,16.740332],[-62.191357,16.804395],[-62.221631,16.699512],[-62.148438,16.740332]]]}}, - {"type":"Feature","properties":{"name":"泽西岛","full_name":"泽西岛","iso_a2":"JE","iso_a3":"JEY","iso_n3":"832"},"geometry":{"type":"Polygon","coordinates":[[[-2.018652,49.23125],[-2.220508,49.266357],[-2.23584,49.176367],[-2.018652,49.23125]]]}}, - {"type":"Feature","properties":{"name":"根西岛","full_name":"根西岛","iso_a2":"GG","iso_a3":"GGY","iso_n3":"831"},"geometry":{"type":"Polygon","coordinates":[[[-2.512305,49.494531],[-2.639014,49.450928],[-2.547363,49.428711],[-2.512305,49.494531]]]}}, - {"type":"Feature","properties":{"name":"马恩岛","full_name":"马恩岛","iso_a2":"IM","iso_a3":"IMN","iso_n3":"833"},"geometry":{"type":"Polygon","coordinates":[[[-4.412061,54.185352],[-4.424707,54.407178],[-4.785352,54.073047],[-4.412061,54.185352]]]}}, - {"type":"Feature","properties":{"name":"英国","full_name":"大不列颠及北爱尔兰联合王国","iso_a2":"GB","iso_a3":"GBR","iso_n3":"826"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-2.667676,51.622998],[-2.433057,51.740723],[-3.135986,51.205029],[-4.188184,51.188525],[-5.622119,50.050684],[-4.19458,50.393311],[-3.679785,50.239941],[-2.999414,50.716602],[-2.03584,50.603076],[-1.416455,50.896875],[0.205078,50.763037],[0.960156,50.925879],[1.414941,51.363281],[0.424512,51.465625],[1.274414,51.845361],[1.743359,52.578516],[1.271289,52.924561],[0.045898,52.905615],[0.270996,53.335498],[-0.659912,53.724023],[0.115332,53.609277],[-0.084375,54.118066],[-1.232422,54.703711],[-1.655371,55.570361],[-2.599316,56.027295],[-3.789062,56.095215],[-2.674268,56.253418],[-3.309961,56.363477],[-2.592676,56.561572],[-1.77793,57.49375],[-2.074072,57.702393],[-4.134521,57.577734],[-3.053076,58.634814],[-4.924658,58.588379],[-5.413184,58.069727],[-5.157227,57.881348],[-5.744922,57.668311],[-5.561914,57.232715],[-5.730615,56.853076],[-6.132764,56.718018],[-5.652441,56.531982],[-5.188379,56.758057],[-5.768213,55.362646],[-4.996973,56.23335],[-5.228223,55.886328],[-4.800293,56.15835],[-4.584082,55.938672],[-5.135498,54.85752],[-4.91123,54.689453],[-3.03623,54.953076],[-3.592041,54.564355],[-3.165967,54.12793],[-2.867578,54.177246],[-3.0646,53.512842],[-2.749512,53.310205],[-4.268555,53.144531],[-4.683057,52.806152],[-4.101465,52.915479],[-4.149365,52.32627],[-5.262305,51.880176],[-3.562354,51.413818],[-2.667676,51.622998]]],[[[-4.196777,53.321436],[-4.567871,53.386475],[-4.373047,53.13418],[-4.196777,53.321436]]],[[[-1.308105,60.5375],[-1.66377,60.28252],[-1.299463,59.878662],[-1.308105,60.5375]]],[[[-3.057422,59.029639],[-3.310352,59.130811],[-2.793018,58.906934],[-3.057422,59.029639]]],[[[-5.777881,56.344336],[-6.286328,56.611865],[-6.313428,56.293652],[-5.777881,56.344336]]],[[[-6.198682,58.363281],[-7.085254,58.182178],[-6.983105,57.75],[-6.198682,58.363281]]],[[[-6.144727,57.50498],[-6.305957,57.671973],[-6.761133,57.442383],[-5.949072,57.045166],[-5.672461,57.252686],[-6.135547,57.314258],[-6.144727,57.50498]]],[[[-6.218018,54.088721],[-5.47041,54.500195],[-6.035791,55.144531],[-7.218652,55.091992],[-8.118262,54.414258],[-7.606543,54.143848],[-7.007715,54.406689],[-6.649805,54.058643],[-6.218018,54.088721]]]]}}, - {"type":"Feature","properties":{"name":"阿联酋","full_name":"阿拉伯联合酋长国","iso_a2":"AE","iso_a3":"ARE","iso_n3":"784"},"geometry":{"type":"Polygon","coordinates":[[[56.297852,25.650684],[56.080469,26.062646],[54.147949,24.171191],[52.118555,23.971094],[51.568359,24.286182],[52.555078,22.932812],[55.18584,22.704102],[55.468457,23.941113],[55.985156,24.063379],[55.76084,24.242676],[55.795703,24.868115],[56.000586,24.953223],[56.063867,24.73877],[56.387988,24.979199],[56.297852,25.650684]]]}}, - {"type":"Feature","properties":{"name":"乌克兰","full_name":"乌克兰","iso_a2":"UA","iso_a3":"UKR","iso_n3":"804"},"geometry":{"type":"Polygon","coordinates":[[[38.214355,47.091455],[38.368848,47.609961],[39.778711,47.887549],[39.95791,48.268896],[39.644727,48.591211],[40.003613,48.82207],[39.686523,49.00791],[40.108789,49.251562],[40.080664,49.576855],[38.258594,50.052344],[38.046875,49.92002],[37.422852,50.411475],[36.619434,50.209229],[35.591113,50.36875],[35.311914,51.043896],[34.213867,51.255371],[34.397852,51.78042],[33.735254,52.344775],[31.763379,52.101074],[30.755273,51.895166],[30.544531,51.265039],[30.160742,51.477881],[29.346484,51.382568],[29.102051,51.627539],[28.73125,51.433398],[27.7,51.477979],[27.141992,51.752051],[25.267188,51.937744],[23.605273,51.51792],[24.089941,50.530469],[22.706152,49.606201],[22.852051,49.062744],[22.538672,49.072705],[22.131836,48.405322],[22.87666,47.947266],[23.202637,48.084521],[24.979102,47.724121],[26.618945,48.259863],[27.549219,48.477734],[29.125391,47.964551],[29.134863,47.489697],[30.131055,46.423096],[28.958398,46.458496],[28.947754,46.049951],[28.2125,45.450439],[29.705859,45.259912],[29.628418,45.722461],[30.219043,45.866748],[30.796289,46.552002],[31.563379,46.777295],[31.872852,46.649756],[31.75918,47.212842],[32.044336,46.64248],[32.578027,46.615625],[31.554883,46.554297],[32.008496,46.42998],[31.83125,46.281689],[33.594141,46.09624],[33.806667,46.208288],[35.001674,45.733383],[34.849609,46.189893],[35.230371,46.440625],[35.014551,46.106006],[35.827148,46.624316],[38.214355,47.091455]]]}}, - {"type":"Feature","properties":{"name":"乌干达","full_name":"乌干达共和国","iso_a2":"UG","iso_a3":"UGA","iso_n3":"800"},"geometry":{"type":"Polygon","coordinates":[[[33.903223,-1.002051],[33.943164,0.173779],[34.978223,1.773633],[34.437695,3.650586],[33.976074,4.220215],[33.489355,3.755078],[32.997266,3.880176],[32.135938,3.519727],[31.798047,3.802637],[31.152344,3.785596],[30.838574,3.490723],[30.728613,2.455371],[31.252734,2.04458],[29.942871,0.819238],[29.576953,-1.387891],[29.930078,-1.469922],[30.509961,-1.067285],[33.903223,-1.002051]]]}}, - {"type":"Feature","properties":{"name":"土库曼斯坦","full_name":"土库曼斯坦","iso_a2":"TM","iso_a3":"TKM","iso_n3":"795"},"geometry":{"type":"Polygon","coordinates":[[[55.977441,41.322217],[55.434375,41.296289],[54.120996,42.335205],[53.055859,42.147754],[52.493848,41.780371],[52.850391,41.200293],[52.97002,41.976221],[53.804688,42.117627],[54.703711,41.071143],[54.377344,40.693262],[53.87002,40.648682],[52.943457,41.038086],[52.733691,40.39873],[53.035547,39.774414],[52.9875,39.987598],[53.487305,39.909375],[53.603125,39.546973],[53.235645,39.608545],[53.156641,39.26499],[53.70459,39.20957],[53.868652,38.949268],[53.91416,37.343555],[54.699414,37.470166],[55.380859,38.051123],[57.193555,38.216406],[58.261621,37.66582],[59.301758,37.510645],[60.341309,36.637646],[61.119629,36.642578],[61.262012,35.61958],[62.307813,35.170801],[62.688086,35.255322],[63.056641,35.445801],[63.12998,35.846191],[64.511035,36.340674],[64.816309,37.13208],[65.55498,37.251172],[65.765039,37.569141],[66.522266,37.348486],[66.60625,37.986719],[65.612891,38.238574],[63.763672,39.160547],[62.483203,39.975635],[61.902832,41.093701],[60.089648,41.399414],[59.985156,42.211719],[58.589063,42.778467],[58.151563,42.628076],[58.474414,42.299365],[58.028906,42.487646],[56.964063,41.856543],[57.017969,41.263477],[55.977441,41.322217]]]}}, - {"type":"Feature","properties":{"name":"土耳其","full_name":"土耳其共和国","iso_a2":"TR","iso_a3":"TUR","iso_n3":"792"},"geometry":{"type":"MultiPolygon","coordinates":[[[[41.510059,41.51748],[40.265234,40.961328],[39.426367,41.106445],[38.381055,40.924512],[36.405371,41.274609],[36.051758,41.682568],[35.297754,41.728516],[35.006445,42.063281],[33.381348,42.017578],[31.254883,41.107617],[29.148145,41.221045],[29.113867,40.937842],[29.849219,40.760107],[28.958008,40.630566],[29.007129,40.389746],[26.738086,40.400244],[26.181348,39.990088],[26.113086,39.467383],[26.899219,39.549658],[26.681836,39.292236],[27.013672,38.886865],[26.763672,38.709619],[27.144238,38.451953],[26.674219,38.335742],[26.441309,38.641211],[26.290723,38.277197],[27.232422,37.978662],[27.067969,37.65791],[27.535059,37.163867],[27.262988,36.976562],[28.242383,37.029053],[27.453906,36.712158],[28.969629,36.715332],[29.689062,36.156689],[30.446094,36.269873],[30.644043,36.865674],[31.240625,36.821729],[32.794824,36.035889],[33.694727,36.181982],[34.703613,36.816797],[35.393164,36.575195],[36.048926,36.910596],[35.892676,35.916553],[36.153613,35.833887],[36.636719,36.233984],[36.658594,36.802539],[37.436328,36.643311],[38.191699,36.901562],[39.356641,36.681592],[40.705664,37.097705],[42.202734,37.297266],[42.358984,37.108594],[42.774609,37.371875],[44.114453,37.301855],[44.281836,36.978027],[44.765137,37.142432],[44.589941,37.710352],[44.211328,37.908057],[44.449902,38.334229],[44.023242,39.377441],[44.389355,39.422119],[44.587109,39.768555],[44.817188,39.650439],[44.768262,39.703516],[43.666211,40.126367],[43.439453,41.107129],[42.754102,41.578906],[41.510059,41.51748]]],[[[28.014453,41.969043],[27.011719,42.058643],[26.320898,41.716553],[26.624902,41.401758],[26.038965,40.726758],[26.79209,40.626611],[26.202734,40.075391],[27.499414,40.973145],[28.95625,41.008203],[29.057227,41.229736],[28.197852,41.554492],[28.014453,41.969043]]]]}}, - {"type":"Feature","properties":{"name":"突尼斯","full_name":"突尼斯共和国","iso_a2":"TN","iso_a3":"TUN","iso_n3":"788"},"geometry":{"type":"Polygon","coordinates":[[[11.50459,33.181934],[10.049023,34.056299],[11.120117,35.240283],[10.476562,36.175146],[11.053906,37.07251],[10.412305,36.731836],[10.196387,37.205859],[9.830273,37.135352],[9.687988,37.340381],[8.576563,36.937207],[8.207617,36.518945],[8.245605,34.734082],[7.500195,33.832471],[8.333398,32.543604],[9.044043,32.072363],[9.51875,30.229395],[10.216406,30.783203],[10.274609,31.684961],[11.50498,32.413672],[11.50459,33.181934]]]}}, - {"type":"Feature","properties":{"name":"特立尼达和多巴哥","full_name":"特立尼达和多巴哥共和国","iso_a2":"TT","iso_a3":"TTO","iso_n3":"780"},"geometry":{"type":"Polygon","coordinates":[[[-61.012109,10.134326],[-60.917627,10.840234],[-61.651172,10.718066],[-61.499316,10.268555],[-61.906104,10.069141],[-61.012109,10.134326]]]}}, - {"type":"Feature","properties":{"name":"汤加","full_name":"汤加王国","iso_a2":"TO","iso_a3":"TON","iso_n3":"776"},"geometry":{"type":"Polygon","coordinates":[[[-175.161914,-21.169336],[-175.362354,-21.106836],[-175.156592,-21.263672],[-175.161914,-21.169336]]]}}, - {"type":"Feature","properties":{"name":"多哥","full_name":"多哥共和国","iso_a2":"TG","iso_a3":"TGO","iso_n3":"768"},"geometry":{"type":"Polygon","coordinates":[[[0.900488,10.993262],[-0.068604,11.115625],[-0.086328,10.673047],[0.380859,10.291846],[0.233398,9.463525],[0.525684,9.398486],[0.372559,8.759277],[0.686328,8.354883],[0.525586,6.850928],[1.187207,6.089404],[1.622656,6.216797],[1.77793,6.294629],[1.530957,6.992432],[1.600195,9.050049],[1.330078,9.996973],[0.763379,10.38667],[0.900488,10.993262]]]}}, - {"type":"Feature","properties":{"name":"东帝汶","full_name":"东帝汶民主共和国","iso_a2":"TL","iso_a3":"TLS","iso_n3":"626"},"geometry":{"type":"MultiPolygon","coordinates":[[[[125.068164,-9.511914],[127.296094,-8.424512],[125.381836,-8.575391],[124.922266,-8.94248],[125.149023,-9.042578],[124.960156,-9.21377],[125.068164,-9.511914]]],[[[124.036328,-9.341602],[124.282324,-9.42793],[124.444434,-9.190332],[124.036328,-9.341602]]]]}}, - {"type":"Feature","properties":{"name":"泰国","full_name":"泰王国","iso_a2":"TH","iso_a3":"THA","iso_n3":"764"},"geometry":{"type":"MultiPolygon","coordinates":[[[[98.409082,7.902051],[98.32207,8.166309],[98.296289,7.776074],[98.409082,7.902051]]],[[[100.122461,20.31665],[99.458887,20.363037],[99.485938,20.149854],[99.074219,20.099365],[98.916699,19.7729],[98.015039,19.749512],[97.745898,18.588184],[97.373926,18.517969],[98.660742,16.33042],[98.888281,16.351904],[98.202148,14.975928],[99.136816,13.716699],[99.123926,13.030762],[99.614746,11.781201],[98.757227,10.660937],[98.702539,10.190381],[98.241797,8.767871],[98.305469,8.226221],[98.703516,8.256738],[100.119141,6.441992],[100.261426,6.682715],[101.053516,6.242578],[101.113965,5.636768],[101.556055,5.907764],[101.873633,5.825293],[102.101074,6.242236],[101.497949,6.865283],[100.423535,7.187842],[100.160742,7.599268],[100.256641,7.774902],[100.545215,7.226904],[100.279395,8.268506],[99.835547,9.288379],[99.253906,9.265234],[99.165039,10.319824],[99.989062,12.170801],[99.990527,13.243457],[100.235645,13.484473],[100.962695,13.431982],[100.897754,12.653809],[101.835742,12.640381],[102.594141,12.203027],[102.933887,11.706689],[102.336328,13.560303],[103.199414,14.332617],[105.183301,14.34624],[105.497363,14.590674],[105.641016,15.656543],[104.819336,16.466064],[104.739648,17.46167],[103.949609,18.318994],[103.366992,18.42334],[102.680078,17.824121],[102.101465,18.210645],[100.955859,17.541113],[101.211914,19.54834],[100.513574,19.553467],[100.519531,20.17793],[100.122461,20.31665]]]]}}, - {"type":"Feature","properties":{"name":"坦桑尼亚","full_name":"坦桑尼亚联合共和国","iso_a2":"TZ","iso_a3":"TZA","iso_n3":"834"},"geometry":{"type":"MultiPolygon","coordinates":[[[[39.496484,-6.174609],[39.308984,-5.721973],[39.182324,-6.172559],[39.480957,-6.453711],[39.496484,-6.174609]]],[[[39.865039,-4.906152],[39.673438,-4.927051],[39.749316,-5.443848],[39.865039,-4.906152]]],[[[32.919922,-9.407422],[33.888867,-9.670117],[33.995605,-9.49541],[34.320898,-9.731543],[34.60791,-11.080469],[34.959473,-11.578125],[35.911328,-11.454688],[36.305664,-11.706348],[37.372852,-11.710449],[37.920215,-11.294727],[38.491797,-11.413281],[40.463574,-10.464355],[39.725195,-10.000488],[39.304004,-8.443848],[39.288477,-7.517871],[39.546094,-7.024023],[38.804688,-6.070117],[39.221777,-4.692383],[37.608203,-3.49707],[37.643848,-3.04541],[33.903223,-1.002051],[30.509961,-1.067285],[30.876562,-2.143359],[30.553613,-2.400098],[30.433496,-2.874512],[30.780273,-2.984863],[30.790234,-3.274609],[29.947266,-4.307324],[29.403223,-4.449316],[29.54082,-6.313867],[30.212695,-7.037891],[30.751172,-8.193652],[31.033398,-8.597656],[32.919922,-9.407422]]]]}}, - {"type":"Feature","properties":{"name":"塔吉克斯坦","full_name":"塔吉克斯坦共和国","iso_a2":"TJ","iso_a3":"TJK","iso_n3":"762"},"geometry":{"type":"Polygon","coordinates":[[[67.758984,37.172217],[68.067773,36.949805],[68.911816,37.333936],[69.303906,37.116943],[69.49209,37.553076],[70.188672,37.582471],[70.214648,37.924414],[70.878906,38.456396],[71.255859,38.306982],[71.278516,37.918408],[71.582227,37.910107],[71.43291,37.127539],[71.665625,36.696924],[73.38291,37.462256],[73.720605,37.41875],[73.653516,37.239355],[74.349023,37.41875],[74.891309,37.231641],[75.11875,37.385693],[74.812305,38.460303],[73.80166,38.606885],[73.631641,39.448877],[72.22998,39.20752],[71.470312,39.603662],[70.799316,39.394727],[70.501172,39.587354],[69.297656,39.524805],[69.530273,40.097314],[69.966797,40.202246],[70.515137,39.949902],[70.958008,40.238867],[70.371582,40.384131],[70.751074,40.721777],[70.401953,41.035107],[69.712891,40.656982],[69.357227,40.767383],[69.274902,40.198096],[68.630664,40.16709],[68.97207,40.089941],[68.463281,39.536719],[67.426172,39.465576],[67.357617,39.216699],[68.13252,38.927637],[68.350293,38.211035],[67.758984,37.172217]]]}}, - {"type":"Feature","properties":{"name":"叙利亚","full_name":"阿拉伯叙利亚共和国","iso_a2":"SY","iso_a3":"SYR","iso_n3":"760"},"geometry":{"type":"Polygon","coordinates":[[[35.892676,35.916553],[35.97627,34.629199],[36.383887,34.65791],[36.584961,34.22124],[35.869141,33.431738],[35.816125,33.361879],[35.787305,32.734912],[36.818359,32.317285],[38.773535,33.372217],[40.987012,34.429053],[41.295996,36.38335],[42.358984,37.108594],[42.202734,37.297266],[40.705664,37.097705],[39.356641,36.681592],[38.191699,36.901562],[37.436328,36.643311],[36.658594,36.802539],[36.636719,36.233984],[36.153613,35.833887],[35.892676,35.916553]]]}}, - {"type":"Feature","properties":{"name":"瑞士","full_name":"瑞士联邦","iso_a2":"CH","iso_a3":"CHE","iso_n3":"756"},"geometry":{"type":"Polygon","coordinates":[[[9.524023,47.524219],[8.572656,47.775635],[8.454004,47.596191],[7.615625,47.592725],[6.968359,47.453223],[5.97002,46.214697],[6.758105,46.415771],[7.021094,45.925781],[7.787891,45.921826],[8.422559,46.446045],[9.02373,45.845703],[9.260156,46.475195],[10.12832,46.238232],[10.452832,46.864941],[9.580273,47.057373],[9.487695,47.062256],[9.527539,47.270752],[9.524023,47.524219]]]}}, - {"type":"Feature","properties":{"name":"瑞典","full_name":"瑞典","iso_a2":"SE","iso_a3":"SWE","iso_n3":"752"},"geometry":{"type":"MultiPolygon","coordinates":[[[[19.076465,57.835938],[18.136523,57.556641],[18.146387,56.920508],[19.076465,57.835938]]],[[[11.388281,59.036523],[11.248242,58.369141],[12.883691,56.617725],[12.80166,56.263916],[12.471191,56.290527],[12.88584,55.411377],[14.17373,55.396631],[14.401953,55.976758],[15.82666,56.124951],[16.34873,56.709277],[16.923828,58.492578],[16.214258,58.63667],[18.285352,59.109375],[18.560254,59.394482],[17.964258,59.359375],[18.970508,59.757227],[17.955762,60.589795],[17.250977,60.700781],[17.410254,62.508398],[20.762695,63.867822],[21.519629,64.463086],[21.410352,65.317432],[22.400977,65.862109],[24.155469,65.805273],[23.638867,67.954395],[20.622168,69.036865],[20.116699,69.020898],[19.969824,68.356396],[18.303027,68.55542],[17.916699,67.964893],[17.324609,68.103809],[16.783594,67.89502],[15.483789,66.305957],[14.543262,66.129346],[14.479688,65.301465],[13.650293,64.581543],[14.141211,64.173535],[12.792773,64],[12.175195,63.595947],[12.155371,61.720752],[12.880762,61.352295],[12.294141,61.002686],[12.486133,60.106787],[11.680762,59.592285],[11.642773,58.926074],[11.388281,59.036523]]]]}}, - {"type":"Feature","properties":{"name":"斯威士兰","full_name":"斯威士兰王国","iso_a2":"SZ","iso_a3":"SWZ","iso_n3":"748"},"geometry":{"type":"Polygon","coordinates":[[[31.948242,-25.957617],[31.207324,-25.843359],[30.794336,-26.764258],[31.469531,-27.295508],[31.958398,-27.305859],[32.112891,-26.839453],[31.948242,-25.957617]]]}}, - {"type":"Feature","properties":{"name":"苏里南","full_name":"苏里南共和国","iso_a2":"SR","iso_a3":"SUR","iso_n3":"740"},"geometry":{"type":"Polygon","coordinates":[[[-54.155957,5.358984],[-54.054199,5.80791],[-54.833691,5.98833],[-55.828174,5.96167],[-55.897607,5.699316],[-56.969824,5.992871],[-57.194775,5.548438],[-57.331006,5.020166],[-57.917041,4.82041],[-58.054297,4.10166],[-57.646729,3.394531],[-57.303662,3.3771],[-57.197363,2.853271],[-56.482812,1.942139],[-55.929639,1.8875],[-56.137695,2.259033],[-55.957471,2.520459],[-54.978662,2.597656],[-54.61626,2.326758],[-54.00957,3.448535],[-54.479688,4.836523],[-54.155957,5.358984]]]}}, - {"type":"Feature","properties":{"name":"南苏丹","full_name":"南苏丹共和国","iso_a2":"SS","iso_a3":"SSD","iso_n3":"728"},"geometry":{"type":"Polygon","coordinates":[[[33.976074,4.220215],[35.268359,5.492285],[34.710645,6.660303],[33.902441,7.509521],[32.998926,7.899512],[33.281055,8.437256],[34.072754,8.545264],[34.078125,9.461523],[33.871484,9.506152],[33.907031,10.181445],[33.130078,10.745947],[33.199316,12.217285],[32.721875,12.223096],[32.736719,12.009668],[32.072266,12.006738],[32.420801,11.089111],[31.224902,9.799268],[30.755371,9.731201],[30.003027,10.277393],[28.844531,9.326074],[28.048926,9.328613],[27.880859,9.601611],[26.658691,9.484131],[25.858203,10.406494],[25.211719,10.329932],[24.531934,8.886914],[24.147363,8.665625],[24.291406,8.291406],[24.85332,8.137549],[25.278906,7.42749],[26.361816,6.635303],[26.514258,6.069238],[27.143945,5.722949],[27.40332,5.10918],[28.19209,4.350244],[29.676855,4.586914],[30.838574,3.490723],[31.152344,3.785596],[31.798047,3.802637],[32.135938,3.519727],[32.997266,3.880176],[33.489355,3.755078],[33.976074,4.220215]]]}}, - {"type":"Feature","properties":{"name":"苏丹","full_name":"苏丹共和国","iso_a2":"SD","iso_a3":"SDN","iso_n3":"729"},"geometry":{"type":"Polygon","coordinates":[[[34.078125,9.461523],[34.343945,10.658643],[34.931445,10.864795],[35.112305,11.816553],[35.670215,12.62373],[36.125195,12.757031],[36.524316,14.256836],[36.426758,15.13208],[37.008984,17.058887],[37.411035,17.061719],[38.609473,18.005078],[37.471289,18.820117],[37.258594,21.108545],[36.871387,21.996729],[31.434473,21.99585],[31.400293,22.202441],[31.092676,21.994873],[24.980273,21.99585],[24.979492,20.002588],[23.980273,19.995947],[23.980273,19.496631],[23.970801,15.721533],[22.933887,15.533105],[22.932324,15.162109],[22.381543,14.550488],[22.538574,14.161865],[22.106445,13.799805],[22.228125,13.32959],[21.825293,12.790527],[22.352344,12.660449],[22.591113,11.579883],[22.922656,11.344873],[22.860059,10.919678],[23.646289,9.8229],[23.537305,8.81582],[24.147363,8.665625],[24.531934,8.886914],[25.211719,10.329932],[25.858203,10.406494],[26.658691,9.484131],[27.880859,9.601611],[28.048926,9.328613],[28.844531,9.326074],[30.003027,10.277393],[30.755371,9.731201],[31.224902,9.799268],[32.420801,11.089111],[32.072266,12.006738],[32.736719,12.009668],[32.721875,12.223096],[33.199316,12.217285],[33.130078,10.745947],[33.907031,10.181445],[33.871484,9.506152],[34.078125,9.461523]]]}}, - {"type":"Feature","properties":{"name":"斯里兰卡","full_name":"斯里兰卡民主社会主义共和国","iso_a2":"LK","iso_a3":"LKA","iso_n3":"144"},"geometry":{"type":"Polygon","coordinates":[[[79.982324,9.812695],[80.42832,9.480957],[80.086328,9.577832],[79.783496,8.018457],[79.712988,8.182324],[79.859375,6.829297],[80.095313,6.153174],[80.724121,5.979053],[81.637402,6.425146],[81.874121,7.28833],[80.711133,9.366357],[79.982324,9.812695]]]}}, - {"type":"Feature","properties":{"name":"西班牙","full_name":"西班牙王国","iso_a2":"ES","iso_a3":"ESP","iso_n3":"724"},"geometry":{"type":"MultiPolygon","coordinates":[[[[3.145313,39.790088],[3.158691,39.970508],[2.37002,39.57207],[3.072852,39.30127],[3.461816,39.697754],[3.145313,39.790088]]],[[[1.445215,38.918701],[1.564453,39.121045],[1.22334,38.903857],[1.445215,38.918701]]],[[[-1.794043,43.407324],[-4.523047,43.415723],[-8.004687,43.694385],[-9.178076,43.174023],[-8.777148,41.941064],[-8.266064,42.137402],[-8.15249,41.811963],[-6.618262,41.942383],[-6.2125,41.532031],[-6.928467,41.009131],[-6.975391,39.798389],[-7.535693,39.661572],[-6.997949,39.056445],[-7.343018,38.457422],[-6.957568,38.187891],[-7.443945,37.728271],[-7.406152,37.179443],[-6.86377,37.278906],[-6.216797,36.913574],[-6.384131,36.637012],[-5.625488,36.025928],[-4.366846,36.718115],[-2.111523,36.77666],[-1.640967,37.386963],[-0.721582,37.631055],[-0.520801,38.317285],[0.201563,38.75918],[-0.327002,39.519873],[0.891113,40.722363],[0.714648,40.822852],[3.004883,41.767432],[3.211426,42.431152],[1.706055,42.50332],[1.448828,42.437451],[1.42832,42.595898],[0.696875,42.845117],[0.631641,42.6896],[-0.586426,42.798975],[-1.46084,43.051758],[-1.794043,43.407324]]],[[[-16.334473,28.379932],[-16.123633,28.575977],[-16.905322,28.3396],[-16.658008,28.007178],[-16.334473,28.379932]]],[[[-14.196777,28.169287],[-13.928027,28.253467],[-13.857227,28.738037],[-14.491797,28.100928],[-14.196777,28.169287]]],[[[-15.400586,28.147363],[-15.682764,28.154053],[-15.710303,27.784082],[-15.436768,27.810693],[-15.400586,28.147363]]]]}}, - {"type":"Feature","properties":{"name":"韩国","full_name":"大韩民国","iso_a2":"KR","iso_a3":"KOR","iso_n3":"410"},"geometry":{"type":"MultiPolygon","coordinates":[[[[126.633887,37.781836],[126.976855,36.939404],[126.487012,37.007471],[126.160547,36.771924],[126.487695,36.693799],[126.753027,35.871973],[126.291113,35.15415],[126.593359,34.824365],[126.264453,34.673242],[126.531445,34.314258],[127.24707,34.755127],[127.324609,34.463281],[127.714844,34.954688],[128.443945,34.870361],[128.510938,35.100977],[129.076758,35.122705],[129.419141,35.497852],[129.418262,37.059033],[128.374609,38.623438],[128.038965,38.308545],[127.090332,38.283887],[126.633887,37.781836]]],[[[128.741016,34.798535],[128.667969,35.008789],[128.489258,34.865283],[128.741016,34.798535]]],[[[126.326953,33.223633],[126.901172,33.515137],[126.337695,33.4604],[126.326953,33.223633]]]]}}, - {"type":"Feature","properties":{"name":"南非","full_name":"南非共和国","iso_a2":"ZA","iso_a3":"ZAF","iso_n3":"710"},"geometry":{"type":"Polygon","coordinates":[[[29.364844,-22.193945],[28.210156,-22.693652],[27.085547,-23.57793],[26.835059,-24.24082],[25.912109,-24.747461],[25.443652,-25.714453],[24.748145,-25.817383],[23.05752,-25.312305],[22.597656,-26.132715],[21.646289,-26.854199],[20.685059,-26.822461],[20.793164,-25.915625],[19.980469,-24.776758],[19.980469,-28.45127],[19.161719,-28.93877],[18.102734,-28.87168],[17.447949,-28.698145],[17.05625,-28.031055],[16.447559,-28.617578],[18.21084,-31.74248],[18.325293,-32.50498],[17.851074,-32.827441],[18.433008,-33.717285],[18.410352,-34.295605],[18.752148,-34.082617],[18.831348,-34.364062],[20.020605,-34.785742],[20.529883,-34.463086],[21.788965,-34.372656],[22.553809,-34.010059],[25.574219,-34.035352],[25.805859,-33.737109],[26.613672,-33.707422],[27.860645,-33.053906],[29.971191,-31.32207],[31.335156,-29.378125],[32.285742,-28.621484],[32.886133,-26.849316],[32.112891,-26.839453],[31.958398,-27.305859],[31.469531,-27.295508],[30.794336,-26.764258],[31.207324,-25.843359],[31.948242,-25.957617],[31.98584,-24.460645],[31.287891,-22.402051],[29.364844,-22.193945]],[[28.736914,-30.101953],[28.39209,-30.147559],[28.056836,-30.631055],[27.753125,-30.6],[27.056934,-29.625586],[27.735547,-28.940039],[28.625781,-28.581738],[29.390723,-29.269727],[29.098047,-29.919043],[28.736914,-30.101953]]]}}, - {"type":"Feature","properties":{"name":"索马里","full_name":"索马里联邦共和国","iso_a2":"SO","iso_a3":"SOM","iso_n3":"706"},"geometry":{"type":"Polygon","coordinates":[[[41.532715,-1.695312],[43.717578,0.857861],[46.878809,3.285645],[47.975293,4.497021],[49.049316,6.173633],[49.852051,7.962549],[50.825,9.428174],[50.930078,10.335547],[51.390234,10.422607],[51.031836,10.444775],[51.254883,11.830713],[50.792285,11.983691],[50.110059,11.529297],[48.938574,11.258447],[48.938086,9.451758],[47.978223,7.99707],[44.940527,4.912012],[43.583496,4.85498],[41.883984,3.977734],[40.964453,2.814648],[40.978711,-0.870313],[41.532715,-1.695312]]]}}, - {"type":"Feature","properties":{"name":"索马里兰","full_name":"索马里兰","iso_a2":"-99","iso_a3":"-99","iso_n3":"-99"},"geometry":{"type":"Polygon","coordinates":[[[48.938574,11.258447],[47.40498,11.174023],[46.565039,10.745996],[45.816699,10.835889],[44.386523,10.430225],[43.245996,11.499805],[42.922754,10.999316],[42.656445,10.6],[42.841602,10.203076],[43.983789,9.008838],[46.978223,7.99707],[47.978223,7.99707],[48.938086,9.451758],[48.938574,11.258447]]]}}, - {"type":"Feature","properties":{"name":"所罗门群岛","full_name":"所罗门群岛","iso_a2":"SB","iso_a3":"SLB","iso_n3":"090"},"geometry":{"type":"MultiPolygon","coordinates":[[[[157.763477,-8.242188],[157.490625,-7.965723],[157.217578,-8.262793],[157.558008,-8.269922],[157.819336,-8.612012],[157.763477,-8.242188]]],[[[157.388965,-8.713477],[157.379492,-8.420898],[157.212305,-8.565039],[157.388965,-8.713477]]],[[[156.687891,-7.923047],[156.809082,-7.722852],[156.560938,-7.574023],[156.687891,-7.923047]]],[[[159.750391,-9.272656],[159.612305,-9.470703],[159.802734,-9.763477],[160.818945,-9.862793],[160.35459,-9.421582],[159.750391,-9.272656]]],[[[159.879102,-8.534277],[159.431445,-8.029004],[158.457422,-7.544727],[159.879102,-8.534277]]],[[[157.486719,-7.330371],[156.452539,-6.638281],[157.101562,-7.323633],[157.486719,-7.330371]]],[[[160.749414,-8.313965],[160.59043,-8.372754],[160.77207,-8.963867],[161.367383,-9.61123],[160.749414,-8.313965]]],[[[161.715332,-10.387305],[161.304785,-10.204395],[161.786816,-10.716895],[162.37334,-10.823242],[162.105371,-10.453809],[161.715332,-10.387305]]]]}}, - {"type":"Feature","properties":{"name":"斯洛伐克","full_name":"斯洛伐克共和国","iso_a2":"SK","iso_a3":"SVK","iso_n3":"703"},"geometry":{"type":"Polygon","coordinates":[[[22.538672,49.072705],[21.639648,49.411963],[20.362988,49.385254],[20.057617,49.181299],[19.441602,49.597705],[19.149414,49.4],[18.832227,49.510791],[16.953125,48.598828],[17.147363,48.005957],[17.761914,47.770166],[18.724219,47.787158],[18.791895,48.000293],[19.950391,48.146631],[20.490039,48.526904],[22.131836,48.405322],[22.538672,49.072705]]]}}, - {"type":"Feature","properties":{"name":"斯洛文尼亚","full_name":"斯洛文尼亚共和国","iso_a2":"SI","iso_a3":"SVN","iso_n3":"705"},"geometry":{"type":"Polygon","coordinates":[[[16.516211,46.499902],[16.093066,46.863281],[14.549805,46.399707],[13.7,46.520264],[13.378223,46.261621],[13.719824,45.587598],[13.57793,45.516895],[14.568848,45.657227],[15.339453,45.467041],[15.635938,46.200732],[16.516211,46.499902]]]}}, - {"type":"Feature","properties":{"name":"新加坡","full_name":"新加坡共和国","iso_a2":"SG","iso_a3":"SGP","iso_n3":"702"},"geometry":{"type":"Polygon","coordinates":[[[103.969727,1.331445],[103.817969,1.44707],[103.650195,1.325537],[103.969727,1.331445]]]}}, - {"type":"Feature","properties":{"name":"塞拉利昂","full_name":"塞拉利昂共和国","iso_a2":"SL","iso_a3":"SLE","iso_n3":"694"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-10.283203,8.485156],[-10.712109,8.335254],[-10.500537,8.687549],[-10.690527,9.314258],[-11.273633,9.996533],[-12.427979,9.898145],[-13.292676,9.049219],[-13.059473,8.881152],[-13.181836,8.576904],[-12.894092,8.629785],[-13.272754,8.429736],[-12.850879,7.818701],[-12.480273,7.753271],[-12.485645,7.386279],[-11.50752,6.906543],[-10.647461,7.759375],[-10.283203,8.485156]]],[[[-12.526074,7.436328],[-12.615234,7.637207],[-12.951611,7.57085],[-12.526074,7.436328]]]]}}, - {"type":"Feature","properties":{"name":"塞舌尔","full_name":"塞舌尔共和国","iso_a2":"SC","iso_a3":"SYC","iso_n3":"690"},"geometry":{"type":"Polygon","coordinates":[[[55.540332,-4.693066],[55.383398,-4.609277],[55.542969,-4.785547],[55.540332,-4.693066]]]}}, - {"type":"Feature","properties":{"name":"塞尔维亚","full_name":"塞尔维亚共和国","iso_a2":"RS","iso_a3":"SRB","iso_n3":"688"},"geometry":{"type":"Polygon","coordinates":[[[22.705078,44.237793],[22.64209,44.650977],[22.093066,44.541943],[21.360059,44.82666],[21.490234,45.1479],[20.241797,46.108594],[18.905371,45.931738],[19.004688,45.399512],[19.4,45.2125],[19.007129,44.869189],[19.348633,44.880908],[19.118457,44.359961],[19.583789,44.043457],[19.24502,43.965039],[19.495117,43.642871],[19.194336,43.533301],[20.344336,42.82793],[20.800586,43.261084],[21.75293,42.669824],[21.5625,42.24751],[22.344043,42.313965],[22.466797,42.84248],[22.967969,43.142041],[22.369629,43.781299],[22.705078,44.237793]]]}}, - {"type":"Feature","properties":{"name":"塞内加尔","full_name":"塞内加尔共和国","iso_a2":"SN","iso_a3":"SEN","iso_n3":"686"},"geometry":{"type":"Polygon","coordinates":[[[-12.280615,14.809033],[-13.409668,16.05918],[-14.53374,16.655957],[-16.239014,16.531299],[-16.535254,15.838379],[-17.147168,14.922021],[-17.535645,14.755127],[-16.618115,14.040527],[-16.766943,13.904932],[-16.562305,13.587305],[-15.509668,13.58623],[-15.10835,13.812109],[-13.826709,13.407812],[-14.246777,13.23584],[-15.151123,13.556494],[-15.834277,13.156445],[-16.76333,13.06416],[-16.743896,12.585449],[-16.442871,12.609473],[-16.760303,12.525781],[-16.711816,12.354834],[-15.196094,12.679932],[-13.729248,12.673926],[-12.399072,12.340088],[-11.389404,12.404395],[-11.390381,12.941992],[-12.054199,13.633057],[-12.280615,14.809033]]]}}, - {"type":"Feature","properties":{"name":"沙特阿拉伯","full_name":"沙特阿拉伯王国","iso_a2":"SA","iso_a3":"SAU","iso_n3":"682"},"geometry":{"type":"MultiPolygon","coordinates":[[[[41.987695,16.715625],[41.860449,17.002539],[41.801563,16.77876],[42.157812,16.570703],[41.987695,16.715625]]],[[[51.977637,18.996143],[54.977344,19.995947],[55.641016,22.001855],[55.18584,22.704102],[52.555078,22.932812],[51.568359,24.286182],[51.267969,24.607227],[50.804395,24.789258],[50.081055,25.961377],[50.149805,26.662646],[48.797168,27.724316],[48.44248,28.54292],[47.671289,28.533154],[47.433203,28.989551],[46.531445,29.09624],[44.69082,29.202344],[42.074414,31.080371],[40.369336,31.938965],[39.14541,32.124512],[36.958594,31.491504],[37.980078,30.5],[37.469238,29.995068],[36.755273,29.866016],[36.068457,29.200537],[34.950781,29.353516],[34.625,28.064502],[35.180469,28.034863],[37.148828,25.291113],[37.543066,24.29165],[38.46416,23.711865],[39.062012,22.592188],[38.987891,21.881738],[39.276074,20.973975],[39.72832,20.390332],[40.75918,19.755469],[41.229492,18.678418],[42.293945,17.434961],[42.799316,16.371777],[43.165039,16.689404],[43.190918,17.359375],[43.417969,17.51626],[43.916992,17.324707],[46.727637,17.265576],[47.143555,16.94668],[48.172168,18.156934],[49.041992,18.581787],[51.977637,18.996143]]]]}}, - {"type":"Feature","properties":{"name":"圣多美和普林西比","full_name":"圣多美和普林西比民主共和国","iso_a2":"ST","iso_a3":"STP","iso_n3":"678"},"geometry":{"type":"Polygon","coordinates":[[[6.659961,0.120654],[6.686914,0.404395],[6.468164,0.227344],[6.659961,0.120654]]]}}, - {"type":"Feature","properties":{"name":"圣马力诺","full_name":"圣马力诺共和国","iso_a2":"SM","iso_a3":"SMR","iso_n3":"674"},"geometry":{"type":"Polygon","coordinates":[[[12.485254,43.901416],[12.503711,43.989746],[12.396875,43.93457],[12.485254,43.901416]]]}}, - {"type":"Feature","properties":{"name":"萨摩亚","full_name":"萨摩亚独立国","iso_a2":"WS","iso_a3":"WSM","iso_n3":"882"},"geometry":{"type":"Polygon","coordinates":[[[-172.333496,-13.465234],[-172.778516,-13.516797],[-172.224951,-13.804297],[-172.333496,-13.465234]]]}}, - {"type":"Feature","properties":{"name":"圣文森特和格林纳丁斯","full_name":"圣文森特和格林纳丁斯","iso_a2":"VC","iso_a3":"VCT","iso_n3":"670"},"geometry":{"type":"Polygon","coordinates":[[[-61.174512,13.158105],[-61.138965,13.35874],[-61.268457,13.287695],[-61.174512,13.158105]]]}}, - {"type":"Feature","properties":{"name":"圣卢西亚","full_name":"圣卢西亚","iso_a2":"LC","iso_a3":"LCA","iso_n3":"662"},"geometry":{"type":"Polygon","coordinates":[[[-60.895215,13.821973],[-60.908105,14.093359],[-61.073145,13.865576],[-60.895215,13.821973]]]}}, - {"type":"Feature","properties":{"name":"圣基茨和尼维斯","full_name":"圣基茨和尼维斯联邦","iso_a2":"KN","iso_a3":"KNA","iso_n3":"659"},"geometry":{"type":"Polygon","coordinates":[[[-62.630664,17.23999],[-62.827051,17.386426],[-62.838916,17.339258],[-62.630664,17.23999]]]}}, - {"type":"Feature","properties":{"name":"卢旺达","full_name":"卢旺达共和国","iso_a2":"RW","iso_a3":"RWA","iso_n3":"646"},"geometry":{"type":"Polygon","coordinates":[[[29.576953,-1.387891],[28.876367,-2.400293],[29.014355,-2.720215],[29.698047,-2.794727],[29.930176,-2.339551],[30.553613,-2.400098],[30.876562,-2.143359],[30.509961,-1.067285],[29.930078,-1.469922],[29.576953,-1.387891]]]}}, - {"type":"Feature","properties":{"name":"俄罗斯","full_name":"俄罗斯联邦","iso_a2":"RU","iso_a3":"RUS","iso_n3":"643"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-179.798535,68.94043],[-180,68.738672],[-180,68.493896],[-180,68.249121],[-180,68.004395],[-180,67.759619],[-180,67.514844],[-180,67.270117],[-180,67.025342],[-180,66.780566],[-180,66.53584],[-180,66.291064],[-180,66.046289],[-180,65.801562],[-180,65.556787],[-180,65.311963],[-180,65.067236],[-179.3521,65.516748],[-179.683301,66.184131],[-178.526562,66.401562],[-178.939062,66.032764],[-178.4125,65.495557],[-176.093262,65.471045],[-175.395117,64.802393],[-173.729736,64.364502],[-173.32749,64.539551],[-173.275488,64.289648],[-172.903174,64.526074],[-172.401465,64.413916],[-173.085791,64.817334],[-172.213184,65.048145],[-172.783301,65.681055],[-171.105859,65.511035],[-171.421533,65.810352],[-170.666309,65.621533],[-169.777881,66.143115],[-170.604443,66.248926],[-171.795557,66.931738],[-173.00752,67.064893],[-172.520117,66.95249],[-173.258936,66.840088],[-173.679688,67.144775],[-174.550098,67.090625],[-173.773975,66.434668],[-174.065039,66.22959],[-174.084766,66.473096],[-174.394092,66.344238],[-174.924902,66.623145],[-175.345215,67.678076],[-178.373047,68.565674],[-178.055811,68.264893],[-179.798535,68.94043]]],[[[130.687305,42.302539],[130.709375,42.656396],[131.158301,42.626025],[131.938965,43.301953],[131.866602,43.095166],[132.30957,43.313525],[132.303809,42.883301],[133.159961,42.696973],[135.131055,43.525732],[137.685449,45.818359],[140.170605,48.523682],[140.520898,50.800195],[141.485254,52.178516],[140.839648,53.087891],[141.18125,53.015283],[141.37373,53.292773],[139.707422,54.277148],[138.657227,54.29834],[138.450684,53.537012],[138.52793,53.959863],[137.950488,53.603564],[137.253711,53.546143],[137.834766,53.946729],[137.339258,54.100537],[137.666016,54.283301],[137.141602,54.182227],[137.155371,53.82168],[136.718848,53.804102],[136.797266,54.620996],[135.851562,54.583936],[135.2625,54.943311],[137.691504,56.139355],[142.580273,59.240137],[145.55459,59.413525],[146.049512,59.170557],[146.537207,59.456982],[148.72666,59.25791],[148.79707,59.532324],[149.642578,59.77041],[152.260645,59.223584],[151.121094,59.08252],[151.326758,58.875098],[152.817871,58.92627],[153.361133,59.214795],[155.160449,59.190137],[154.971289,59.449609],[154.149805,59.528516],[154.293066,59.83335],[157.08418,61.675684],[160.309375,61.894385],[159.79043,60.956641],[160.378906,61.025488],[160.173633,60.638428],[162.392578,61.662109],[163.085254,61.570557],[163.331738,62.550928],[164.418359,62.704639],[165.417383,62.44707],[164.207227,62.292236],[163.709961,60.916797],[162.068164,60.466406],[158.275195,58.008984],[156.829883,57.779639],[156.848828,57.290186],[155.98252,56.695215],[155.620312,54.864551],[156.847461,51.006592],[158.103516,51.809619],[158.47207,53.032373],[160.025098,53.12959],[160.074414,54.18916],[162.105566,54.752148],[161.723926,55.496143],[162.084961,56.089648],[162.671484,56.490088],[163.038379,56.521875],[162.628125,56.232275],[162.840332,56.065625],[163.335547,56.23252],[163.256543,56.688037],[162.791113,56.875391],[163.225781,57.790381],[162.654297,57.948242],[162.391406,57.717236],[161.960059,58.076904],[163.743848,60.028027],[164.953711,59.843604],[166.352148,60.484814],[166.273047,59.85625],[167.226758,60.406299],[169.226758,60.595947],[170.350977,59.965527],[170.608203,60.434912],[172.856543,61.469189],[177.159473,62.560986],[177.023535,62.777246],[179.120703,62.320361],[179.570508,62.6875],[178.44043,63.605566],[178.381445,64.260889],[177.6875,64.304736],[177.427441,64.763379],[176.140918,64.58584],[176.056543,64.904736],[174.548828,64.683887],[176.061133,64.960889],[177.06875,64.78667],[177.037305,64.999658],[176.341016,65.047314],[176.880859,65.081934],[178.519531,64.602979],[180,65.067236],[180,68.983447],[179.272656,69.259668],[175.921484,69.895312],[173.277441,69.823828],[170.486816,70.107568],[170.160938,69.626562],[170.99541,69.045312],[169.609863,68.786035],[167.856836,69.728223],[166.820312,69.499561],[163.201367,69.714746],[161.536914,69.379541],[161.565625,68.905176],[160.856055,68.53833],[161.309863,68.982275],[160.910742,69.606348],[159.729395,69.870215],[160.006445,70.309668],[159.350684,70.790723],[156.68457,71.09375],[152.508789,70.834473],[151.582422,71.286963],[150.097656,71.226562],[150.599805,71.520117],[148.968164,71.690479],[150.016895,71.895654],[149.501563,72.164307],[147.261816,72.327881],[146.073242,71.80835],[145.188574,71.695801],[145.758594,72.225879],[146.113281,71.944971],[146.831836,72.29541],[144.294922,72.192627],[146.25293,72.442236],[140.808203,72.890967],[141.079297,72.586914],[139.14082,72.329736],[140.187695,72.191309],[139.359277,71.951367],[139.98418,71.491504],[138.23418,71.596338],[137.939648,71.133398],[136.090332,71.61958],[133.688867,71.434229],[132.653906,71.925977],[131.021582,70.746094],[129.761914,71.119531],[128.843262,71.663477],[129.210254,71.916943],[128.911426,71.755322],[127.841406,72.308252],[129.410645,72.166309],[128.418262,72.535156],[129.250391,72.705176],[128.599023,72.895166],[129.100586,73.112354],[127.740332,73.481543],[126.552539,73.334912],[124.541211,73.75127],[123.491113,73.666357],[123.622266,73.193262],[122.260156,72.880566],[119.750391,72.979102],[118.430273,73.246533],[118.870898,73.537891],[115.337695,73.702588],[113.510352,73.50498],[113.664551,72.634521],[113.032813,73.913867],[112.147266,73.708936],[111.550586,74.028516],[110.261426,74.017432],[109.706738,73.74375],[110.868164,73.730713],[109.855273,73.472461],[105.143945,72.777051],[112.924902,75.015039],[113.726172,75.450635],[112.453027,75.830176],[113.567578,75.568408],[113.272656,76.25166],[112.65625,76.053564],[111.39248,76.68667],[106.413574,76.512256],[107.429785,76.926562],[104.202441,77.101807],[106.05957,77.390527],[104.014551,77.73042],[100.989941,76.990479],[101.597754,76.439209],[98.805664,76.480664],[99.540723,75.798584],[98.662012,76.242676],[96.49707,75.891211],[95.65332,75.892188],[95.578711,76.137305],[93.259277,76.098779],[92.89043,75.909961],[94.075195,75.912891],[87.005957,75.169824],[87.041797,74.778857],[85.791016,74.645117],[87.229688,74.363867],[86.001367,74.316016],[87.571191,73.810742],[85.938965,73.456494],[86.677051,73.106787],[85.792578,73.43833],[86.892969,73.887109],[80.583203,73.568457],[80.827051,72.488281],[83.534375,71.683936],[83.15127,71.103613],[83.735938,70.546484],[83.080762,70.093018],[82.869141,70.954834],[82.221191,70.395703],[82.163184,70.598145],[83.233594,71.668164],[81.661621,71.715967],[79.42207,72.380762],[77.471582,72.192139],[78.232422,71.952295],[77.777539,71.836426],[76.871387,72.033008],[76.032422,71.9104],[76.433398,71.55249],[78.942187,70.933789],[75.332031,71.341748],[75.741406,72.29624],[75.152441,72.852734],[74.992188,72.144824],[73.08623,71.444922],[74.343359,70.578711],[73.578125,69.802979],[73.836035,69.143213],[76.000977,69.235059],[77.650684,68.903027],[77.588281,67.751904],[78.922461,67.589111],[77.174414,67.778516],[77.238477,68.46958],[76.10752,68.975732],[74.57959,68.751221],[74.769531,67.766357],[72.321582,66.332129],[70.339453,66.342383],[69.194336,66.578662],[69.217773,66.828613],[70.690723,66.745312],[70.724902,66.519434],[71.539551,66.683105],[71.365234,66.961523],[73.066797,67.766943],[73.591699,68.481885],[72.576758,68.968701],[72.704492,70.963232],[71.867285,71.457373],[72.812109,72.691406],[69.611816,72.981934],[68.269238,71.682812],[66.639648,71.081396],[67.284766,70.738721],[66.89668,69.553809],[67.624121,69.584424],[68.542773,68.96709],[69.140527,68.950635],[68.371191,68.314258],[64.19043,69.534668],[60.909082,69.847119],[60.170605,69.590918],[60.933594,68.986768],[59.725684,68.351611],[59.099023,68.444336],[59.057324,69.006055],[57.126855,68.554004],[55.418066,68.567822],[54.861328,68.201855],[53.260547,68.26748],[53.930859,68.435547],[53.797656,68.907471],[54.491211,68.992334],[53.801953,68.995898],[52.344043,68.608154],[52.39668,68.351709],[51.994727,68.53877],[48.754297,67.895947],[48.833203,67.681494],[47.874707,67.58418],[47.655859,66.975928],[46.492383,66.800195],[44.902148,67.413135],[45.528711,67.757568],[46.69043,67.848828],[45.891992,68.479688],[43.333203,68.673389],[44.204688,68.25376],[44.104395,66.008594],[42.210547,66.519678],[39.816504,65.597949],[40.444922,64.778711],[39.758008,64.577051],[36.882812,65.172363],[36.624219,64.750537],[38.062207,64.091016],[37.442188,63.813379],[35.035352,64.440234],[34.406445,65.395752],[34.691797,65.951855],[31.895313,67.161426],[34.482617,66.550342],[38.653906,66.069043],[40.10332,66.299951],[41.188965,66.826172],[40.966406,67.713477],[35.85791,69.191748],[33.684375,69.310254],[33.141211,69.068701],[33.454297,69.428174],[32.377734,69.479102],[32.176758,69.674023],[33.007812,69.722119],[31.98457,69.953662],[31.546973,69.696924],[30.869727,69.783447],[30.860742,69.538428],[30.180176,69.63584],[28.96582,69.021973],[28.414062,68.90415],[28.685156,68.189795],[29.988086,67.668262],[29.066211,66.891748],[30.102734,65.72627],[29.604199,64.968408],[30.51377,64.2],[29.991504,63.735156],[31.533984,62.8854],[27.797656,60.536133],[28.512793,60.677295],[29.069141,60.191455],[30.172656,59.957129],[28.058008,59.781543],[28.0125,59.484277],[27.43418,58.787256],[27.778516,57.870703],[27.351953,57.528125],[27.828613,57.293311],[27.639453,56.845654],[28.147949,56.14292],[29.375,55.938721],[29.482227,55.68457],[30.233594,55.845215],[30.906836,55.57002],[30.798828,54.783252],[31.754199,53.810449],[32.706445,53.419434],[32.141992,53.091162],[31.417871,53.196045],[31.258789,53.016699],[31.763379,52.101074],[33.735254,52.344775],[34.397852,51.78042],[34.213867,51.255371],[35.311914,51.043896],[35.591113,50.36875],[36.619434,50.209229],[37.422852,50.411475],[38.046875,49.92002],[38.258594,50.052344],[40.080664,49.576855],[40.108789,49.251562],[39.686523,49.00791],[40.003613,48.82207],[39.644727,48.591211],[39.95791,48.268896],[39.778711,47.887549],[38.368848,47.609961],[38.214355,47.091455],[39.02373,47.272217],[39.293457,47.105762],[38.500977,46.663672],[37.766504,46.636133],[38.492285,46.090527],[37.933105,46.001709],[37.647168,45.377197],[36.865918,45.427051],[36.627637,45.151318],[38.717285,44.288086],[39.97832,43.419824],[40.648047,43.533887],[41.580566,43.219238],[42.760645,43.16958],[43.825977,42.571533],[44.870996,42.756396],[45.705273,42.498096],[45.638574,42.205078],[46.429883,41.890967],[47.791016,41.199268],[48.572852,41.844482],[47.463184,43.035059],[47.646484,43.884619],[47.462793,43.555029],[47.307031,44.103125],[46.707227,44.50332],[47.463281,45.679688],[48.72959,45.896826],[49.232227,46.337158],[48.541211,46.605615],[48.959375,46.774609],[48.166992,47.708789],[47.292383,47.740918],[46.660938,48.412256],[47.031348,49.150293],[46.889551,49.696973],[47.429199,50.357959],[48.334961,49.858252],[48.758984,49.92832],[48.625098,50.612695],[50.793945,51.729199],[51.344531,51.475342],[52.219141,51.709375],[53.338086,51.482373],[54.555273,50.535791],[54.641602,51.011572],[55.68623,50.582861],[56.491406,51.019531],[57.442188,50.888867],[57.838867,51.09165],[59.523047,50.492871],[60.058594,50.850293],[60.942285,50.695508],[61.389453,50.861035],[61.554688,51.324609],[60.030273,51.933252],[60.994531,52.336865],[60.774414,52.675781],[61.047461,52.972461],[62.082715,53.00542],[61.199219,53.287158],[61.534961,53.523291],[60.979492,53.621729],[61.231055,54.019482],[65.088379,54.340186],[65.476953,54.623291],[68.155859,54.976709],[68.977246,55.3896],[70.182422,55.162451],[70.738086,55.305176],[71.093164,54.212207],[72.186035,54.325635],[72.446777,53.941846],[72.622266,54.134326],[73.712402,54.042383],[73.406934,53.447559],[73.858984,53.619727],[74.351562,53.487646],[76.837305,54.442383],[76.484766,54.022559],[77.859961,53.269189],[79.98623,50.774561],[80.735254,51.293408],[81.465918,50.739844],[82.493945,50.727588],[83.357324,50.99458],[84.323242,50.23916],[84.989453,50.061426],[85.232617,49.61582],[86.180859,49.499316],[86.675488,49.777295],[87.322852,49.085791],[87.814258,49.162305],[88.192578,49.451709],[89.395605,49.611523],[90.053711,50.09375],[92.354785,50.86416],[94.251074,50.556396],[94.614746,50.02373],[97.359766,49.741455],[98.250293,50.302441],[97.835742,51.05166],[98.893164,52.117285],[102.111523,51.353467],[102.288379,50.585107],[103.304395,50.200293],[105.383594,50.47373],[106.711133,50.312598],[107.233301,49.989404],[107.916602,49.947803],[108.613672,49.322803],[110.709766,49.142969],[112.806445,49.523584],[114.29707,50.274414],[115.429199,49.896484],[116.216797,50.009277],[116.683301,49.823779],[117.873438,49.513477],[119.259863,50.066406],[119.163672,50.406006],[120.749805,52.096533],[120.656152,52.56665],[120.094531,52.787207],[120.985449,53.28457],[123.607813,53.546533],[125.649023,53.042285],[127.590234,50.208984],[127.550781,49.801807],[129.498145,49.388818],[130.553125,48.861182],[130.961914,47.709326],[132.47627,47.71499],[133.144043,48.105664],[134.293359,48.373438],[135.083406,48.436324],[134.665234,48.253906],[134.752344,47.71543],[134.167676,47.302197],[133.113477,45.130713],[131.851855,45.326855],[130.981641,44.844336],[131.257324,43.378076],[131.068555,42.902246],[130.424805,42.727051],[130.526953,42.5354],[130.687305,42.302539]]],[[[47.441992,80.853662],[44.90498,80.611279],[46.141406,80.446729],[47.705273,80.765186],[48.683594,80.633252],[47.441992,80.853662]]],[[[50.278125,80.927246],[49.087793,80.515771],[46.644434,80.300342],[47.737305,80.081689],[51.703613,80.687646],[50.278125,80.927246]]],[[[67.765332,76.237598],[68.941699,76.707666],[67.651855,77.011572],[64.463477,76.378174],[61.20166,76.282031],[58.88125,75.854785],[57.606836,75.34126],[55.810059,75.124902],[56.49873,74.95708],[55.582227,74.627686],[56.137109,74.496094],[53.762891,73.766162],[54.299902,73.350977],[56.963867,73.366553],[58.534668,74.498926],[59.674023,74.610156],[61.355957,75.314844],[67.765332,76.237598]]],[[[55.319824,73.308301],[53.251172,73.182959],[51.511328,71.648096],[53.411621,71.530127],[54.155664,71.125488],[53.383594,70.873535],[57.145898,70.589111],[57.625391,70.728809],[55.297852,71.935352],[56.42959,73.201172],[55.319824,73.308301]]],[[[96.526563,81.075586],[95.15957,81.270996],[92.710352,80.872168],[91.523828,80.358545],[93.654688,80.009619],[97.298438,80.272754],[97.869922,80.763281],[96.526563,81.075586]]],[[[97.674512,80.158252],[94.987305,80.096826],[93.070801,79.495312],[95.02041,79.052686],[98.411133,78.787793],[99.929297,78.961426],[99.041797,79.293018],[100.06123,79.7771],[98.596484,80.052197],[97.65166,79.760645],[97.674512,80.158252]]],[[[102.884766,79.253955],[101.590625,79.350439],[99.500293,77.976074],[105.312598,78.499902],[103.800781,79.149268],[102.412305,78.835449],[102.884766,79.253955]]],[[[140.04873,75.828955],[138.919531,76.196729],[138.207617,76.114941],[136.947656,75.325537],[139.099121,74.656543],[139.68125,74.964062],[142.472754,74.82041],[143.12793,74.970312],[142.30791,75.691699],[144.019727,75.044678],[145.359961,75.530469],[141.485449,76.137158],[140.815918,75.630713],[140.04873,75.828955]]],[[[146.795215,75.370752],[146.5375,75.581787],[146.148535,75.198291],[148.296875,74.800439],[150.646289,74.94458],[150.822363,75.156543],[146.795215,75.370752]]],[[[178.861523,70.826416],[180,70.993018],[180,71.537744],[178.683887,71.105664],[178.861523,70.826416]]],[[[142.761035,54.393945],[142.334961,54.280713],[142.705957,53.895703],[142.526172,53.447461],[141.823535,53.339502],[141.66084,52.272949],[142.206738,51.222559],[141.866309,48.750098],[142.181738,48.013379],[141.830371,46.451074],[142.077148,45.917041],[142.578027,46.700781],[143.282324,46.558984],[143.431641,46.028662],[143.580664,46.360693],[142.556934,47.737891],[143.10498,49.198828],[144.04873,49.24917],[144.71377,48.640283],[143.299512,51.632373],[143.324707,52.963086],[142.761035,54.393945]]],[[[-178.876465,71.577051],[-179.999951,71.537744],[-179.999951,70.993018],[-177.523584,71.166895],[-178.876465,71.577051]]],[[[52.90332,71.36499],[52.249609,71.284912],[53.022656,70.968701],[52.90332,71.36499]]],[[[96.285449,77.02666],[96.528418,77.205518],[95.270312,77.018848],[96.285449,77.02666]]],[[[74.660547,72.873438],[74.961523,73.0625],[74.198535,73.109082],[74.660547,72.873438]]],[[[58.622363,81.04165],[57.210938,81.01709],[58.285645,80.764893],[58.622363,81.04165]]],[[[50.265234,69.185596],[48.844922,69.494727],[48.439062,68.804883],[49.62627,68.859717],[50.265234,69.185596]]],[[[63.373828,80.700098],[65.437402,80.930713],[64.802051,81.197266],[62.592578,80.853027],[63.373828,80.700098]]],[[[57.95625,80.123242],[59.255469,80.343213],[57.075,80.493945],[57.95625,80.123242]]],[[[62.167773,80.834766],[59.592285,80.816504],[59.649805,80.43125],[61.05127,80.418604],[62.167773,80.834766]]],[[[61.14082,80.950342],[61.457422,81.103955],[60.07832,80.99917],[61.14082,80.950342]]],[[[53.521387,80.185205],[52.853906,80.402393],[52.343555,80.213232],[53.521387,80.185205]]],[[[57.078711,80.350928],[55.811621,80.087158],[56.986914,80.071484],[57.078711,80.350928]]],[[[57.810254,81.546045],[55.716699,81.188477],[57.769727,81.169727],[57.810254,81.546045]]],[[[54.718945,81.115967],[54.668164,80.738672],[57.580371,80.755469],[54.718945,81.115967]]],[[[92.683496,79.685205],[93.803125,79.904541],[91.229297,80.030713],[92.683496,79.685205]]],[[[141.010254,73.999463],[141.038574,74.242725],[140.193555,74.236719],[140.409473,73.92168],[141.010254,73.999463]]],[[[142.184863,73.895898],[141.084766,73.865869],[139.785547,73.355225],[143.451465,73.231299],[142.184863,73.895898]]],[[[137.940527,55.092627],[137.577344,55.197021],[137.23291,54.790576],[137.721484,54.663232],[138.206152,55.033545],[137.940527,55.092627]]],[[[169.200781,69.580469],[169.374805,69.882617],[167.864746,69.901074],[169.200781,69.580469]]],[[[163.635156,58.603369],[164.615723,58.885596],[164.572656,59.221143],[163.760938,59.015039],[163.635156,58.603369]]],[[[166.650293,54.839062],[166.275781,55.311963],[165.751074,55.294531],[166.650293,54.839062]]],[[[155.921094,50.302197],[156.096875,50.771875],[155.243066,50.094629],[155.921094,50.302197]]],[[[152.002051,46.897168],[152.288867,47.142188],[151.723438,46.828809],[152.002051,46.897168]]],[[[149.687695,45.642041],[150.553125,46.208545],[149.44707,45.593359],[149.687695,45.642041]]],[[[148.599512,45.317627],[148.812207,45.51001],[148.324219,45.282422],[147.924023,45.383301],[146.897461,44.404297],[148.599512,45.317627]]],[[[146.207617,44.497656],[145.461719,43.870898],[145.555859,43.6646],[146.567773,44.44043],[146.207617,44.497656]]],[[[113.387207,74.400439],[112.084473,74.548975],[111.503418,74.353076],[112.782422,74.095068],[113.387207,74.400439]]],[[[70.673926,73.09502],[71.626172,73.173975],[70.940234,73.514404],[69.995898,73.359375],[70.040723,73.037158],[70.673926,73.09502]]],[[[77.63252,72.29126],[78.365137,72.482422],[77.748535,72.631201],[76.871094,72.317041],[77.63252,72.29126]]],[[[79.501465,72.721924],[79.164258,73.094336],[78.633203,72.850732],[79.501465,72.721924]]],[[[60.450488,69.934863],[59.048047,70.460498],[58.519922,70.318311],[59.637012,69.721045],[60.440234,69.725928],[60.450488,69.934863]]],[[[20.957813,55.278906],[20.899805,55.28667],[19.604395,54.45918],[22.766211,54.356787],[22.567285,55.059131],[21.235742,55.264111],[20.995898,54.902686],[20.594824,54.982373],[20.957813,55.278906]]],[[[33.594141,46.09624],[32.508008,45.403809],[33.555176,45.097656],[33.450684,44.553662],[33.755664,44.398926],[35.472559,45.098486],[36.393359,45.065381],[36.575,45.393555],[35.45752,45.316309],[35.001674,45.733383],[33.806667,46.208288],[33.594141,46.09624]]]]}}, - {"type":"Feature","properties":{"name":"罗马尼亚","full_name":"罗马尼亚","iso_a2":"RO","iso_a3":"ROU","iso_n3":"642"},"geometry":{"type":"Polygon","coordinates":[[[28.2125,45.450439],[28.071777,46.978418],[26.618945,48.259863],[24.979102,47.724121],[23.202637,48.084521],[22.87666,47.947266],[21.999707,47.505029],[21.12168,46.282422],[20.241797,46.108594],[21.490234,45.1479],[21.360059,44.82666],[22.093066,44.541943],[22.64209,44.650977],[22.705078,44.237793],[23.028516,44.077979],[22.919043,43.834473],[25.49707,43.670801],[27.086914,44.167383],[28.585352,43.742236],[28.891504,44.918652],[29.55752,44.843408],[29.705859,45.259912],[28.2125,45.450439]]]}}, - {"type":"Feature","properties":{"name":"卡塔尔","full_name":"卡塔尔国","iso_a2":"QA","iso_a3":"QAT","iso_n3":"634"},"geometry":{"type":"Polygon","coordinates":[[[51.267969,24.607227],[51.608887,25.052881],[51.543066,25.902393],[51.262305,26.153271],[50.762891,25.444727],[50.804395,24.789258],[51.267969,24.607227]]]}}, - {"type":"Feature","properties":{"name":"葡萄牙","full_name":"葡萄牙共和国","iso_a2":"PT","iso_a3":"PRT","iso_n3":"620"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-17.190869,32.868604],[-17.018262,32.662793],[-16.693262,32.758008],[-17.190869,32.868604]]],[[[-8.777148,41.941064],[-8.684619,40.752539],[-9.479736,38.798779],[-9.356738,38.6979],[-9.135791,38.742773],[-8.791602,39.078174],[-9.021484,38.746875],[-9.250391,38.656738],[-9.213281,38.448096],[-8.798877,38.518164],[-8.668311,38.424316],[-8.881104,38.44668],[-8.81416,37.430811],[-8.997803,37.032275],[-7.406152,37.179443],[-7.443945,37.728271],[-6.957568,38.187891],[-7.343018,38.457422],[-6.997949,39.056445],[-7.535693,39.661572],[-6.975391,39.798389],[-6.928467,41.009131],[-6.2125,41.532031],[-6.618262,41.942383],[-8.15249,41.811963],[-8.266064,42.137402],[-8.777148,41.941064]]]]}}, - {"type":"Feature","properties":{"name":"波兰","full_name":"波兰共和国","iso_a2":"PL","iso_a3":"POL","iso_n3":"616"},"geometry":{"type":"Polygon","coordinates":[[[23.605273,51.51792],[23.652441,52.040381],[23.175098,52.286621],[23.91543,52.770264],[23.484668,53.939795],[22.766211,54.356787],[19.604395,54.45918],[18.836426,54.36958],[18.43623,54.744727],[18.759277,54.68457],[18.323438,54.838184],[17.842969,54.816699],[14.211426,53.950342],[14.213672,53.870752],[14.583496,53.639355],[14.258887,53.729639],[14.128613,52.878223],[14.619434,52.528516],[15.016602,51.252734],[14.809375,50.858984],[14.99375,51.014355],[16.282227,50.655615],[16.63916,50.102148],[16.880078,50.427051],[17.702246,50.307178],[17.627051,50.116406],[18.562402,49.879346],[18.832227,49.510791],[19.149414,49.4],[19.441602,49.597705],[20.057617,49.181299],[20.362988,49.385254],[21.639648,49.411963],[22.538672,49.072705],[22.852051,49.062744],[22.706152,49.606201],[24.089941,50.530469],[23.605273,51.51792]]]}}, - {"type":"Feature","properties":{"name":"菲律宾","full_name":"菲律宾共和国","iso_a2":"PH","iso_a3":"PHL","iso_n3":"608"},"geometry":{"type":"MultiPolygon","coordinates":[[[[121.101562,18.615283],[120.599707,18.507861],[120.36875,16.10957],[119.772559,16.255127],[120.082129,14.851074],[120.43877,14.453369],[120.583691,14.88125],[120.941309,14.645068],[120.637109,13.804492],[121.344141,13.649121],[121.77793,13.937646],[122.599902,13.194141],[122.595215,13.907617],[123.310938,13.044092],[123.948535,12.916406],[124.059766,12.56709],[124.142773,13.035791],[123.785156,13.110547],[123.549609,13.645752],[123.815723,13.837109],[123.320312,14.06167],[123.101953,13.750244],[122.627148,14.317529],[122.199707,14.148047],[122.211719,13.930176],[121.766602,14.168066],[121.392285,15.324414],[121.595313,15.933252],[122.135156,16.184814],[122.519141,17.124854],[122.152344,17.664404],[122.265527,18.458838],[121.845605,18.29541],[121.101562,18.615283]]],[[[117.311133,8.4396],[119.686914,10.500342],[119.55332,11.313525],[119.223828,10.477295],[117.349902,8.713574],[117.311133,8.4396]]],[[[122.496191,11.615088],[121.916016,11.854346],[122.103516,11.64292],[121.954004,10.444385],[122.769922,10.823828],[123.119531,11.286816],[123.158301,11.535547],[122.496191,11.615088]]],[[[123.130859,9.064111],[123.320508,9.272949],[123.162012,9.864258],[123.567578,10.780762],[123.256641,10.993945],[122.983301,10.886621],[122.855566,10.086914],[122.399512,9.823047],[123.130859,9.064111]]],[[[124.574609,11.343066],[124.330664,11.535205],[124.445508,10.923584],[124.786719,10.781396],[124.780762,10.168066],[125.026563,10.033105],[124.9875,10.367578],[125.268457,10.307715],[125.026563,11.211719],[124.574609,11.343066]]],[[[125.239551,12.527881],[124.294727,12.569336],[124.445703,12.152783],[124.99502,11.764941],[125.034277,11.34126],[125.735645,11.049609],[125.535645,12.191406],[125.239551,12.527881]]],[[[120.704395,13.479492],[120.338477,13.412354],[121.236719,12.218799],[121.540625,12.638184],[121.522754,13.131201],[121.202734,13.432324],[120.704395,13.479492]]],[[[126.005957,9.320947],[125.471289,9.756787],[125.49873,9.014746],[124.868945,8.972266],[124.731152,8.562988],[124.404883,8.599854],[123.799414,8.049121],[123.849219,8.432715],[123.43457,8.70332],[122.911133,8.156445],[122.243359,7.945117],[121.964258,6.968213],[122.616211,7.763135],[123.390918,7.40752],[123.66582,7.817773],[124.206641,7.396436],[123.985254,6.993701],[124.078125,6.404443],[124.927344,5.875342],[125.231543,6.069531],[125.346484,5.598975],[125.667969,5.978662],[125.380664,6.689941],[125.689258,7.263037],[125.824414,7.333301],[126.189355,6.309668],[126.19209,6.852539],[126.581543,7.247754],[126.458691,8.202832],[126.139551,8.595654],[126.30459,8.952051],[126.005957,9.320947]]],[[[123.370313,9.449609],[124.00498,10.400098],[124.038867,11.273535],[123.38623,9.96709],[123.370313,9.449609]]],[[[121.159375,6.075635],[120.876367,5.952637],[121.411035,5.939844],[121.159375,6.075635]]],[[[120.1,12.167676],[119.885742,12.299854],[120.010547,12.008252],[120.314551,12.012402],[120.1,12.167676]]],[[[124.593848,9.787207],[124.335742,10.159912],[123.817187,9.817383],[124.122461,9.599316],[124.593848,9.787207]]],[[[122.092871,6.42832],[122.323535,6.602246],[121.832031,6.664062],[122.092871,6.42832]]],[[[125.690234,9.914453],[125.666797,10.440137],[125.494824,10.118701],[125.690234,9.914453]]],[[[121.914844,13.540332],[122.004883,13.20498],[122.114551,13.463184],[121.914844,13.540332]]],[[[122.094043,12.354883],[122.14502,12.652637],[122.013965,12.105615],[122.094043,12.354883]]],[[[123.281836,12.853418],[122.95752,13.107178],[123.367188,12.70083],[123.281836,12.853418]]],[[[123.716602,12.287354],[123.236426,12.583496],[123.157813,11.925635],[123.47373,12.21665],[124.045508,11.752441],[123.716602,12.287354]]],[[[124.353613,13.632227],[124.224902,14.077588],[124.038867,13.663135],[124.353613,13.632227]]],[[[122.033496,15.005029],[121.839844,15.038135],[121.933008,14.656055],[122.033496,15.005029]]]]}}, - {"type":"Feature","properties":{"name":"秘鲁","full_name":"秘鲁共和国","iso_a2":"PE","iso_a3":"PER","iso_n3":"604"},"geometry":{"type":"Polygon","coordinates":[[[-69.965918,-4.235938],[-70.339502,-3.814355],[-70.735107,-3.781543],[-70.09585,-2.658203],[-70.968555,-2.206836],[-72.941113,-2.394043],[-73.664307,-1.248828],[-74.246387,-0.970605],[-74.801758,-0.200098],[-75.284473,-0.106543],[-75.62627,-0.122852],[-75.259375,-0.590137],[-75.570557,-1.53125],[-76.679102,-2.562598],[-77.860596,-2.981641],[-78.158496,-3.465137],[-78.345361,-3.397363],[-78.686035,-4.562402],[-79.033301,-4.969141],[-79.330957,-4.927832],[-79.638525,-4.454883],[-80.478564,-4.430078],[-80.490137,-4.010059],[-80.179248,-3.877734],[-80.324658,-3.387891],[-81.283203,-4.322266],[-81.336621,-4.669531],[-80.881934,-5.635059],[-81.142041,-6.056738],[-79.994971,-6.768945],[-78.762256,-8.616992],[-77.633203,-11.287793],[-76.223633,-13.371191],[-76.289014,-14.133105],[-75.104248,-15.411914],[-72.467676,-16.708105],[-70.418262,-18.345605],[-69.926367,-18.206055],[-69.8521,-17.703809],[-69.510938,-17.506055],[-69.624854,-17.200195],[-68.842773,-16.337891],[-69.217578,-16.149121],[-69.420898,-15.640625],[-69.172461,-15.236621],[-69.359473,-14.795312],[-68.870898,-14.169727],[-69.074121,-13.682813],[-68.978613,-12.880078],[-68.685254,-12.501953],[-69.578613,-10.951758],[-70.642334,-11.010254],[-70.541113,-9.4375],[-71.237939,-9.966016],[-72.142969,-10.005176],[-72.379053,-9.510156],[-73.209424,-9.411426],[-72.974023,-8.993164],[-74.002051,-7.556055],[-73.72041,-7.309277],[-73.758105,-6.905762],[-73.137354,-6.46582],[-73.235547,-6.098438],[-72.887061,-5.122754],[-70.799512,-4.17334],[-69.965918,-4.235938]]]}}, - {"type":"Feature","properties":{"name":"巴拉圭","full_name":"巴拉圭共和国","iso_a2":"PY","iso_a3":"PRY","iso_n3":"600"},"geometry":{"type":"Polygon","coordinates":[[[-58.159766,-20.164648],[-58.180176,-19.817871],[-59.090527,-19.28623],[-61.756836,-19.645312],[-62.276318,-20.5625],[-62.650977,-22.233691],[-61.03291,-23.755664],[-59.89248,-24.093555],[-57.643896,-25.328418],[-58.604834,-27.314355],[-56.437158,-27.553809],[-56.164062,-27.321484],[-55.714648,-27.414844],[-54.825488,-26.652246],[-54.615869,-25.576074],[-54.241797,-24.047266],[-54.625488,-23.8125],[-55.415918,-23.951367],[-55.84917,-22.307617],[-56.447803,-22.076172],[-56.937256,-22.271289],[-57.955908,-22.10918],[-57.830225,-20.997949],[-58.159766,-20.164648]]]}}, - {"type":"Feature","properties":{"name":"巴布亚新几内亚","full_name":"巴布亚新几内亚独立国","iso_a2":"PG","iso_a3":"PNG","iso_n3":"598"},"geometry":{"type":"MultiPolygon","coordinates":[[[[152.96582,-4.756348],[153.016797,-4.105664],[152.03291,-3.251367],[150.825391,-2.572949],[150.746094,-2.738867],[152.279395,-3.582422],[152.96582,-4.756348]]],[[[151.915625,-4.296777],[151.593066,-4.200781],[151.671191,-4.883301],[150.900293,-5.447168],[150.183105,-5.523633],[150.090039,-5.011816],[149.831445,-5.524121],[148.432031,-5.471777],[148.337207,-5.669434],[149.652539,-6.29043],[150.473535,-6.263379],[151.515137,-5.552344],[152.077051,-5.458301],[151.983691,-5.074414],[152.351172,-4.822168],[152.405664,-4.340723],[151.915625,-4.296777]]],[[[140.976172,-9.11875],[142.647168,-9.327832],[143.366211,-8.961035],[143.111816,-8.474512],[142.206836,-8.195801],[143.61377,-8.200391],[143.518164,-8.000684],[143.942285,-7.944238],[143.654883,-7.460352],[144.142871,-7.757227],[144.509863,-7.567383],[146.033203,-8.076367],[147.768652,-10.070117],[149.754102,-10.353027],[150.319922,-10.654883],[150.647168,-10.517969],[150.446094,-10.307324],[150.849512,-10.236035],[149.874414,-10.012988],[149.76123,-9.805859],[150.011035,-9.688184],[149.263184,-9.497852],[149.19834,-9.03125],[148.583105,-9.051758],[148.126758,-8.103613],[147.190039,-7.378125],[146.953613,-6.834082],[147.845508,-6.662402],[147.566699,-6.056934],[145.745215,-5.402441],[145.766992,-4.823047],[144.477734,-3.825293],[140.973438,-2.609766],[140.976172,-9.11875]]],[[[151.080957,-10.020117],[150.776074,-9.709082],[151.230859,-10.194727],[151.296484,-9.956738],[151.080957,-10.020117]]],[[[150.34541,-9.493848],[150.208301,-9.206348],[150.109766,-9.361914],[150.34541,-9.493848]]],[[[147.067578,-1.960156],[146.65625,-1.974023],[146.546484,-2.208594],[147.438086,-2.058984],[147.067578,-1.960156]]],[[[150.436621,-2.661816],[150.227148,-2.38418],[149.961621,-2.473828],[150.436621,-2.661816]]],[[[150.528418,-9.346582],[150.43623,-9.624609],[150.894043,-9.66748],[150.528418,-9.346582]]],[[[153.536133,-11.476172],[153.203613,-11.324121],[153.759863,-11.586328],[153.536133,-11.476172]]],[[[155.957617,-6.686816],[154.729297,-5.444434],[154.759277,-5.931348],[155.344043,-6.72168],[155.719336,-6.862793],[155.957617,-6.686816]]]]}}, - {"type":"Feature","properties":{"name":"巴拿马","full_name":"巴拿马共和国","iso_a2":"PA","iso_a3":"PAN","iso_n3":"591"},"geometry":{"type":"Polygon","coordinates":[[[-77.374219,8.658301],[-78.082764,9.236279],[-79.577295,9.597852],[-81.354785,8.780566],[-81.894482,9.14043],[-81.780225,8.957227],[-82.244189,9.031494],[-82.563574,9.57666],[-82.939844,9.44917],[-82.727832,8.916064],[-83.027344,8.337744],[-82.879346,8.070654],[-82.781152,8.303516],[-82.235449,8.311035],[-81.727637,8.137549],[-81.268408,7.625488],[-81.063867,7.899756],[-80.845557,7.220068],[-80.438867,7.274951],[-80.01123,7.500049],[-80.458984,8.213867],[-79.50708,8.970068],[-79.086377,8.997168],[-78.409863,8.355322],[-78.099463,8.496973],[-77.760547,8.133252],[-78.141895,8.386084],[-78.421582,8.060986],[-77.901172,7.229346],[-77.761914,7.698828],[-77.538281,7.56626],[-77.195996,7.972461],[-77.374219,8.658301]]]}}, - {"type":"Feature","properties":{"name":"帕劳","full_name":"帕劳共和国","iso_a2":"PW","iso_a3":"PLW","iso_n3":"585"},"geometry":{"type":"Polygon","coordinates":[[[134.59541,7.382031],[134.651172,7.712109],[134.515723,7.525781],[134.59541,7.382031]]]}}, - {"type":"Feature","properties":{"name":"巴基斯坦","full_name":"巴基斯坦伊斯兰共和国","iso_a2":"PK","iso_a3":"PAK","iso_n3":"586"},"geometry":{"type":"Polygon","coordinates":[[[76.766895,35.661719],[75.912305,36.048975],[75.772168,36.694922],[74.541406,37.022168],[72.249805,36.734717],[71.23291,36.121777],[71.620508,35.183008],[70.965625,34.530371],[71.051563,34.049707],[69.889648,34.007275],[70.261133,33.289014],[69.501562,33.020068],[69.279297,31.936816],[68.868945,31.634229],[68.161035,31.802979],[67.452832,31.234619],[66.829297,31.263672],[66.346875,30.802783],[66.23125,29.865723],[64.09873,29.391943],[62.476562,29.40835],[60.843359,29.858691],[61.889844,28.546533],[62.758008,28.243555],[62.762988,27.250195],[63.301563,27.151465],[63.157812,26.649756],[61.842383,26.225928],[61.587891,25.202344],[64.059375,25.40293],[64.658984,25.184082],[66.467676,25.445312],[66.131152,25.493262],[66.324219,25.601807],[66.698633,25.226318],[66.703027,24.860938],[67.171484,24.756104],[67.563086,23.881836],[68.165039,23.857324],[68.724121,23.964697],[68.781152,24.313721],[69.805176,24.165234],[71.044043,24.400098],[70.648438,25.666943],[70.100195,25.910059],[70.147656,26.506445],[69.506934,26.742676],[69.537012,27.122949],[70.403711,28.025049],[70.797949,27.709619],[71.870313,27.9625],[72.341895,28.751904],[72.90332,29.02876],[73.381641,29.934375],[73.80918,30.093359],[74.632812,31.034668],[74.555566,31.818555],[75.333496,32.279199],[74.685742,32.493799],[74.663281,32.757666],[74.35459,32.768701],[74.003809,33.189453],[73.976465,33.721289],[74.250879,33.946094],[73.904102,34.075684],[73.96123,34.653467],[75.70918,34.503076],[77.048633,35.109912],[76.766895,35.661719]]]}}, - {"type":"Feature","properties":{"name":"阿曼","full_name":"阿曼苏丹国","iso_a2":"OM","iso_a3":"OMN","iso_n3":"512"},"geometry":{"type":"MultiPolygon","coordinates":[[[[53.085645,16.648389],[54.068164,17.005518],[55.06416,17.038916],[55.479102,17.843262],[56.383496,17.987988],[56.825977,18.753516],[57.811621,19.01709],[57.861816,20.244141],[58.169434,20.589502],[58.474219,20.406885],[59.8,22.219922],[59.823242,22.508984],[59.429395,22.66084],[58.773047,23.517188],[57.123047,23.980713],[56.387988,24.979199],[56.063867,24.73877],[56.000586,24.953223],[55.795703,24.868115],[55.76084,24.242676],[55.985156,24.063379],[55.468457,23.941113],[55.18584,22.704102],[55.641016,22.001855],[54.977344,19.995947],[51.977637,18.996143],[53.085645,16.648389]]],[[[56.297852,25.650684],[56.413086,26.351172],[56.080469,26.062646],[56.297852,25.650684]]]]}}, - {"type":"Feature","properties":{"name":"挪威","full_name":"挪威王国","iso_a2":"NO","iso_a3":"NOR","iso_n3":"578"},"geometry":{"type":"MultiPolygon","coordinates":[[[[20.622168,69.036865],[21.59375,69.273584],[22.410938,68.719873],[23.854004,68.805908],[24.941406,68.593262],[26.072461,69.691553],[27.747852,70.064844],[29.141602,69.671436],[28.96582,69.021973],[30.180176,69.63584],[30.860742,69.538428],[30.869727,69.783447],[29.79209,69.727881],[28.804297,70.092529],[30.944141,70.274414],[30.065137,70.702979],[28.831543,70.863965],[28.192969,70.248584],[28.392285,70.975293],[27.59707,71.091309],[26.989355,70.511377],[26.585059,70.41001],[26.661328,70.939746],[25.043848,70.109033],[25.768164,70.853174],[24.658008,71.001025],[23.353906,69.983398],[22.68457,70.374756],[21.355762,70.233398],[21.974707,69.83457],[20.62207,69.913916],[20.739453,69.520508],[20.054492,69.332666],[20.324219,69.945312],[19.641504,69.424023],[19.722461,69.781641],[19.197266,69.747852],[18.915918,69.335596],[18.259766,69.470605],[18.101465,69.156299],[16.514355,68.532568],[17.552832,68.42627],[16.203809,68.316748],[16.312305,67.881445],[16.00791,68.228711],[14.798926,67.809326],[15.594434,67.348535],[14.961914,67.574268],[14.441699,67.271387],[15.415723,67.202441],[14.108789,67.119238],[13.211426,66.64082],[13.118848,66.230664],[14.03418,66.297559],[12.783789,66.100439],[12.133887,65.27915],[12.915527,65.339258],[11.489355,64.97583],[9.567285,63.706152],[10.055078,63.512695],[11.306641,64.048877],[11.370703,63.804834],[10.020996,63.39082],[9.696875,63.624561],[9.156055,63.459326],[8.576172,63.601172],[8.158008,63.161523],[8.623145,62.84624],[7.571875,63.099512],[6.734961,62.720703],[8.045508,62.77124],[7.653125,62.564014],[6.35293,62.611133],[6.136133,62.407471],[6.580078,62.407275],[5.143164,62.159912],[5.266895,61.935596],[6.730762,61.869775],[4.930078,61.87832],[5.106738,61.187549],[7.173535,61.165967],[7.442578,61.434619],[7.604492,61.210547],[7.038672,60.95293],[6.777832,61.142432],[5.008594,61.038184],[5.11582,60.635986],[5.64834,60.687988],[5.137109,60.445605],[5.688574,60.123193],[5.205664,60.087939],[5.145801,59.638818],[6.15332,60.34624],[6.995703,60.511963],[5.730469,59.863086],[6.216602,59.818359],[5.242188,59.564307],[5.173242,59.162549],[6.415332,59.547119],[5.88916,59.097949],[6.363281,59.000928],[5.555566,58.975195],[5.706836,58.523633],[7.004883,58.024219],[8.166113,58.145312],[9.557227,59.112695],[10.179395,59.009277],[10.595312,59.764551],[10.834473,59.183936],[11.388281,59.036523],[11.642773,58.926074],[11.680762,59.592285],[12.486133,60.106787],[12.294141,61.002686],[12.880762,61.352295],[12.155371,61.720752],[12.175195,63.595947],[12.792773,64],[14.141211,64.173535],[13.650293,64.581543],[14.479688,65.301465],[14.543262,66.129346],[15.483789,66.305957],[16.783594,67.89502],[17.324609,68.103809],[17.916699,67.964893],[18.303027,68.55542],[19.969824,68.356396],[20.116699,69.020898],[20.622168,69.036865]]],[[[23.440527,70.815771],[21.994531,70.657129],[22.829102,70.541553],[23.440527,70.815771]]],[[[15.207129,68.943115],[14.404688,68.663232],[15.22207,68.616309],[15.207129,68.943115]]],[[[19.255078,70.066406],[19.132715,70.244141],[18.129883,69.557861],[19.334766,69.820264],[19.255078,70.066406]]],[[[17.503027,69.59624],[17.08252,69.013672],[17.950684,69.198145],[17.503027,69.59624]]],[[[15.760352,68.56123],[16.048047,69.302051],[15.412598,68.61582],[14.25752,68.190771],[15.975293,68.40249],[16.328906,68.876318],[15.760352,68.56123]]],[[[32.525977,80.119141],[33.629297,80.217432],[31.481934,80.10791],[32.525977,80.119141]]],[[[21.608105,78.595703],[20.22793,78.477832],[21.653125,77.923535],[20.928125,77.459668],[22.685352,77.553516],[22.553711,77.26665],[24.901855,77.756592],[23.116699,77.991504],[21.608105,78.595703]]],[[[20.897852,80.249951],[19.733301,80.477832],[19.343359,80.116406],[17.916895,80.143115],[18.725,79.760742],[20.784082,79.748584],[20.128223,79.4896],[23.947754,79.194287],[25.641211,79.403027],[26.86084,80.16001],[24.297559,80.3604],[23.114551,80.186963],[23.008008,80.473975],[22.289746,80.049219],[20.897852,80.249951]]],[[[16.786719,79.906738],[16.245703,80.049463],[16.34375,78.976123],[14.593652,79.79873],[14.02959,79.344141],[12.555371,79.569482],[13.692871,79.860986],[10.804004,79.798779],[10.737598,79.520166],[13.150195,78.2375],[14.638281,78.4146],[14.689258,78.720947],[15.417383,78.473242],[16.782617,78.663623],[17.00293,78.369385],[13.680566,78.028125],[14.089941,77.771387],[16.914062,77.897998],[13.995703,77.508203],[16.700488,76.579297],[19.676758,78.60957],[21.38877,78.74043],[18.677832,79.261719],[18.397363,79.605176],[17.66875,79.385938],[16.786719,79.906738]]],[[[11.250293,78.610693],[10.558203,78.90293],[12.116406,78.232568],[11.250293,78.610693]]]]}}, - {"type":"Feature","properties":{"name":"朝鲜","full_name":"朝鲜民主主义人民共和国","iso_a2":"KP","iso_a3":"PRK","iso_n3":"408"},"geometry":{"type":"Polygon","coordinates":[[[128.374609,38.623438],[127.394531,39.20791],[127.568164,39.781982],[129.708691,40.857324],[129.756348,41.712256],[130.687305,42.302539],[130.526953,42.5354],[129.898242,42.998145],[129.697852,42.448145],[128.923438,42.038232],[128.045215,41.9875],[128.149414,41.387744],[126.743066,41.724854],[125.989062,40.904639],[124.362109,40.004053],[124.638281,39.615088],[124.775293,39.758057],[125.36084,39.526611],[125.168848,38.805518],[125.554492,38.68623],[124.690918,38.129199],[125.206738,38.081543],[124.98877,37.931445],[125.357813,37.724805],[125.769141,37.985352],[126.116699,37.74292],[126.633887,37.781836],[127.090332,38.283887],[128.038965,38.308545],[128.374609,38.623438]]]}}, - {"type":"Feature","properties":{"name":"尼日利亚","full_name":"尼日利亚联邦共和国","iso_a2":"NG","iso_a3":"NGA","iso_n3":"566"},"geometry":{"type":"Polygon","coordinates":[[[13.606348,13.70459],[12.463184,13.09375],[10.475879,13.330225],[9.615918,12.810645],[8.750586,12.908154],[7.830469,13.340918],[7.005078,12.995557],[6.299805,13.658789],[5.491992,13.872852],[4.664844,13.733203],[4.147559,13.457715],[4.03877,12.934668],[3.64668,12.52998],[3.59541,11.696289],[3.487793,11.39541],[3.834473,10.607422],[3.044922,9.083838],[2.774805,9.048535],[2.706445,6.369238],[3.716992,6.597949],[3.450781,6.427051],[4.431348,6.348584],[5.112402,5.641553],[5.456641,5.611719],[5.199219,5.533545],[5.549707,5.474219],[5.367969,5.337744],[5.493262,4.83877],[6.076563,4.290625],[6.860352,4.37334],[6.767676,4.724707],[6.923242,4.390674],[7.154688,4.514404],[7.076563,4.716162],[7.800781,4.522266],[8.293066,4.557617],[8.252734,4.923975],[8.555859,4.755225],[8.997168,5.917725],[9.779883,6.760156],[10.60625,7.063086],[11.237305,6.450537],[11.861426,7.116406],[12.233398,8.282324],[12.782227,8.817871],[13.699902,10.873145],[14.575391,11.532422],[14.619727,12.150977],[14.197461,12.383789],[14.063965,13.078516],[13.606348,13.70459]]]}}, - {"type":"Feature","properties":{"name":"尼日尔","full_name":"尼日尔共和国","iso_a2":"NE","iso_a3":"NER","iso_n3":"562"},"geometry":{"type":"Polygon","coordinates":[[[13.606348,13.70459],[13.448242,14.380664],[15.474316,16.908398],[15.735059,19.904053],[15.963184,20.346191],[15.181836,21.523389],[14.979004,22.996191],[14.215527,22.619678],[13.48125,23.180176],[11.967871,23.517871],[7.481738,20.873096],[5.836621,19.47915],[4.227637,19.142773],[4.234668,16.996387],[3.842969,15.701709],[3.504297,15.356348],[1.300195,15.272266],[0.947461,14.982129],[0.21748,14.911475],[0.429199,13.972119],[1.201172,13.35752],[0.988477,13.364844],[0.987305,13.041895],[1.564941,12.6354],[2.10459,12.70127],[2.072949,12.309375],[2.38916,11.89707],[2.366016,12.221924],[2.805273,12.383838],[3.59541,11.696289],[3.64668,12.52998],[4.03877,12.934668],[4.147559,13.457715],[4.664844,13.733203],[5.491992,13.872852],[6.299805,13.658789],[7.005078,12.995557],[7.830469,13.340918],[8.750586,12.908154],[9.615918,12.810645],[10.475879,13.330225],[12.463184,13.09375],[13.606348,13.70459]]]}}, - {"type":"Feature","properties":{"name":"尼加拉瓜","full_name":"尼加拉瓜共和国","iso_a2":"NI","iso_a3":"NIC","iso_n3":"558"},"geometry":{"type":"Polygon","coordinates":[[[-83.15752,14.993066],[-84.453564,14.643701],[-84.985156,14.752441],[-85.733936,13.858691],[-86.040381,14.050146],[-86.733643,13.763477],[-86.729297,13.284375],[-87.337256,12.979248],[-87.667529,12.903564],[-85.744336,11.062109],[-84.63418,11.045605],[-83.919287,10.735352],[-83.641992,10.917236],[-83.867871,11.300049],[-83.651758,11.642041],[-83.754248,12.501953],[-83.593359,12.713086],[-83.510938,12.411816],[-83.567334,13.320312],[-83.187744,14.340088],[-83.413721,14.825342],[-83.15752,14.993066]]]}}, - {"type":"Feature","properties":{"name":"新西兰","full_name":"新西兰","iso_a2":"NZ","iso_a3":"NZL","iso_n3":"554"},"geometry":{"type":"MultiPolygon","coordinates":[[[[173.115332,-41.279297],[172.704395,-40.667773],[172.943652,-40.51875],[172.640625,-40.518262],[171.48623,-41.794727],[171.011719,-42.885059],[170.969922,-42.718359],[169.178906,-43.913086],[168.457422,-44.030566],[167.908984,-44.664746],[167.194531,-44.963477],[167.155664,-45.410938],[166.743066,-45.468457],[167.00332,-45.712109],[166.488281,-45.831836],[166.916699,-45.957227],[166.731543,-46.197852],[167.539453,-46.148535],[168.382129,-46.605371],[169.342285,-46.620508],[170.77627,-45.870898],[171.240723,-44.26416],[172.179785,-43.895996],[172.035547,-43.701758],[173.065625,-43.874609],[172.52666,-43.464746],[172.624023,-43.272461],[173.221191,-42.976562],[174.283105,-41.740625],[174.069336,-41.429492],[174.370117,-41.103711],[174.038574,-41.241895],[174.302539,-41.019531],[173.797852,-41.271973],[173.947168,-40.924121],[173.115332,-41.279297]]],[[[168.144922,-46.862207],[167.783984,-46.699805],[167.521973,-47.258691],[168.240918,-47.07002],[168.144922,-46.862207]]],[[[173.269434,-34.934766],[173.043945,-34.429102],[172.705957,-34.455176],[173.313965,-35.443359],[173.626172,-35.319141],[173.412207,-35.542578],[174.054688,-36.359766],[173.914453,-35.908691],[174.392773,-36.240039],[174.401562,-36.601953],[174.188867,-36.492285],[174.475586,-36.941895],[174.928906,-37.084766],[174.58584,-37.097754],[174.928027,-37.804492],[174.597363,-38.785059],[173.763672,-39.31875],[175.155957,-40.114941],[175.1625,-40.621582],[174.635352,-41.289453],[175.309766,-41.610645],[176.842188,-40.157812],[177.076758,-39.221777],[177.522949,-39.073828],[177.908789,-39.239551],[178.53623,-37.69209],[178.00918,-37.554883],[177.274023,-37.993457],[176.108398,-37.645117],[175.46084,-36.475684],[175.54248,-37.201367],[174.722461,-36.841211],[174.802148,-36.309473],[174.320312,-35.24668],[173.269434,-34.934766]]],[[[166.221094,-50.761523],[166.101367,-50.538965],[165.88916,-50.807715],[166.221094,-50.761523]]],[[[-176.177637,-43.740332],[-176.847656,-43.823926],[-176.515527,-44.116602],[-176.516553,-43.784766],[-176.177637,-43.740332]]]]}}, - {"type":"Feature","properties":{"name":"纽埃","full_name":"纽埃","iso_a2":"NU","iso_a3":"NIU","iso_n3":"570"},"geometry":{"type":"Polygon","coordinates":[[[-169.803418,-19.083008],[-169.834033,-18.966016],[-169.94834,-19.072852],[-169.803418,-19.083008]]]}}, - {"type":"Feature","properties":{"name":"库克群岛","full_name":"库克群岛","iso_a2":"CK","iso_a3":"COK","iso_n3":"184"},"geometry":{"type":"Polygon","coordinates":[[[-159.740527,-21.249219],[-159.768359,-21.188477],[-159.832031,-21.200488],[-159.740527,-21.249219]]]}}, - {"type":"Feature","properties":{"name":"荷兰","full_name":"荷兰王国","iso_a2":"NL","iso_a3":"NLD","iso_n3":"528"},"geometry":{"type":"MultiPolygon","coordinates":[[[[5.993945,50.750439],[5.94873,51.802686],[6.800391,51.967383],[7.035156,52.380225],[6.710742,52.617871],[7.033008,52.651367],[7.197266,53.282275],[6.062207,53.40708],[4.76875,52.941309],[3.946875,51.810547],[4.274121,51.471631],[3.448926,51.540771],[4.226172,51.386475],[5.030957,51.469092],[5.796484,51.153076],[5.639453,50.843604],[5.993945,50.750439]]],[[[4.226172,51.386475],[3.350098,51.377686],[3.902051,51.207666],[4.226172,51.386475]]]]}}, - {"type":"Feature","properties":{"name":"阿鲁巴","full_name":"阿鲁巴(荷兰)","iso_a2":"AW","iso_a3":"ABW","iso_n3":"533"},"geometry":{"type":"Polygon","coordinates":[[[-69.899121,12.452002],[-70.035107,12.614111],[-70.066113,12.546973],[-69.899121,12.452002]]]}}, - {"type":"Feature","properties":{"name":"库拉索","full_name":"库拉索岛(荷兰)","iso_a2":"CW","iso_a3":"CUW","iso_n3":"531"},"geometry":{"type":"Polygon","coordinates":[[[-68.751074,12.059766],[-69.158887,12.380273],[-68.995117,12.141846],[-68.751074,12.059766]]]}}, - {"type":"Feature","properties":{"name":"尼泊尔","full_name":"尼泊尔","iso_a2":"NP","iso_a3":"NPL","iso_n3":"524"},"geometry":{"type":"Polygon","coordinates":[[[88.109766,27.870605],[87.141406,27.83833],[86.137012,28.114355],[85.994531,27.9104],[85.67832,28.277441],[85.122461,28.315967],[85.159082,28.592236],[84.228711,28.911768],[84.101367,29.219971],[83.583496,29.183594],[82.043359,30.326758],[81.010254,30.164502],[80.401855,29.730273],[80.070703,28.830176],[82.733398,27.518994],[84.091016,27.491357],[85.794531,26.60415],[87.995117,26.382373],[88.109766,27.870605]]]}}, - {"type":"Feature","properties":{"name":"瑙鲁","full_name":"瑙鲁共和国","iso_a2":"NR","iso_a3":"NRU","iso_n3":"520"},"geometry":{"type":"Polygon","coordinates":[[[166.958398,-0.516602],[166.907031,-0.52373],[166.938965,-0.550781],[166.958398,-0.516602]]]}}, - {"type":"Feature","properties":{"name":"纳米比亚","full_name":"纳米比亚共和国","iso_a2":"NA","iso_a3":"NAM","iso_n3":"516"},"geometry":{"type":"Polygon","coordinates":[[[23.380664,-17.640625],[20.745508,-18.019727],[18.955273,-17.803516],[18.396387,-17.399414],[14.01748,-17.408887],[13.101172,-16.967676],[11.743066,-17.249219],[11.775879,-18.001758],[14.462793,-22.449121],[14.501562,-24.201953],[14.967773,-26.318066],[15.341504,-27.386523],[16.447559,-28.617578],[17.05625,-28.031055],[17.447949,-28.698145],[18.102734,-28.87168],[19.161719,-28.93877],[19.980469,-28.45127],[19.980469,-24.776758],[19.977344,-22.000195],[20.979492,-21.961914],[20.974121,-18.318848],[23.219336,-17.999707],[23.599707,-18.459961],[24.243945,-18.023438],[25.258789,-17.793555],[24.73291,-17.517773],[23.380664,-17.640625]]]}}, - {"type":"Feature","properties":{"name":"莫桑比克","full_name":"莫桑比克共和国","iso_a2":"MZ","iso_a3":"MOZ","iso_n3":"508"},"geometry":{"type":"Polygon","coordinates":[[[31.287891,-22.402051],[31.98584,-24.460645],[31.948242,-25.957617],[32.112891,-26.839453],[32.886133,-26.849316],[32.954883,-26.083594],[32.848828,-26.268066],[32.59043,-26.004102],[32.792188,-25.644336],[34.99209,-24.650586],[35.489648,-24.065527],[35.530078,-22.248145],[35.315723,-22.396875],[34.649414,-19.701367],[34.947852,-19.812695],[36.403711,-18.769727],[37.244531,-17.739941],[39.844629,-16.435645],[40.558984,-15.473438],[40.844531,-14.718652],[40.436816,-12.983105],[40.463574,-10.464355],[38.491797,-11.413281],[37.920215,-11.294727],[37.372852,-11.710449],[36.305664,-11.706348],[35.911328,-11.454688],[34.959473,-11.578125],[34.618555,-11.620215],[34.357813,-12.164746],[34.563672,-13.360156],[35.247461,-13.896875],[35.892773,-14.891797],[35.755273,-16.058301],[35.358496,-16.160547],[35.167188,-16.560254],[35.272559,-17.118457],[34.248242,-15.8875],[34.54082,-15.297266],[34.505273,-14.598145],[34.33252,-14.408594],[33.636426,-14.568164],[33.201758,-14.013379],[30.231836,-14.990332],[30.396094,-15.643066],[30.437793,-15.995313],[31.23623,-16.023633],[32.948047,-16.712305],[32.993066,-18.35957],[32.699707,-18.940918],[32.992773,-19.984863],[32.492383,-20.659766],[32.429785,-21.29707],[31.287891,-22.402051]]]}}, - {"type":"Feature","properties":{"name":"摩洛哥","full_name":"摩洛哥王国","iso_a2":"MA","iso_a3":"MAR","iso_n3":"504"},"geometry":{"type":"Polygon","coordinates":[[[-2.219629,35.104199],[-2.839941,35.127832],[-2.972217,35.407275],[-4.62832,35.206396],[-5.252686,35.614746],[-5.277832,35.902734],[-5.924805,35.785791],[-6.900977,33.969043],[-8.512842,33.252441],[-9.24585,32.572461],[-9.808691,31.424609],[-9.66709,30.109277],[-10.200586,29.380371],[-11.552686,28.310107],[-12.948926,27.91416],[-13.575781,26.735107],[-14.413867,26.253711],[-14.904297,24.719775],[-15.899316,23.844434],[-17.003076,21.420703],[-14.750977,21.500586],[-14.221191,22.310156],[-13.891113,23.691016],[-12.431152,24.830664],[-12.060986,25.99082],[-11.718213,26.104102],[-11.392578,26.883398],[-9.817871,26.850195],[-8.794873,27.120703],[-8.68335,27.656445],[-8.678418,28.689404],[-7.685156,29.349512],[-5.448779,29.956934],[-4.968262,30.465381],[-3.666797,30.964014],[-3.826758,31.661914],[-3.017383,31.834277],[-2.887207,32.068848],[-1.225928,32.107227],[-1.065527,32.468311],[-1.679199,33.318652],[-1.795605,34.751904],[-2.219629,35.104199]]]}}, - {"type":"Feature","properties":{"name":"西撒哈拉","full_name":"西撒哈拉","iso_a2":"EH","iso_a3":"ESH","iso_n3":"732"},"geometry":{"type":"Polygon","coordinates":[[[-8.68335,27.656445],[-8.794873,27.120703],[-9.817871,26.850195],[-11.392578,26.883398],[-11.718213,26.104102],[-12.060986,25.99082],[-12.431152,24.830664],[-13.891113,23.691016],[-14.221191,22.310156],[-14.750977,21.500586],[-17.003076,21.420703],[-17.048047,20.806152],[-16.964551,21.329248],[-13.016211,21.333936],[-13.153271,22.820508],[-12.023438,23.467578],[-12.016309,25.99541],[-8.682227,25.995508],[-8.68335,27.285938],[-8.68335,27.656445]]]}}, - {"type":"Feature","properties":{"name":"黑山","full_name":"黑山","iso_a2":"ME","iso_a3":"MNE","iso_n3":"499"},"geometry":{"type":"Polygon","coordinates":[[[19.194336,43.533301],[18.460156,42.9979],[18.436328,42.559717],[18.51748,42.43291],[19.342383,41.869092],[19.654492,42.628564],[20.063965,42.547266],[20.344336,42.82793],[19.194336,43.533301]]]}}, - {"type":"Feature","properties":{"name":"蒙古","full_name":"蒙古国","iso_a2":"MN","iso_a3":"MNG","iso_n3":"496"},"geometry":{"type":"Polygon","coordinates":[[[87.814258,49.162305],[87.979688,48.555127],[89.047656,48.002539],[90.02793,47.877686],[90.869922,46.954492],[90.877246,45.196094],[93.516211,44.944482],[95.350293,44.278076],[96.385449,42.720361],[101.495313,42.53877],[102.156641,42.158105],[103.711133,41.751318],[104.498242,41.877002],[104.498242,41.658691],[104.982031,41.595508],[106.77002,42.288721],[109.339844,42.438379],[110.400391,42.773682],[111.933203,43.711426],[111.402246,44.367285],[111.898047,45.064062],[113.587012,44.745703],[114.560156,45.38999],[115.681055,45.458252],[116.562598,46.289795],[117.333398,46.362012],[117.438086,46.58623],[119.867188,46.672168],[119.711133,47.15],[118.498438,47.983984],[117.768359,47.987891],[117.350781,47.652197],[116.760547,47.869775],[115.898242,47.686914],[115.616406,47.874805],[116.683301,49.823779],[116.216797,50.009277],[115.429199,49.896484],[114.29707,50.274414],[112.806445,49.523584],[110.709766,49.142969],[108.613672,49.322803],[107.916602,49.947803],[107.233301,49.989404],[106.711133,50.312598],[105.383594,50.47373],[103.304395,50.200293],[102.288379,50.585107],[102.111523,51.353467],[98.893164,52.117285],[97.835742,51.05166],[98.250293,50.302441],[97.359766,49.741455],[94.614746,50.02373],[94.251074,50.556396],[92.354785,50.86416],[90.053711,50.09375],[89.395605,49.611523],[88.192578,49.451709],[87.814258,49.162305]]]}}, - {"type":"Feature","properties":{"name":"摩尔多瓦","full_name":"摩尔多瓦共和国","iso_a2":"MD","iso_a3":"MDA","iso_n3":"498"},"geometry":{"type":"Polygon","coordinates":[[[26.618945,48.259863],[28.071777,46.978418],[28.2125,45.450439],[28.947754,46.049951],[28.958398,46.458496],[30.131055,46.423096],[29.134863,47.489697],[29.125391,47.964551],[27.549219,48.477734],[26.618945,48.259863]]]}}, - {"type":"Feature","properties":{"name":"摩纳哥","full_name":"摩纳哥公国","iso_a2":"MC","iso_a3":"MCO","iso_n3":"492"},"geometry":{"type":"Polygon","coordinates":[[[7.438672,43.750439],[7.39502,43.765332],[7.377734,43.731738],[7.438672,43.750439]]]}}, - {"type":"Feature","properties":{"name":"墨西哥","full_name":"墨西哥合众国","iso_a2":"MX","iso_a3":"MEX","iso_n3":"484"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-117.128271,32.53335],[-115.673828,29.756396],[-114.048486,28.426172],[-114.265869,27.934473],[-114.069336,27.675684],[-114.300586,27.872998],[-114.993506,27.736035],[-113.598535,26.721289],[-113.155811,26.94624],[-113.020752,26.583252],[-112.377246,26.213916],[-112.069873,25.572852],[-112.072559,24.840039],[-110.362695,23.604932],[-110.00625,22.894043],[-109.495703,23.159814],[-109.42085,23.480127],[-110.262891,24.344531],[-110.367432,24.100488],[-110.659326,24.341455],[-111.569678,26.707617],[-111.795264,26.879688],[-111.699414,26.580957],[-111.862646,26.678516],[-112.734033,27.825977],[-112.87085,28.424219],[-114.550488,30.022266],[-114.933594,31.900732],[-113.046729,31.179248],[-113.057666,30.651025],[-112.161768,29.018896],[-111.121387,27.966992],[-110.529883,27.864209],[-110.377295,27.233301],[-109.27627,26.533887],[-109.116699,26.252734],[-109.425635,26.032568],[-109.384961,25.727148],[-108.886572,25.733447],[-109.028809,25.480469],[-108.051465,25.067041],[-108.280762,25.081543],[-107.951172,24.614893],[-107.511914,24.48916],[-107.764941,24.471924],[-105.791797,22.62749],[-105.649121,21.988086],[-105.208691,21.49082],[-105.51084,20.80874],[-105.260156,20.579053],[-105.669434,20.385596],[-104.938477,19.309375],[-103.912451,18.828467],[-103.441602,18.325391],[-101.918701,17.959766],[-100.847803,17.200488],[-97.754785,15.966846],[-96.213574,15.693066],[-94.799414,16.209668],[-95.02085,16.277637],[-94.858691,16.419727],[-94.587109,16.31582],[-94.661523,16.201904],[-94.00127,16.018945],[-94.37417,16.284766],[-93.166895,15.448047],[-92.235156,14.54541],[-92.204248,15.275],[-91.736572,16.070166],[-90.447168,16.072705],[-90.416992,16.391016],[-91.409619,17.255859],[-90.992969,17.252441],[-90.98916,17.816406],[-89.161475,17.814844],[-88.295654,18.472412],[-88.031738,18.838916],[-87.881982,18.273877],[-87.761816,18.446143],[-87.424756,19.58335],[-87.687695,19.637109],[-86.771777,21.150537],[-87.034766,21.592236],[-88.466699,21.569385],[-90.353125,21.009424],[-90.739258,19.352246],[-91.43667,18.889795],[-91.275244,18.624463],[-91.533984,18.456543],[-92.441016,18.675293],[-94.459766,18.16665],[-95.181836,18.700732],[-95.920361,18.81958],[-97.186328,20.717041],[-97.753809,22.02666],[-97.40918,21.272559],[-97.314502,21.564209],[-97.84248,22.510303],[-97.667676,24.38999],[-97.14624,25.961475],[-99.107764,26.446924],[-99.505322,27.54834],[-101.440381,29.776855],[-102.614941,29.752344],[-103.168311,28.998193],[-104.110596,29.386133],[-104.978809,30.645947],[-106.44541,31.768408],[-108.211816,31.779346],[-108.214453,31.329443],[-111.041992,31.324219],[-114.835938,32.508301],[-114.724756,32.715332],[-117.128271,32.53335]]],[[[-115.170605,28.069385],[-115.233545,28.368359],[-115.35293,28.103955],[-115.170605,28.069385]]],[[[-112.203076,29.005322],[-112.423535,29.203662],[-112.514062,28.847607],[-112.278418,28.769336],[-112.203076,29.005322]]],[[[-112.057275,24.545703],[-112.159424,25.285645],[-112.296777,24.789648],[-112.057275,24.545703]]]]}}, - {"type":"Feature","properties":{"name":"毛里求斯","full_name":"毛里求斯共和国","iso_a2":"MU","iso_a3":"MUS","iso_n3":"480"},"geometry":{"type":"Polygon","coordinates":[[[57.65127,-20.484863],[57.656543,-19.989941],[57.317676,-20.427637],[57.65127,-20.484863]]]}}, - {"type":"Feature","properties":{"name":"毛里塔尼亚","full_name":"毛里塔尼亚伊斯兰共和国","iso_a2":"MR","iso_a3":"MRT","iso_n3":"478"},"geometry":{"type":"Polygon","coordinates":[[[-12.280615,14.809033],[-11.940918,14.886914],[-11.502686,15.636816],[-10.895605,15.150488],[-10.696582,15.422656],[-9.446924,15.458203],[-9.350586,15.677393],[-9.176807,15.496094],[-5.5125,15.496289],[-5.359912,16.282861],[-5.628662,16.568652],[-5.941016,19.296191],[-6.594092,24.994629],[-4.822607,24.995605],[-8.68335,27.285938],[-8.682227,25.995508],[-12.016309,25.99541],[-12.023438,23.467578],[-13.153271,22.820508],[-13.016211,21.333936],[-16.964551,21.329248],[-17.048047,20.806152],[-16.92793,21.114795],[-16.210449,20.22793],[-16.514453,19.361963],[-16.213086,19.00332],[-16.030322,17.887939],[-16.535254,15.838379],[-16.239014,16.531299],[-14.53374,16.655957],[-13.409668,16.05918],[-12.280615,14.809033]]]}}, - {"type":"Feature","properties":{"name":"马耳他","full_name":"马耳他共和国","iso_a2":"MT","iso_a3":"MLT","iso_n3":"470"},"geometry":{"type":"Polygon","coordinates":[[[14.566211,35.852734],[14.35127,35.978418],[14.436426,35.82168],[14.566211,35.852734]]]}}, - {"type":"Feature","properties":{"name":"马里","full_name":"马里共和国","iso_a2":"ML","iso_a3":"MLI","iso_n3":"466"},"geometry":{"type":"Polygon","coordinates":[[[-11.389404,12.404395],[-11.502197,12.198633],[-11.305176,12.01543],[-10.933203,12.205176],[-10.709229,11.89873],[-10.274854,12.212646],[-9.754004,12.029932],[-9.358105,12.25542],[-9.395361,12.464648],[-9.043066,12.402344],[-8.822021,11.673242],[-8.398535,11.366553],[-8.666699,11.009473],[-8.337402,10.990625],[-7.990625,10.1625],[-7.497949,10.439795],[-7.01709,10.143262],[-6.65415,10.656445],[-6.261133,10.724072],[-6.196875,10.232129],[-5.523535,10.426025],[-5.288135,11.82793],[-4.428711,12.337598],[-4.151025,13.306201],[-3.301758,13.280762],[-3.248633,13.65835],[-2.95083,13.648438],[-2.586719,14.227588],[-2.113232,14.168457],[-1.973047,14.456543],[-0.760449,15.047754],[0.21748,14.911475],[0.947461,14.982129],[1.300195,15.272266],[3.504297,15.356348],[3.842969,15.701709],[4.234668,16.996387],[4.227637,19.142773],[3.119727,19.103174],[3.130273,19.850195],[1.685449,20.378369],[1.145508,21.102246],[-4.822607,24.995605],[-6.594092,24.994629],[-5.941016,19.296191],[-5.628662,16.568652],[-5.359912,16.282861],[-5.5125,15.496289],[-9.176807,15.496094],[-9.350586,15.677393],[-9.446924,15.458203],[-10.696582,15.422656],[-10.895605,15.150488],[-11.502686,15.636816],[-11.940918,14.886914],[-12.280615,14.809033],[-12.054199,13.633057],[-11.390381,12.941992],[-11.389404,12.404395]]]}}, - {"type":"Feature","properties":{"name":"马尔代夫","full_name":"马尔代夫共和国","iso_a2":"MV","iso_a3":"MDV","iso_n3":"462"},"geometry":{"type":"Polygon","coordinates":[[[73.512207,4.164551],[73.517773,4.247656],[73.473047,4.170703],[73.512207,4.164551]]]}}, - {"type":"Feature","properties":{"name":"马来西亚","full_name":"马来西亚","iso_a2":"MY","iso_a3":"MYS","iso_n3":"458"},"geometry":{"type":"MultiPolygon","coordinates":[[[[100.119141,6.441992],[100.71543,3.966211],[101.299902,3.253271],[101.295508,2.885205],[103.480273,1.329492],[103.694531,1.449658],[103.991504,1.454785],[103.981445,1.623633],[104.250098,1.388574],[103.812207,2.580469],[103.439453,2.933105],[103.41582,4.850293],[103.09707,5.408447],[102.101074,6.242236],[101.873633,5.825293],[101.556055,5.907764],[101.113965,5.636768],[101.053516,6.242578],[100.261426,6.682715],[100.119141,6.441992]]],[[[117.574414,4.170605],[118.54834,4.379248],[118.260547,4.988867],[118.9125,5.0229],[119.266309,5.308105],[118.353125,5.806055],[117.973633,5.70625],[118.003809,6.05332],[117.501172,5.884668],[117.69375,6.35],[117.229883,6.93999],[116.788086,6.606104],[116.749805,6.9771],[115.877148,5.613525],[115.419043,5.413184],[115.554492,5.093555],[115.140039,4.899756],[115.290625,4.352588],[115.026758,4.899707],[114.74668,4.718066],[114.654102,4.037646],[114.063867,4.592676],[112.987891,3.161914],[111.5125,2.743018],[111.028711,1.557812],[111.223242,1.39585],[109.864844,1.764453],[109.628906,2.027539],[109.654004,1.614893],[110.505762,0.861963],[111.808984,1.01167],[112.476172,1.559082],[113.622266,1.235938],[114.5125,1.452002],[114.786426,2.250488],[115.179102,2.523193],[115.117578,2.894873],[115.454395,3.034326],[115.678809,4.193018],[116.514746,4.370801],[117.574414,4.170605]]],[[[99.848047,6.465723],[99.646289,6.418359],[99.74375,6.263281],[99.848047,6.465723]]]]}}, - {"type":"Feature","properties":{"name":"马拉维","full_name":"马拉维共和国","iso_a2":"MW","iso_a3":"MWI","iso_n3":"454"},"geometry":{"type":"Polygon","coordinates":[[[33.201758,-14.013379],[33.636426,-14.568164],[34.33252,-14.408594],[34.505273,-14.598145],[34.54082,-15.297266],[34.248242,-15.8875],[35.272559,-17.118457],[35.167188,-16.560254],[35.358496,-16.160547],[35.755273,-16.058301],[35.892773,-14.891797],[35.247461,-13.896875],[34.563672,-13.360156],[34.357813,-12.164746],[34.618555,-11.620215],[34.959473,-11.578125],[34.60791,-11.080469],[34.320898,-9.731543],[33.995605,-9.49541],[33.888867,-9.670117],[32.919922,-9.407422],[33.661523,-10.553125],[33.261328,-10.893359],[33.252344,-12.112598],[33.512305,-12.347754],[33.021582,-12.630469],[32.67041,-13.59043],[33.201758,-14.013379]]]}}, - {"type":"Feature","properties":{"name":"马达加斯加","full_name":"马达加斯加共和国","iso_a2":"MG","iso_a3":"MDG","iso_n3":"450"},"geometry":{"type":"MultiPolygon","coordinates":[[[[49.538281,-12.432129],[49.207031,-12.07959],[48.786328,-12.470898],[48.796484,-13.26748],[48.255273,-13.719336],[47.941016,-13.662402],[47.77334,-14.369922],[47.96416,-14.672559],[47.47832,-15.009375],[47.351953,-14.766113],[47.099219,-15.43418],[46.942285,-15.219043],[46.475098,-15.513477],[46.399609,-15.924609],[46.15752,-15.738281],[44.476172,-16.217285],[43.979395,-17.391602],[44.404688,-19.92207],[43.501855,-21.356445],[43.264844,-22.383594],[44.035352,-24.995703],[45.205762,-25.570508],[46.728516,-25.149902],[47.177344,-24.787207],[49.362891,-18.336328],[49.449316,-17.240625],[49.839063,-16.486523],[49.664355,-15.521582],[49.892578,-15.457715],[50.208984,-15.960449],[50.482715,-15.385645],[49.9375,-13.072266],[49.538281,-12.432129]]],[[[49.936426,-16.90293],[50.023047,-16.695312],[49.824023,-17.086523],[49.936426,-16.90293]]]]}}, - {"type":"Feature","properties":{"name":"北马其顿","full_name":"北马其顿共和国","iso_a2":"MK","iso_a3":"MKD","iso_n3":"807"},"geometry":{"type":"Polygon","coordinates":[[[21.5625,42.24751],[20.566211,41.873682],[20.488965,41.272607],[20.964258,40.849902],[22.916016,41.336279],[23.003613,41.739844],[22.344043,42.313965],[21.5625,42.24751]]]}}, - {"type":"Feature","properties":{"name":"卢森堡","full_name":"卢森堡大公国","iso_a2":"LU","iso_a3":"LUX","iso_n3":"442"},"geometry":{"type":"Polygon","coordinates":[[[6.116504,50.120996],[5.744043,49.919629],[5.789746,49.538281],[6.344336,49.452734],[6.487305,49.798486],[6.116504,50.120996]]]}}, - {"type":"Feature","properties":{"name":"立陶宛","full_name":"立陶宛共和国","iso_a2":"LT","iso_a3":"LTU","iso_n3":"440"},"geometry":{"type":"MultiPolygon","coordinates":[[[[20.957813,55.278906],[21.114844,55.616504],[20.899805,55.28667],[20.957813,55.278906]]],[[[22.766211,54.356787],[23.484668,53.939795],[24.317969,53.892969],[25.461133,54.292773],[25.749219,54.156982],[25.780859,54.833252],[26.775684,55.273096],[26.457617,55.34248],[26.593555,55.667529],[24.841016,56.411182],[24.120703,56.264258],[22.08457,56.406738],[21.046094,56.070068],[21.235742,55.264111],[22.567285,55.059131],[22.766211,54.356787]]]]}}, - {"type":"Feature","properties":{"name":"列支敦士登","full_name":"列支敦士登公国","iso_a2":"LI","iso_a3":"LIE","iso_n3":"438"},"geometry":{"type":"Polygon","coordinates":[[[9.580273,47.057373],[9.527539,47.270752],[9.487695,47.062256],[9.580273,47.057373]]]}}, - {"type":"Feature","properties":{"name":"利比亚","full_name":"利比亚国","iso_a2":"LY","iso_a3":"LBY","iso_n3":"434"},"geometry":{"type":"Polygon","coordinates":[[[9.51875,30.229395],[9.310254,30.115234],[9.805273,29.176953],[9.916016,27.785693],[9.883203,26.630811],[9.448242,26.067139],[10.255859,24.591016],[11.507617,24.314355],[11.967871,23.517871],[13.48125,23.180176],[14.215527,22.619678],[14.979004,22.996191],[15.984082,23.445215],[20.147656,21.389258],[23.980273,19.496631],[23.980273,19.995947],[24.979492,20.002588],[24.980273,21.99585],[24.980273,29.181885],[24.703223,30.201074],[24.961426,30.678516],[24.852734,31.334814],[25.150488,31.65498],[24.878516,31.984277],[23.286328,32.213818],[23.090625,32.61875],[21.635938,32.937305],[20.121484,32.21875],[19.926367,31.817529],[20.111523,30.963721],[19.713281,30.488379],[19.12373,30.266113],[17.830469,30.927588],[15.705957,31.426416],[15.176563,32.391162],[13.283496,32.914648],[12.279883,32.858545],[11.50459,33.181934],[11.50498,32.413672],[10.274609,31.684961],[10.216406,30.783203],[9.51875,30.229395]]]}}, - {"type":"Feature","properties":{"name":"利比里亚","full_name":"利比里亚共和国","iso_a2":"LR","iso_a3":"LBR","iso_n3":"430"},"geometry":{"type":"Polygon","coordinates":[[[-11.50752,6.906543],[-9.132178,5.054639],[-7.544971,4.351318],[-7.454395,5.841309],[-8.603564,6.507812],[-8.302344,6.980957],[-8.486426,7.558496],[-8.659766,7.688379],[-9.117578,7.215918],[-9.463818,7.415869],[-9.518262,8.346094],[-10.283203,8.485156],[-10.647461,7.759375],[-11.50752,6.906543]]]}}, - {"type":"Feature","properties":{"name":"莱索托","full_name":"莱索托王国","iso_a2":"LS","iso_a3":"LSO","iso_n3":"426"},"geometry":{"type":"Polygon","coordinates":[[[28.736914,-30.101953],[29.098047,-29.919043],[29.390723,-29.269727],[28.625781,-28.581738],[27.735547,-28.940039],[27.056934,-29.625586],[27.753125,-30.6],[28.056836,-30.631055],[28.39209,-30.147559],[28.736914,-30.101953]]]}}, - {"type":"Feature","properties":{"name":"黎巴嫩","full_name":"黎巴嫩共和国","iso_a2":"LB","iso_a3":"LBN","iso_n3":"422"},"geometry":{"type":"Polygon","coordinates":[[[35.97627,34.629199],[35.108594,33.083691],[35.840723,33.415674],[35.869141,33.431738],[36.584961,34.22124],[36.383887,34.65791],[35.97627,34.629199]]]}}, - {"type":"Feature","properties":{"name":"拉脱维亚","full_name":"拉脱维亚共和国","iso_a2":"LV","iso_a3":"LVA","iso_n3":"428"},"geometry":{"type":"Polygon","coordinates":[[[26.593555,55.667529],[27.576758,55.798779],[28.147949,56.14292],[27.639453,56.845654],[27.828613,57.293311],[27.351953,57.528125],[26.462109,57.544482],[25.282617,58.048486],[24.322559,57.870605],[24.382617,57.250049],[23.647754,56.971045],[22.55459,57.724268],[21.728711,57.570996],[21.071289,56.82373],[21.046094,56.070068],[22.08457,56.406738],[24.120703,56.264258],[24.841016,56.411182],[26.593555,55.667529]]]}}, - {"type":"Feature","properties":{"name":"老挝","full_name":"老挝人民民主共和国","iso_a2":"LA","iso_a3":"LAO","iso_n3":"418"},"geometry":{"type":"Polygon","coordinates":[[[102.127441,22.379199],[101.73877,22.495264],[101.524512,22.253662],[101.800586,21.212598],[101.247852,21.197314],[101.138867,21.56748],[100.703125,21.251367],[100.622949,20.85957],[100.249316,20.730273],[100.122461,20.31665],[100.519531,20.17793],[100.513574,19.553467],[101.211914,19.54834],[100.955859,17.541113],[102.101465,18.210645],[102.680078,17.824121],[103.366992,18.42334],[103.949609,18.318994],[104.739648,17.46167],[104.819336,16.466064],[105.641016,15.656543],[105.497363,14.590674],[105.183301,14.34624],[106.066797,13.921191],[105.978906,14.343018],[106.501465,14.578223],[106.938086,14.327344],[107.519434,14.705078],[107.653125,15.255225],[107.165918,15.80249],[107.396387,16.043018],[106.656445,16.492627],[106.502246,16.954102],[105.114551,18.405273],[105.146484,18.650977],[103.891602,19.30498],[104.032031,19.675146],[104.587891,19.61875],[104.92793,20.018115],[104.367773,20.441406],[104.583203,20.64668],[104.101367,20.945508],[103.635059,20.69707],[103.104492,20.89165],[102.851172,21.265918],[102.949609,21.681348],[102.662012,21.676025],[102.127441,22.379199]]]}}, - {"type":"Feature","properties":{"name":"吉尔吉斯斯坦","full_name":"吉尔吉斯共和国","iso_a2":"KG","iso_a3":"KGZ","iso_n3":"417"},"geometry":{"type":"Polygon","coordinates":[[[70.958008,40.238867],[70.515137,39.949902],[69.966797,40.202246],[69.530273,40.097314],[69.297656,39.524805],[70.501172,39.587354],[70.799316,39.394727],[71.470312,39.603662],[72.22998,39.20752],[73.631641,39.448877],[73.991602,40.043115],[74.835156,40.482617],[75.555566,40.625195],[75.677148,40.305811],[76.318555,40.352246],[76.907715,41.02417],[78.123438,41.075635],[80.209375,42.190039],[79.12666,42.775732],[75.366211,42.836963],[74.209082,43.240381],[73.55625,43.002783],[73.492969,42.409033],[71.760547,42.821484],[71.256641,42.733545],[70.946777,42.248682],[71.228516,42.162891],[70.200879,41.514453],[71.393066,41.123389],[71.664941,41.541211],[72.187305,41.025928],[73.136914,40.810645],[71.69248,40.152344],[70.958008,40.238867]]]}}, - {"type":"Feature","properties":{"name":"科威特","full_name":"科威特国","iso_a2":"KW","iso_a3":"KWT","iso_n3":"414"},"geometry":{"type":"Polygon","coordinates":[[[48.44248,28.54292],[48.051465,29.355371],[47.722656,29.393018],[48.143457,29.572461],[47.978711,29.982812],[47.148242,30.000977],[46.531445,29.09624],[47.433203,28.989551],[47.671289,28.533154],[48.44248,28.54292]]]}}, - {"type":"Feature","properties":{"name":"科索沃","full_name":"科索沃","iso_a2":"XK","iso_a3":"-99","iso_n3":"-99"},"geometry":{"type":"Polygon","coordinates":[[[20.344336,42.82793],[20.063965,42.547266],[20.566211,41.873682],[21.5625,42.24751],[21.75293,42.669824],[20.800586,43.261084],[20.344336,42.82793]]]}}, - {"type":"Feature","properties":{"name":"基里巴斯","full_name":"基里巴斯共和国","iso_a2":"KI","iso_a3":"KIR","iso_n3":"296"},"geometry":{"type":"Polygon","coordinates":[[[-157.342139,1.855566],[-157.578955,1.902051],[-157.175781,1.739844],[-157.342139,1.855566]]]}}, - {"type":"Feature","properties":{"name":"肯尼亚","full_name":"肯尼亚共和国","iso_a2":"KE","iso_a3":"KEN","iso_n3":"404"},"geometry":{"type":"Polygon","coordinates":[[[33.903223,-1.002051],[37.643848,-3.04541],[37.608203,-3.49707],[39.221777,-4.692383],[40.11543,-3.250586],[40.222461,-2.688379],[41.532715,-1.695312],[40.978711,-0.870313],[40.964453,2.814648],[41.883984,3.977734],[41.14043,3.962988],[40.765234,4.273047],[39.494434,3.456104],[38.086133,3.648828],[36.905566,4.411475],[36.021973,4.468115],[35.74502,5.343994],[35.268359,5.492285],[33.976074,4.220215],[34.437695,3.650586],[34.978223,1.773633],[33.943164,0.173779],[33.903223,-1.002051]]]}}, - {"type":"Feature","properties":{"name":"哈萨克斯坦","full_name":"哈萨克斯坦共和国","iso_a2":"KZ","iso_a3":"KAZ","iso_n3":"398"},"geometry":{"type":"Polygon","coordinates":[[[70.946777,42.248682],[71.256641,42.733545],[71.760547,42.821484],[73.492969,42.409033],[73.55625,43.002783],[74.209082,43.240381],[75.366211,42.836963],[79.12666,42.775732],[80.209375,42.190039],[80.202246,42.734473],[80.785742,43.161572],[80.355273,44.097266],[80.481543,44.714648],[79.871875,44.883789],[81.691992,45.349365],[82.521484,45.125488],[82.315234,45.594922],[83.029492,47.185938],[84.786133,46.830713],[85.484766,47.063525],[85.749414,48.385059],[86.549414,48.528613],[86.808301,49.049707],[87.322852,49.085791],[86.675488,49.777295],[86.180859,49.499316],[85.232617,49.61582],[84.989453,50.061426],[84.323242,50.23916],[83.357324,50.99458],[82.493945,50.727588],[81.465918,50.739844],[80.735254,51.293408],[79.98623,50.774561],[77.859961,53.269189],[76.484766,54.022559],[76.837305,54.442383],[74.351562,53.487646],[73.858984,53.619727],[73.406934,53.447559],[73.712402,54.042383],[72.622266,54.134326],[72.446777,53.941846],[72.186035,54.325635],[71.093164,54.212207],[70.738086,55.305176],[70.182422,55.162451],[68.977246,55.3896],[68.155859,54.976709],[65.476953,54.623291],[65.088379,54.340186],[61.231055,54.019482],[60.979492,53.621729],[61.534961,53.523291],[61.199219,53.287158],[62.082715,53.00542],[61.047461,52.972461],[60.774414,52.675781],[60.994531,52.336865],[60.030273,51.933252],[61.554688,51.324609],[61.389453,50.861035],[60.942285,50.695508],[60.058594,50.850293],[59.523047,50.492871],[57.838867,51.09165],[57.442188,50.888867],[56.491406,51.019531],[55.68623,50.582861],[54.641602,51.011572],[54.555273,50.535791],[53.338086,51.482373],[52.219141,51.709375],[51.344531,51.475342],[50.793945,51.729199],[48.625098,50.612695],[48.758984,49.92832],[48.334961,49.858252],[47.429199,50.357959],[46.889551,49.696973],[47.031348,49.150293],[46.660938,48.412256],[47.292383,47.740918],[48.166992,47.708789],[48.959375,46.774609],[48.541211,46.605615],[49.232227,46.337158],[51.178027,47.110156],[52.138281,46.828613],[52.483203,46.990674],[53.069434,46.856055],[53.135254,46.19165],[52.773828,45.572754],[53.200391,45.331982],[51.415723,45.357861],[51.009375,44.921826],[51.543555,44.531006],[50.409473,44.624023],[50.25293,44.461523],[50.830762,44.192773],[51.29541,43.174121],[52.596582,42.760156],[52.493848,41.780371],[53.055859,42.147754],[54.120996,42.335205],[55.434375,41.296289],[55.977441,41.322217],[55.975684,44.994922],[58.555273,45.555371],[61.00791,44.393799],[61.990234,43.492139],[64.905469,43.714697],[65.803027,42.876953],[66.100293,42.99082],[66.00957,42.004883],[66.498633,41.994873],[66.709668,41.17915],[67.935742,41.196582],[68.291895,40.656104],[68.572656,40.622656],[69.153613,41.425244],[70.946777,42.248682]]]}}, - {"type":"Feature","properties":{"name":"约旦","full_name":"约旦哈希姆王国","iso_a2":"JO","iso_a3":"JOR","iso_n3":"400"},"geometry":{"type":"Polygon","coordinates":[[[35.787305,32.734912],[35.551465,32.395508],[35.450586,31.479297],[34.973438,29.555029],[34.950781,29.353516],[36.068457,29.200537],[36.755273,29.866016],[37.469238,29.995068],[37.980078,30.5],[36.958594,31.491504],[39.14541,32.124512],[38.773535,33.372217],[36.818359,32.317285],[35.787305,32.734912]]]}}, - {"type":"Feature","properties":{"name":"日本","full_name":"日本国","iso_a2":"JP","iso_a3":"JPN","iso_n3":"392"},"geometry":{"type":"MultiPolygon","coordinates":[[[[134.932812,34.288135],[135.004688,34.544043],[134.667871,34.294141],[134.932812,34.288135]]],[[[143.824316,44.116992],[141.937695,45.509521],[141.667969,45.40127],[141.760938,44.48252],[141.374121,43.279639],[140.392383,43.303125],[140.432227,42.954102],[139.860156,42.581738],[140.085156,41.434082],[140.659863,41.815576],[141.150977,41.805078],[140.32666,42.293359],[140.480469,42.559375],[140.986133,42.342139],[141.851367,42.579053],[143.236523,42.000195],[143.969336,42.881396],[145.833008,43.385938],[145.34082,43.302539],[145.139648,43.6625],[145.369531,44.327393],[144.715234,43.927979],[143.824316,44.116992]]],[[[131.174609,33.602588],[130.715625,33.927783],[129.580078,33.236279],[129.991699,32.851562],[129.679102,33.059961],[129.768555,32.570996],[130.34043,32.701855],[130.2375,33.177637],[130.547266,32.831592],[130.147266,31.408496],[130.58877,31.178516],[130.77627,31.706299],[130.685742,31.015137],[131.070801,31.436865],[131.337207,31.404688],[132.002148,32.882373],[131.896582,33.25459],[131.537402,33.274072],[131.696289,33.602832],[131.174609,33.602588]]],[[[134.357422,34.256348],[133.94834,34.348047],[133.193066,33.933203],[132.935156,34.095312],[132.032617,33.33999],[132.412793,33.430469],[132.495117,32.916602],[132.804297,32.752002],[133.632031,33.510986],[134.181641,33.247217],[134.738867,33.820508],[134.6375,34.226611],[134.357422,34.256348]]],[[[141.229297,41.372656],[140.936914,41.505566],[140.801855,41.253662],[141.244238,41.205615],[141.118555,40.882275],[140.748633,40.830322],[140.627637,41.19541],[140.344434,41.20332],[139.741504,39.92085],[140.064746,39.624414],[139.801953,38.881592],[139.363867,38.099023],[138.319922,37.218408],[137.246289,36.753174],[136.899902,37.117676],[137.322656,37.52207],[136.843457,37.382129],[135.903125,35.606885],[135.326953,35.525537],[135.174316,35.74707],[132.922949,35.511279],[131.354395,34.413184],[131.004199,34.392578],[130.918848,33.975732],[131.740527,34.052051],[132.146484,33.83877],[132.312598,34.324951],[133.142383,34.302441],[134.740039,34.765234],[135.415918,34.61748],[135.12793,34.006982],[135.695312,33.486963],[136.329883,34.176855],[136.853711,34.324072],[136.533008,34.678369],[136.804199,35.050293],[136.871289,34.733105],[137.275195,34.77251],[137.061719,34.582812],[138.189063,34.596338],[138.719629,35.124072],[138.8375,34.619238],[139.249414,35.278027],[139.675,35.149268],[139.834766,35.658057],[140.096875,35.585156],[139.843945,34.914893],[140.354688,35.181445],[140.874023,35.724951],[140.573535,36.231348],[141.00166,37.114648],[140.962109,38.148877],[141.46748,38.40415],[141.976953,39.428809],[141.430469,40.72334],[141.455469,41.404736],[141.229297,41.372656]]],[[[128.258789,26.652783],[128.254883,26.881885],[127.907227,26.693604],[127.653125,26.094727],[128.258789,26.652783]]],[[[129.452539,28.208984],[129.689551,28.51748],[129.164648,28.249756],[129.452539,28.208984]]]]}}, - {"type":"Feature","properties":{"name":"牙买加","full_name":"牙买加","iso_a2":"JM","iso_a3":"JAM","iso_n3":"388"},"geometry":{"type":"Polygon","coordinates":[[[-77.261475,18.457422],[-77.873438,18.522217],[-78.339502,18.287207],[-77.20498,17.714941],[-76.853223,17.97373],[-76.210791,17.913525],[-77.261475,18.457422]]]}}, - {"type":"Feature","properties":{"name":"意大利","full_name":"意大利共和国","iso_a2":"IT","iso_a3":"ITA","iso_n3":"380"},"geometry":{"type":"MultiPolygon","coordinates":[[[[7.021094,45.925781],[6.790918,45.740869],[7.146387,45.381738],[6.634766,45.068164],[6.992676,44.827295],[6.900195,44.335742],[7.637207,44.164844],[7.493164,43.767139],[8.76582,44.422314],[10.188086,43.94751],[10.514844,42.967529],[11.141211,42.389893],[15.692773,39.990186],[16.209961,38.941113],[15.645801,38.034229],[16.056836,37.941846],[16.616699,38.800146],[17.174609,38.998096],[17.114551,39.380615],[16.521875,39.747559],[16.928223,40.458057],[17.865039,40.280176],[18.34375,39.821387],[18.460645,40.221045],[15.900488,41.512061],[16.164648,41.896191],[15.16875,41.934033],[14.540723,42.244287],[13.56416,43.571289],[12.396289,44.223877],[12.274316,45.446045],[13.206348,45.771387],[13.719824,45.587598],[13.378223,46.261621],[13.7,46.520264],[12.388281,46.702637],[12.169434,47.082129],[10.993262,46.777002],[10.452832,46.864941],[10.12832,46.238232],[9.260156,46.475195],[9.02373,45.845703],[8.422559,46.446045],[7.787891,45.921826],[7.021094,45.925781]],[[12.43916,41.898389],[12.430566,41.897559],[12.430566,41.905469],[12.43916,41.898389]],[[12.485254,43.901416],[12.396875,43.93457],[12.503711,43.989746],[12.485254,43.901416]]],[[[15.576563,38.220312],[13.788867,37.981201],[12.734375,38.183057],[12.435547,37.819775],[15.112598,36.687842],[15.295703,37.055176],[15.099512,37.458594],[15.576563,38.220312]]],[[[9.632031,40.882031],[9.228418,41.25708],[8.571875,40.850195],[8.224219,40.91333],[8.648535,38.926562],[9.056348,39.23916],[9.5625,39.166016],[9.632031,40.882031]]]]}}, - {"type":"Feature","properties":{"name":"以色列","full_name":"以色列国","iso_a2":"IL","iso_a3":"ISR","iso_n3":"376"},"geometry":{"type":"Polygon","coordinates":[[[35.840723,33.415674],[35.108594,33.083691],[34.477344,31.584863],[34.245313,31.208301],[34.904297,29.477344],[34.973438,29.555029],[35.450586,31.479297],[34.872754,31.396875],[35.203711,31.75],[34.953809,31.84126],[35.065039,32.460449],[35.551465,32.395508],[35.787305,32.734912],[35.816125,33.361879],[35.840723,33.415674]]]}}, - {"type":"Feature","properties":{"name":"巴勒斯坦","full_name":"巴勒斯坦国","iso_a2":"PS","iso_a3":"PSE","iso_n3":"275"},"geometry":{"type":"MultiPolygon","coordinates":[[[[34.477344,31.584863],[34.198145,31.322607],[34.245313,31.208301],[34.477344,31.584863]]],[[[35.551465,32.395508],[35.065039,32.460449],[34.953809,31.84126],[35.203711,31.75],[34.872754,31.396875],[35.450586,31.479297],[35.551465,32.395508]]]]}}, - {"type":"Feature","properties":{"name":"爱尔兰","full_name":"爱尔兰","iso_a2":"IE","iso_a3":"IRL","iso_n3":"372"},"geometry":{"type":"Polygon","coordinates":[[[-7.218652,55.091992],[-6.96167,55.237891],[-7.308789,55.36582],[-7.65874,54.970947],[-7.66709,55.256494],[-8.274609,55.146289],[-8.763916,54.681201],[-8.133447,54.64082],[-8.545557,54.241211],[-10.056396,54.257812],[-9.578223,53.80542],[-10.09126,53.412842],[-8.930127,53.20708],[-9.916602,52.569727],[-8.783447,52.679639],[-10.356689,52.206934],[-9.909668,52.122949],[-10.341064,51.798926],[-9.598828,51.874414],[-10.120752,51.600684],[-8.813428,51.584912],[-8.40918,51.88877],[-6.325,52.24668],[-6.027393,52.9271],[-6.218018,54.088721],[-6.649805,54.058643],[-7.007715,54.406689],[-7.606543,54.143848],[-8.118262,54.414258],[-7.218652,55.091992]]]}}, - {"type":"Feature","properties":{"name":"伊拉克","full_name":"伊拉克共和国","iso_a2":"IQ","iso_a3":"IRQ","iso_n3":"368"},"geometry":{"type":"Polygon","coordinates":[[[42.358984,37.108594],[41.295996,36.38335],[40.987012,34.429053],[38.773535,33.372217],[39.14541,32.124512],[40.369336,31.938965],[42.074414,31.080371],[44.69082,29.202344],[46.531445,29.09624],[47.148242,30.000977],[47.978711,29.982812],[48.546484,29.962354],[48.014941,30.465625],[48.010645,30.989795],[47.679492,31.002393],[47.82998,31.794434],[47.371289,32.42373],[46.112793,32.957666],[46.019922,33.415723],[45.39707,33.97085],[46.273438,35.773242],[45.361621,36.015332],[44.765137,37.142432],[44.281836,36.978027],[44.114453,37.301855],[42.774609,37.371875],[42.358984,37.108594]]]}}, - {"type":"Feature","properties":{"name":"伊朗","full_name":"伊朗伊斯兰共和国","iso_a2":"IR","iso_a3":"IRN","iso_n3":"364"},"geometry":{"type":"MultiPolygon","coordinates":[[[[56.187988,26.921143],[55.757617,26.947656],[55.762598,26.811963],[55.311523,26.592627],[56.187988,26.921143]]],[[[53.91416,37.343555],[53.970117,36.818311],[52.190137,36.621729],[51.118555,36.742578],[50.130469,37.407129],[49.171191,37.600586],[48.86875,38.435498],[47.996484,38.85376],[48.322168,39.399072],[47.995898,39.683936],[46.490625,38.906689],[46.114453,38.877783],[45.479688,39.00625],[44.817188,39.650439],[44.587109,39.768555],[44.389355,39.422119],[44.023242,39.377441],[44.449902,38.334229],[44.211328,37.908057],[44.589941,37.710352],[44.765137,37.142432],[45.361621,36.015332],[46.273438,35.773242],[45.39707,33.97085],[46.019922,33.415723],[46.112793,32.957666],[47.371289,32.42373],[47.82998,31.794434],[47.679492,31.002393],[48.010645,30.989795],[48.014941,30.465625],[48.546484,29.962354],[48.919141,30.120898],[49.001953,30.506543],[49.554883,30.028955],[50.071582,30.198535],[51.278906,28.131348],[53.705762,26.725586],[54.759277,26.505078],[55.424023,26.770557],[55.650293,26.977539],[56.356152,27.200244],[56.982227,26.905469],[57.33457,25.791553],[59.046094,25.417285],[61.587891,25.202344],[61.842383,26.225928],[63.157812,26.649756],[63.301563,27.151465],[62.762988,27.250195],[62.758008,28.243555],[61.889844,28.546533],[60.843359,29.858691],[61.81084,30.913281],[61.660156,31.382422],[60.820703,31.495166],[60.561914,33.058789],[60.916992,33.505225],[60.51084,33.638916],[60.485742,34.094775],[60.889453,34.319434],[60.72627,34.518262],[61.262012,35.61958],[61.119629,36.642578],[60.341309,36.637646],[59.301758,37.510645],[58.261621,37.66582],[57.193555,38.216406],[55.380859,38.051123],[54.699414,37.470166],[53.91416,37.343555]]]]}}, - {"type":"Feature","properties":{"name":"印度尼西亚","full_name":"印度尼西亚共和国","iso_a2":"ID","iso_a3":"IDN","iso_n3":"360"},"geometry":{"type":"MultiPolygon","coordinates":[[[[97.481543,1.465088],[97.079199,1.425488],[97.82041,0.564453],[97.931934,0.973926],[97.481543,1.465088]]],[[[99.163867,-1.77793],[98.932617,-0.954004],[98.676074,-0.970508],[98.827734,-1.609961],[99.163867,-1.77793]]],[[[116.64082,-8.613867],[116.718945,-8.336035],[116.401563,-8.204199],[115.857324,-8.787891],[116.586523,-8.886133],[116.64082,-8.613867]]],[[[115.447852,-8.155176],[114.467578,-8.166309],[115.055078,-8.573047],[115.144922,-8.849023],[115.704297,-8.407129],[115.447852,-8.155176]]],[[[106.045703,-1.669434],[105.45957,-1.574707],[105.133398,-2.042578],[105.78584,-2.181348],[105.99873,-2.824902],[106.667188,-3.071777],[106.818457,-2.57334],[106.365918,-2.464844],[106.045703,-1.669434]]],[[[123.179785,-4.551172],[123.074609,-4.386914],[122.85332,-4.618359],[122.64502,-5.663379],[123.187305,-5.333008],[122.97168,-5.138477],[123.179785,-4.551172]]],[[[122.645117,-5.269434],[122.701953,-4.618652],[122.368945,-4.767188],[122.283105,-5.319531],[122.645117,-5.269434]]],[[[108.316016,3.689648],[108.24834,4.217139],[108.002344,3.982861],[108.316016,3.689648]]],[[[108.207227,-2.997656],[108.215137,-2.696973],[107.666309,-2.566309],[107.614453,-3.209375],[108.055273,-3.226855],[108.207227,-2.997656]]],[[[135.383008,-0.651367],[135.915039,-1.178418],[136.375293,-1.094043],[135.383008,-0.651367]]],[[[135.474219,-1.591797],[136.228125,-1.893652],[136.892578,-1.799707],[135.474219,-1.591797]]],[[[130.813281,-0.004102],[130.236621,-0.209668],[130.750195,-0.443848],[130.899219,-0.344434],[130.622168,-0.085938],[131.005371,-0.360742],[131.339746,-0.290332],[130.813281,-0.004102]]],[[[128.453906,2.051758],[128.602148,2.597607],[128.217969,2.297461],[128.453906,2.051758]]],[[[128.153027,-1.660547],[127.64668,-1.332422],[127.39502,-1.589844],[128.153027,-1.660547]]],[[[126.024219,-1.789746],[125.387207,-1.843066],[126.331738,-1.822852],[126.024219,-1.789746]]],[[[124.969531,-1.705469],[124.417578,-1.659277],[124.329688,-1.858887],[125.314063,-1.877148],[124.969531,-1.705469]]],[[[131.325586,-7.999512],[131.624414,-7.626172],[131.643457,-7.112793],[131.137793,-7.684863],[131.11377,-7.997363],[131.325586,-7.999512]]],[[[126.800977,-7.667871],[125.975293,-7.663379],[125.798242,-7.98457],[126.47207,-7.950391],[126.800977,-7.667871]]],[[[124.575586,-8.14082],[124.380664,-8.415137],[125.131738,-8.326465],[124.575586,-8.14082]]],[[[131.001855,-1.315527],[131.033008,-0.917578],[130.672949,-0.959766],[131.001855,-1.315527]]],[[[96.492578,5.229346],[95.227832,5.564795],[95.578613,4.661963],[96.968945,3.575146],[97.59082,2.846582],[97.700781,2.358545],[98.595313,1.8646],[99.15918,0.351758],[99.669824,0.045068],[100.308203,-0.82666],[100.889551,-2.248535],[101.578613,-3.166992],[104.601562,-5.90459],[104.639551,-5.52041],[105.081348,-5.745508],[105.349414,-5.549512],[105.74834,-5.818262],[106.044336,-3.10625],[105.396973,-2.380176],[104.650781,-2.595215],[104.845215,-2.092969],[104.515918,-1.819434],[104.360547,-1.038379],[103.721094,-0.886719],[103.438574,-0.575586],[103.428516,-0.191797],[103.786719,0.046973],[103.672656,0.288916],[103.338965,0.513721],[102.55,0.216455],[103.031836,0.578906],[102.389941,0.841992],[102.019922,1.442139],[101.47666,1.693066],[101.046191,2.257471],[100.828223,2.242578],[100.887891,1.948242],[100.523828,2.18916],[99.732324,3.183057],[98.307324,4.092871],[97.547168,5.205859],[96.492578,5.229346]]],[[[122.78291,-8.611719],[122.916992,-8.105566],[122.323242,-8.62832],[120.610254,-8.24043],[119.874805,-8.419824],[119.80791,-8.697656],[121.035254,-8.935449],[122.78291,-8.611719]]],[[[120.0125,-9.374707],[118.958789,-9.519336],[120.144824,-10.200098],[120.698047,-10.206641],[120.784473,-9.957031],[120.0125,-9.374707]]],[[[118.242383,-8.317773],[117.755273,-8.149512],[118.234863,-8.591895],[117.969531,-8.728027],[117.164844,-8.367188],[116.835059,-8.532422],[116.788477,-9.006348],[119.129687,-8.668164],[118.926172,-8.297656],[118.242383,-8.317773]]],[[[124.888867,0.995312],[125.233789,1.502295],[125.110938,1.685693],[123.930762,0.850439],[122.838281,0.845703],[121.081738,1.327637],[120.602539,0.854395],[120.269531,0.970801],[119.721875,-0.088477],[119.844336,-0.861914],[119.711328,-0.680762],[119.508203,-0.906738],[119.321875,-1.929688],[118.783301,-2.720801],[118.867676,-3.398047],[119.46748,-3.512988],[119.623633,-4.034375],[119.360352,-5.31416],[119.557422,-5.611035],[120.430371,-5.591016],[120.261035,-2.949316],[120.653613,-2.667578],[121.052148,-2.75166],[120.891797,-3.520605],[121.618066,-4.092676],[121.588672,-4.75957],[122.038086,-4.832422],[122.114258,-4.540234],[122.872266,-4.391992],[122.847949,-4.064551],[122.25293,-3.62041],[122.291699,-2.907617],[121.355469,-1.878223],[122.250684,-1.555273],[122.902832,-0.900977],[123.37793,-1.004102],[123.43418,-0.778223],[123.171484,-0.570703],[121.969629,-0.933301],[121.575586,-0.828516],[121.148535,-1.339453],[120.667383,-1.370117],[120.062891,-0.555566],[120.192285,0.268506],[120.579004,0.52832],[123.753809,0.305518],[124.427539,0.470605],[124.888867,0.995312]]],[[[107.373926,-6.007617],[106.075,-5.91416],[105.255469,-6.835254],[106.198242,-6.927832],[106.519727,-7.053711],[106.455273,-7.368652],[107.91748,-7.724121],[109.281641,-7.704883],[111.509961,-8.305078],[113.25332,-8.286719],[114.583789,-8.769629],[114.386914,-8.405176],[114.409277,-7.79248],[113.013574,-7.657715],[112.539258,-6.926465],[111.181543,-6.686719],[110.834766,-6.424219],[110.42627,-6.947266],[108.677832,-6.790527],[108.330176,-6.286035],[107.373926,-6.007617]]],[[[109.628906,2.027539],[109.075879,1.495898],[108.944531,0.355664],[109.25752,0.031152],[109.258789,-0.807422],[109.983301,-1.274805],[110.256055,-2.966113],[111.694727,-2.889453],[111.858105,-3.551855],[112.758008,-3.322168],[113.033984,-2.933496],[113.705078,-3.455273],[114.344336,-3.235156],[114.693555,-4.169727],[115.956152,-3.59502],[116.257227,-3.126367],[116.166309,-2.93457],[116.56543,-2.299707],[116.275488,-1.784863],[116.753418,-1.327344],[116.739844,-1.044238],[116.913965,-1.223633],[117.5625,-0.770898],[117.522168,0.235889],[117.911621,1.098682],[118.534766,0.813525],[118.984961,0.982129],[117.789258,2.026855],[118.066602,2.317822],[117.055957,3.622656],[117.777246,3.689258],[117.574414,4.170605],[116.514746,4.370801],[115.678809,4.193018],[115.454395,3.034326],[115.117578,2.894873],[115.179102,2.523193],[114.786426,2.250488],[114.5125,1.452002],[113.622266,1.235938],[112.476172,1.559082],[111.808984,1.01167],[110.505762,0.861963],[109.654004,1.614893],[109.628906,2.027539]]],[[[127.732715,0.848145],[127.652832,1.013867],[128.011719,1.331738],[128.036426,2.199023],[127.631738,1.843701],[127.428516,1.13999],[127.691602,-0.241895],[128.425488,-0.892676],[127.887402,0.29834],[127.983105,0.471875],[128.899609,0.21626],[128.260645,0.733789],[128.702637,1.106396],[128.688379,1.572559],[127.732715,0.848145]]],[[[129.754688,-2.86582],[128.198535,-2.865918],[127.902344,-3.496289],[128.132031,-3.157422],[128.516602,-3.449121],[128.8625,-3.234961],[129.467676,-3.453223],[129.844141,-3.327148],[130.805078,-3.857715],[130.569922,-3.130859],[129.754688,-2.86582]]],[[[126.861133,-3.087891],[126.088281,-3.105469],[126.056543,-3.420996],[126.686328,-3.823633],[127.22959,-3.633008],[126.861133,-3.087891]]],[[[124.922266,-8.94248],[124.444434,-9.190332],[124.282324,-9.42793],[124.036328,-9.341602],[123.589258,-9.966797],[123.747266,-10.347168],[124.427539,-10.148633],[125.068164,-9.511914],[124.960156,-9.21377],[125.149023,-9.042578],[124.922266,-8.94248]]],[[[134.746973,-5.707031],[134.570801,-5.427344],[134.205371,-5.707227],[134.343066,-5.833008],[134.154883,-6.062891],[134.441113,-6.334863],[134.71416,-6.295117],[134.746973,-5.707031]]],[[[134.536816,-6.442285],[134.114648,-6.19082],[134.09082,-6.833789],[134.322754,-6.84873],[134.536816,-6.442285]]],[[[138.535352,-8.273633],[138.989063,-7.696094],[138.769824,-7.39043],[138.081836,-7.566211],[137.650391,-8.386133],[138.535352,-8.273633]]],[[[140.973438,-2.609766],[139.789551,-2.348242],[137.80625,-1.483203],[137.123438,-1.840918],[137.07207,-2.105078],[136.389941,-2.27334],[135.85918,-2.995313],[135.251563,-3.368555],[134.886816,-3.209863],[134.627441,-2.536719],[134.459961,-2.832324],[134.194824,-2.309082],[134.25957,-1.362988],[133.974512,-0.744336],[132.39375,-0.355469],[131.257227,-0.855469],[130.995898,-1.424707],[131.930371,-1.559668],[132.307617,-2.242285],[133.921582,-2.102051],[133.700098,-2.624609],[133.191016,-2.437793],[132.725,-2.789062],[131.971191,-2.788574],[132.751367,-3.294629],[132.914453,-4.056934],[133.400879,-3.899023],[133.841504,-3.054785],[133.67832,-3.479492],[133.973828,-3.817969],[134.886523,-3.938477],[134.679688,-4.079102],[135.195605,-4.450684],[137.279785,-4.94541],[138.06084,-5.465234],[138.087109,-5.70918],[138.339648,-5.675684],[138.199609,-5.807031],[138.864551,-6.858398],[138.601367,-6.936523],[139.176855,-7.19043],[138.747949,-7.251465],[139.087988,-7.587207],[138.890625,-8.237793],[139.248828,-7.982422],[139.385645,-8.189062],[140.116992,-7.92373],[140.00293,-8.195508],[140.976172,-9.11875],[140.973438,-2.609766]]],[[[138.895117,-8.388672],[138.796191,-8.173633],[138.563379,-8.309082],[138.895117,-8.388672]]],[[[101.708105,2.078418],[101.409668,2.02168],[101.500781,1.733203],[101.719434,1.78916],[101.708105,2.078418]]],[[[103.027539,0.746631],[102.506641,1.08877],[102.49043,0.856641],[103.027539,0.746631]]],[[[104.585352,1.216113],[104.251953,1.014893],[104.575195,0.831934],[104.585352,1.216113]]],[[[104.778613,-0.175977],[104.542676,0.017725],[104.44707,-0.18916],[105.005371,-0.282812],[104.778613,-0.175977]]],[[[104.474219,-0.334668],[104.257129,-0.463281],[104.363184,-0.658594],[104.590137,-0.466602],[104.474219,-0.334668]]],[[[109.710254,-1.180664],[109.475977,-0.985352],[109.463672,-1.277539],[109.710254,-1.180664]]],[[[113.844531,-7.105371],[114.073633,-6.960156],[113.067383,-6.87998],[112.725879,-7.072754],[113.844531,-7.105371]]],[[[127.566992,-0.318945],[127.3,-0.500293],[127.761133,-0.883691],[127.566992,-0.318945]]],[[[128.275586,-3.674609],[128.329102,-3.515918],[127.925,-3.699316],[128.275586,-3.674609]]],[[[130.35332,-1.690527],[129.737695,-1.866895],[130.248047,-2.047754],[130.35332,-1.690527]]],[[[100.425098,-3.18291],[100.198535,-2.785547],[100.465137,-3.328516],[100.425098,-3.18291]]],[[[100.204102,-2.741016],[99.987891,-2.525391],[100.014941,-2.819727],[100.204102,-2.741016]]],[[[98.459277,-0.530469],[98.544141,-0.257617],[98.322949,-0.000781],[98.459277,-0.530469]]],[[[122.042969,-5.437988],[121.913672,-5.072266],[121.808496,-5.256152],[122.042969,-5.437988]]],[[[123.212305,-1.171289],[122.908008,-1.182227],[122.89043,-1.587207],[123.150391,-1.304492],[123.172949,-1.616016],[123.511914,-1.447363],[123.212305,-1.171289]]],[[[120.52832,-6.298438],[120.477344,-5.775293],[120.487305,-6.464844],[120.52832,-6.298438]]],[[[115.377051,-6.970801],[115.546094,-6.938672],[115.240527,-6.86123],[115.377051,-6.970801]]],[[[116.30332,-3.868164],[116.269727,-3.251074],[116.058789,-4.006934],[116.30332,-3.868164]]],[[[122.948926,-10.909277],[123.418164,-10.65127],[123.371094,-10.474902],[122.845703,-10.761816],[122.948926,-10.909277]]],[[[123.924805,-8.272461],[123.391211,-8.280469],[123.230078,-8.530664],[123.924805,-8.272461]]],[[[123.242383,-4.112988],[122.969043,-4.02998],[123.076172,-4.227148],[123.242383,-4.112988]]],[[[117.649023,4.168994],[117.736816,4.004004],[117.884766,4.186133],[117.649023,4.168994]]],[[[121.883008,-10.590332],[121.99834,-10.446973],[121.704688,-10.555664],[121.883008,-10.590332]]]]}}, - {"type":"Feature","properties":{"name":"印度","full_name":"印度共和国","iso_a2":"IN","iso_a3":"IND","iso_n3":"356"},"geometry":{"type":"MultiPolygon","coordinates":[[[[68.165039,23.857324],[68.234961,23.596973],[68.776758,23.8521],[68.41748,23.571484],[69.235937,22.848535],[69.664648,22.759082],[70.489258,23.089502],[70.177246,22.572754],[68.969922,22.290283],[70.485059,20.840186],[71.024609,20.738867],[72.015234,21.155713],[72.254004,21.531006],[72.037207,21.823047],[72.182813,22.269727],[72.80918,22.233301],[72.553027,22.159961],[72.543066,21.696582],[73.1125,21.750439],[72.613281,21.461816],[72.89375,20.672754],[72.667773,19.830957],[72.987207,19.277441],[72.803027,19.079297],[72.97207,19.15332],[72.875488,18.642822],[73.476074,16.054248],[74.382227,14.494727],[74.945508,12.564551],[75.723828,11.361768],[76.553418,8.902783],[77.517578,8.07832],[78.060156,8.38457],[78.421484,9.105029],[79.411426,9.192383],[78.939941,9.565771],[79.314551,10.256689],[79.838184,10.322559],[79.693164,11.312549],[80.342383,13.361328],[80.062109,13.60625],[80.306543,13.485059],[80.053418,15.074023],[80.293457,15.710742],[80.646582,15.89502],[80.978711,15.75835],[81.286133,16.337061],[82.258789,16.559863],[82.35957,17.096191],[84.104102,18.292676],[85.441602,19.626562],[85.180762,19.594873],[85.248633,19.757666],[86.279492,19.919434],[86.975488,20.700146],[86.954102,21.365332],[87.82373,21.727344],[88.159277,22.121729],[87.941406,22.374316],[88.196289,22.139551],[88.12207,21.635791],[88.584668,21.659717],[88.641602,22.121973],[88.74502,21.584375],[89.05166,21.654102],[89.051465,22.093164],[88.928125,23.186621],[88.567383,23.674414],[88.723535,24.274902],[88.023438,24.627832],[88.45625,25.188428],[88.95166,25.259277],[88.08457,25.888232],[88.418164,26.571533],[88.828027,26.252197],[89.018652,26.410254],[89.369727,26.006104],[89.670898,26.213818],[89.833301,25.292773],[92.049707,25.169482],[92.468359,24.944141],[91.876953,24.195312],[91.36709,24.093506],[91.160449,23.660645],[91.315234,23.104395],[91.619531,22.979688],[91.92959,23.685986],[92.246094,23.683594],[92.574902,21.978076],[93.151172,22.230615],[93.32627,24.064209],[94.127637,23.876465],[94.707617,25.04873],[94.579883,25.319824],[95.132422,26.04126],[95.128711,26.597266],[96.19082,27.261279],[96.731641,27.331494],[97.102051,27.11543],[96.876855,27.586719],[97.086778,27.7475],[96.275479,28.228241],[95.832003,28.295186],[95.39715,28.142259],[95.28628,27.939955],[94.88592,27.743098],[94.277372,27.58143],[93.817265,27.025183],[93.111399,26.880082],[92.64698,26.952656],[91.99834,26.85498],[89.609961,26.719434],[88.857617,26.961475],[88.891406,27.316064],[88.803711,28.006934],[88.109766,27.870605],[87.995117,26.382373],[85.794531,26.60415],[84.091016,27.491357],[82.733398,27.518994],[80.070703,28.830176],[80.401855,29.730273],[81.010254,30.164502],[79.707774,31.013593],[78.807678,31.099982],[78.389648,32.519873],[78.700879,32.597021],[78.918945,32.358203],[79.219336,32.501074],[78.72666,34.013379],[78.970117,34.302637],[78.326953,34.606396],[78.042676,35.479785],[77.799414,35.495898],[77.048633,35.109912],[75.70918,34.503076],[73.96123,34.653467],[73.904102,34.075684],[74.250879,33.946094],[73.976465,33.721289],[74.003809,33.189453],[74.35459,32.768701],[74.663281,32.757666],[74.685742,32.493799],[75.333496,32.279199],[74.555566,31.818555],[74.632812,31.034668],[73.80918,30.093359],[73.381641,29.934375],[72.90332,29.02876],[72.341895,28.751904],[71.870313,27.9625],[70.797949,27.709619],[70.403711,28.025049],[69.537012,27.122949],[69.506934,26.742676],[70.147656,26.506445],[70.100195,25.910059],[70.648438,25.666943],[71.044043,24.400098],[69.805176,24.165234],[68.781152,24.313721],[68.724121,23.964697],[68.165039,23.857324]]],[[[93.890039,6.831055],[93.822461,7.236621],[93.658008,7.016064],[93.890039,6.831055]]],[[[92.502832,10.554883],[92.510352,10.897461],[92.352832,10.751123],[92.502832,10.554883]]],[[[92.722754,11.536084],[93.062305,13.545459],[92.533887,11.873389],[92.722754,11.536084]]]]}}, - {"type":"Feature","properties":{"name":"冰岛","full_name":"冰岛共和国","iso_a2":"IS","iso_a3":"ISL","iso_n3":"352"},"geometry":{"type":"Polygon","coordinates":[[[-15.543115,66.228516],[-16.249316,66.5229],[-16.48501,66.195947],[-17.550439,65.964404],[-18.297168,66.157422],[-18.141943,65.734082],[-18.845898,66.183936],[-19.489697,65.768066],[-20.20752,66.100098],[-21.129687,65.266602],[-21.406885,66.025586],[-22.944336,66.429443],[-22.441699,65.908301],[-23.452539,66.181006],[-23.832617,65.849219],[-23.285352,65.75],[-24.092627,65.776465],[-23.856738,65.538379],[-24.475684,65.525195],[-21.844385,65.447363],[-22.509082,65.196777],[-21.892139,65.048779],[-24.026172,64.863428],[-21.590625,64.626367],[-22.053369,64.313916],[-21.46333,64.37915],[-22.701172,64.083203],[-22.652197,63.827734],[-21.152393,63.944531],[-20.198145,63.555811],[-18.653613,63.406689],[-14.927393,64.319678],[-13.599316,65.035938],[-13.617871,65.519336],[-14.8271,65.764258],[-15.117383,66.125635],[-14.59585,66.381543],[-15.543115,66.228516]]]}}, - {"type":"Feature","properties":{"name":"匈牙利","full_name":"匈牙利","iso_a2":"HU","iso_a3":"HUN","iso_n3":"348"},"geometry":{"type":"Polygon","coordinates":[[[22.131836,48.405322],[20.490039,48.526904],[19.950391,48.146631],[18.791895,48.000293],[18.724219,47.787158],[17.761914,47.770166],[17.147363,48.005957],[17.066602,47.707568],[16.421289,47.674463],[16.676563,47.536035],[16.093066,46.863281],[16.516211,46.499902],[17.807129,45.79043],[18.905371,45.931738],[20.241797,46.108594],[21.12168,46.282422],[21.999707,47.505029],[22.87666,47.947266],[22.131836,48.405322]]]}}, - {"type":"Feature","properties":{"name":"洪都拉斯","full_name":"洪都拉斯共和国","iso_a2":"HN","iso_a3":"HND","iso_n3":"340"},"geometry":{"type":"Polygon","coordinates":[[[-83.15752,14.993066],[-84.095068,15.400928],[-83.765283,15.405469],[-84.261426,15.822607],[-84.97373,15.989893],[-88.22832,15.729004],[-89.142578,15.072314],[-89.362598,14.416016],[-88.482666,13.854248],[-87.802246,13.88999],[-87.814209,13.39917],[-87.489111,13.35293],[-87.337256,12.979248],[-86.729297,13.284375],[-86.733643,13.763477],[-86.040381,14.050146],[-85.733936,13.858691],[-84.985156,14.752441],[-84.453564,14.643701],[-83.15752,14.993066]]]}}, - {"type":"Feature","properties":{"name":"海地","full_name":"海地共和国","iso_a2":"HT","iso_a3":"HTI","iso_n3":"332"},"geometry":{"type":"Polygon","coordinates":[[[-71.779248,19.718164],[-73.400537,19.807422],[-72.703223,19.441064],[-72.811084,19.071582],[-72.376074,18.574463],[-72.789355,18.434814],[-74.227734,18.662695],[-74.478125,18.45],[-73.884961,18.041895],[-73.385156,18.251172],[-71.768311,18.03916],[-72.000391,18.5979],[-71.645312,19.163525],[-71.779248,19.718164]]]}}, - {"type":"Feature","properties":{"name":"圭亚那","full_name":"圭亚那合作共和国","iso_a2":"GY","iso_a3":"GUY","iso_n3":"328"},"geometry":{"type":"Polygon","coordinates":[[[-60.742139,5.202051],[-60.142041,5.238818],[-59.990674,5.082861],[-60.148633,4.533252],[-59.703271,4.381104],[-59.551123,3.933545],[-59.854395,3.5875],[-59.994336,2.68999],[-59.666602,1.746289],[-59.231201,1.376025],[-58.821777,1.201221],[-57.31748,1.963477],[-56.482812,1.942139],[-57.197363,2.853271],[-57.303662,3.3771],[-57.646729,3.394531],[-58.054297,4.10166],[-57.917041,4.82041],[-57.331006,5.020166],[-57.194775,5.548438],[-57.227539,6.178418],[-57.982568,6.785889],[-58.41499,6.851172],[-58.672949,6.390771],[-58.511084,7.398047],[-60.017529,8.549316],[-59.849072,8.248682],[-60.718652,7.535937],[-60.3521,7.002881],[-61.145605,6.694531],[-61.128711,6.214307],[-61.39082,5.93877],[-60.742139,5.202051]]]}}, - {"type":"Feature","properties":{"name":"几内亚比绍","full_name":"几内亚比绍共和国","iso_a2":"GW","iso_a3":"GNB","iso_n3":"624"},"geometry":{"type":"Polygon","coordinates":[[[-16.711816,12.354834],[-16.24458,12.237109],[-16.328076,12.051611],[-15.941748,11.786621],[-15.078271,11.968994],[-15.501904,11.723779],[-15.072656,11.597803],[-15.479492,11.410303],[-15.043018,10.940137],[-14.682959,11.508496],[-13.732764,11.736035],[-13.948877,12.178174],[-13.70791,12.312695],[-13.729248,12.673926],[-15.196094,12.679932],[-16.711816,12.354834]]]}}, - {"type":"Feature","properties":{"name":"几内亚","full_name":"几内亚共和国","iso_a2":"GN","iso_a3":"GIN","iso_n3":"324"},"geometry":{"type":"Polygon","coordinates":[[[-10.283203,8.485156],[-9.518262,8.346094],[-9.463818,7.415869],[-9.117578,7.215918],[-8.659766,7.688379],[-8.486426,7.558496],[-8.231885,7.556738],[-8.009863,8.078516],[-8.236963,8.455664],[-7.681201,8.410352],[-7.950977,8.786816],[-7.896191,9.415869],[-8.136963,9.495703],[-7.990625,10.1625],[-8.337402,10.990625],[-8.666699,11.009473],[-8.398535,11.366553],[-8.822021,11.673242],[-9.043066,12.402344],[-9.395361,12.464648],[-9.358105,12.25542],[-9.754004,12.029932],[-10.274854,12.212646],[-10.709229,11.89873],[-10.933203,12.205176],[-11.305176,12.01543],[-11.502197,12.198633],[-11.389404,12.404395],[-12.399072,12.340088],[-13.729248,12.673926],[-13.70791,12.312695],[-13.948877,12.178174],[-13.732764,11.736035],[-14.682959,11.508496],[-15.043018,10.940137],[-14.593506,10.766699],[-14.426904,10.24834],[-13.689795,9.927783],[-13.691357,9.535791],[-13.292676,9.049219],[-12.427979,9.898145],[-11.273633,9.996533],[-10.690527,9.314258],[-10.500537,8.687549],[-10.712109,8.335254],[-10.283203,8.485156]]]}}, - {"type":"Feature","properties":{"name":"危地马拉","full_name":"危地马拉共和国","iso_a2":"GT","iso_a3":"GTM","iso_n3":"320"},"geometry":{"type":"Polygon","coordinates":[[[-92.235156,14.54541],[-91.377344,13.990186],[-90.095215,13.736523],[-89.362598,14.416016],[-89.142578,15.072314],[-88.22832,15.729004],[-88.894043,15.890625],[-89.2375,15.894434],[-89.161475,17.814844],[-90.98916,17.816406],[-90.992969,17.252441],[-91.409619,17.255859],[-90.416992,16.391016],[-90.447168,16.072705],[-91.736572,16.070166],[-92.204248,15.275],[-92.235156,14.54541]]]}}, - {"type":"Feature","properties":{"name":"格林纳达","full_name":"格林纳达","iso_a2":"GD","iso_a3":"GRD","iso_n3":"308"},"geometry":{"type":"Polygon","coordinates":[[[-61.715527,12.012646],[-61.607031,12.223291],[-61.71499,12.185156],[-61.715527,12.012646]]]}}, - {"type":"Feature","properties":{"name":"希腊","full_name":"希腊共和国","iso_a2":"GR","iso_a3":"GRC","iso_n3":"300"},"geometry":{"type":"MultiPolygon","coordinates":[[[[20.612305,38.38335],[20.352539,38.179883],[20.761328,38.070557],[20.612305,38.38335]]],[[[20.07793,39.432715],[19.926074,39.77373],[19.646484,39.76709],[20.07793,39.432715]]],[[[23.41543,38.958643],[22.870313,38.870508],[24.536523,37.979736],[24.127539,38.648486],[23.41543,38.958643]]],[[[26.824414,37.811426],[26.581055,37.72373],[27.055078,37.709277],[26.824414,37.811426]]],[[[26.094043,38.218066],[26.160352,38.540723],[25.846094,38.574023],[26.094043,38.218066]]],[[[26.410156,39.329443],[25.844141,39.200049],[26.46875,38.972803],[26.410156,39.329443]]],[[[27.842773,35.929297],[28.231836,36.433643],[27.716309,36.171582],[27.842773,35.929297]]],[[[23.852246,35.535449],[23.569824,35.534766],[23.592773,35.257227],[24.799805,34.934473],[26.165625,35.018604],[26.320215,35.315137],[25.791309,35.122852],[25.730176,35.348584],[23.852246,35.535449]]],[[[26.320898,41.716553],[25.92334,41.311914],[25.251172,41.243555],[24.487891,41.555225],[22.916016,41.336279],[20.964258,40.849902],[20.657422,40.117383],[20.00127,39.709424],[20.713379,39.035156],[21.118359,39.02998],[20.768555,38.874414],[21.182617,38.345557],[22.42168,38.438525],[23.148926,38.176074],[22.920313,37.958301],[21.824707,38.328125],[21.124707,37.891602],[21.678906,37.387207],[21.58291,37.080957],[21.892383,36.737305],[22.080469,37.028955],[22.427734,36.475781],[22.717188,36.793945],[23.160156,36.448096],[22.725391,37.542139],[23.489258,37.440186],[23.036328,37.878369],[23.501758,38.034863],[24.019727,37.677734],[24.024512,38.139795],[22.569141,38.86748],[23.066699,39.037939],[22.921387,39.306348],[23.327734,39.174902],[22.592188,40.036914],[22.629492,40.495557],[22.922266,40.590869],[23.627344,39.924072],[23.42627,40.263965],[23.94707,39.965576],[23.72793,40.329736],[24.343359,40.147705],[23.762793,40.747803],[25.104492,40.994727],[26.038965,40.726758],[26.624902,41.401758],[26.320898,41.716553]]]]}}, - {"type":"Feature","properties":{"name":"加纳","full_name":"加纳共和国","iso_a2":"GH","iso_a3":"GHA","iso_n3":"288"},"geometry":{"type":"Polygon","coordinates":[[[-0.068604,11.115625],[-0.627148,10.927393],[-2.829932,10.998389],[-2.69585,9.481348],[-2.505859,8.20874],[-3.235791,6.807227],[-2.75498,5.43252],[-3.019141,5.130811],[-3.086719,5.12832],[-3.114014,5.088672],[-2.001855,4.762451],[0.259668,5.757324],[0.949707,5.810254],[1.187207,6.089404],[0.525586,6.850928],[0.686328,8.354883],[0.372559,8.759277],[0.525684,9.398486],[0.233398,9.463525],[0.380859,10.291846],[-0.086328,10.673047],[-0.068604,11.115625]]]}}, - {"type":"Feature","properties":{"name":"德国","full_name":"德意志联邦共和国","iso_a2":"DE","iso_a3":"DEU","iso_n3":"276"},"geometry":{"type":"MultiPolygon","coordinates":[[[[9.524023,47.524219],[10.183008,47.278809],[10.439453,47.551562],[11.041992,47.393115],[12.209277,47.718262],[13.014355,47.478076],[12.760352,48.106982],[13.814746,48.766943],[12.681152,49.414502],[12.089844,50.301758],[12.942676,50.406445],[14.319727,51.037793],[14.809375,50.858984],[15.016602,51.252734],[14.619434,52.528516],[14.128613,52.878223],[14.258887,53.729639],[12.575391,54.467383],[11.399609,53.944629],[10.85459,54.009814],[11.013379,54.37915],[9.868652,54.472461],[9.739746,54.825537],[8.670313,54.903418],[8.92041,53.965332],[9.783984,53.554639],[8.618945,53.875],[8.495215,53.394238],[8.009277,53.690723],[7.285254,53.681348],[7.197266,53.282275],[7.033008,52.651367],[6.710742,52.617871],[7.035156,52.380225],[6.800391,51.967383],[5.94873,51.802686],[5.993945,50.750439],[6.340918,50.451758],[6.116504,50.120996],[6.487305,49.798486],[6.344336,49.452734],[6.735449,49.160596],[8.134863,48.973584],[7.615625,47.592725],[8.454004,47.596191],[8.572656,47.775635],[9.524023,47.524219]]],[[[13.70918,54.382715],[13.336816,54.697119],[13.190039,54.325635],[13.70918,54.382715]]],[[[14.211426,53.950342],[13.827734,54.127246],[14.213672,53.870752],[14.211426,53.950342]]]]}}, - {"type":"Feature","properties":{"name":"格鲁吉亚","full_name":"格鲁吉亚","iso_a2":"GE","iso_a3":"GEO","iso_n3":"268"},"geometry":{"type":"Polygon","coordinates":[[[43.439453,41.107129],[45.001367,41.290967],[45.280957,41.449561],[46.534375,41.088574],[46.672559,41.286816],[46.182129,41.65708],[46.429883,41.890967],[45.638574,42.205078],[45.705273,42.498096],[44.870996,42.756396],[43.825977,42.571533],[42.760645,43.16958],[41.580566,43.219238],[40.648047,43.533887],[39.97832,43.419824],[41.48877,42.659326],[41.762988,41.97002],[41.510059,41.51748],[42.754102,41.578906],[43.439453,41.107129]]]}}, - {"type":"Feature","properties":{"name":"冈比亚","full_name":"冈比亚共和国","iso_a2":"GM","iso_a3":"GMB","iso_n3":"270"},"geometry":{"type":"Polygon","coordinates":[[[-16.562305,13.587305],[-16.351807,13.343359],[-15.42749,13.468359],[-16.413379,13.269727],[-16.669336,13.475],[-16.76333,13.06416],[-15.834277,13.156445],[-15.151123,13.556494],[-14.246777,13.23584],[-13.826709,13.407812],[-15.10835,13.812109],[-15.509668,13.58623],[-16.562305,13.587305]]]}}, - {"type":"Feature","properties":{"name":"加蓬","full_name":"加蓬共和国","iso_a2":"GA","iso_a3":"GAB","iso_n3":"266"},"geometry":{"type":"Polygon","coordinates":[[[13.293555,2.161572],[11.328711,2.167432],[11.335352,0.999707],[9.59082,1.031982],[9.617969,0.576514],[9.324805,0.5521],[10.001465,0.194971],[9.796777,0.044238],[9.354883,0.343604],[9.29668,-0.35127],[8.946387,-0.68877],[8.703125,-0.591016],[9.064648,-1.29834],[9.501074,-1.555176],[9.318848,-1.632031],[9.036328,-1.308887],[9.624609,-2.36709],[10.062012,-2.549902],[9.72207,-2.467578],[11.130176,-3.916309],[11.504297,-3.520312],[11.879883,-3.665918],[11.93418,-3.318555],[11.537793,-2.836719],[11.605469,-2.342578],[12.446387,-2.32998],[12.59043,-1.826855],[12.991992,-2.313379],[13.464941,-2.39541],[13.733789,-2.138477],[13.993848,-2.490625],[14.383984,-1.890039],[14.474121,-0.573438],[13.860059,-0.20332],[13.949609,0.353809],[14.429883,0.901465],[14.180859,1.370215],[13.216309,1.248438],[13.293555,2.161572]]]}}, - {"type":"Feature","properties":{"name":"法国","full_name":"法兰西共和国","iso_a2":"FR","iso_a3":"FRA","iso_n3":"250"},"geometry":{"type":"MultiPolygon","coordinates":[[[[9.480371,42.80542],[9.363184,43.017383],[9.313379,42.713184],[8.565625,42.357715],[8.80752,41.588379],[9.186133,41.384912],[9.550684,42.129736],[9.480371,42.80542]]],[[[7.615625,47.592725],[8.134863,48.973584],[6.735449,49.160596],[6.344336,49.452734],[5.789746,49.538281],[4.867578,49.788135],[4.818652,50.153174],[4.149316,49.971582],[4.174609,50.246484],[2.759375,50.750635],[2.524902,51.097119],[1.672266,50.88501],[1.592773,50.252197],[0.186719,49.703027],[0.416895,49.448389],[-1.138525,49.387891],[-1.258643,49.680176],[-1.856445,49.683789],[-1.376465,48.652588],[-2.692334,48.536816],[-3.231445,48.84082],[-4.7625,48.450244],[-4.241406,48.303662],[-4.678809,48.039502],[-4.312109,47.8229],[-1.742529,47.215967],[-2.19707,47.162939],[-2.059375,46.810303],[-1.146289,46.311377],[-1.195996,45.714453],[-0.548486,45.000586],[-1.081006,45.532422],[-1.076953,44.689844],[-1.484863,43.56377],[-1.794043,43.407324],[-1.46084,43.051758],[-0.586426,42.798975],[0.631641,42.6896],[0.696875,42.845117],[1.42832,42.595898],[1.706055,42.50332],[3.211426,42.431152],[3.051758,42.915137],[3.91084,43.563086],[6.115918,43.072363],[7.377734,43.731738],[7.39502,43.765332],[7.438672,43.750439],[7.493164,43.767139],[7.637207,44.164844],[6.900195,44.335742],[6.992676,44.827295],[6.634766,45.068164],[7.146387,45.381738],[6.790918,45.740869],[7.021094,45.925781],[6.758105,46.415771],[5.97002,46.214697],[6.968359,47.453223],[7.615625,47.592725]]],[[[55.797363,-21.339355],[55.661914,-20.90625],[55.311328,-20.904102],[55.362695,-21.273633],[55.797363,-21.339355]]],[[[-60.82627,14.494482],[-61.21333,14.848584],[-61.063721,14.46709],[-60.82627,14.494482]]],[[[-61.327148,16.23042],[-61.172607,16.256104],[-61.471191,16.506641],[-61.522168,16.228027],[-61.327148,16.23042]]],[[[-54.61626,2.326758],[-54.130078,2.121045],[-53.767773,2.354834],[-52.903467,2.211523],[-51.652539,4.061279],[-51.827539,4.635693],[-52.00293,4.352295],[-52.058105,4.717383],[-52.899316,5.425049],[-53.847168,5.782227],[-54.155957,5.358984],[-54.479688,4.836523],[-54.00957,3.448535],[-54.61626,2.326758]]]]}}, - {"type":"Feature","properties":{"name":"圣皮埃尔和密克隆群岛","full_name":"圣皮埃尔和密克隆群岛(法国)","iso_a2":"PM","iso_a3":"SPM","iso_n3":"666"},"geometry":{"type":"Polygon","coordinates":[[[-56.26709,46.838477],[-56.364648,47.098975],[-56.384766,46.819434],[-56.26709,46.838477]]]}}, - {"type":"Feature","properties":{"name":"瓦利斯和富图纳群岛","full_name":"瓦利斯和富图纳群岛","iso_a2":"WF","iso_a3":"WLF","iso_n3":"876"},"geometry":{"type":"Polygon","coordinates":[[[-178.04668,-14.318359],[-178.194385,-14.255469],[-178.158594,-14.311914],[-178.04668,-14.318359]]]}}, - {"type":"Feature","properties":{"name":"法属圣马丁","full_name":"法属圣马丁岛","iso_a2":"MF","iso_a3":"MAF","iso_n3":"663"},"geometry":{"type":"Polygon","coordinates":[[[-63.011182,18.068945],[-63.063086,18.115332],[-63.123047,18.068945],[-63.011182,18.068945]]]}}, - {"type":"Feature","properties":{"name":"圣巴泰勒米","full_name":"圣巴泰勒米(法国)","iso_a2":"BL","iso_a3":"BLM","iso_n3":"652"},"geometry":{"type":"Polygon","coordinates":[[[-62.831934,17.876465],[-62.799707,17.908691],[-62.874219,17.922266],[-62.831934,17.876465]]]}}, - {"type":"Feature","properties":{"name":"法属波利尼西亚","full_name":"法属波利尼西亚","iso_a2":"PF","iso_a3":"PYF","iso_n3":"258"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-149.321533,-17.690039],[-149.63501,-17.564258],[-149.181787,-17.862305],[-149.321533,-17.690039]]],[[[-139.024316,-9.695215],[-139.134082,-9.829492],[-138.827344,-9.741602],[-139.024316,-9.695215]]]]}}, - {"type":"Feature","properties":{"name":"新喀里多尼亚","full_name":"新喀里多尼亚","iso_a2":"NC","iso_a3":"NCL","iso_n3":"540"},"geometry":{"type":"MultiPolygon","coordinates":[[[[164.202344,-20.246094],[164.059668,-20.141504],[164.927441,-21.289844],[166.467969,-22.256055],[166.970313,-22.322852],[166.942383,-22.090137],[165.191797,-20.768848],[164.202344,-20.246094]]],[[[168.010938,-21.42998],[167.81543,-21.392676],[167.966797,-21.641602],[168.010938,-21.42998]]],[[[167.400879,-21.160645],[167.297949,-20.73252],[167.055762,-20.720215],[167.072656,-20.997266],[167.400879,-21.160645]]]]}}, - {"type":"Feature","properties":{"name":"法属南部和南极领地","full_name":"法属南部和南极领地","iso_a2":"TF","iso_a3":"ATF","iso_n3":"260"},"geometry":{"type":"Polygon","coordinates":[[[69.184863,-49.10957],[69.002441,-48.66123],[68.814746,-49.699609],[69.153125,-49.529688],[70.124316,-49.704395],[70.247754,-49.530664],[69.759961,-49.430176],[70.386133,-49.433984],[70.555469,-49.201465],[70.320215,-49.058594],[69.542383,-49.255664],[69.592773,-48.970996],[69.184863,-49.10957]]]}}, - {"type":"Feature","properties":{"name":"奥兰群岛","full_name":"奥兰群岛(芬兰)","iso_a2":"AX","iso_a3":"ALA","iso_n3":"248"},"geometry":{"type":"Polygon","coordinates":[[[19.989551,60.351172],[19.799805,60.081738],[20.258887,60.261279],[19.989551,60.351172]]]}}, - {"type":"Feature","properties":{"name":"芬兰","full_name":"芬兰共和国","iso_a2":"FI","iso_a3":"FIN","iso_n3":"246"},"geometry":{"type":"Polygon","coordinates":[[[24.155469,65.805273],[24.628027,65.85918],[25.347852,65.479248],[25.288184,64.860352],[24.55791,64.801025],[21.143848,62.73999],[21.436035,60.596387],[22.584961,60.380566],[22.462695,60.029199],[22.911719,60.209717],[23.021289,59.816016],[25.758008,60.267529],[26.569336,60.624561],[26.534668,60.412891],[27.797656,60.536133],[31.533984,62.8854],[29.991504,63.735156],[30.51377,64.2],[29.604199,64.968408],[30.102734,65.72627],[29.066211,66.891748],[29.988086,67.668262],[28.685156,68.189795],[28.414062,68.90415],[28.96582,69.021973],[29.141602,69.671436],[27.747852,70.064844],[26.072461,69.691553],[24.941406,68.593262],[23.854004,68.805908],[22.410938,68.719873],[21.59375,69.273584],[20.622168,69.036865],[23.638867,67.954395],[24.155469,65.805273]]]}}, - {"type":"Feature","properties":{"name":"斐济","full_name":"斐济共和国","iso_a2":"FJ","iso_a3":"FJI","iso_n3":"242"},"geometry":{"type":"MultiPolygon","coordinates":[[[[179.999219,-16.168555],[180,-16.15293],[178.497461,-16.787891],[178.706641,-16.976172],[179.202344,-16.712695],[179.92793,-16.744434],[179.930371,-16.519434],[179.56416,-16.636914],[179.999219,-16.168555]]],[[[178.280176,-17.371973],[177.504492,-17.539551],[177.321387,-18.077539],[177.955469,-18.264062],[178.667676,-18.080859],[178.280176,-17.371973]]]]}}, - {"type":"Feature","properties":{"name":"埃塞俄比亚","full_name":"埃塞俄比亚联邦民主共和国","iso_a2":"ET","iso_a3":"ETH","iso_n3":"231"},"geometry":{"type":"Polygon","coordinates":[[[35.268359,5.492285],[35.74502,5.343994],[36.021973,4.468115],[36.905566,4.411475],[38.086133,3.648828],[39.494434,3.456104],[40.765234,4.273047],[41.14043,3.962988],[41.883984,3.977734],[43.583496,4.85498],[44.940527,4.912012],[47.978223,7.99707],[46.978223,7.99707],[43.983789,9.008838],[42.841602,10.203076],[42.656445,10.6],[42.922754,10.999316],[41.798242,10.980469],[41.792676,11.686035],[42.378516,12.466406],[40.820117,14.11167],[40.140625,14.456055],[39.023828,14.628223],[38.431445,14.428613],[37.88418,14.852295],[37.571191,14.149072],[37.257227,14.45376],[36.524316,14.256836],[36.125195,12.757031],[35.670215,12.62373],[35.112305,11.816553],[34.931445,10.864795],[34.343945,10.658643],[34.078125,9.461523],[34.072754,8.545264],[33.281055,8.437256],[32.998926,7.899512],[33.902441,7.509521],[34.710645,6.660303],[35.268359,5.492285]]]}}, - {"type":"Feature","properties":{"name":"爱沙尼亚","full_name":"爱沙尼亚共和国","iso_a2":"EE","iso_a3":"EST","iso_n3":"233"},"geometry":{"type":"MultiPolygon","coordinates":[[[[27.351953,57.528125],[27.778516,57.870703],[27.43418,58.787256],[28.0125,59.484277],[25.509277,59.639014],[23.494434,59.195654],[23.767578,58.36084],[24.529102,58.354248],[24.322559,57.870605],[25.282617,58.048486],[26.462109,57.544482],[27.351953,57.528125]]],[[[22.617383,58.62124],[21.862305,58.497168],[21.996875,57.931348],[23.323242,58.45083],[22.617383,58.62124]]],[[[22.92373,58.826904],[22.649414,59.087109],[22.05625,58.943604],[22.542188,58.68999],[22.92373,58.826904]]]]}}, - {"type":"Feature","properties":{"name":"厄立特里亚","full_name":"厄立特里亚国","iso_a2":"ER","iso_a3":"ERI","iso_n3":"232"},"geometry":{"type":"MultiPolygon","coordinates":[[[[36.524316,14.256836],[37.257227,14.45376],[37.571191,14.149072],[37.88418,14.852295],[38.431445,14.428613],[39.023828,14.628223],[40.140625,14.456055],[40.820117,14.11167],[42.378516,12.466406],[42.703711,12.380322],[43.116699,12.708594],[41.176465,14.620312],[40.204102,15.014111],[39.86377,15.470312],[39.785547,15.124854],[38.609473,18.005078],[37.411035,17.061719],[37.008984,17.058887],[36.426758,15.13208],[36.524316,14.256836]]],[[[40.141211,15.696143],[39.956738,15.889404],[39.975195,15.612451],[40.399023,15.579883],[40.141211,15.696143]]]]}}, - {"type":"Feature","properties":{"name":"赤道几内亚","full_name":"赤道几内亚共和国","iso_a2":"GQ","iso_a3":"GNQ","iso_n3":"226"},"geometry":{"type":"MultiPolygon","coordinates":[[[[8.735742,3.758301],[8.474902,3.264648],[8.704004,3.223633],[8.946094,3.627539],[8.735742,3.758301]]],[[[11.328711,2.167432],[9.800781,2.304443],[9.385938,1.139258],[9.59082,1.031982],[11.335352,0.999707],[11.328711,2.167432]]]]}}, - {"type":"Feature","properties":{"name":"萨尔瓦多","full_name":"萨尔瓦多共和国","iso_a2":"SV","iso_a3":"SLV","iso_n3":"222"},"geometry":{"type":"Polygon","coordinates":[[[-89.362598,14.416016],[-90.095215,13.736523],[-88.512012,13.183936],[-88.685645,13.281494],[-87.930859,13.180664],[-87.814209,13.39917],[-87.802246,13.88999],[-88.482666,13.854248],[-89.362598,14.416016]]]}}, - {"type":"Feature","properties":{"name":"埃及","full_name":"阿拉伯埃及共和国","iso_a2":"EG","iso_a3":"EGY","iso_n3":"818"},"geometry":{"type":"Polygon","coordinates":[[[36.871387,21.996729],[35.697852,22.946191],[35.504395,23.779297],[35.783887,23.937793],[35.194141,24.475146],[33.959082,26.649023],[33.54707,27.898145],[32.359766,29.630664],[32.565723,29.973975],[33.247754,28.567725],[34.220117,27.764307],[34.904297,29.477344],[34.245313,31.208301],[34.198145,31.322607],[32.60332,31.06875],[32.216211,31.29375],[32.101758,31.092822],[31.771094,31.292578],[31.892188,31.482471],[32.136035,31.341064],[31.888965,31.541406],[31.08291,31.60332],[29.07207,30.830273],[25.150488,31.65498],[24.852734,31.334814],[24.961426,30.678516],[24.703223,30.201074],[24.980273,29.181885],[24.980273,21.99585],[31.092676,21.994873],[31.400293,22.202441],[31.434473,21.99585],[36.871387,21.996729]]]}}, - {"type":"Feature","properties":{"name":"厄瓜多尔","full_name":"厄瓜多尔共和国","iso_a2":"EC","iso_a3":"ECU","iso_n3":"218"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-75.284473,-0.106543],[-76.270605,0.439404],[-76.494629,0.235449],[-77.396338,0.393896],[-77.702881,0.837842],[-78.859668,1.455371],[-78.899658,1.20625],[-80.088281,0.784766],[-80.046143,0.155371],[-80.482275,-0.368262],[-80.282373,-0.620508],[-80.902393,-1.078906],[-80.760596,-1.93457],[-80.932178,-2.269141],[-80.284717,-2.706738],[-80.006641,-2.353809],[-79.925586,-2.548535],[-79.842139,-2.067383],[-79.729883,-2.579102],[-79.96333,-3.157715],[-80.324658,-3.387891],[-80.179248,-3.877734],[-80.490137,-4.010059],[-80.478564,-4.430078],[-79.638525,-4.454883],[-79.330957,-4.927832],[-79.033301,-4.969141],[-78.686035,-4.562402],[-78.345361,-3.397363],[-78.158496,-3.465137],[-77.860596,-2.981641],[-76.679102,-2.562598],[-75.570557,-1.53125],[-75.259375,-0.590137],[-75.62627,-0.122852],[-75.284473,-0.106543]]],[[[-80.131592,-2.973145],[-79.909033,-2.725586],[-80.223682,-2.753125],[-80.131592,-2.973145]]],[[[-90.334863,-0.771582],[-90.269385,-0.484668],[-90.531689,-0.581445],[-90.334863,-0.771582]]],[[[-89.418896,-0.911035],[-89.287842,-0.689844],[-89.608594,-0.888574],[-89.418896,-0.911035]]],[[[-91.272168,0.025146],[-91.596826,0.0021],[-91.120947,-0.559082],[-91.49541,-0.860938],[-91.131055,-1.019629],[-90.799658,-0.752051],[-91.272168,0.025146]]]]}}, - {"type":"Feature","properties":{"name":"多米尼加","full_name":"多米尼加共和国","iso_a2":"DO","iso_a3":"DOM","iso_n3":"214"},"geometry":{"type":"Polygon","coordinates":[[[-71.768311,18.03916],[-71.438965,17.635596],[-71.027832,18.273193],[-69.274512,18.439844],[-68.687402,18.214941],[-68.33916,18.611523],[-69.623633,19.117822],[-69.232471,19.271826],[-69.739404,19.299219],[-69.956836,19.671875],[-70.95415,19.913965],[-71.779248,19.718164],[-71.645312,19.163525],[-72.000391,18.5979],[-71.768311,18.03916]]]}}, - {"type":"Feature","properties":{"name":"多米尼克","full_name":"多米尼克国","iso_a2":"DM","iso_a3":"DMA","iso_n3":"212"},"geometry":{"type":"Polygon","coordinates":[[[-61.281689,15.249023],[-61.277246,15.526709],[-61.458105,15.633105],[-61.281689,15.249023]]]}}, - {"type":"Feature","properties":{"name":"美国本土外小岛屿","full_name":"美国本土外小岛屿","iso_a2":"UM","iso_a3":"UMI","iso_n3":"581"},"geometry":{"type":"Polygon","coordinates":[[[166.61939537900003,19.281642971000053],[166.64421634200005,19.27558014500005],[166.63819420700008,19.286444403000075],[166.61939537900003,19.281642971000053]]]}}, - {"type":"Feature","properties":{"name":"吉布提","full_name":"吉布提共和国","iso_a2":"DJ","iso_a3":"DJI","iso_n3":"262"},"geometry":{"type":"Polygon","coordinates":[[[43.245996,11.499805],[42.521777,11.572168],[43.380273,12.09126],[43.116699,12.708594],[42.703711,12.380322],[42.378516,12.466406],[41.792676,11.686035],[41.798242,10.980469],[42.922754,10.999316],[43.245996,11.499805]]]}}, - {"type":"Feature","properties":{"name":"格陵兰","full_name":"格陵兰","iso_a2":"GL","iso_a3":"GRL","iso_n3":"304"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-29.952881,83.564844],[-37.72334,83.497754],[-38.15625,82.998633],[-40.356836,83.332178],[-41.300146,83.100781],[-43.009277,83.2646],[-46.169043,83.063867],[-46.136816,82.858838],[-41.369629,82.75],[-45.556543,82.747021],[-44.238867,82.368164],[-44.729492,81.779834],[-50.037109,82.472412],[-49.541064,81.918066],[-53.022559,82.321729],[-53.555664,81.653271],[-54.548877,82.350635],[-59.261816,82.006641],[-56.615137,81.362891],[-59.281934,81.884033],[-60.842871,81.855371],[-61.435986,81.133594],[-62.903369,81.218359],[-63.028662,80.889551],[-64.515527,81],[-67.050635,80.384521],[-66.843652,80.076221],[-64.17915,80.099268],[-65.825537,79.17373],[-71.651318,78.623145],[-72.818066,78.194336],[-69.351367,77.467139],[-66.691211,77.681201],[-66.389453,77.280273],[-68.135547,77.37959],[-71.141455,77.028662],[-68.114258,76.650635],[-69.484082,76.39917],[-68.14873,76.067041],[-66.674805,75.977393],[-66.992578,76.212939],[-63.291309,76.352051],[-58.516211,75.689062],[-58.565527,75.352734],[-56.255469,74.526807],[-57.230566,74.125293],[-56.225391,74.129102],[-55.288281,73.3271],[-55.668555,73.00791],[-54.737939,72.87251],[-55.601709,72.453467],[-55.581445,72.178857],[-54.840137,72.356104],[-55.594043,71.553516],[-53.962988,71.458984],[-53.652148,72.362646],[-53.440088,71.579004],[-51.769922,71.671729],[-53.007568,71.17998],[-51.018945,71.001318],[-51.774316,71.010449],[-50.872363,70.364893],[-52.801953,70.750586],[-54.530762,70.699268],[-52.254639,70.058936],[-50.291699,70.014453],[-50.945703,68.682666],[-51.623145,68.534814],[-52.60459,68.70874],[-53.383154,68.297363],[-51.210156,68.419922],[-51.456494,68.116064],[-53.151562,68.207764],[-53.735205,67.549023],[-52.344824,67.836914],[-50.968848,67.806641],[-50.613477,67.52793],[-52.666455,67.749707],[-53.884424,67.135547],[-52.386865,66.881152],[-53.41875,66.648535],[-53.53877,66.139355],[-51.225,66.881543],[-53.392041,66.04834],[-53.198975,65.594043],[-52.55127,65.461377],[-51.091895,65.775781],[-52.537695,65.328809],[-51.922607,64.21875],[-50.721582,64.797607],[-50.960645,65.201123],[-50.121631,64.70376],[-50.008984,64.447266],[-50.49209,64.693164],[-51.40376,64.463184],[-51.584912,64.103174],[-50.260693,64.214258],[-51.54751,64.006104],[-51.468848,63.642285],[-50.390088,62.822021],[-49.793115,63.044629],[-50.319238,62.473193],[-49.553467,62.232715],[-49.623779,61.998584],[-48.828711,62.079688],[-49.380273,61.890186],[-49.289062,61.589941],[-48.386426,61.004736],[-47.770312,60.997754],[-48.180811,60.769238],[-46.874463,60.816406],[-45.870215,61.218311],[-46.046631,60.615723],[-45.380518,60.444922],[-44.756738,60.6646],[-45.379248,60.20293],[-44.613281,60.01665],[-44.224365,60.273535],[-44.412939,59.922607],[-43.906543,59.815479],[-43.955029,60.025488],[-43.1229,60.06123],[-43.212988,60.390674],[-43.922705,60.595361],[-43.044092,60.523682],[-42.585303,61.71748],[-42.110205,61.857227],[-42.152979,62.568457],[-42.94165,62.720215],[-41.908984,62.737109],[-41.634473,62.972461],[-42.174512,63.208789],[-41.387891,63.061865],[-40.550391,63.725244],[-40.617773,64.131738],[-41.581006,64.29834],[-40.781738,64.221777],[-40.182227,64.479932],[-41.084424,65.10083],[-39.937256,65.141602],[-39.57793,65.340771],[-40.173535,65.556152],[-38.203369,65.711719],[-38.520361,66.009668],[-38.139941,65.903516],[-37.752344,66.261523],[-38.156641,66.385596],[-37.278711,66.304395],[-37.954785,65.633594],[-36.665186,65.790088],[-36.527246,66.007715],[-36.379199,65.830811],[-35.630078,66.139941],[-35.867236,66.441406],[-35.188574,66.250293],[-34.198242,66.655078],[-32.164551,67.991113],[-32.327441,68.437305],[-30.978564,68.061328],[-26.48291,68.675928],[-22.287061,70.033398],[-25.529883,70.353174],[-27.38418,69.991602],[-27.56084,70.124463],[-26.621777,70.463379],[-29.07207,70.444971],[-28.069873,70.699023],[-28.398438,70.99292],[-26.71792,70.950488],[-25.742236,71.183594],[-25.842725,71.480176],[-27.087207,71.626562],[-25.885156,71.571924],[-24.562207,71.223535],[-23.327832,70.450977],[-22.690674,70.437305],[-22.437012,70.86001],[-22.384131,70.462402],[-21.522656,70.526221],[-21.752246,71.47832],[-22.479639,71.383447],[-21.959668,71.744678],[-25.117871,72.346973],[-24.81333,72.901514],[-26.657617,72.71582],[-24.62998,73.037646],[-24.069043,72.49873],[-22.293213,72.119531],[-22.036328,72.918457],[-24.132666,73.409375],[-27.348047,73.067822],[-27.561621,73.138477],[-26.541846,73.248975],[-27.27041,73.436279],[-26.062305,73.253027],[-24.79126,73.511279],[-25.521289,73.851611],[-22.346875,73.269238],[-20.509668,73.492871],[-20.367285,73.848242],[-22.134814,73.990479],[-22.321582,74.302539],[-21.94292,74.565723],[-21.954932,74.244287],[-20.653125,74.137354],[-19.369141,74.284033],[-19.287012,74.546387],[-19.984912,74.975195],[-20.861572,74.635938],[-20.985791,75.074365],[-22.232861,75.119727],[-20.484961,75.314258],[-19.526367,75.180225],[-19.508984,75.75752],[-20.103613,76.219092],[-21.488232,76.271875],[-22.609326,76.704297],[-20.486719,76.920801],[-18.510303,76.778174],[-18.442627,77.259375],[-20.680811,77.618994],[-19.49043,77.718896],[-20.862598,77.911865],[-21.72959,77.708545],[-21.13374,78.658643],[-18.991992,79.178369],[-20.150146,80.01123],[-19.429199,80.257715],[-16.48877,80.251953],[-16.760596,80.573389],[-11.430664,81.456836],[-14.241992,81.813867],[-17.456055,81.397705],[-19.629932,81.639893],[-23.117725,80.778174],[-21.337988,82.068701],[-24.293066,81.700977],[-25.148828,82.001123],[-29.887402,82.054834],[-23.118066,82.324707],[-21.58252,82.63418],[-25.123389,83.159619],[-32.032715,82.983447],[-25.795068,83.260986],[-29.952881,83.564844]]],[[[-52.731152,69.944727],[-54.371631,70.317285],[-54.919141,69.713623],[-53.578418,69.256641],[-51.900195,69.604785],[-52.731152,69.944727]]],[[[-55.016895,72.791113],[-56.214795,72.719189],[-55.566602,72.564355],[-55.016895,72.791113]]],[[[-44.864551,82.083643],[-44.91748,82.480518],[-47.272266,82.656934],[-44.864551,82.083643]]],[[[-25.432324,70.921338],[-27.617236,70.91377],[-28.035254,70.486816],[-26.604687,70.553369],[-26.217871,70.454053],[-25.432324,70.921338]]],[[[-17.6125,79.825879],[-17.98291,80.055176],[-19.138281,79.852344],[-17.6125,79.825879]]],[[[-18.000537,75.407324],[-18.856055,75.319141],[-18.670801,75.00166],[-17.391992,75.036914],[-18.000537,75.407324]]],[[[-46.266699,60.781396],[-46.205225,60.943506],[-46.788086,60.758398],[-46.266699,60.781396]]]]}}, - {"type":"Feature","properties":{"name":"法罗群岛","full_name":"法罗群岛(丹麦)","iso_a2":"FO","iso_a3":"FRO","iso_n3":"234"},"geometry":{"type":"Polygon","coordinates":[[[-6.631055,62.227881],[-7.172168,62.285596],[-6.725195,61.951465],[-6.631055,62.227881]]]}}, - {"type":"Feature","properties":{"name":"丹麦","full_name":"丹麦王国","iso_a2":"DK","iso_a3":"DNK","iso_n3":"208"},"geometry":{"type":"MultiPolygon","coordinates":[[[[12.56875,55.785059],[12.218945,56.118652],[11.819727,55.697656],[11.627734,55.956885],[10.978906,55.721533],[11.862305,54.772607],[12.56875,55.785059]]],[[[9.739746,54.825537],[9.591113,55.493213],[10.926172,56.443262],[10.282715,56.620508],[10.609961,57.736914],[8.284082,56.852344],[8.468359,56.664551],[9.254883,57.011719],[8.67168,56.495654],[8.163965,56.606885],[8.132129,55.599805],[8.615918,55.418213],[8.670313,54.903418],[9.739746,54.825537]]],[[[10.645117,55.609814],[9.860645,55.515479],[9.98877,55.163184],[10.785254,55.133398],[10.645117,55.609814]]],[[[11.361426,54.89165],[11.035547,54.773096],[11.765918,54.679443],[11.361426,54.89165]]]]}}, - {"type":"Feature","properties":{"name":"捷克","full_name":"捷克共和国","iso_a2":"CZ","iso_a3":"CZE","iso_n3":"203"},"geometry":{"type":"Polygon","coordinates":[[[18.832227,49.510791],[18.562402,49.879346],[17.627051,50.116406],[17.702246,50.307178],[16.880078,50.427051],[16.63916,50.102148],[16.282227,50.655615],[14.99375,51.014355],[14.809375,50.858984],[14.319727,51.037793],[12.942676,50.406445],[12.089844,50.301758],[12.681152,49.414502],[13.814746,48.766943],[14.691309,48.599219],[15.066797,48.997852],[16.953125,48.598828],[18.832227,49.510791]]]}}, - {"type":"Feature","properties":{"name":"北塞浦路斯","full_name":"北塞浦路斯土耳其共和国","iso_a2":"-99","iso_a3":"-99","iso_n3":"-99"},"geometry":{"type":"Polygon","coordinates":[[[34.004492,35.065234],[33.941992,35.292041],[34.556055,35.662061],[32.712695,35.171045],[34.004492,35.065234]]]}}, - {"type":"Feature","properties":{"name":"塞浦路斯","full_name":"塞浦路斯共和国","iso_a2":"CY","iso_a3":"CYP","iso_n3":"196"},"geometry":{"type":"Polygon","coordinates":[[[32.712695,35.171045],[32.317188,34.95332],[33.00791,34.56958],[34.004492,35.065234],[32.712695,35.171045]]]}}, - {"type":"Feature","properties":{"name":"古巴","full_name":"古巴共和国","iso_a2":"CU","iso_a3":"CUB","iso_n3":"192"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-81.837451,23.163037],[-84.044922,22.666016],[-84.361279,22.378906],[-84.326367,22.074316],[-84.887207,21.856982],[-84.030957,21.943115],[-82.738037,22.689258],[-81.838818,22.672461],[-81.710352,22.49668],[-82.077734,22.387695],[-81.849414,22.213672],[-81.185498,22.267969],[-79.357422,21.585156],[-78.727686,21.592725],[-78.116357,20.761865],[-77.22959,20.64375],[-77.103809,20.40752],[-77.715088,19.855469],[-75.116406,19.901416],[-74.153711,20.168555],[-74.882568,20.650635],[-75.724561,20.714551],[-75.722949,21.111035],[-77.252881,21.483496],[-77.366162,21.612646],[-77.144141,21.643604],[-77.497266,21.871631],[-79.275684,22.407617],[-79.820264,22.887012],[-81.837451,23.163037]]],[[[-82.561768,21.57168],[-82.991211,21.942725],[-82.973584,21.592285],[-83.183789,21.593457],[-82.561768,21.57168]]]]}}, - {"type":"Feature","properties":{"name":"克罗地亚","full_name":"克罗地亚共和国","iso_a2":"HR","iso_a3":"HRV","iso_n3":"191"},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.57793,45.516895],[13.860742,44.837402],[14.550488,45.297705],[15.470996,44.271973],[15.18584,44.172119],[15.985547,43.519775],[16.903125,43.392432],[17.585156,42.938379],[15.736621,44.76582],[15.788086,45.178955],[16.293359,45.008838],[16.918652,45.276562],[19.007129,44.869189],[19.4,45.2125],[19.004688,45.399512],[18.905371,45.931738],[17.807129,45.79043],[16.516211,46.499902],[15.635938,46.200732],[15.339453,45.467041],[14.568848,45.657227],[13.57793,45.516895]]],[[[14.810254,44.977051],[14.571094,45.224756],[14.450391,45.079199],[14.810254,44.977051]]],[[[18.436328,42.559717],[17.667578,42.897119],[17.04541,43.014893],[18.51748,42.43291],[18.436328,42.559717]]]]}}, - {"type":"Feature","properties":{"name":"科特迪瓦","full_name":"科特迪瓦共和国","iso_a2":"CI","iso_a3":"CIV","iso_n3":"384"},"geometry":{"type":"Polygon","coordinates":[[[-7.990625,10.1625],[-8.136963,9.495703],[-7.896191,9.415869],[-7.950977,8.786816],[-7.681201,8.410352],[-8.236963,8.455664],[-8.009863,8.078516],[-8.231885,7.556738],[-8.486426,7.558496],[-8.302344,6.980957],[-8.603564,6.507812],[-7.454395,5.841309],[-7.544971,4.351318],[-5.061816,5.130664],[-5.282373,5.210254],[-3.347559,5.130664],[-3.199951,5.354492],[-3.019141,5.130811],[-2.75498,5.43252],[-3.235791,6.807227],[-2.505859,8.20874],[-2.69585,9.481348],[-3.223535,9.895459],[-4.62583,9.713574],[-5.523535,10.426025],[-6.196875,10.232129],[-6.261133,10.724072],[-6.65415,10.656445],[-7.01709,10.143262],[-7.497949,10.439795],[-7.990625,10.1625]]]}}, - {"type":"Feature","properties":{"name":"哥斯达黎加","full_name":"哥斯达黎加共和国","iso_a2":"CR","iso_a3":"CRI","iso_n3":"188"},"geometry":{"type":"Polygon","coordinates":[[[-82.563574,9.57666],[-83.641992,10.917236],[-83.919287,10.735352],[-84.63418,11.045605],[-85.744336,11.062109],[-85.908008,10.897559],[-85.667236,10.74502],[-85.849658,10.292041],[-85.681006,9.958594],[-85.114502,9.581787],[-84.886426,9.820947],[-85.263184,10.256641],[-85.025049,10.115723],[-84.581592,9.568359],[-83.637256,9.035352],[-83.604736,8.480322],[-83.291504,8.406006],[-83.469727,8.706836],[-83.285791,8.664355],[-82.879346,8.070654],[-83.027344,8.337744],[-82.727832,8.916064],[-82.939844,9.44917],[-82.563574,9.57666]]]}}, - {"type":"Feature","properties":{"name":"刚果(金)","full_name":"刚果民主共和国","iso_a2":"CD","iso_a3":"COD","iso_n3":"180"},"geometry":{"type":"Polygon","coordinates":[[[30.751172,-8.193652],[30.212695,-7.037891],[29.54082,-6.313867],[29.403223,-4.449316],[29.014355,-2.720215],[28.876367,-2.400293],[29.576953,-1.387891],[29.942871,0.819238],[31.252734,2.04458],[30.728613,2.455371],[30.838574,3.490723],[29.676855,4.586914],[28.19209,4.350244],[27.40332,5.10918],[25.525098,5.312109],[25.065234,4.967432],[22.864551,4.723877],[22.422168,4.134961],[20.558105,4.462695],[19.500977,5.12749],[18.594141,4.34624],[18.610352,3.478418],[18.072168,2.013281],[17.752832,-0.549023],[16.879883,-1.225879],[16.215332,-2.177832],[15.990039,-3.766211],[14.70791,-4.881738],[14.410742,-4.83125],[14.358301,-4.299414],[13.71709,-4.454492],[13.414941,-4.837402],[13.072754,-4.634766],[12.451465,-5.071484],[12.503711,-5.695801],[12.213672,-5.758691],[12.45293,-6.000488],[13.068164,-5.864844],[16.431445,-5.900195],[17.57959,-8.099023],[19.34082,-7.966602],[19.527637,-7.144434],[19.875195,-6.986328],[20.590039,-6.919922],[20.607813,-7.277734],[21.781641,-7.314648],[21.813184,-9.46875],[22.274512,-10.259082],[22.226172,-11.121973],[23.966504,-10.871777],[24.365723,-11.129883],[24.37793,-11.41709],[25.28877,-11.212402],[25.349414,-11.623047],[26.025977,-11.890137],[26.824023,-11.965234],[27.15918,-11.579199],[27.573828,-12.227051],[28.412891,-12.518066],[29.014258,-13.368848],[29.554199,-13.248926],[29.775195,-13.438086],[29.795117,-12.155469],[29.485547,-12.418457],[29.064355,-12.348828],[28.383398,-11.566699],[28.645508,-10.550195],[28.400684,-9.224805],[28.898145,-8.485449],[30.751172,-8.193652]]]}}, - {"type":"Feature","properties":{"name":"刚果(布)","full_name":"刚果共和国","iso_a2":"CG","iso_a3":"COG","iso_n3":"178"},"geometry":{"type":"Polygon","coordinates":[[[11.130176,-3.916309],[12.018359,-5.004297],[12.798242,-4.430566],[13.072754,-4.634766],[13.414941,-4.837402],[13.71709,-4.454492],[14.358301,-4.299414],[14.410742,-4.83125],[14.70791,-4.881738],[15.990039,-3.766211],[16.215332,-2.177832],[16.879883,-1.225879],[17.752832,-0.549023],[18.072168,2.013281],[18.610352,3.478418],[17.491602,3.687305],[16.610742,3.505371],[16.183398,2.270068],[16.059375,1.676221],[14.578906,2.199121],[13.293555,2.161572],[13.216309,1.248438],[14.180859,1.370215],[14.429883,0.901465],[13.949609,0.353809],[13.860059,-0.20332],[14.474121,-0.573438],[14.383984,-1.890039],[13.993848,-2.490625],[13.733789,-2.138477],[13.464941,-2.39541],[12.991992,-2.313379],[12.59043,-1.826855],[12.446387,-2.32998],[11.605469,-2.342578],[11.537793,-2.836719],[11.93418,-3.318555],[11.879883,-3.665918],[11.504297,-3.520312],[11.130176,-3.916309]]]}}, - {"type":"Feature","properties":{"name":"科摩罗","full_name":"科摩罗联盟","iso_a2":"KM","iso_a3":"COM","iso_n3":"174"},"geometry":{"type":"MultiPolygon","coordinates":[[[[44.476367,-12.081543],[44.220117,-12.171387],[44.50498,-12.356543],[44.476367,-12.081543]]],[[[43.46582,-11.90127],[43.299023,-11.374512],[43.22666,-11.751855],[43.46582,-11.90127]]]]}}, - {"type":"Feature","properties":{"name":"哥伦比亚","full_name":"哥伦比亚共和国","iso_a2":"CO","iso_a3":"COL","iso_n3":"170"},"geometry":{"type":"Polygon","coordinates":[[[-71.319727,11.861914],[-71.137305,12.046338],[-71.262109,12.335303],[-71.714551,12.419971],[-73.313379,11.295752],[-74.14292,11.32085],[-74.400879,10.765234],[-74.330225,10.99668],[-74.84458,11.109717],[-75.70835,10.143408],[-75.538574,10.205176],[-75.639355,9.450439],[-76.920459,8.57373],[-76.786572,7.931592],[-77.374219,8.658301],[-77.195996,7.972461],[-77.538281,7.56626],[-77.761914,7.698828],[-77.901172,7.229346],[-77.368799,6.575586],[-77.469434,6.176758],[-77.249268,5.780176],[-77.534424,5.537109],[-77.286328,4.721729],[-77.520703,4.212793],[-77.076807,3.913281],[-77.813574,2.716357],[-78.591699,2.356641],[-78.576904,1.773779],[-79.025439,1.623682],[-78.859668,1.455371],[-77.702881,0.837842],[-77.396338,0.393896],[-76.494629,0.235449],[-76.270605,0.439404],[-75.284473,-0.106543],[-74.801758,-0.200098],[-74.246387,-0.970605],[-73.664307,-1.248828],[-72.941113,-2.394043],[-70.968555,-2.206836],[-70.09585,-2.658203],[-70.735107,-3.781543],[-70.339502,-3.814355],[-69.965918,-4.235938],[-69.400244,-1.194922],[-69.633984,-0.509277],[-70.070508,-0.138867],[-70.053906,0.578613],[-69.15332,0.658789],[-69.311816,1.050488],[-69.852148,1.059521],[-69.848584,1.70874],[-68.176562,1.719824],[-68.193799,1.987012],[-67.93623,1.748486],[-67.400439,2.116699],[-67.082275,1.1854],[-66.876025,1.223047],[-67.21084,2.390137],[-67.859082,2.793604],[-67.311133,3.415869],[-67.855273,4.506885],[-67.481982,6.180273],[-69.427148,6.123975],[-70.129199,6.953613],[-72.006641,7.032617],[-72.471973,7.524268],[-72.390332,8.287061],[-72.796387,9.108984],[-73.366211,9.194141],[-72.690088,10.83584],[-71.958105,11.666406],[-71.319727,11.861914]]]}}, - {"type":"Feature","properties":{"name":"中国","full_name":"中华人民共和国","iso_a2":"CN","iso_a3":"CHN","iso_n3":"156"},"geometry":{"type":"MultiPolygon","coordinates":[[[[107.972656,21.507959],[108.502148,21.633447],[108.479883,21.904639],[109.081543,21.440283],[109.521484,21.693408],[109.930762,21.480566],[109.662598,20.916895],[110.123145,20.263721],[110.517578,20.46001],[110.154004,20.944629],[110.410937,21.338135],[110.567187,21.214062],[111.602734,21.559082],[111.943945,21.849658],[112.30498,21.741699],[112.359668,21.978027],[112.586328,21.776855],[113.08877,22.207959],[113.548145,22.222607],[113.331055,22.912012],[113.519727,23.1021],[114.01543,22.511914],[113.937305,22.36499],[114.267969,22.295557],[114.266016,22.540967],[116.470703,22.945898],[116.910645,23.64668],[117.367676,23.588623],[118.056055,24.246094],[117.842676,24.474316],[118.657031,24.621436],[118.977539,25.209277],[119.285547,25.232227],[119.180078,25.449805],[119.622461,25.391162],[119.61875,26.003564],[119.139453,26.121777],[119.463086,26.054688],[119.881055,26.33418],[119.588184,26.784961],[120.086719,26.671582],[120.747656,28.009961],[121.145703,28.32666],[121.609961,28.292139],[121.487109,29.193164],[121.917773,29.13501],[121.941211,29.605908],[121.50625,29.48457],[122.08291,29.870361],[121.258008,30.304102],[120.194629,30.241309],[121.87793,30.916992],[120.715527,31.98374],[120.035937,31.936279],[120.520117,32.105859],[121.856348,31.816455],[120.853223,32.661377],[120.266699,34.274023],[119.165332,34.848828],[120.284766,35.984424],[120.183301,36.202441],[120.637891,36.129932],[120.81084,36.632812],[121.932715,36.959473],[122.340918,36.832227],[122.666992,37.402832],[121.640234,37.460352],[120.75,37.833936],[119.760547,37.155078],[119.287402,37.138281],[118.952637,37.331152],[118.940039,38.042773],[117.766699,38.31167],[117.616699,38.852881],[117.865723,39.19126],[118.976953,39.182568],[119.391113,39.75249],[120.479102,40.230957],[121.174512,40.90127],[121.834863,40.974268],[122.275,40.541846],[121.26748,39.544678],[121.818457,39.386523],[121.106738,38.920801],[121.163574,38.731641],[122.840039,39.60083],[124.362109,40.004053],[125.989062,40.904639],[126.743066,41.724854],[128.149414,41.387744],[128.045215,41.9875],[128.923438,42.038232],[129.697852,42.448145],[129.898242,42.998145],[130.526953,42.5354],[130.424805,42.727051],[131.068555,42.902246],[131.257324,43.378076],[130.981641,44.844336],[131.851855,45.326855],[133.113477,45.130713],[134.167676,47.302197],[134.752344,47.71543],[134.665234,48.253906],[135.083406,48.436324],[134.293359,48.373438],[133.144043,48.105664],[132.47627,47.71499],[130.961914,47.709326],[130.553125,48.861182],[129.498145,49.388818],[127.550781,49.801807],[127.590234,50.208984],[125.649023,53.042285],[123.607813,53.546533],[120.985449,53.28457],[120.094531,52.787207],[120.656152,52.56665],[120.749805,52.096533],[119.163672,50.406006],[119.259863,50.066406],[117.873438,49.513477],[116.683301,49.823779],[115.616406,47.874805],[115.898242,47.686914],[116.760547,47.869775],[117.350781,47.652197],[117.768359,47.987891],[118.498438,47.983984],[119.711133,47.15],[119.867188,46.672168],[117.438086,46.58623],[117.333398,46.362012],[116.562598,46.289795],[115.681055,45.458252],[114.560156,45.38999],[113.587012,44.745703],[111.898047,45.064062],[111.402246,44.367285],[111.933203,43.711426],[110.400391,42.773682],[109.339844,42.438379],[106.77002,42.288721],[104.982031,41.595508],[104.498242,41.658691],[104.498242,41.877002],[103.711133,41.751318],[102.156641,42.158105],[101.495313,42.53877],[96.385449,42.720361],[95.350293,44.278076],[93.516211,44.944482],[90.877246,45.196094],[90.869922,46.954492],[90.02793,47.877686],[89.047656,48.002539],[87.979688,48.555127],[87.814258,49.162305],[87.322852,49.085791],[86.808301,49.049707],[86.549414,48.528613],[85.749414,48.385059],[85.484766,47.063525],[84.786133,46.830713],[83.029492,47.185938],[82.315234,45.594922],[82.521484,45.125488],[81.691992,45.349365],[79.871875,44.883789],[80.481543,44.714648],[80.355273,44.097266],[80.785742,43.161572],[80.202246,42.734473],[80.209375,42.190039],[78.123438,41.075635],[76.907715,41.02417],[76.318555,40.352246],[75.677148,40.305811],[75.555566,40.625195],[74.835156,40.482617],[73.991602,40.043115],[73.631641,39.448877],[73.80166,38.606885],[74.812305,38.460303],[75.11875,37.385693],[74.891309,37.231641],[74.372168,37.157715],[74.541406,37.022168],[75.772168,36.694922],[75.912305,36.048975],[76.766895,35.661719],[77.799414,35.495898],[78.042676,35.479785],[78.326953,34.606396],[78.970117,34.302637],[78.72666,34.013379],[79.219336,32.501074],[78.918945,32.358203],[78.700879,32.597021],[78.389648,32.519873],[78.807678,31.099982],[79.707774,31.013593],[81.010254,30.164502],[82.043359,30.326758],[83.583496,29.183594],[84.101367,29.219971],[84.228711,28.911768],[85.159082,28.592236],[85.122461,28.315967],[85.67832,28.277441],[85.994531,27.9104],[86.137012,28.114355],[87.141406,27.83833],[88.109766,27.870605],[88.803711,28.006934],[88.891406,27.316064],[89.981055,28.311182],[90.352734,28.080225],[91.273047,28.078369],[91.631934,27.759961],[91.594727,27.557666],[92.083398,27.290625],[91.99834,26.85498],[92.64698,26.952656],[93.111399,26.880082],[93.817265,27.025183],[94.277372,27.58143],[94.88592,27.743098],[95.28628,27.939955],[95.39715,28.142259],[95.832003,28.295186],[96.275479,28.228241],[97.086778,27.7475],[97.335156,27.937744],[97.322461,28.217969],[97.599219,28.517041],[98.061621,28.185889],[98.298828,27.550098],[98.651172,27.572461],[98.738477,26.785742],[98.65625,25.863574],[97.819531,25.251855],[97.583301,24.774805],[97.564551,23.911035],[98.835059,24.121191],[98.676758,23.905078],[98.86377,23.19126],[99.507129,22.959131],[99.192969,22.125977],[99.917676,22.028027],[100.147656,21.480518],[101.079785,21.755859],[101.138867,21.56748],[101.247852,21.197314],[101.800586,21.212598],[101.524512,22.253662],[101.73877,22.495264],[102.127441,22.379199],[102.470898,22.750928],[102.981934,22.448242],[103.32666,22.769775],[103.941504,22.540088],[105.275391,23.345215],[105.842969,22.922803],[106.780273,22.778906],[106.550391,22.501367],[106.663574,21.978906],[107.972656,21.507959]]],[[[110.88877,19.991943],[110.651758,20.137744],[109.263477,19.882666],[108.665527,19.304102],[108.701563,18.535254],[109.519336,18.218262],[110.067383,18.447559],[111.013672,19.655469],[110.88877,19.991943]]],[[[121.008789,22.620361],[121.397461,23.17251],[121.929004,24.97373],[121.593652,25.275342],[121.040625,25.032812],[120.132129,23.65293],[120.232813,22.71792],[120.839844,21.925],[121.008789,22.620361]]],[[[116.769628,20.771721],[116.889736,20.683284],[116.749302,20.600958],[116.862635,20.588633],[116.925461,20.726949],[116.769628,20.771721]]],[[[113.896887,7.607204],[114.058879,7.537794],[114.368696,7.638642],[114.540543,7.945783],[113.896887,7.607204]]],[[[109.463972,7.344339],[109.948716,7.522962],[109.653065,7.559745],[109.463972,7.344339]]],[[[116.48876,10.395686],[116.467202,10.309144],[116.644592,10.335051],[116.48876,10.395686]]],[[[122.518653,23.460785],[122.798614,24.573674],[122.779218,24.578553],[122.499257,23.465664],[122.518653,23.460785]]],[[[121.172026,20.805459],[121.909388,21.687433],[121.894044,21.700262],[121.156682,20.818287],[121.172026,20.805459]]],[[[119.473662,18.007073],[120.025697,19.024038],[120.00812,19.033579],[119.456084,18.016614],[119.473662,18.007073]]],[[[119.072182,15.007514],[119.072676,16.043885],[119.052676,16.043885],[119.052184,15.00781],[119.072182,15.007514]]],[[[118.686467,11.189592],[118.524042,10.912566],[118.540436,10.90292],[118.704762,11.181475],[118.874599,11.607472],[118.98895,11.985731],[118.969805,11.991519],[118.855579,11.613671],[118.686467,11.189592]]],[[[115.544669,7.146723],[116.250486,7.979279],[116.23523,7.992212],[115.529413,7.159656],[115.544669,7.146723]]],[[[112.307052,3.534873],[111.786901,3.416873],[111.791326,3.397368],[112.312489,3.515623],[112.521474,3.578591],[112.852064,3.732569],[112.843614,3.750696],[112.515016,3.597533],[112.307052,3.534873]]],[[[108.290133,6.012663],[108.308786,6.019877],[108.279563,6.095434],[108.256117,6.227526],[108.2168,6.538165],[108.218763,6.949641],[108.244195,7.073907],[108.224601,7.077917],[108.198768,6.950725],[108.196797,6.537606],[108.236307,6.224768],[108.26004,6.090984],[108.290133,6.012663]]],[[[110.128228,11.368945],[110.055537,11.253354],[110.072467,11.242707],[110.145887,11.359542],[110.207005,11.481288],[110.259018,11.604996],[110.304569,11.783642],[110.328228,11.945713],[110.334243,12.141598],[110.332274,12.240384],[110.312278,12.239982],[110.314245,12.141953],[110.308355,11.948035],[110.284855,11.787051],[110.239823,11.610665],[110.188981,11.489964],[110.128228,11.368945]]],[[[109.845225,15.153166],[109.864809,15.157224],[109.848892,15.233933],[109.789745,15.450683],[109.690053,15.675484],[109.591475,15.836774],[109.532015,15.922592],[109.30888,16.207258],[109.29314,16.194919],[109.515744,15.910958],[109.57456,15.826099],[109.672646,15.665615],[109.77065,15.444688],[109.829516,15.228968],[109.845225,15.153166]]]]}}, - {"type":"Feature","properties":{"name":"智利","full_name":"智利共和国","iso_a2":"CL","iso_a3":"CHL","iso_n3":"152"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-70.418262,-18.345605],[-70.080029,-21.356836],[-70.593359,-23.255469],[-70.392334,-23.565918],[-70.445361,-25.172656],[-70.925781,-27.588672],[-71.519238,-28.926465],[-71.315723,-29.649707],[-71.708936,-30.628027],[-71.452246,-32.65957],[-72.223779,-35.096191],[-73.215967,-37.166895],[-73.662402,-37.341016],[-73.226465,-39.224414],[-73.965869,-41.118262],[-73.624023,-41.773633],[-72.318262,-41.499023],[-72.824072,-41.908789],[-72.499414,-41.980859],[-72.412354,-42.388184],[-72.773242,-42.257715],[-72.758008,-43.039453],[-73.224463,-43.897949],[-73.265088,-44.168652],[-72.663867,-44.436426],[-72.680078,-44.593945],[-73.444971,-45.238184],[-72.933838,-45.452344],[-73.730762,-45.47998],[-73.591846,-45.899121],[-73.845361,-46.566016],[-73.735254,-45.811719],[-73.967578,-46.154102],[-74.392969,-46.217383],[-74.019922,-46.055859],[-73.957178,-45.404395],[-74.157861,-45.767188],[-75.066699,-45.874902],[-74.924463,-46.159668],[-75.706396,-46.705273],[-75.430371,-46.93457],[-75.540332,-46.69873],[-74.98418,-46.512109],[-75.005957,-46.741113],[-74.313574,-46.788184],[-74.158398,-47.18252],[-74.482666,-47.430469],[-74.134082,-47.59082],[-74.654932,-47.702246],[-74.227051,-47.968945],[-73.715869,-47.655469],[-73.391064,-48.145898],[-74.584668,-47.999023],[-74.474414,-48.463965],[-74.009082,-48.475],[-74.341016,-48.595703],[-74.366553,-49.400488],[-73.934961,-49.020898],[-73.836377,-49.609375],[-74.29082,-49.604102],[-73.958594,-49.994727],[-74.62959,-50.194043],[-73.950342,-50.510547],[-74.185596,-50.485352],[-73.978027,-50.827051],[-73.654443,-50.492676],[-73.806543,-50.938379],[-74.139404,-50.817773],[-74.365576,-50.487891],[-74.644482,-50.360938],[-74.685742,-50.662012],[-75.094678,-50.68125],[-74.814746,-51.062891],[-73.939502,-51.266309],[-74.19668,-51.680566],[-73.518164,-52.041016],[-72.600049,-51.799121],[-73.16875,-51.453906],[-72.76123,-51.573242],[-72.489648,-51.763672],[-72.52334,-52.255469],[-72.677051,-52.384668],[-72.79502,-51.949512],[-73.834473,-52.233984],[-74.264941,-52.104883],[-74.014453,-52.639355],[-73.123926,-52.487988],[-73.645215,-52.837012],[-73.122461,-53.073926],[-72.712109,-52.535547],[-71.511279,-52.605371],[-72.727686,-52.762305],[-73.052734,-53.243457],[-72.548926,-53.460742],[-72.278027,-53.132324],[-71.227148,-52.810645],[-71.791455,-53.48457],[-71.941699,-53.234082],[-72.412891,-53.350195],[-71.297754,-53.883398],[-70.795117,-52.76875],[-69.241016,-52.205469],[-68.443359,-52.356641],[-69.960254,-52.008203],[-71.918652,-51.989551],[-72.407666,-51.54082],[-72.340234,-50.681836],[-73.15293,-50.738281],[-73.50127,-50.125293],[-73.554199,-49.463867],[-72.354736,-48.36582],[-72.51792,-47.876367],[-71.699658,-46.651367],[-71.746191,-45.578906],[-71.349316,-45.331934],[-72.063721,-44.771875],[-71.261133,-44.763086],[-71.159717,-44.560254],[-71.82002,-44.383105],[-71.750635,-43.237305],[-72.146436,-42.990039],[-72.108203,-42.251855],[-71.75,-42.046777],[-71.932129,-40.691699],[-71.401562,-38.935059],[-70.858643,-38.604492],[-71.192187,-36.843652],[-70.404785,-36.061719],[-70.555176,-35.246875],[-69.852441,-34.224316],[-69.819629,-33.283789],[-70.084863,-33.201758],[-70.51958,-31.148438],[-69.844287,-30.175],[-70.026807,-29.324023],[-69.656934,-28.413574],[-68.846338,-27.153711],[-68.318652,-26.973242],[-68.591602,-26.47041],[-68.384229,-25.091895],[-68.562012,-24.747363],[-67.356201,-24.033789],[-67.008789,-23.001367],[-67.194873,-22.82168],[-67.879443,-22.822949],[-68.197021,-21.300293],[-68.760547,-20.416211],[-68.462891,-19.432813],[-68.968311,-18.967969],[-69.093945,-18.050488],[-69.510938,-17.506055],[-69.8521,-17.703809],[-69.926367,-18.206055],[-70.418262,-18.345605]],[[-74.385742,-52.922363],[-74.712012,-52.74873],[-73.135205,-53.353906],[-74.385742,-52.922363]]],[[[-68.629932,-52.652637],[-69.414062,-52.48623],[-69.935449,-52.821094],[-70.380127,-52.751953],[-70.329297,-53.377637],[-69.355957,-53.416309],[-70.151123,-53.888086],[-69.044336,-54.406738],[-69.253174,-54.557422],[-70.535303,-54.136133],[-70.531299,-53.627344],[-70.863086,-54.110449],[-70.310986,-54.528516],[-70.797266,-54.327246],[-71.927734,-54.528711],[-68.653223,-54.853613],[-68.629932,-52.652637]]],[[[-67.079932,-55.153809],[-68.301367,-54.980664],[-68.07002,-55.221094],[-67.079932,-55.153809]]],[[[-73.773389,-43.345898],[-73.436328,-42.936523],[-73.789258,-42.585742],[-73.4229,-42.192871],[-73.527832,-41.896289],[-74.03667,-41.795508],[-74.387354,-43.231641],[-73.773389,-43.345898]]],[[[-74.476172,-49.147852],[-74.546094,-48.766895],[-74.89624,-48.733203],[-75.549805,-49.791309],[-75.066016,-49.852344],[-74.723828,-49.423828],[-74.594727,-50.006641],[-74.476172,-49.147852]]],[[[-75.510254,-48.763477],[-75.158496,-48.622656],[-75.391406,-48.019727],[-75.510254,-48.763477]]],[[[-74.567285,-48.591992],[-74.895654,-47.839355],[-75.212891,-48.141699],[-74.923047,-48.626465],[-74.567285,-48.591992]]],[[[-72.923242,-53.481641],[-73.845459,-53.545801],[-73.210645,-53.98584],[-72.76377,-53.864844],[-72.870996,-54.126562],[-72.20542,-53.807422],[-72.923242,-53.481641]]],[[[-74.822949,-51.630176],[-75.105371,-51.788867],[-74.694482,-52.279199],[-74.822949,-51.630176]]],[[[-69.702979,-54.919043],[-69.979785,-55.147461],[-69.411816,-55.444238],[-69.192627,-55.171875],[-68.04834,-55.643164],[-68.458008,-54.959668],[-69.702979,-54.919043]]],[[[-71.390479,-54.032812],[-71.996484,-53.884863],[-72.210449,-54.047754],[-71.948535,-54.300879],[-71.143262,-54.374023],[-71.021924,-54.111816],[-71.390479,-54.032812]]],[[[-73.735352,-44.394531],[-73.994922,-44.140234],[-74.617773,-44.647949],[-74.01626,-45.344922],[-73.728174,-45.195898],[-74.002051,-44.590918],[-73.735352,-44.394531]]],[[[-72.986133,-44.780078],[-72.776367,-44.508594],[-73.207715,-44.334961],[-73.39707,-44.774316],[-72.986133,-44.780078]]],[[[-75.302002,-50.67998],[-75.115332,-50.510449],[-75.427637,-50.480566],[-75.302002,-50.67998]]],[[[-75.106689,-48.836523],[-75.583105,-48.858887],[-75.641162,-49.19541],[-75.106689,-48.836523]]],[[[-74.558643,-51.277051],[-75.153662,-51.278809],[-75.289111,-51.625391],[-74.558643,-51.277051]]],[[[-75.054785,-50.296094],[-74.875977,-50.109961],[-75.32666,-50.011816],[-75.449121,-50.343359],[-75.054785,-50.296094]]],[[[-74.312891,-45.691504],[-74.310547,-45.172656],[-74.689844,-45.662598],[-74.312891,-45.691504]]],[[[-70.991602,-54.867969],[-71.437207,-54.889258],[-70.297852,-55.11377],[-70.991602,-54.867969]]]]}}, - {"type":"Feature","properties":{"name":"乍得","full_name":"乍得共和国","iso_a2":"TD","iso_a3":"TCD","iso_n3":"148"},"geometry":{"type":"Polygon","coordinates":[[[23.980273,19.496631],[20.147656,21.389258],[15.984082,23.445215],[14.979004,22.996191],[15.181836,21.523389],[15.963184,20.346191],[15.735059,19.904053],[15.474316,16.908398],[13.448242,14.380664],[13.606348,13.70459],[14.063965,13.078516],[14.461719,13.021777],[14.84707,12.5021],[15.132227,10.648486],[15.654883,10.007812],[14.243262,9.979736],[13.977246,9.691553],[15.116211,8.557324],[15.549805,7.787891],[15.480078,7.523779],[15.957617,7.507568],[16.545313,7.865479],[16.784766,7.550977],[17.649414,7.983594],[18.56416,8.045898],[19.108691,8.656152],[18.95625,8.938867],[20.34209,9.1271],[21.682715,10.289844],[21.771484,10.642822],[22.493848,10.99624],[22.860059,10.919678],[22.922656,11.344873],[22.591113,11.579883],[22.352344,12.660449],[21.825293,12.790527],[22.228125,13.32959],[22.106445,13.799805],[22.538574,14.161865],[22.381543,14.550488],[22.932324,15.162109],[22.933887,15.533105],[23.970801,15.721533],[23.980273,19.496631]]]}}, - {"type":"Feature","properties":{"name":"中非","full_name":"中非共和国","iso_a2":"CF","iso_a3":"CAF","iso_n3":"140"},"geometry":{"type":"Polygon","coordinates":[[[24.147363,8.665625],[23.537305,8.81582],[23.646289,9.8229],[22.860059,10.919678],[22.493848,10.99624],[21.771484,10.642822],[21.682715,10.289844],[20.34209,9.1271],[18.95625,8.938867],[19.108691,8.656152],[18.56416,8.045898],[17.649414,7.983594],[16.784766,7.550977],[16.545313,7.865479],[15.957617,7.507568],[15.480078,7.523779],[14.431152,6.038721],[14.73125,4.602393],[15.063574,4.284863],[15.128711,3.826904],[16.063477,2.908594],[16.183398,2.270068],[16.610742,3.505371],[17.491602,3.687305],[18.610352,3.478418],[18.594141,4.34624],[19.500977,5.12749],[20.558105,4.462695],[22.422168,4.134961],[22.864551,4.723877],[25.065234,4.967432],[25.525098,5.312109],[27.40332,5.10918],[27.143945,5.722949],[26.514258,6.069238],[26.361816,6.635303],[25.278906,7.42749],[24.85332,8.137549],[24.291406,8.291406],[24.147363,8.665625]]]}}, - {"type":"Feature","properties":{"name":"佛得角","full_name":"佛得角共和国","iso_a2":"CV","iso_a3":"CPV","iso_n3":"132"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-25.169824,16.946484],[-25.034668,17.176465],[-25.337109,17.091016],[-25.169824,16.946484]]],[[[-23.444238,15.007959],[-23.748096,15.328516],[-23.705371,14.961328],[-23.444238,15.007959]]],[[[-22.917725,16.237256],[-22.959277,16.045117],[-22.710107,16.043359],[-22.917725,16.237256]]]]}}, - {"type":"Feature","properties":{"name":"加拿大","full_name":"加拿大","iso_a2":"CA","iso_a3":"CAN","iso_n3":"124"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-132.655518,54.12749],[-133.048389,54.158936],[-133.079492,53.837012],[-132.347266,53.189209],[-131.957422,53.308691],[-131.667627,54.141357],[-132.534668,53.651709],[-132.166113,53.955225],[-132.655518,54.12749]]],[[[-131.753711,53.195557],[-132.546777,53.1375],[-131.221533,52.153613],[-131.455225,52.701709],[-131.971777,52.879834],[-131.634668,52.922168],[-131.753711,53.195557]]],[[[-127.197314,50.640381],[-127.918066,50.860547],[-128.346045,50.744238],[-128.05835,50.498486],[-127.465918,50.583105],[-127.486523,50.404639],[-127.905859,50.445215],[-127.863916,50.127734],[-127.249805,50.137988],[-127.114307,49.879736],[-126.134082,49.672314],[-126.519141,49.396777],[-125.9354,49.401465],[-125.489453,48.933789],[-124.812646,49.212646],[-125.120703,48.760791],[-123.310645,48.411035],[-125.48208,50.316797],[-127.197314,50.640381]]],[[[-130.025098,55.888232],[-130.048486,55.057275],[-129.795166,55.55957],[-129.560645,55.462549],[-130.430273,54.420996],[-129.626025,54.230273],[-130.335254,53.723926],[-129.563721,53.251465],[-128.959375,53.841455],[-128.532129,53.858105],[-128.675537,53.55459],[-127.927832,53.274707],[-128.905615,53.559326],[-128.85459,53.704541],[-129.171582,53.533594],[-128.365039,52.825781],[-128.053271,52.910693],[-128.271533,52.362988],[-127.940234,52.545166],[-128.357617,52.158887],[-128.102246,51.788428],[-127.791895,52.289355],[-126.951367,52.751025],[-127.193994,52.457666],[-126.713965,52.060693],[-127.437939,52.356152],[-127.795361,52.191016],[-127.850537,51.673193],[-127.668701,51.477588],[-127.338721,51.707373],[-126.691455,51.703418],[-127.419678,51.608057],[-127.708105,51.151172],[-127.057568,50.867529],[-126.517334,51.056836],[-126.514355,50.679395],[-125.904102,50.704932],[-126.447461,50.587744],[-126.094336,50.497607],[-125.058789,50.513867],[-124.859863,50.872412],[-125.056689,50.418652],[-124.782373,50.020117],[-124.141602,49.792676],[-123.880127,50.173633],[-123.874414,49.736816],[-123.582471,49.68125],[-124.028613,49.602881],[-123.530566,49.397314],[-123.1875,49.680322],[-123.276758,49.343945],[-122.879102,49.398926],[-123.196338,49.147705],[-122.78877,48.993018],[-114.585107,48.993066],[-106.483838,48.993115],[-97.529834,48.993164],[-95.162061,48.991748],[-95.155273,49.369678],[-94.620898,48.742627],[-92.99624,48.611816],[-91.518311,48.058301],[-90.916064,48.209131],[-89.455664,47.99624],[-88.378174,48.303076],[-84.875977,46.899902],[-84.561768,46.457373],[-84.149463,46.542773],[-83.977783,46.084912],[-83.615967,46.116846],[-83.592676,45.817139],[-82.551074,45.347363],[-82.137842,43.570898],[-82.545312,42.624707],[-83.149658,42.141943],[-82.690039,41.675195],[-79.036719,42.802344],[-79.171875,43.466553],[-78.72041,43.624951],[-76.819971,43.628809],[-76.151172,44.303955],[-74.708887,45.003857],[-73.973828,45.345117],[-74.315088,45.531055],[-73.476611,45.738232],[-72.981006,46.209717],[-71.87959,46.686816],[-71.267773,46.795947],[-70.705859,47.139795],[-69.994434,47.739893],[-69.865527,48.172266],[-71.018262,48.455615],[-69.673877,48.19917],[-68.281934,49.197168],[-67.372021,49.348438],[-66.495508,50.211865],[-62.71543,50.30166],[-61.724854,50.104053],[-59.886328,50.316406],[-58.510352,51.295068],[-56.975977,51.457666],[-55.695215,52.137793],[-56.011719,52.394482],[-55.746484,52.474561],[-56.324902,52.544531],[-55.802832,52.643164],[-55.797949,53.211963],[-56.46499,53.765039],[-57.331738,53.469092],[-57.416064,54.162744],[-58.19209,54.228174],[-57.935986,54.091162],[-60.329492,53.266113],[-60.100293,53.486963],[-60.39541,53.65332],[-58.633203,54.049561],[-57.404492,54.590869],[-57.962451,54.875732],[-58.780176,54.838379],[-59.25957,55.199951],[-59.837793,54.813965],[-59.437891,55.175928],[-59.758789,55.30957],[-60.617139,55.060205],[-60.341016,55.784668],[-61.449512,55.995703],[-61.364697,56.216016],[-61.713086,56.230957],[-61.425293,56.360645],[-62.497266,56.801709],[-61.371631,56.680811],[-61.33374,57.010596],[-62.495557,57.489209],[-61.967969,57.611914],[-61.958643,57.911768],[-62.48623,58.154053],[-63.261523,58.014697],[-62.593848,58.474023],[-63.537061,58.329932],[-62.873877,58.672461],[-63.248437,59.068311],[-63.971143,59.053809],[-63.415137,59.194385],[-63.945459,59.380176],[-64.283496,60.064062],[-64.768457,60.012109],[-64.499414,60.268262],[-64.817334,60.331055],[-65.028174,59.770703],[-65.433398,59.776514],[-65.038232,59.387891],[-65.475098,59.470312],[-65.383545,59.060205],[-66.043066,58.820654],[-66.002393,58.431201],[-66.362402,58.791162],[-67.678271,57.991113],[-68.021045,58.485303],[-68.413574,58.051758],[-69.04082,57.90249],[-68.356543,58.163232],[-68.381152,58.743506],[-68.698193,58.904541],[-70.154346,58.760596],[-69.531641,58.869238],[-69.344043,59.303076],[-69.681885,59.341748],[-69.733936,59.918018],[-70.654834,60.026221],[-69.67373,60.075879],[-69.50332,61.04043],[-69.992432,60.856494],[-71.422705,61.158936],[-71.638281,61.617188],[-72.215869,61.587256],[-72.686963,62.124561],[-73.705078,62.473145],[-74.632568,62.115674],[-77.372412,62.57251],[-78.133398,62.282275],[-77.514355,61.556299],[-78.181348,60.819141],[-77.589551,60.808594],[-77.349072,59.578955],[-77.726172,59.675879],[-78.515088,58.682373],[-76.809814,57.657959],[-76.604053,56.199561],[-77.775293,55.29126],[-79.712354,54.671826],[-78.996045,54.00249],[-78.448096,52.261377],[-78.981641,51.774561],[-78.903174,51.200293],[-79.338672,51.628174],[-79.737451,51.186279],[-79.3479,50.762646],[-80.103564,51.282861],[-80.851221,51.125],[-80.443311,51.388574],[-80.588037,51.667236],[-81.827881,52.224219],[-81.599414,52.432617],[-82.291553,53.030713],[-82.393262,55.067822],[-83.910596,55.314648],[-85.365283,55.079297],[-85.559326,55.540186],[-87.482422,56.021289],[-88.948486,56.851318],[-90.897461,57.256934],[-92.798145,56.921973],[-92.432812,57.320312],[-93.17876,58.725635],[-94.123193,58.736719],[-94.332227,58.297363],[-94.287061,58.716016],[-94.957324,59.068848],[-94.761719,60.498242],[-93.312012,61.767285],[-93.581787,61.942041],[-92.905518,62.215137],[-93.205371,62.364941],[-92.527979,62.168408],[-92.551416,62.546729],[-91.93584,62.592383],[-92.361279,62.819385],[-90.698584,63.063867],[-90.970068,63.442773],[-91.841846,63.697559],[-92.465088,63.555078],[-92.156885,63.691699],[-93.559814,63.865283],[-93.696338,64.147168],[-90.811914,63.580908],[-90.154736,63.689648],[-90.04165,64.140869],[-89.131543,63.968506],[-88.105615,64.183301],[-87.027539,65.198096],[-88.974023,65.348291],[-89.924072,65.780273],[-91.427246,65.9479],[-89.749414,65.936035],[-87.452881,65.338965],[-85.95874,66.119043],[-86.708154,66.523047],[-85.603857,66.568262],[-83.869043,66.213574],[-84.223047,66.682471],[-85.113721,66.906934],[-84.538477,66.972803],[-83.406445,66.37124],[-81.467578,67.069873],[-81.294336,67.497412],[-82.552686,68.446484],[-81.281543,68.657227],[-81.95791,68.883643],[-81.377832,69.185645],[-82.227539,69.248877],[-82.618359,69.691064],[-85.507373,69.845264],[-84.867578,68.77334],[-85.643164,68.699707],[-86.560791,67.482129],[-87.359375,67.177246],[-88.313818,67.950342],[-88.346973,68.288281],[-87.813574,68.345703],[-88.223535,68.915039],[-89.279541,69.255469],[-90.204785,68.257471],[-91.237207,69.285547],[-90.415576,69.456982],[-92.887793,69.668213],[-91.976709,70.038672],[-92.320508,70.235352],[-91.564062,70.178271],[-94.734863,71.982959],[-95.872314,71.573145],[-95.564258,71.336768],[-96.446582,71.239893],[-95.878613,70.548975],[-96.551367,70.210303],[-95.964941,69.802783],[-93.532275,69.480908],[-94.600439,68.803223],[-93.852441,69.000342],[-93.448926,68.618896],[-95.460693,68.021387],[-95.25874,67.262549],[-95.399658,66.949463],[-96.036865,66.9375],[-95.787549,66.616797],[-96.422559,67.051758],[-95.418896,67.013232],[-96.369141,67.509766],[-95.970312,68.249121],[-96.72207,68.03877],[-96.430664,68.310596],[-97.410352,68.496533],[-98.650488,68.363525],[-98.192529,67.922998],[-97.206543,67.855078],[-97.454932,67.616992],[-98.631543,68.072559],[-98.412109,67.807178],[-98.920459,67.725781],[-102.320361,67.735645],[-103.474121,68.115039],[-104.486816,68.063184],[-106.164453,68.919873],[-108.313477,68.610791],[-108.718115,68.297461],[-107.73418,68.17373],[-105.750195,68.592285],[-107.958398,67.818604],[-107.259473,66.398535],[-108.496045,67.092285],[-107.988721,67.256396],[-110.073926,67.99292],[-112.503027,67.681934],[-115.133203,67.819189],[-115.127051,68.132031],[-113.964404,68.399072],[-115.442285,68.940918],[-117.226953,68.913428],[-122.070068,69.816162],[-123.025781,69.81001],[-123.528418,69.389355],[-124.338086,69.364844],[-124.555029,70.151221],[-125.524951,69.351563],[-127.991016,70.573828],[-127.683789,70.260352],[-128.853027,69.751025],[-128.898926,69.966162],[-130.117627,69.720068],[-130.970654,69.209082],[-131.063428,69.450684],[-131.788379,69.431982],[-133.196826,68.739844],[-132.81748,69.205762],[-129.648291,69.997754],[-129.675635,70.192969],[-134.174316,69.252832],[-134.408936,69.681787],[-135.691455,69.311182],[-135.939014,68.97417],[-135.258838,68.684326],[-141.002148,69.650781],[-141.002148,64.975537],[-141.002148,60.300244],[-139.079248,60.343701],[-139.185156,60.083594],[-137.438574,58.903125],[-135.475928,59.793262],[-133.401123,58.410889],[-131.824268,56.58999],[-130.097852,56.109277],[-130.025098,55.888232]]],[[[-109.815967,78.650391],[-110.877588,78.735059],[-113.223047,78.2979],[-109.484473,78.316406],[-109.815967,78.650391]]],[[[-110.458057,78.103223],[-113.215186,77.903516],[-112.372656,77.364111],[-110.198486,77.524512],[-110.865625,77.834131],[-109.622266,78.074756],[-110.458057,78.103223]]],[[[-115.55127,77.363281],[-116.511328,77.547607],[-116.843555,77.339551],[-119.090186,77.305078],[-122.519385,76.353174],[-122.533057,75.950928],[-120.848389,76.182666],[-119.912891,75.858838],[-117.880811,76.805078],[-117.233594,76.281543],[-115.55127,77.363281]]],[[[-108.292383,76.057129],[-109.098242,76.811865],[-110.314453,76.369385],[-108.947168,75.541797],[-111.052686,75.548535],[-112.697607,76.201709],[-114.998486,76.497461],[-115.822168,76.27002],[-114.778613,76.172607],[-116.209863,76.194434],[-116.664551,75.957568],[-114.991504,75.896338],[-117.163623,75.644873],[-115.141846,75.678516],[-117.565234,75.23335],[-115.728857,74.968115],[-114.451758,75.087891],[-114.016504,75.434277],[-113.711768,75.068604],[-111.093457,75.256299],[-114.312695,74.715088],[-112.519336,74.416846],[-108.831299,75.064893],[-107.153418,74.927148],[-106.092627,75.089453],[-105.632666,75.945361],[-106.677002,76.02373],[-106.913525,75.679639],[-108.292383,76.057129]]],[[[-114.521533,72.59292],[-114.051709,73.070996],[-114.638232,73.372656],[-118.133105,72.632812],[-118.987695,71.764258],[-117.742334,71.659326],[-117.935645,71.39209],[-115.303418,71.493701],[-118.269092,71.034717],[-117.587061,70.629541],[-113.757275,70.690723],[-111.632568,70.308838],[-117.19541,70.054053],[-116.513477,69.424609],[-113.694141,69.19502],[-113.127734,68.494141],[-109.472119,68.676709],[-107.439893,69.002148],[-106.659082,69.4396],[-104.571436,68.872119],[-101.857129,69.023975],[-102.045947,69.464844],[-103.120215,69.20459],[-103.464893,69.644482],[-102.621094,69.551514],[-102.182129,69.845947],[-100.982373,69.679883],[-101.042676,70.110791],[-103.58457,70.630859],[-104.514795,71.064258],[-105.415137,72.78833],[-106.482129,73.196191],[-108.029053,73.34873],[-108.237402,73.149902],[-107.306006,71.894678],[-107.812842,71.626172],[-110.008447,72.983643],[-110.66084,73.008203],[-110.205127,72.661279],[-111.675098,72.300146],[-111.269727,72.713721],[-112.753613,72.986035],[-114.521533,72.59292]]],[[[-119.736328,74.112646],[-121.50415,74.545117],[-124.69624,74.348193],[-123.797266,73.768164],[-125.845312,71.978662],[-124.007764,71.677441],[-123.095654,71.093799],[-120.619336,71.505762],[-120.179883,72.212646],[-115.446875,73.438867],[-117.514844,74.231738],[-119.736328,74.112646]]],[[[-69.488867,83.016797],[-72.81167,83.081201],[-72.658691,82.721631],[-74.41416,83.013135],[-77.124902,83.008545],[-76.009375,82.535156],[-77.618066,82.89585],[-80.154932,82.911133],[-78.748779,82.679395],[-81.010156,82.779053],[-82.447559,82.39502],[-79.465625,81.851123],[-84.896826,82.449414],[-86.615625,82.218555],[-85.044824,81.982812],[-88.063184,82.096484],[-91.647559,81.683838],[-89.82168,81.634863],[-90.416309,81.405371],[-87.597021,81.52583],[-89.673682,81.328613],[-89.623047,81.032471],[-84.941211,81.28623],[-89.166895,80.941309],[-88.003662,80.675391],[-83.288818,81.147949],[-86.250342,80.565771],[-81.007031,80.654883],[-76.885107,81.430273],[-78.716211,80.95166],[-76.862988,80.864795],[-82.987012,80.322607],[-80.475928,79.60625],[-83.723633,80.228955],[-86.498535,80.258252],[-86.420752,79.845215],[-83.575879,79.053662],[-84.412012,78.996582],[-81.750098,78.975781],[-83.271436,78.770312],[-86.80791,78.774365],[-87.551758,78.176611],[-85.920068,78.342871],[-85.585938,78.10957],[-84.783203,78.527588],[-84.222705,78.176025],[-85.547559,77.927686],[-85.289355,77.559033],[-83.779395,77.532617],[-82.710352,77.849512],[-83.721289,77.414209],[-84.738672,77.361035],[-86.755078,77.863721],[-88.016992,77.784717],[-86.812256,77.184912],[-89.499756,76.826807],[-89.369629,76.474463],[-88.545801,76.420898],[-88.49585,76.772852],[-88.395996,76.405273],[-87.497559,76.386279],[-87.489795,76.58584],[-86.680225,76.376611],[-86.453711,76.584863],[-85.14126,76.30459],[-84.275342,76.356543],[-84.223779,76.675342],[-83.885693,76.453125],[-82.233154,76.46582],[-82.529834,76.723291],[-80.799707,76.173584],[-78.284326,76.57124],[-78.288867,76.977979],[-82.056787,77.296533],[-81.659082,77.525439],[-78.708496,77.342139],[-78.012598,77.946045],[-75.865967,78.009814],[-75.193457,78.327734],[-76.416113,78.511523],[-74.486328,78.750098],[-78.581641,79.075],[-74.640918,79.035547],[-76.898828,79.512305],[-73.361523,79.504004],[-74.394482,79.874072],[-71.387842,79.761768],[-70.568408,80.093701],[-72.055957,80.123242],[-70.264893,80.233594],[-70.712598,80.5396],[-69.550684,80.383252],[-64.780078,81.492871],[-68.688525,81.293311],[-61.615381,82.184424],[-63.641016,82.812598],[-68.469336,82.653369],[-66.422559,82.926855],[-69.488867,83.016797]]],[[[-95.484375,77.791992],[-95.987061,77.484131],[-93.543945,77.46665],[-93.300977,77.739795],[-95.484375,77.791992]]],[[[-93.542578,75.02793],[-94.878174,75.630029],[-96.599609,75.031787],[-94.534521,74.636719],[-93.573096,74.668848],[-93.542578,75.02793]]],[[[-100.001904,73.945898],[-100.962988,73.791406],[-100.52168,73.449316],[-101.523193,73.486377],[-99.825146,73.213867],[-100.536377,73.197852],[-100.128125,72.906689],[-101.273193,72.72168],[-102.204004,73.077295],[-102.70874,72.764502],[-98.662891,71.3021],[-98.322705,71.852344],[-97.582275,71.629688],[-96.613428,71.833838],[-96.445605,72.552441],[-97.636328,73.027637],[-98.421777,72.941016],[-97.170508,73.824854],[-99.157959,73.731592],[-100.001904,73.945898]]],[[[-84.919629,65.261084],[-85.554688,65.918652],[-86.2521,64.136865],[-87.151904,63.585645],[-85.768945,63.700342],[-85.392627,63.119678],[-83.303955,64.143799],[-81.046387,63.461572],[-80.302051,63.762207],[-80.828955,64.089941],[-81.887109,64.016406],[-82.05,64.644287],[-84.501123,65.458447],[-84.919629,65.261084]]],[[[-97.700928,76.466504],[-98.71084,76.693848],[-98.890332,76.465576],[-100.829736,76.523877],[-99.541064,76.146289],[-99.865479,75.924219],[-101.415186,76.424902],[-102.104688,76.331201],[-100.972803,75.798438],[-102.144727,75.875049],[-102.587402,75.513672],[-99.19458,75.698389],[-100.711914,75.406348],[-100.234375,75.007715],[-97.674316,75.127295],[-97.700928,76.466504]]],[[[-103.426025,79.315625],[-105.514551,79.24248],[-104.895508,78.808154],[-104.151953,78.989893],[-103.371582,78.736328],[-104.763574,78.35166],[-102.731348,78.371045],[-100.274658,77.832715],[-99.166406,77.856934],[-99.609424,78.583057],[-101.703662,79.078906],[-102.576172,78.879395],[-103.426025,79.315625]]],[[[-91.885547,81.132861],[-94.220117,81.330762],[-93.286719,81.100293],[-95.514746,80.838135],[-93.92793,80.55918],[-95.926953,80.720654],[-96.394092,80.315039],[-94.262598,80.194873],[-96.773242,80.135791],[-96.589062,79.91665],[-95.739355,79.660156],[-94.401855,79.736328],[-95.662891,79.527344],[-95.103174,79.289893],[-91.299902,79.372705],[-94.1146,78.928906],[-91.866895,78.542676],[-92.35127,78.312891],[-89.525684,78.159619],[-90.037109,78.606836],[-88.822412,78.185889],[-88.040186,78.995312],[-87.617383,78.676318],[-85.042139,79.28457],[-85.647852,79.611426],[-87.295166,79.580176],[-87.675,80.372119],[-88.857324,80.166211],[-91.885547,81.132861]]],[[[-94.294971,76.912451],[-95.849512,77.066211],[-96.880713,76.73833],[-95.273877,76.264404],[-93.091748,76.354004],[-91.549121,74.655566],[-90.880225,74.817773],[-89.558691,74.554736],[-88.534961,74.831738],[-88.423047,74.494141],[-84.425537,74.508105],[-83.531885,74.585693],[-83.52207,74.901465],[-82.735791,74.530273],[-80.262744,74.584473],[-80.347754,74.902979],[-79.401416,74.917627],[-80.381982,75.03418],[-79.509082,75.259814],[-80.321973,75.629102],[-83.931982,75.818945],[-85.951465,75.39502],[-88.644971,75.658447],[-88.916699,75.453955],[-91.407324,76.220068],[-89.284521,76.301611],[-91.415088,76.455859],[-90.542627,76.495752],[-91.305029,76.680762],[-93.53457,76.447705],[-93.230029,76.770264],[-94.294971,76.912451]]],[[[-96.204492,78.531299],[-98.332617,78.773535],[-96.989648,77.806006],[-94.934277,78.075635],[-94.915381,78.390527],[-96.204492,78.531299]]],[[[-93.17085,74.160986],[-94.973535,74.041406],[-94.697607,73.663574],[-95.63291,73.695459],[-95.007861,72.012793],[-94.037549,72.02876],[-93.555176,72.421143],[-94.211328,72.756934],[-92.11792,72.753809],[-90.381396,73.824756],[-93.17085,74.160986]]],[[[-97.439453,69.642676],[-98.200488,69.796973],[-98.041357,69.456641],[-98.545996,69.5729],[-99.494678,68.95957],[-96.401562,68.470703],[-95.267773,68.826074],[-97.439453,69.642676]]],[[[-61.105176,45.944727],[-60.494531,46.270264],[-60.408203,47.003516],[-61.408643,46.170361],[-61.449805,45.716211],[-60.672949,45.59082],[-59.842188,45.941553],[-60.297949,46.31123],[-60.737891,45.751416],[-61.059033,45.703369],[-60.865234,45.983496],[-61.105176,45.944727]]],[[[-67.124854,45.169434],[-66.439844,45.095898],[-66.026562,45.417578],[-65.884473,45.2229],[-64.778516,45.638428],[-64.632715,45.946631],[-64.314648,45.835693],[-64.873145,45.35459],[-63.368018,45.364795],[-64.135498,45.023047],[-64.448145,45.337451],[-66.090625,44.504932],[-65.868018,44.568799],[-66.125732,43.813818],[-65.481689,43.518066],[-64.286084,44.550342],[-63.609766,44.47998],[-63.604004,44.683203],[-61.031543,45.291748],[-61.955518,45.868164],[-62.750098,45.648242],[-64.541504,46.240332],[-64.831396,47.060791],[-65.318896,47.101221],[-64.703223,47.724854],[-65.607227,47.67002],[-66.704395,48.022461],[-65.926709,48.188867],[-65.259424,48.02124],[-64.348828,48.423193],[-64.513721,48.841113],[-64.216211,48.873633],[-64.836328,49.191748],[-66.178174,49.213135],[-68.238184,48.626416],[-70.519482,47.03252],[-71.261182,46.75625],[-72.109277,46.551221],[-73.15957,46.010059],[-73.558105,45.425098],[-74.708887,45.003857],[-71.517529,45.007568],[-71.327295,45.290088],[-70.865039,45.270703],[-69.242871,47.462988],[-68.937207,47.21123],[-68.235498,47.345947],[-67.806787,47.082812],[-67.802246,45.727539],[-67.124854,45.169434]]],[[[-55.45874,51.536523],[-56.025586,51.568359],[-57.035937,51.01084],[-57.791309,49.48999],[-58.213379,49.38667],[-57.990527,48.987939],[-58.403662,49.084326],[-58.716455,48.598047],[-58.841797,48.746436],[-59.167676,48.558496],[-58.330225,48.522119],[-59.320654,47.736914],[-59.116943,47.570703],[-58.336865,47.730859],[-56.774121,47.56499],[-55.85791,47.819189],[-56.127246,47.502832],[-55.576123,47.465234],[-54.784619,47.664746],[-55.919238,47.016895],[-55.788525,46.867236],[-54.488135,47.403857],[-54.191846,47.859814],[-53.849512,47.440332],[-54.17373,46.880371],[-53.597363,47.145996],[-53.589795,46.638867],[-53.114844,46.655811],[-52.653662,47.549414],[-52.782422,47.769434],[-53.169824,47.512109],[-52.866016,48.112988],[-53.672363,47.648242],[-53.86958,48.019678],[-53.027588,48.634717],[-54.114453,48.393604],[-53.706348,48.655518],[-54.161279,48.787695],[-53.619434,49.321631],[-54.448242,49.329443],[-54.502197,49.527344],[-55.353174,49.079443],[-55.229541,49.508154],[-56.087305,49.451953],[-55.869824,49.670166],[-56.140186,49.619141],[-55.50293,49.983154],[-56.161279,49.940137],[-56.179395,50.11499],[-56.822168,49.613477],[-55.8,51.033301],[-56.031104,51.328369],[-55.45874,51.536523]]],[[[-86.589355,71.010791],[-85.023389,71.353223],[-86.218457,71.899121],[-86.656299,72.724023],[-84.946777,73.721631],[-85.950781,73.850146],[-88.705176,73.403271],[-89.861523,72.411914],[-89.805371,71.462305],[-87.140088,71.011621],[-89.455908,71.061719],[-88.782715,70.494482],[-87.838135,70.246582],[-86.396875,70.465332],[-85.780029,70.03667],[-81.564697,69.942725],[-80.921729,69.730908],[-81.651953,70.094629],[-78.889648,69.97749],[-79.066406,70.603564],[-75.647754,69.212549],[-76.557227,69.009473],[-76.585059,68.69873],[-74.716699,69.045508],[-74.892969,68.808154],[-74.270117,68.541211],[-73.822119,68.685986],[-72.22002,67.254297],[-74.416406,66.16709],[-73.550781,65.485254],[-75.798682,65.29751],[-75.4521,64.841602],[-75.82832,65.227051],[-77.326709,65.453125],[-78.095605,64.939258],[-78.045215,64.499268],[-76.856152,64.237646],[-74.694727,64.496582],[-74.681396,64.830664],[-74.064795,64.424658],[-73.271289,64.58252],[-72.498438,63.823486],[-71.380859,63.580322],[-71.992236,63.416162],[-71.347266,63.066113],[-69.604736,62.767725],[-68.535889,62.255615],[-66.123877,61.893066],[-65.980176,62.208887],[-68.911084,63.703223],[-67.722559,63.422754],[-67.893262,63.73374],[-66.697461,63.069531],[-66.65498,63.264746],[-65.108496,62.626465],[-65.162793,62.932617],[-64.672363,62.921973],[-65.191846,63.764258],[-64.664648,63.245361],[-64.410937,63.706348],[-65.580322,64.293848],[-65.074609,64.43667],[-65.529346,64.504785],[-65.274805,64.631543],[-66.635498,65.000342],[-66.697412,64.815186],[-67.117969,65.440381],[-67.936768,65.564893],[-68.748926,66.200049],[-67.350439,65.929736],[-67.883398,66.467432],[-67.225391,66.310254],[-66.986328,66.62749],[-66.063721,66.132715],[-65.656348,66.204736],[-65.825732,65.996924],[-64.445361,66.317139],[-65.401611,65.764014],[-64.555078,65.116602],[-64.269678,65.400781],[-63.606592,64.928076],[-63.45874,65.853027],[-62.658887,65.639941],[-62.624121,66.01626],[-61.991602,66.035303],[-62.553125,66.406836],[-61.570801,66.3729],[-62.12334,66.643066],[-61.353418,66.689209],[-62.123584,67.046729],[-63.701562,66.822363],[-63.040137,67.23501],[-64.699951,67.350537],[-63.850195,67.566064],[-65.021094,67.787549],[-64.922314,68.031641],[-65.40127,67.674854],[-65.942383,68.070947],[-66.443945,67.833838],[-66.212402,68.28042],[-66.923096,68.065723],[-66.742725,68.457764],[-69.319092,68.856982],[-67.883203,68.783984],[-67.832617,69.065967],[-69.040625,69.097998],[-68.406299,69.232227],[-66.707422,69.168213],[-67.236963,69.460107],[-69.250781,69.511914],[-67.221631,69.730713],[-67.363672,70.034424],[-68.059082,70.317236],[-68.744043,69.941406],[-68.778223,70.203564],[-70.057715,70.042627],[-68.363525,70.48125],[-69.949805,70.84502],[-71.429443,70.127783],[-71.275879,70.500293],[-71.890186,70.431543],[-70.672656,71.052197],[-72.632715,70.830762],[-71.229395,71.33877],[-72.901953,71.677783],[-73.180615,71.282861],[-73.712842,71.587598],[-74.197266,71.40415],[-73.814062,71.771436],[-74.996191,71.218115],[-74.700781,71.675586],[-75.204785,71.709131],[-74.209326,71.978662],[-74.903174,72.100488],[-75.922803,71.717236],[-75.052686,72.226367],[-75.704297,72.571533],[-77.753223,72.724756],[-78.484277,72.470605],[-77.516504,72.177783],[-78.699268,72.351416],[-78.585107,71.880615],[-79.831299,72.446289],[-80.925146,71.907666],[-80.611475,72.45083],[-81.229346,72.311719],[-80.277246,72.770166],[-81.946143,73.729834],[-85.454736,73.105469],[-84.256641,72.796729],[-85.262109,72.954004],[-85.649902,72.722168],[-84.28374,72.044482],[-85.321875,72.233154],[-85.911621,71.986523],[-84.699414,71.631445],[-84.82373,71.028613],[-86.589355,71.010791]]],[[[-61.801123,49.093896],[-62.858545,49.705469],[-64.485205,49.886963],[-63.041504,49.224951],[-61.801123,49.093896]]],[[[-63.811279,46.468701],[-63.993555,47.061572],[-64.388037,46.640869],[-63.641016,46.230469],[-62.978467,46.316357],[-63.02207,46.066602],[-62.531348,45.977295],[-62.02373,46.421582],[-63.811279,46.468701]]],[[[-82.000488,62.954199],[-83.376416,62.904932],[-83.910498,62.45415],[-83.698877,62.160254],[-82.568262,62.403223],[-82.000488,62.954199]]],[[[-79.545312,62.411719],[-80.260059,62.109033],[-79.816113,61.594629],[-79.323926,62.026074],[-79.545312,62.411719]]],[[[-75.675879,68.32251],[-76.595801,68.278955],[-77.125879,67.94707],[-76.944189,67.250293],[-75.201953,67.45918],[-75.078125,68.173145],[-75.675879,68.32251]]],[[[-79.537305,73.654492],[-80.848877,73.72124],[-79.820703,72.826318],[-76.183398,72.843066],[-77.206543,73.499561],[-79.537305,73.654492]]],[[[-80.731689,52.747266],[-81.135596,53.205811],[-82.039258,53.049902],[-80.731689,52.747266]]],[[[-78.935596,56.266064],[-79.272412,56.600439],[-79.536328,56.180078],[-79.458887,56.539746],[-79.9875,55.892139],[-79.544727,56.128369],[-79.764746,55.806787],[-79.495117,55.874756],[-79.182129,56.212158],[-79.175488,55.885059],[-78.935596,56.266064]]],[[[-89.833252,77.267627],[-91.019043,77.643896],[-90.993213,77.329492],[-89.833252,77.267627]]],[[[-98.791602,79.981104],[-100.053271,80.093359],[-98.945215,79.724072],[-98.791602,79.981104]]],[[[-105.288916,72.919922],[-104.5875,73.578076],[-106.613965,73.695605],[-106.921533,73.479834],[-105.288916,72.919922]]],[[[-102.227344,76.014893],[-102.728027,76.307031],[-104.350635,76.182324],[-103.314746,75.764209],[-102.227344,76.014893]]],[[[-104.022852,76.583105],[-104.35752,76.334619],[-103.311377,76.347559],[-104.022852,76.583105]]],[[[-118.328125,75.579688],[-117.633691,76.115088],[-119.39458,75.617334],[-118.328125,75.579688]]],[[[-113.832471,77.754639],[-114.330371,78.077539],[-114.98042,77.91543],[-113.832471,77.754639]]],[[[-130.236279,53.958545],[-130.447998,54.089014],[-130.703174,53.892236],[-130.236279,53.958545]]],[[[-128.552441,52.939746],[-129.033252,53.279932],[-129.175928,52.964941],[-128.746338,52.763379],[-128.678955,52.289648],[-128.552441,52.939746]]],[[[-73.566504,45.469092],[-73.476074,45.704736],[-73.960547,45.441406],[-73.566504,45.469092]]],[[[-68.233789,60.240918],[-67.818848,60.449512],[-68.087598,60.587842],[-68.233789,60.240918]]],[[[-64.832617,61.366064],[-64.789648,61.662207],[-65.432129,61.649512],[-64.832617,61.366064]]],[[[-79.063086,75.925879],[-79.009326,76.145898],[-79.63877,75.84292],[-79.063086,75.925879]]],[[[-79.384277,51.951953],[-79.271289,52.086816],[-79.64375,52.010059],[-79.384277,51.951953]]],[[[-73.621729,67.783838],[-73.49375,68.000635],[-74.706543,68.06709],[-74.573389,67.828662],[-73.621729,67.783838]]],[[[-77.876709,63.470557],[-78.536768,63.42373],[-77.942432,63.114404],[-77.532715,63.233643],[-77.876709,63.470557]]],[[[-98.270361,73.868506],[-97.698242,74.108691],[-99.416992,73.89541],[-98.270361,73.868506]]],[[[-79.430664,69.787793],[-80.794775,69.689258],[-79.977832,69.509668],[-79.430664,69.787793]]],[[[-76.995361,69.14375],[-76.668848,69.366162],[-77.187549,69.440088],[-76.995361,69.14375]]],[[[-83.725977,65.796729],[-84.407178,66.131006],[-84.118262,65.771777],[-83.332422,65.631055],[-83.725977,65.796729]]],[[[-101.693555,77.696582],[-101.193213,77.829785],[-102.447705,77.880615],[-101.693555,77.696582]]],[[[-96.078564,75.510107],[-96.367822,75.654639],[-96.915137,75.379688],[-96.078564,75.510107]]],[[[-104.119922,75.036328],[-104.346191,75.429932],[-104.887402,75.147754],[-104.119922,75.036328]]]]}}, - {"type":"Feature","properties":{"name":"喀麦隆","full_name":"喀麦隆共和国","iso_a2":"CM","iso_a3":"CMR","iso_n3":"120"},"geometry":{"type":"Polygon","coordinates":[[[8.555859,4.755225],[8.574414,4.526221],[8.918262,4.55376],[9.000098,4.091602],[9.688867,4.056396],[9.556152,3.798047],[9.948438,3.079053],[9.800781,2.304443],[11.328711,2.167432],[13.293555,2.161572],[14.578906,2.199121],[16.059375,1.676221],[16.183398,2.270068],[16.063477,2.908594],[15.128711,3.826904],[15.063574,4.284863],[14.73125,4.602393],[14.431152,6.038721],[15.480078,7.523779],[15.549805,7.787891],[15.116211,8.557324],[13.977246,9.691553],[14.243262,9.979736],[15.654883,10.007812],[15.132227,10.648486],[14.84707,12.5021],[14.461719,13.021777],[14.063965,13.078516],[14.197461,12.383789],[14.619727,12.150977],[14.575391,11.532422],[13.699902,10.873145],[12.782227,8.817871],[12.233398,8.282324],[11.861426,7.116406],[11.237305,6.450537],[10.60625,7.063086],[9.779883,6.760156],[8.997168,5.917725],[8.555859,4.755225]]]}}, - {"type":"Feature","properties":{"name":"柬埔寨","full_name":"柬埔寨王国","iso_a2":"KH","iso_a3":"KHM","iso_n3":"116"},"geometry":{"type":"Polygon","coordinates":[[[107.519434,14.705078],[106.938086,14.327344],[106.501465,14.578223],[105.978906,14.343018],[106.066797,13.921191],[105.183301,14.34624],[103.199414,14.332617],[102.336328,13.560303],[102.933887,11.706689],[103.152832,10.913721],[103.532422,11.14668],[103.721875,10.890137],[103.587109,10.552197],[104.426367,10.41123],[104.850586,10.534473],[105.045703,10.911377],[105.755078,10.98999],[106.163965,10.794922],[105.851465,11.63501],[106.399219,11.687012],[106.413867,11.948438],[107.506445,12.364551],[107.605469,13.437793],[107.331445,14.126611],[107.519434,14.705078]]]}}, - {"type":"Feature","properties":{"name":"缅甸","full_name":"缅甸联邦共和国","iso_a2":"MM","iso_a3":"MMR","iso_n3":"104"},"geometry":{"type":"MultiPolygon","coordinates":[[[[100.122461,20.31665],[100.249316,20.730273],[100.622949,20.85957],[100.703125,21.251367],[101.138867,21.56748],[101.079785,21.755859],[100.147656,21.480518],[99.917676,22.028027],[99.192969,22.125977],[99.507129,22.959131],[98.86377,23.19126],[98.676758,23.905078],[98.835059,24.121191],[97.564551,23.911035],[97.583301,24.774805],[97.819531,25.251855],[98.65625,25.863574],[98.738477,26.785742],[98.651172,27.572461],[98.298828,27.550098],[98.061621,28.185889],[97.599219,28.517041],[97.322461,28.217969],[97.335156,27.937744],[96.876855,27.586719],[97.102051,27.11543],[96.731641,27.331494],[96.19082,27.261279],[95.128711,26.597266],[95.132422,26.04126],[94.579883,25.319824],[94.707617,25.04873],[94.127637,23.876465],[93.32627,24.064209],[93.151172,22.230615],[92.574902,21.978076],[92.631641,21.306201],[92.17959,21.293115],[92.324121,20.791846],[92.722852,20.295605],[92.735645,20.562695],[92.82832,20.177588],[93.066797,20.377637],[93.129492,19.858008],[93.25,20.070117],[93.707031,19.912158],[93.998145,19.440869],[93.824902,19.238477],[93.493066,19.369482],[93.929199,18.899658],[94.044922,19.287402],[94.588965,17.569336],[94.223828,16.016455],[94.70332,16.511914],[94.661523,15.904395],[94.893164,16.182812],[94.942578,15.818262],[95.176953,15.825684],[95.346777,16.097607],[95.389551,15.722754],[96.324316,16.444434],[96.189063,16.768311],[96.431152,16.504932],[96.76543,16.710352],[96.851465,17.401025],[97.375879,16.522949],[97.725977,16.568555],[97.584277,16.01958],[97.812305,14.858936],[98.110645,13.712891],[98.200391,13.980176],[98.575977,13.161914],[98.636328,11.738379],[98.875977,11.719727],[98.464941,10.67583],[98.562598,10.034961],[98.702539,10.190381],[98.757227,10.660937],[99.614746,11.781201],[99.123926,13.030762],[99.136816,13.716699],[98.202148,14.975928],[98.888281,16.351904],[98.660742,16.33042],[97.373926,18.517969],[97.745898,18.588184],[98.015039,19.749512],[98.916699,19.7729],[99.074219,20.099365],[99.485938,20.149854],[99.458887,20.363037],[100.122461,20.31665]]],[[[98.553809,11.744873],[98.376465,11.791504],[98.434766,11.56709],[98.553809,11.744873]]],[[[93.69082,18.684277],[93.744727,18.865527],[93.4875,18.867529],[93.69082,18.684277]]]]}}, - {"type":"Feature","properties":{"name":"布隆迪","full_name":"布隆迪共和国","iso_a2":"BI","iso_a3":"BDI","iso_n3":"108"},"geometry":{"type":"Polygon","coordinates":[[[30.553613,-2.400098],[29.930176,-2.339551],[29.698047,-2.794727],[29.014355,-2.720215],[29.403223,-4.449316],[29.947266,-4.307324],[30.790234,-3.274609],[30.780273,-2.984863],[30.433496,-2.874512],[30.553613,-2.400098]]]}}, - {"type":"Feature","properties":{"name":"布基纳法索","full_name":"布基纳法索","iso_a2":"BF","iso_a3":"BFA","iso_n3":"854"},"geometry":{"type":"Polygon","coordinates":[[[0.900488,10.993262],[1.426758,11.447119],[1.980371,11.418408],[2.38916,11.89707],[2.072949,12.309375],[2.10459,12.70127],[1.564941,12.6354],[0.987305,13.041895],[0.988477,13.364844],[1.201172,13.35752],[0.429199,13.972119],[0.21748,14.911475],[-0.760449,15.047754],[-1.973047,14.456543],[-2.113232,14.168457],[-2.586719,14.227588],[-2.95083,13.648438],[-3.248633,13.65835],[-3.301758,13.280762],[-4.151025,13.306201],[-4.428711,12.337598],[-5.288135,11.82793],[-5.523535,10.426025],[-4.62583,9.713574],[-3.223535,9.895459],[-2.69585,9.481348],[-2.829932,10.998389],[-0.627148,10.927393],[-0.068604,11.115625],[0.900488,10.993262]]]}}, - {"type":"Feature","properties":{"name":"保加利亚","full_name":"保加利亚共和国","iso_a2":"BG","iso_a3":"BGR","iso_n3":"100"},"geometry":{"type":"Polygon","coordinates":[[[28.014453,41.969043],[27.484766,42.468066],[28.585352,43.742236],[27.086914,44.167383],[25.49707,43.670801],[22.919043,43.834473],[23.028516,44.077979],[22.705078,44.237793],[22.369629,43.781299],[22.967969,43.142041],[22.466797,42.84248],[22.344043,42.313965],[23.003613,41.739844],[22.916016,41.336279],[24.487891,41.555225],[25.251172,41.243555],[25.92334,41.311914],[26.320898,41.716553],[27.011719,42.058643],[28.014453,41.969043]]]}}, - {"type":"Feature","properties":{"name":"文莱","full_name":"文莱达鲁萨兰国","iso_a2":"BN","iso_a3":"BRN","iso_n3":"096"},"geometry":{"type":"MultiPolygon","coordinates":[[[[115.140039,4.899756],[115.026758,4.899707],[115.290625,4.352588],[115.140039,4.899756]]],[[[115.026758,4.899707],[114.063867,4.592676],[114.654102,4.037646],[114.74668,4.718066],[115.026758,4.899707]]]]}}, - {"type":"Feature","properties":{"name":"巴西","full_name":"巴西联邦共和国","iso_a2":"BR","iso_a3":"BRA","iso_n3":"076"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-66.876025,1.223047],[-67.082275,1.1854],[-67.400439,2.116699],[-67.93623,1.748486],[-68.193799,1.987012],[-68.176562,1.719824],[-69.848584,1.70874],[-69.852148,1.059521],[-69.311816,1.050488],[-69.15332,0.658789],[-70.053906,0.578613],[-70.070508,-0.138867],[-69.633984,-0.509277],[-69.400244,-1.194922],[-69.965918,-4.235938],[-70.799512,-4.17334],[-72.887061,-5.122754],[-73.235547,-6.098438],[-73.137354,-6.46582],[-73.758105,-6.905762],[-73.72041,-7.309277],[-74.002051,-7.556055],[-72.974023,-8.993164],[-73.209424,-9.411426],[-72.379053,-9.510156],[-72.142969,-10.005176],[-71.237939,-9.966016],[-70.541113,-9.4375],[-70.642334,-11.010254],[-69.578613,-10.951758],[-68.622656,-11.10918],[-66.575342,-9.899902],[-65.396143,-9.712402],[-65.389893,-11.246289],[-64.992529,-11.975195],[-64.420508,-12.439746],[-63.06748,-12.669141],[-61.789941,-13.525586],[-61.077002,-13.489746],[-60.506592,-13.789844],[-60.27334,-15.08877],[-60.583203,-15.09834],[-60.242334,-15.47959],[-60.175586,-16.269336],[-58.345605,-16.284375],[-58.395996,-17.234277],[-57.832471,-17.512109],[-57.495654,-18.214648],[-58.131494,-19.744531],[-57.860742,-19.97959],[-58.159766,-20.164648],[-57.830225,-20.997949],[-57.955908,-22.10918],[-56.937256,-22.271289],[-56.447803,-22.076172],[-55.84917,-22.307617],[-55.415918,-23.951367],[-54.625488,-23.8125],[-54.241797,-24.047266],[-54.615869,-25.576074],[-53.891162,-25.668848],[-53.668555,-26.288184],[-53.838184,-27.121094],[-55.725488,-28.204102],[-57.608887,-30.187793],[-56.832715,-30.107227],[-56.044824,-30.777637],[-56.004687,-31.079199],[-55.603027,-30.850781],[-53.761719,-32.056836],[-53.125586,-32.736719],[-53.531348,-33.170898],[-53.370605,-33.742188],[-52.652246,-33.137793],[-51.972461,-31.383789],[-51.283057,-30.751562],[-51.298047,-30.034863],[-51.024951,-30.368652],[-50.563525,-30.253613],[-50.581934,-30.438867],[-51.272168,-31.476953],[-52.063232,-31.830371],[-52.039209,-32.114844],[-50.921387,-31.258398],[-49.745996,-29.363184],[-48.799658,-28.575293],[-48.55415,-27.195996],[-48.748291,-26.268652],[-48.401172,-25.597363],[-48.731738,-25.36875],[-48.202734,-25.416504],[-46.867285,-24.236328],[-45.97207,-23.795508],[-45.464307,-23.802539],[-44.569678,-23.274023],[-44.637256,-23.055469],[-43.22417,-22.991211],[-43.154297,-22.725195],[-42.958301,-22.96709],[-42.042383,-22.94707],[-41.705518,-22.309668],[-41.000293,-21.999023],[-40.954541,-21.237891],[-39.783301,-19.571777],[-39.650781,-18.252344],[-39.154004,-17.703906],[-38.880615,-15.864258],[-39.089355,-13.588184],[-38.851758,-12.790137],[-38.690967,-12.623926],[-38.498926,-12.956641],[-38.239746,-12.844238],[-37.359229,-11.252539],[-37.356006,-11.403906],[-35.340869,-9.230664],[-34.834668,-7.971484],[-34.805469,-7.288379],[-35.481689,-5.166016],[-37.174658,-4.912402],[-38.475781,-3.71748],[-39.964697,-2.861523],[-41.479932,-2.916504],[-43.380078,-2.376074],[-44.192676,-2.80957],[-44.228613,-2.471289],[-44.723047,-3.204785],[-44.381836,-2.365527],[-44.435449,-2.168066],[-44.756348,-2.265527],[-44.537793,-2.052734],[-44.65127,-1.745801],[-45.076367,-1.466406],[-45.32915,-1.717285],[-45.458594,-1.35625],[-47.398096,-0.62666],[-48.115088,-0.7375],[-48.449805,-1.145508],[-48.349805,-1.482129],[-48.71001,-1.487695],[-49.211035,-1.916504],[-49.636523,-2.656934],[-49.313672,-1.731738],[-50.403223,-2.015527],[-50.690039,-1.761719],[-50.894922,-0.937598],[-51.947559,-1.586719],[-52.66416,-1.551758],[-51.980811,-1.367969],[-51.28291,-0.085205],[-49.898877,1.162988],[-49.957129,1.659863],[-50.714404,2.134033],[-51.219922,4.093604],[-51.54707,4.310889],[-51.652539,4.061279],[-52.903467,2.211523],[-53.767773,2.354834],[-54.130078,2.121045],[-54.61626,2.326758],[-54.978662,2.597656],[-55.957471,2.520459],[-56.137695,2.259033],[-55.929639,1.8875],[-56.482812,1.942139],[-57.31748,1.963477],[-58.821777,1.201221],[-59.231201,1.376025],[-59.666602,1.746289],[-59.994336,2.68999],[-59.854395,3.5875],[-59.551123,3.933545],[-59.703271,4.381104],[-60.148633,4.533252],[-59.990674,5.082861],[-60.142041,5.238818],[-60.742139,5.202051],[-60.603857,4.949365],[-61.002832,4.535254],[-62.712109,4.01792],[-62.856982,3.593457],[-63.338672,3.943896],[-64.021484,3.929102],[-64.788672,4.276025],[-64.221094,3.587402],[-64.046582,2.502393],[-63.389258,2.411914],[-63.43252,2.155566],[-64.008496,1.931592],[-64.205029,1.529492],[-65.473389,0.69126],[-65.681445,0.983447],[-66.347119,0.767188],[-66.876025,1.223047]]],[[[-49.628662,-0.229199],[-50.645508,-0.272852],[-50.796094,-0.90625],[-50.507617,-1.787988],[-49.805127,-1.790234],[-48.833594,-1.390039],[-48.392676,-0.297363],[-49.628662,-0.229199]]],[[[-45.260254,-23.88916],[-45.302344,-23.727539],[-45.451416,-23.895605],[-45.260254,-23.88916]]],[[[-49.738232,0.268164],[-50.272656,0.231738],[-50.339453,0.043359],[-49.91709,-0.023193],[-49.738232,0.268164]]],[[[-49.443896,-0.112402],[-49.503467,0.083691],[-49.830078,-0.093896],[-49.443896,-0.112402]]],[[[-50.426123,0.139258],[-50.372754,0.590869],[-50.623926,0.054395],[-50.426123,0.139258]]],[[[-50.15293,0.393018],[-50.058838,0.638037],[-50.281689,0.516504],[-50.15293,0.393018]]],[[[-51.83252,-1.433789],[-51.276318,-1.021777],[-51.254004,-0.541406],[-51.546045,-0.649609],[-51.83252,-1.433789]]]]}}, - {"type":"Feature","properties":{"name":"博茨瓦纳","full_name":"博茨瓦纳共和国","iso_a2":"BW","iso_a3":"BWA","iso_n3":"072"},"geometry":{"type":"Polygon","coordinates":[[[25.258789,-17.793555],[24.243945,-18.023438],[23.599707,-18.459961],[23.219336,-17.999707],[20.974121,-18.318848],[20.979492,-21.961914],[19.977344,-22.000195],[19.980469,-24.776758],[20.793164,-25.915625],[20.685059,-26.822461],[21.646289,-26.854199],[22.597656,-26.132715],[23.05752,-25.312305],[24.748145,-25.817383],[25.443652,-25.714453],[25.912109,-24.747461],[26.835059,-24.24082],[27.085547,-23.57793],[28.210156,-22.693652],[29.364844,-22.193945],[29.025586,-21.796875],[28.014063,-21.554199],[27.669434,-21.064258],[27.679297,-20.503027],[27.280762,-20.478711],[27.178223,-20.100977],[26.168066,-19.538281],[25.258789,-17.793555]]]}}, - {"type":"Feature","properties":{"name":"波黑","full_name":"波斯尼亚和黑塞哥维那","iso_a2":"BA","iso_a3":"BIH","iso_n3":"070"},"geometry":{"type":"Polygon","coordinates":[[[19.194336,43.533301],[19.495117,43.642871],[19.24502,43.965039],[19.583789,44.043457],[19.118457,44.359961],[19.348633,44.880908],[19.007129,44.869189],[16.918652,45.276562],[16.293359,45.008838],[15.788086,45.178955],[15.736621,44.76582],[17.585156,42.938379],[17.667578,42.897119],[18.436328,42.559717],[18.460156,42.9979],[19.194336,43.533301]]]}}, - {"type":"Feature","properties":{"name":"玻利维亚","full_name":"多民族玻利维亚国","iso_a2":"BO","iso_a3":"BOL","iso_n3":"068"},"geometry":{"type":"Polygon","coordinates":[[[-69.510938,-17.506055],[-69.093945,-18.050488],[-68.968311,-18.967969],[-68.462891,-19.432813],[-68.760547,-20.416211],[-68.197021,-21.300293],[-67.879443,-22.822949],[-67.194873,-22.82168],[-66.220166,-21.802539],[-65.771045,-22.099609],[-64.605518,-22.228809],[-64.325293,-22.827637],[-63.92168,-22.028613],[-62.843359,-21.997266],[-62.650977,-22.233691],[-62.276318,-20.5625],[-61.756836,-19.645312],[-59.090527,-19.28623],[-58.180176,-19.817871],[-58.159766,-20.164648],[-57.860742,-19.97959],[-58.131494,-19.744531],[-57.495654,-18.214648],[-57.832471,-17.512109],[-58.395996,-17.234277],[-58.345605,-16.284375],[-60.175586,-16.269336],[-60.242334,-15.47959],[-60.583203,-15.09834],[-60.27334,-15.08877],[-60.506592,-13.789844],[-61.077002,-13.489746],[-61.789941,-13.525586],[-63.06748,-12.669141],[-64.420508,-12.439746],[-64.992529,-11.975195],[-65.389893,-11.246289],[-65.396143,-9.712402],[-66.575342,-9.899902],[-68.622656,-11.10918],[-69.578613,-10.951758],[-68.685254,-12.501953],[-68.978613,-12.880078],[-69.074121,-13.682813],[-68.870898,-14.169727],[-69.359473,-14.795312],[-69.172461,-15.236621],[-69.420898,-15.640625],[-69.217578,-16.149121],[-68.842773,-16.337891],[-69.624854,-17.200195],[-69.510938,-17.506055]]]}}, - {"type":"Feature","properties":{"name":"不丹","full_name":"不丹王国","iso_a2":"BT","iso_a3":"BTN","iso_n3":"064"},"geometry":{"type":"Polygon","coordinates":[[[91.631934,27.759961],[91.273047,28.078369],[90.352734,28.080225],[89.981055,28.311182],[88.891406,27.316064],[88.857617,26.961475],[89.609961,26.719434],[91.99834,26.85498],[92.083398,27.290625],[91.594727,27.557666],[91.631934,27.759961]]]}}, - {"type":"Feature","properties":{"name":"贝宁","full_name":"贝宁共和国","iso_a2":"BJ","iso_a3":"BEN","iso_n3":"204"},"geometry":{"type":"Polygon","coordinates":[[[1.622656,6.216797],[2.706445,6.369238],[2.774805,9.048535],[3.044922,9.083838],[3.834473,10.607422],[3.487793,11.39541],[3.59541,11.696289],[2.805273,12.383838],[2.366016,12.221924],[2.38916,11.89707],[1.980371,11.418408],[1.426758,11.447119],[0.900488,10.993262],[0.763379,10.38667],[1.330078,9.996973],[1.600195,9.050049],[1.530957,6.992432],[1.77793,6.294629],[1.622656,6.216797]]]}}, - {"type":"Feature","properties":{"name":"伯利兹","full_name":"伯利兹","iso_a2":"BZ","iso_a3":"BLZ","iso_n3":"084"},"geometry":{"type":"Polygon","coordinates":[[[-89.161475,17.814844],[-89.2375,15.894434],[-88.894043,15.890625],[-88.313428,16.632764],[-88.085254,18.226123],[-88.295654,18.472412],[-89.161475,17.814844]]]}}, - {"type":"Feature","properties":{"name":"比利时","full_name":"比利时王国","iso_a2":"BE","iso_a3":"BEL","iso_n3":"056"},"geometry":{"type":"Polygon","coordinates":[[[4.226172,51.386475],[3.902051,51.207666],[3.350098,51.377686],[2.524902,51.097119],[2.759375,50.750635],[4.174609,50.246484],[4.149316,49.971582],[4.818652,50.153174],[4.867578,49.788135],[5.789746,49.538281],[5.744043,49.919629],[6.116504,50.120996],[6.340918,50.451758],[5.993945,50.750439],[5.639453,50.843604],[5.796484,51.153076],[5.030957,51.469092],[4.226172,51.386475]]]}}, - {"type":"Feature","properties":{"name":"白俄罗斯","full_name":"白俄罗斯共和国","iso_a2":"BY","iso_a3":"BLR","iso_n3":"112"},"geometry":{"type":"Polygon","coordinates":[[[31.763379,52.101074],[31.258789,53.016699],[31.417871,53.196045],[32.141992,53.091162],[32.706445,53.419434],[31.754199,53.810449],[30.798828,54.783252],[30.906836,55.57002],[30.233594,55.845215],[29.482227,55.68457],[29.375,55.938721],[28.147949,56.14292],[27.576758,55.798779],[26.593555,55.667529],[26.457617,55.34248],[26.775684,55.273096],[25.780859,54.833252],[25.749219,54.156982],[25.461133,54.292773],[24.317969,53.892969],[23.484668,53.939795],[23.91543,52.770264],[23.175098,52.286621],[23.652441,52.040381],[23.605273,51.51792],[25.267188,51.937744],[27.141992,51.752051],[27.7,51.477979],[28.73125,51.433398],[29.102051,51.627539],[29.346484,51.382568],[30.160742,51.477881],[30.544531,51.265039],[30.755273,51.895166],[31.763379,52.101074]]]}}, - {"type":"Feature","properties":{"name":"巴巴多斯","full_name":"巴巴多斯","iso_a2":"BB","iso_a3":"BRB","iso_n3":"052"},"geometry":{"type":"Polygon","coordinates":[[[-59.493311,13.081982],[-59.427637,13.152783],[-59.64668,13.303125],[-59.493311,13.081982]]]}}, - {"type":"Feature","properties":{"name":"孟加拉国","full_name":"孟加拉人民共和国","iso_a2":"BD","iso_a3":"BGD","iso_n3":"050"},"geometry":{"type":"MultiPolygon","coordinates":[[[[89.051465,22.093164],[89.353711,21.721094],[89.483203,22.275537],[89.568555,21.767432],[89.985156,22.466406],[89.918066,22.116162],[90.20957,22.156592],[90.230566,21.829785],[90.616113,22.362158],[90.435059,22.751904],[90.590918,23.266406],[90.269141,23.455859],[90.604004,23.591357],[90.945605,22.597021],[91.480078,22.884814],[91.863379,22.350488],[92.324121,20.791846],[92.17959,21.293115],[92.631641,21.306201],[92.574902,21.978076],[92.246094,23.683594],[91.92959,23.685986],[91.619531,22.979688],[91.315234,23.104395],[91.160449,23.660645],[91.36709,24.093506],[91.876953,24.195312],[92.468359,24.944141],[92.049707,25.169482],[89.833301,25.292773],[89.670898,26.213818],[89.369727,26.006104],[89.018652,26.410254],[88.828027,26.252197],[88.418164,26.571533],[88.08457,25.888232],[88.95166,25.259277],[88.45625,25.188428],[88.023438,24.627832],[88.723535,24.274902],[88.567383,23.674414],[88.928125,23.186621],[89.051465,22.093164]]],[[[90.777637,22.089307],[90.868164,22.484863],[90.596484,22.863525],[90.680469,22.32749],[90.515039,22.065137],[90.777637,22.089307]]]]}}, - {"type":"Feature","properties":{"name":"巴林","full_name":"巴林王国","iso_a2":"BH","iso_a3":"BHR","iso_n3":"048"},"geometry":{"type":"Polygon","coordinates":[[[50.607227,25.883105],[50.585938,26.240723],[50.469922,26.228955],[50.607227,25.883105]]]}}, - {"type":"Feature","properties":{"name":"巴哈马","full_name":"巴哈马国","iso_a2":"BS","iso_a3":"BHS","iso_n3":"044"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-77.657715,24.249463],[-77.999902,24.219824],[-77.57373,23.73916],[-77.657715,24.249463]]],[[[-77.225635,25.904199],[-77.066357,26.530176],[-77.533887,26.903418],[-77.94375,26.903564],[-77.238623,26.561133],[-77.403174,26.024707],[-77.225635,25.904199]]],[[[-73.026855,21.192383],[-73.523096,21.19082],[-73.681152,20.975586],[-73.164551,20.97915],[-73.026855,21.192383]]],[[[-77.743848,24.707422],[-78.211377,25.19126],[-78.366504,24.544189],[-78.044922,24.287451],[-77.743848,24.707422]]],[[[-78.492871,26.729053],[-78.985645,26.689502],[-78.743652,26.500684],[-77.922461,26.691113],[-78.492871,26.729053]]],[[[-76.648828,25.487402],[-76.1604,25.119336],[-76.169531,24.649414],[-76.126611,25.140527],[-76.648828,25.487402]]],[[[-74.840479,22.894336],[-75.315967,23.668359],[-75.22334,23.165332],[-74.840479,22.894336]]],[[[-75.308398,24.2],[-75.72666,24.689355],[-75.503223,24.139062],[-75.308398,24.2]]]]}}, - {"type":"Feature","properties":{"name":"阿塞拜疆","full_name":"阿塞拜疆共和国","iso_a2":"AZ","iso_a3":"AZE","iso_n3":"031"},"geometry":{"type":"MultiPolygon","coordinates":[[[[44.817188,39.650439],[45.479688,39.00625],[46.114453,38.877783],[45.784473,39.545605],[44.768262,39.703516],[44.817188,39.650439]]],[[[48.86875,38.435498],[49.551172,40.194141],[50.365918,40.279492],[49.456738,40.799854],[48.572852,41.844482],[47.791016,41.199268],[46.429883,41.890967],[46.182129,41.65708],[46.672559,41.286816],[46.534375,41.088574],[45.280957,41.449561],[45.001367,41.290967],[45.5875,40.846924],[45.454395,40.532373],[45.964648,40.233789],[45.579785,39.977539],[46.481445,39.555176],[46.490625,38.906689],[47.995898,39.683936],[48.322168,39.399072],[47.996484,38.85376],[48.86875,38.435498]]]]}}, - {"type":"Feature","properties":{"name":"奥地利","full_name":"奥地利共和国","iso_a2":"AT","iso_a3":"AUT","iso_n3":"040"},"geometry":{"type":"Polygon","coordinates":[[[9.527539,47.270752],[9.580273,47.057373],[10.452832,46.864941],[10.993262,46.777002],[12.169434,47.082129],[12.388281,46.702637],[13.7,46.520264],[14.549805,46.399707],[16.093066,46.863281],[16.676563,47.536035],[16.421289,47.674463],[17.066602,47.707568],[17.147363,48.005957],[16.953125,48.598828],[15.066797,48.997852],[14.691309,48.599219],[13.814746,48.766943],[12.760352,48.106982],[13.014355,47.478076],[12.209277,47.718262],[11.041992,47.393115],[10.439453,47.551562],[10.183008,47.278809],[9.524023,47.524219],[9.527539,47.270752]]]}}, - {"type":"Feature","properties":{"name":"澳大利亚","full_name":"澳大利亚联邦","iso_a2":"AU","iso_a3":"AUS","iso_n3":"036"},"geometry":{"type":"MultiPolygon","coordinates":[[[[143.178906,-11.954492],[142.872559,-11.821387],[142.779688,-11.115332],[142.456445,-10.707324],[142.168359,-10.946582],[141.961133,-12.054297],[141.688574,-12.351074],[141.929785,-12.739844],[141.613574,-12.943457],[141.625488,-15.056641],[141.291406,-16.463477],[140.830469,-17.414453],[140.03584,-17.702637],[139.248438,-17.328613],[139.009863,-16.899316],[138.24502,-16.718359],[135.45332,-14.923145],[135.954492,-13.934863],[135.927344,-13.304297],[136.461035,-13.225195],[136.897461,-12.243555],[136.443359,-11.951465],[136.081836,-12.422461],[136.008496,-12.191406],[135.704395,-12.209863],[135.922461,-11.825781],[135.029688,-12.19375],[131.961523,-11.180859],[131.822461,-11.302441],[132.072852,-11.474707],[132.674219,-11.649023],[132.712793,-12.123438],[131.438281,-12.276953],[131.291602,-12.067871],[130.867383,-12.557813],[130.622656,-12.431055],[130.168164,-12.957422],[130.259766,-13.302246],[129.838867,-13.572949],[129.378711,-14.39248],[129.84873,-14.828906],[129.637109,-14.850977],[129.634766,-15.139746],[129.267578,-14.871484],[129.21582,-15.160254],[129.058203,-14.884375],[128.575781,-14.774512],[128.069434,-15.329297],[128.180469,-14.711621],[127.457617,-14.031445],[126.903223,-13.744141],[126.569727,-14.160938],[126.053906,-13.977246],[126.020703,-14.494531],[125.661621,-14.529492],[125.627734,-14.256641],[125.178711,-14.714746],[125.355664,-15.119824],[124.839063,-15.160742],[125.062988,-15.442285],[124.692578,-15.273633],[124.439551,-15.493555],[124.381641,-15.758203],[124.648535,-15.870215],[124.404883,-16.298926],[124.771973,-16.402637],[123.607031,-16.224023],[123.49043,-16.490723],[123.874414,-16.918652],[123.831055,-17.120801],[123.593555,-17.030371],[123.563086,-17.520898],[122.970703,-16.436816],[122.260938,-17.135742],[122.34541,-18.111914],[120.997949,-19.604395],[119.104492,-19.995313],[117.40625,-20.721191],[116.706738,-20.653809],[114.709277,-21.823438],[114.141602,-22.483105],[114.022852,-21.881445],[113.417676,-24.435645],[114.214258,-25.851562],[114.215723,-26.289453],[113.72373,-26.129785],[113.451367,-25.599121],[113.836426,-26.500586],[113.581641,-26.558105],[113.356055,-26.080469],[113.184766,-26.182227],[114.028125,-27.347266],[114.165137,-28.080664],[114.856836,-29.142969],[115.07793,-30.560449],[115.698438,-31.694531],[115.683008,-33.192871],[115.358789,-33.639941],[114.993848,-33.515332],[115.008789,-34.255859],[116.517188,-34.987891],[117.581934,-35.097754],[119.450586,-34.368262],[119.854102,-33.974707],[123.506836,-33.916211],[124.24375,-33.015234],[125.917188,-32.296973],[127.319824,-32.264062],[129.187695,-31.659961],[131.143652,-31.495703],[134.23418,-32.548535],[134.173535,-32.979102],[134.791016,-33.32832],[135.45,-34.581055],[135.123047,-34.585742],[135.647559,-34.939648],[135.969727,-34.981836],[135.891016,-34.660938],[136.430664,-34.02998],[137.237305,-33.629492],[137.783203,-32.578125],[137.931836,-33.579102],[137.493848,-34.161133],[137.391016,-34.913281],[137.014258,-34.91582],[136.883594,-35.239746],[137.691699,-35.142969],[138.089258,-34.169824],[138.511133,-35.024414],[138.184375,-35.612695],[139.28252,-35.375391],[139.289453,-35.611328],[138.968945,-35.580762],[139.729004,-36.371387],[139.784277,-37.245801],[140.39043,-37.89668],[141.424219,-38.363477],[142.455859,-38.386328],[143.538965,-38.820898],[144.891309,-37.899805],[145.119922,-38.091309],[144.717773,-38.340332],[144.95957,-38.500781],[145.475781,-38.24375],[145.397266,-38.535352],[146.4,-39.145508],[146.21748,-38.727441],[146.856836,-38.663477],[147.876758,-37.93418],[149.932715,-37.528516],[150.195312,-35.833594],[150.80459,-35.012891],[151.29209,-33.580957],[152.47041,-32.439062],[152.943945,-31.434863],[153.616895,-28.673047],[153.116797,-27.194434],[153.164941,-25.96416],[151.831641,-24.122949],[150.843164,-23.458008],[150.672461,-22.418164],[150.541309,-22.559082],[150.076172,-22.164453],[149.974414,-22.550684],[149.703906,-22.440527],[149.454102,-21.578711],[148.683691,-20.580176],[148.884766,-20.480859],[148.759375,-20.289551],[147.418555,-19.378125],[146.383398,-18.977051],[146.032227,-18.272852],[145.912109,-16.9125],[145.426074,-16.406152],[145.287695,-14.943164],[144.473047,-14.231836],[143.756348,-14.348828],[143.178906,-11.954492]]],[[[139.507812,-16.573047],[139.587891,-16.395215],[139.15957,-16.741699],[139.507812,-16.573047]]],[[[136.714648,-13.803906],[136.424707,-13.864844],[136.335449,-14.211816],[136.894336,-14.293066],[136.89082,-13.786621],[136.714648,-13.803906]]],[[[130.459277,-11.679297],[130.294922,-11.336816],[130.043262,-11.787305],[130.60625,-11.816602],[130.459277,-11.679297]]],[[[130.618848,-11.376074],[130.38457,-11.192188],[130.511914,-11.617871],[130.950977,-11.926465],[131.538574,-11.436914],[131.268262,-11.189844],[130.618848,-11.376074]]],[[[137.596484,-35.738672],[137.334082,-35.59248],[136.540625,-35.890137],[137.448438,-36.074805],[138.123438,-35.852344],[137.596484,-35.738672]]],[[[146.27832,-18.23125],[146.098828,-18.251758],[146.298828,-18.484766],[146.27832,-18.23125]]],[[[136.338672,-11.602344],[136.479297,-11.465918],[136.180273,-11.676758],[136.338672,-11.602344]]],[[[145.042969,-40.786719],[144.718555,-40.672266],[144.766113,-41.390039],[145.516602,-42.354492],[145.198828,-42.230859],[145.487598,-42.92666],[146.208008,-43.316211],[146.043164,-43.547168],[146.873926,-43.6125],[147.297949,-42.790918],[147.94541,-43.181836],[148.213672,-41.97002],[148.342578,-42.215332],[148.215234,-40.854883],[146.31748,-41.163477],[145.042969,-40.786719]]],[[[148.000391,-39.757617],[147.767188,-39.870313],[148.105664,-40.262109],[148.297363,-39.985742],[148.000391,-39.757617]]],[[[148.32627,-40.306934],[148.020117,-40.404199],[148.404004,-40.486523],[148.32627,-40.306934]]]]}}, - {"type":"Feature","properties":{"name":"圣诞岛","full_name":"圣诞岛(澳大利亚)","iso_a2":"CX","iso_a3":"CXR","iso_n3":"162"},"geometry":{"type":"Polygon","coordinates":[[[105.725391,-10.492969],[105.584082,-10.5125],[105.696875,-10.56416],[105.725391,-10.492969]]]}}, - {"type":"Feature","properties":{"name":"赫德岛和麦克唐纳群岛","full_name":"赫德岛和麦克唐纳群岛","iso_a2":"HM","iso_a3":"HMD","iso_n3":"334"},"geometry":{"type":"Polygon","coordinates":[[[73.707422,-53.137109],[73.251172,-52.975781],[73.465137,-53.18418],[73.707422,-53.137109]]]}}, - {"type":"Feature","properties":{"name":"诺福克岛","full_name":"诺福克岛(澳大利亚)","iso_a2":"NF","iso_a3":"NFK","iso_n3":"574"},"geometry":{"type":"Polygon","coordinates":[[[167.939453,-29.017676],[167.960742,-29.096289],[167.99043,-29.04209],[167.939453,-29.017676]]]}}, - {"type":"Feature","properties":{"name":"阿什莫尔和卡捷群岛","full_name":"阿什莫尔和卡捷群岛(澳大利亚)","iso_a2":"AU","iso_a3":"AUS","iso_n3":"036"},"geometry":{"type":"Polygon","coordinates":[[[123.594531,-12.425684],[123.572461,-12.423926],[123.573145,-12.43418],[123.594531,-12.425684]]]}}, - {"type":"Feature","properties":{"name":"亚美尼亚","full_name":"亚美尼亚共和国","iso_a2":"AM","iso_a3":"ARM","iso_n3":"051"},"geometry":{"type":"Polygon","coordinates":[[[44.768262,39.703516],[45.784473,39.545605],[46.114453,38.877783],[46.490625,38.906689],[46.481445,39.555176],[45.579785,39.977539],[45.964648,40.233789],[45.454395,40.532373],[45.5875,40.846924],[45.001367,41.290967],[43.439453,41.107129],[43.666211,40.126367],[44.768262,39.703516]]]}}, - {"type":"Feature","properties":{"name":"阿根廷","full_name":"阿根廷共和国","iso_a2":"AR","iso_a3":"ARG","iso_n3":"032"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-57.608887,-30.187793],[-55.725488,-28.204102],[-53.838184,-27.121094],[-53.668555,-26.288184],[-53.891162,-25.668848],[-54.615869,-25.576074],[-54.825488,-26.652246],[-55.714648,-27.414844],[-56.164062,-27.321484],[-56.437158,-27.553809],[-58.604834,-27.314355],[-57.643896,-25.328418],[-59.89248,-24.093555],[-61.03291,-23.755664],[-62.650977,-22.233691],[-62.843359,-21.997266],[-63.92168,-22.028613],[-64.325293,-22.827637],[-64.605518,-22.228809],[-65.771045,-22.099609],[-66.220166,-21.802539],[-67.194873,-22.82168],[-67.008789,-23.001367],[-67.356201,-24.033789],[-68.562012,-24.747363],[-68.384229,-25.091895],[-68.591602,-26.47041],[-68.318652,-26.973242],[-68.846338,-27.153711],[-69.656934,-28.413574],[-70.026807,-29.324023],[-69.844287,-30.175],[-70.51958,-31.148438],[-70.084863,-33.201758],[-69.819629,-33.283789],[-69.852441,-34.224316],[-70.555176,-35.246875],[-70.404785,-36.061719],[-71.192187,-36.843652],[-70.858643,-38.604492],[-71.401562,-38.935059],[-71.932129,-40.691699],[-71.75,-42.046777],[-72.108203,-42.251855],[-72.146436,-42.990039],[-71.750635,-43.237305],[-71.82002,-44.383105],[-71.159717,-44.560254],[-71.261133,-44.763086],[-72.063721,-44.771875],[-71.349316,-45.331934],[-71.746191,-45.578906],[-71.699658,-46.651367],[-72.51792,-47.876367],[-72.354736,-48.36582],[-73.554199,-49.463867],[-73.50127,-50.125293],[-73.15293,-50.738281],[-72.340234,-50.681836],[-72.407666,-51.54082],[-71.918652,-51.989551],[-69.960254,-52.008203],[-68.443359,-52.356641],[-68.965332,-51.677148],[-69.46543,-51.584473],[-69.035303,-51.488965],[-69.358594,-51.028125],[-69.044775,-50.499121],[-68.421875,-50.15791],[-68.97959,-50.003027],[-68.667578,-49.752539],[-68.257227,-50.10459],[-67.825977,-49.919629],[-67.466309,-48.951758],[-65.810059,-47.941113],[-66.225244,-47.826758],[-65.738086,-47.344922],[-65.998535,-47.09375],[-66.776855,-47.005859],[-67.599561,-46.052539],[-66.941406,-45.257324],[-65.63877,-45.007812],[-65.252344,-43.571875],[-64.319141,-42.968945],[-64.970703,-42.666309],[-64.487842,-42.513477],[-64.034766,-42.88125],[-63.617334,-42.695801],[-63.795557,-42.113867],[-64.42041,-42.433789],[-64.986377,-42.102051],[-65.133398,-40.880664],[-64.869482,-40.73584],[-63.621777,-41.159766],[-62.39502,-40.89082],[-62.053662,-39.373828],[-62.334766,-38.800098],[-61.602539,-38.998828],[-59.82832,-38.838184],[-57.546973,-38.085645],[-56.672021,-36.85127],[-56.698096,-36.426465],[-57.335449,-36.026758],[-57.303662,-35.188477],[-58.28335,-34.683496],[-58.525488,-34.296191],[-58.39248,-34.192969],[-58.547217,-33.663477],[-58.424463,-33.111523],[-58.170996,-32.959277],[-58.201172,-32.47168],[-57.608887,-30.187793]]],[[[-68.653223,-54.853613],[-66.511133,-55.032129],[-65.179004,-54.678125],[-67.294238,-54.049805],[-68.488525,-53.260938],[-68.240137,-53.081836],[-68.629932,-52.652637],[-68.653223,-54.853613]]],[[[-64.54917,-54.716211],[-64.637354,-54.902539],[-63.81543,-54.725098],[-64.54917,-54.716211]]]]}}, - {"type":"Feature","properties":{"name":"安提瓜和巴布达","full_name":"安提瓜和巴布达","iso_a2":"AG","iso_a3":"ATG","iso_n3":"028"},"geometry":{"type":"Polygon","coordinates":[[[-61.716064,17.037012],[-61.817285,17.168945],[-61.887109,17.098145],[-61.716064,17.037012]]]}}, - {"type":"Feature","properties":{"name":"安哥拉","full_name":"安哥拉共和国","iso_a2":"AO","iso_a3":"AGO","iso_n3":"024"},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.072754,-4.634766],[12.798242,-4.430566],[12.018359,-5.004297],[12.213672,-5.758691],[12.503711,-5.695801],[12.451465,-5.071484],[13.072754,-4.634766]]],[[[23.966504,-10.871777],[22.226172,-11.121973],[22.274512,-10.259082],[21.813184,-9.46875],[21.781641,-7.314648],[20.607813,-7.277734],[20.590039,-6.919922],[19.875195,-6.986328],[19.527637,-7.144434],[19.34082,-7.966602],[17.57959,-8.099023],[16.431445,-5.900195],[13.068164,-5.864844],[12.283301,-6.124316],[13.378516,-8.369727],[12.998535,-9.048047],[13.833594,-10.929688],[13.785352,-11.812793],[12.550488,-13.437793],[11.750879,-15.831934],[11.743066,-17.249219],[13.101172,-16.967676],[14.01748,-17.408887],[18.396387,-17.399414],[18.955273,-17.803516],[20.745508,-18.019727],[23.380664,-17.640625],[22.040234,-16.262793],[21.978906,-13.000977],[23.962988,-12.988477],[23.966504,-10.871777]]]]}}, - {"type":"Feature","properties":{"name":"安道尔","full_name":"安道尔公国","iso_a2":"AD","iso_a3":"AND","iso_n3":"020"},"geometry":{"type":"Polygon","coordinates":[[[1.706055,42.50332],[1.42832,42.595898],[1.448828,42.437451],[1.706055,42.50332]]]}}, - {"type":"Feature","properties":{"name":"阿尔及利亚","full_name":"阿尔及利亚民主人民共和国","iso_a2":"DZ","iso_a3":"DZA","iso_n3":"012"},"geometry":{"type":"Polygon","coordinates":[[[8.576563,36.937207],[6.486523,37.085742],[5.29541,36.648242],[4.758105,36.896338],[3.779004,36.896191],[1.257227,36.51958],[-0.048242,35.832812],[-2.219629,35.104199],[-1.795605,34.751904],[-1.679199,33.318652],[-1.065527,32.468311],[-1.225928,32.107227],[-2.887207,32.068848],[-3.017383,31.834277],[-3.826758,31.661914],[-3.666797,30.964014],[-4.968262,30.465381],[-5.448779,29.956934],[-7.685156,29.349512],[-8.678418,28.689404],[-8.68335,27.656445],[-8.68335,27.285938],[-4.822607,24.995605],[1.145508,21.102246],[1.685449,20.378369],[3.130273,19.850195],[3.119727,19.103174],[4.227637,19.142773],[5.836621,19.47915],[7.481738,20.873096],[11.967871,23.517871],[11.507617,24.314355],[10.255859,24.591016],[9.448242,26.067139],[9.883203,26.630811],[9.916016,27.785693],[9.805273,29.176953],[9.310254,30.115234],[9.51875,30.229395],[9.044043,32.072363],[8.333398,32.543604],[7.500195,33.832471],[8.245605,34.734082],[8.207617,36.518945],[8.576563,36.937207]]]}}, - {"type":"Feature","properties":{"name":"阿尔巴尼亚","full_name":"阿尔巴尼亚共和国","iso_a2":"AL","iso_a3":"ALB","iso_n3":"008"},"geometry":{"type":"Polygon","coordinates":[[[19.342383,41.869092],[19.575684,41.64043],[19.322266,40.40708],[20.00127,39.709424],[20.657422,40.117383],[20.964258,40.849902],[20.488965,41.272607],[20.566211,41.873682],[20.063965,42.547266],[19.654492,42.628564],[19.342383,41.869092]]]}}, - {"type":"Feature","properties":{"name":"阿富汗","full_name":"阿富汗","iso_a2":"AF","iso_a3":"AFG","iso_n3":"004"},"geometry":{"type":"Polygon","coordinates":[[[66.522266,37.348486],[65.765039,37.569141],[65.55498,37.251172],[64.816309,37.13208],[64.511035,36.340674],[63.12998,35.846191],[63.056641,35.445801],[62.688086,35.255322],[62.307813,35.170801],[61.262012,35.61958],[60.72627,34.518262],[60.889453,34.319434],[60.485742,34.094775],[60.51084,33.638916],[60.916992,33.505225],[60.561914,33.058789],[60.820703,31.495166],[61.660156,31.382422],[61.81084,30.913281],[60.843359,29.858691],[62.476562,29.40835],[64.09873,29.391943],[66.23125,29.865723],[66.346875,30.802783],[66.829297,31.263672],[67.452832,31.234619],[68.161035,31.802979],[68.868945,31.634229],[69.279297,31.936816],[69.501562,33.020068],[70.261133,33.289014],[69.889648,34.007275],[71.051563,34.049707],[70.965625,34.530371],[71.620508,35.183008],[71.23291,36.121777],[72.249805,36.734717],[74.541406,37.022168],[74.372168,37.157715],[74.891309,37.231641],[74.349023,37.41875],[73.653516,37.239355],[73.720605,37.41875],[73.38291,37.462256],[71.665625,36.696924],[71.43291,37.127539],[71.582227,37.910107],[71.278516,37.918408],[71.255859,38.306982],[70.878906,38.456396],[70.214648,37.924414],[70.188672,37.582471],[69.49209,37.553076],[69.303906,37.116943],[68.911816,37.333936],[68.067773,36.949805],[67.758984,37.172217],[66.522266,37.348486]]]}}, - {"type":"Feature","properties":{"name":"锡亚琴冰川","full_name":"锡亚琴冰川","iso_a2":"-99","iso_a3":"-99","iso_n3":"-99"},"geometry":{"type":"Polygon","coordinates":[[[77.048633,35.109912],[77.799414,35.495898],[76.766895,35.661719],[77.048633,35.109912]]]}}, - {"type":"Feature","properties":{"name":"南极洲","full_name":"南极洲","iso_a2":"AQ","iso_a3":"ATA","iso_n3":"010"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-57.020654,-63.372852],[-57.389648,-63.22627],[-58.87207,-63.551855],[-60.86416,-64.073438],[-61.631787,-64.604688],[-62.503467,-64.656445],[-63.059082,-65.139355],[-63.760254,-65.033496],[-63.818115,-65.531543],[-64.646582,-65.747852],[-64.613525,-66.019043],[-65.617285,-66.135254],[-65.766406,-66.624902],[-66.503613,-66.689844],[-66.498682,-67.289062],[-67.034473,-66.945117],[-67.493359,-67.112793],[-67.544531,-67.534668],[-66.677246,-67.560254],[-67.390527,-68.86123],[-66.974902,-69.161035],[-67.371777,-69.412305],[-68.707959,-69.432227],[-66.827734,-72.09043],[-68.000342,-72.935547],[-72.929199,-73.447949],[-73.996045,-73.699805],[-75.293066,-73.63877],[-77.048926,-73.844141],[-76.850488,-73.460449],[-78.78623,-73.506738],[-80.442236,-72.944531],[-80.336377,-73.41416],[-81.176416,-73.248828],[-81.30874,-73.738281],[-82.183496,-73.856836],[-85.801416,-73.19209],[-86.791016,-73.363672],[-88.419385,-73.229004],[-88.194092,-72.7875],[-88.77998,-72.683008],[-90.920947,-73.319141],[-92.828369,-73.164648],[-96.394238,-73.301172],[-98.208594,-73.022266],[-102.409277,-72.987402],[-102.855859,-72.716211],[-103.375,-72.818848],[-102.908789,-73.285156],[-98.896143,-73.611133],[-102.862744,-73.783594],[-101.251709,-74.485742],[-100.118604,-74.515039],[-100.47334,-74.872363],[-98.752344,-75.31709],[-101.039355,-75.421875],[-101.708105,-75.127344],[-103.121045,-75.095215],[-106.618848,-75.343945],[-111.358789,-75.219922],[-110.229785,-74.536328],[-111.180176,-74.188086],[-111.69624,-74.792188],[-114.110449,-74.981836],[-113.508496,-74.088867],[-114.62373,-73.90293],[-115.222607,-74.487402],[-118.655762,-74.392773],[-121.543945,-74.75],[-135.362061,-74.69043],[-136.649854,-75.161719],[-139.691162,-75.212793],[-141.22334,-75.545898],[-140.874316,-75.745898],[-142.329834,-75.490918],[-145.987744,-75.88877],[-145.44209,-76.40918],[-148.458984,-76.117969],[-149.654248,-76.365332],[-147.34043,-76.438379],[-145.750488,-76.749023],[-145.677148,-77.488086],[-148.572412,-77.105078],[-148.155713,-77.462305],[-149.717725,-77.797461],[-154.814941,-77.126953],[-158.213574,-77.157129],[-158.285889,-77.950781],[-157.266797,-78.199805],[-154.293018,-78.259082],[-156.114551,-78.744629],[-152.137695,-79.115918],[-148.176514,-79.775879],[-150.575391,-80.353711],[-148.023438,-80.835742],[-156.528223,-81.162305],[-157.03252,-81.319141],[-153.956641,-81.700195],[-153.009863,-82.449609],[-159.444385,-83.543164],[-174.235938,-82.793457],[-167.801221,-83.79082],[-164.950879,-83.805859],[-165.135352,-84.409863],[-156.986328,-84.811133],[-156.459131,-85.186035],[-171.703662,-84.542383],[-180,-84.351562],[-180,-85.05752],[-180,-85.763379],[-180,-86.469336],[-180,-87.175195],[-180,-87.881055],[-180,-88.587012],[-180,-89.292969],[-180,-89.58291],[-180,-89.998926],[-1.40625,-89.998926],[180,-89.998926],[180,-84.351562],[171.035742,-83.448438],[168.110059,-83.362012],[168.607324,-83.065332],[164.980078,-82.384961],[161.283203,-82.489941],[163.602344,-82.120605],[160.469824,-81.340625],[160.637305,-80.449902],[158.573633,-80.423438],[160.558789,-80.010547],[159.975879,-79.585645],[160.873535,-79.049707],[161.951465,-79.02998],[161.669238,-78.536133],[162.639453,-78.897754],[164.634766,-78.603223],[167.130273,-78.606152],[165.662988,-78.305664],[165.417578,-78.042188],[163.977637,-78.223828],[164.420898,-77.883496],[162.450293,-76.955664],[162.815723,-75.846191],[160.910742,-75.334668],[162.410059,-75.237598],[163.397852,-74.382129],[165.408594,-74.558594],[164.812988,-73.396777],[165.733691,-73.866699],[165.860156,-73.592676],[167.709082,-73.394238],[166.452832,-72.936035],[167.155664,-73.147266],[169.54502,-73.050391],[169.828613,-72.728809],[168.428418,-72.383398],[170.206445,-72.565332],[170.030078,-72.115527],[170.859082,-71.868555],[170.435742,-71.41875],[170.162305,-71.630469],[166.626953,-70.664258],[163.566504,-70.642285],[162.674805,-70.30459],[162.021973,-70.439844],[162.189453,-71.039551],[159.783984,-69.521875],[155.520313,-69.024414],[153.908008,-68.323145],[153.081836,-68.856836],[151.28877,-68.81709],[150.935938,-68.358496],[147.093652,-68.368652],[145.975195,-67.624219],[143.977344,-67.864551],[144.621191,-67.141406],[143.730371,-66.876758],[142.6875,-67.012793],[135.351953,-66.127148],[134.289453,-66.476758],[133.148242,-66.094824],[130.120508,-66.291504],[129.236914,-67.041602],[128.430566,-67.119141],[125.865625,-66.364453],[119.133008,-67.370703],[120.374805,-66.983789],[116.713477,-67.047168],[114.026563,-67.441211],[113.991211,-67.211914],[115.635352,-66.771191],[113.099414,-65.799902],[110.906738,-66.07666],[110.622266,-66.524023],[109.462793,-66.908691],[102.674219,-65.865137],[101.381348,-65.973047],[100.889063,-66.358008],[99.370117,-66.648242],[98.257617,-66.46748],[94.839844,-66.501367],[93.964258,-66.689648],[92.073438,-66.50791],[84.485156,-67.114453],[83.304297,-67.603027],[79.035156,-68.175391],[77.81748,-69.068945],[75.423828,-69.893066],[73.324805,-69.848926],[71.771387,-70.80127],[71.276758,-71.623926],[68.419824,-72.515039],[67.32207,-73.300293],[66.497656,-73.125488],[69.250195,-70.431055],[67.267969,-70.273145],[68.178125,-69.837305],[69.082617,-69.866602],[68.90625,-69.372754],[69.629492,-69.231641],[69.982227,-68.464258],[69.55918,-67.763184],[68.32793,-67.889551],[63.699023,-67.508301],[59.250781,-67.484961],[57.627441,-67.014063],[56.154883,-67.264551],[56.145898,-66.626074],[57.185449,-66.613281],[55.504492,-66.002637],[53.671777,-65.858691],[51.88457,-66.02002],[50.332422,-66.444629],[50.553027,-67.194336],[49.24707,-66.941602],[48.465234,-67.043457],[49.219336,-67.226855],[48.374512,-67.988086],[48.209961,-67.699316],[47.489844,-67.72793],[47.351562,-67.361914],[46.559668,-67.268164],[46.399023,-67.617578],[42.960938,-68.095312],[40.215625,-68.804883],[38.885547,-70.171875],[37.787109,-69.725684],[35.357031,-69.681348],[34.595898,-69.094531],[33.813672,-69.099316],[34.192871,-68.702441],[33.465625,-68.670703],[32.641602,-68.868945],[32.621289,-70.000586],[30.00332,-70.3],[26.498828,-71.019531],[24.756738,-70.89209],[24.024121,-70.413379],[23.149902,-70.796289],[22.44541,-70.739746],[21.70498,-70.258496],[21.070801,-70.843457],[19.651855,-70.920605],[19.009375,-70.212109],[18.124609,-70.540332],[16.381055,-70.145117],[13.822656,-70.343164],[13.065625,-70.053613],[11.70127,-70.766602],[9.141602,-70.183691],[8.523047,-70.473828],[7.676758,-70.356348],[2.609473,-70.900098],[-0.543164,-71.712695],[-1.067773,-71.265625],[-6.11748,-71.325977],[-5.936328,-70.712695],[-7.752734,-70.842773],[-7.713721,-71.546484],[-8.497705,-71.674805],[-10.270605,-70.935742],[-10.825439,-71.55332],[-12.351318,-71.389746],[-11.009229,-71.75791],[-11.496973,-72.412891],[-13.208594,-72.785059],[-14.297754,-72.733008],[-14.164697,-73.102441],[-15.595996,-73.096777],[-16.435205,-73.425684],[-16.220117,-73.915723],[-14.573828,-73.9375],[-15.67251,-74.407324],[-17.43584,-74.379102],[-18.749219,-75.24209],[-18.30459,-75.431348],[-26.059326,-75.957227],[-34.075781,-77.425391],[-36.23916,-78.774219],[-32.994238,-79.228809],[-30.645264,-79.124121],[-29.949316,-79.599023],[-23.406836,-79.858984],[-31.01543,-80.308105],[-35.327002,-80.650684],[-37.209277,-81.063867],[-38.771729,-80.882324],[-41.125879,-81.214844],[-45.04375,-82.437988],[-46.516748,-82.45459],[-46.258057,-81.946973],[-48.360791,-81.892285],[-53.986084,-82.200586],[-59.516016,-83.458398],[-61.425293,-83.395605],[-62.735645,-82.527344],[-60.527734,-82.199902],[-64.91958,-82.370508],[-66.133838,-81.953418],[-62.490234,-81.556738],[-65.573682,-81.460547],[-70.687891,-80.62627],[-73.029492,-80.917285],[-75.075586,-80.860059],[-76.407324,-80.094922],[-79.6604,-79.996875],[-76.557861,-79.903516],[-76.217676,-79.387207],[-80.151172,-79.268066],[-80.891992,-79.501855],[-83.696631,-78.537305],[-83.779004,-77.983594],[-80.292285,-78.822754],[-77.54502,-78.65957],[-77.858105,-78.350977],[-81.580957,-77.846094],[-80.601562,-77.751953],[-74.812061,-78.177832],[-73.251562,-77.894238],[-72.851953,-77.590234],[-75.748145,-77.398438],[-77.190039,-76.629785],[-70.550781,-76.718066],[-63.363379,-75.451465],[-64.279541,-75.292871],[-63.231055,-75.153809],[-63.924707,-75.004492],[-63.178125,-74.68418],[-62.137793,-74.926367],[-62.235303,-74.441309],[-60.704297,-74.307129],[-61.842773,-74.289648],[-60.790283,-73.711816],[-62.008301,-73.147656],[-60.122217,-73.275293],[-60.009766,-72.937891],[-61.286133,-72.600781],[-60.719434,-72.072656],[-62.256641,-72.017578],[-60.949023,-71.747266],[-61.213574,-71.564062],[-61.958789,-71.657812],[-60.962256,-71.244629],[-62.04043,-70.801367],[-61.504687,-70.490527],[-62.377783,-70.364844],[-61.961084,-70.120117],[-63.747021,-68.70459],[-62.933301,-68.442578],[-63.924463,-68.497656],[-64.078467,-68.771191],[-65.15835,-68.617969],[-65.452002,-68.336719],[-64.829492,-68.127441],[-65.639502,-68.130566],[-65.503125,-67.377246],[-64.819287,-67.307324],[-64.686279,-66.80625],[-63.754736,-66.872949],[-63.752539,-66.277734],[-62.628906,-66.706152],[-62.682031,-66.237305],[-61.756006,-66.429199],[-61.431934,-66.144727],[-61.028418,-66.336523],[-60.618311,-65.933105],[-61.839062,-66.119531],[-62.293262,-65.916406],[-61.703125,-64.987207],[-61.059863,-64.98125],[-59.963086,-64.431348],[-59.645996,-64.583691],[-59.546777,-64.358789],[-58.786084,-64.524219],[-59.005322,-64.194922],[-57.460645,-63.513574],[-56.834766,-63.63125],[-57.020654,-63.372852]]],[[[-45.222656,-78.810742],[-43.947217,-78.597559],[-44.093994,-78.167285],[-45.993164,-77.826855],[-47.69209,-77.840137],[-50.14165,-78.556738],[-50.339258,-79.479492],[-52.297168,-80.141211],[-53.393896,-80.108789],[-54.1625,-80.870117],[-43.52793,-80.191406],[-43.544336,-78.901953],[-45.222656,-78.810742]]],[[[-59.733936,-80.344141],[-60.578809,-79.741016],[-61.633301,-80.344141],[-66.771143,-80.293848],[-60.582812,-80.948145],[-59.733936,-80.344141]]],[[[-70.051123,-69.189062],[-70.416992,-68.788965],[-72.137891,-69.114551],[-71.728516,-70.053711],[-69.618359,-70.398047],[-71.190039,-70.65957],[-69.875781,-70.875977],[-69.869775,-71.125684],[-70.380664,-70.946387],[-72.710449,-71.072949],[-72.21167,-71.335059],[-75.335449,-71.645215],[-73.995605,-72.169824],[-73.775977,-71.848926],[-72.927637,-71.92168],[-72.336621,-71.632227],[-70.820996,-71.906543],[-71.892188,-72.152832],[-70.206006,-72.227734],[-72.7375,-72.280566],[-72.36748,-72.669727],[-69.209326,-72.53418],[-68.241016,-71.822168],[-68.459473,-70.68291],[-70.051123,-69.189062]]],[[[-98.091113,-71.9125],[-98.61543,-71.76377],[-99.833203,-72.046094],[-100.218652,-71.83291],[-102.313623,-72.081055],[-98.407812,-72.547656],[-95.575391,-72.409961],[-95.6854,-72.056641],[-96.978906,-72.221875],[-96.38335,-71.836328],[-97.345215,-72.189062],[-97.584766,-71.882617],[-98.167969,-72.123047],[-98.091113,-71.9125]]],[[[-120.55625,-73.756055],[-123.112158,-73.682227],[-122.859082,-74.342676],[-121.002441,-74.326367],[-120.55625,-73.756055]]],[[[-126.329883,-73.28623],[-127.267627,-73.304004],[-127.211621,-73.724414],[-123.937402,-74.256152],[-124.128516,-73.833984],[-125.798584,-73.801953],[-125.263965,-73.666406],[-126.329883,-73.28623]]],[[[-159.05293,-79.807422],[-159.963525,-79.324316],[-163.256104,-78.72207],[-164.225781,-79.320801],[-159.05293,-79.807422]]],[[[167.084082,-77.32168],[166.506348,-77.189355],[166.729004,-77.850977],[169.352734,-77.524707],[167.084082,-77.32168]]],[[[-31.118848,-79.798438],[-31.68042,-79.634277],[-31.594238,-79.887695],[-29.614453,-79.90957],[-30.779932,-79.647363],[-31.118848,-79.798438]]],[[[-33.93418,-79.32041],[-35.534668,-79.090039],[-36.565967,-79.208789],[-33.93418,-79.32041]]],[[[-70.334082,-79.679883],[-67.038135,-78.315723],[-71.454004,-79.128906],[-71.68667,-79.568066],[-70.334082,-79.679883]]],[[[-57.845996,-64.053906],[-57.925684,-63.806055],[-58.424951,-64.067773],[-58.214062,-64.369727],[-57.294678,-64.366992],[-57.479736,-63.961621],[-57.845996,-64.053906]]],[[[-55.528027,-63.173535],[-56.462842,-63.418066],[-55.075195,-63.324316],[-55.528027,-63.173535]]],[[[-57.978418,-61.911914],[-59.003711,-62.209766],[-57.639551,-62.02041],[-57.978418,-61.911914]]],[[[-63.180566,-64.469531],[-63.485596,-64.260547],[-64.27207,-64.697559],[-63.739502,-64.834277],[-62.836523,-64.571875],[-63.180566,-64.469531]]],[[[-62.325781,-64.424414],[-62.058496,-64.138086],[-62.451416,-64.012402],[-62.781787,-64.479004],[-62.325781,-64.424414]]],[[[-67.988477,-67.474414],[-67.830518,-66.624316],[-69.120361,-67.57793],[-68.58042,-67.732813],[-67.988477,-67.474414]]],[[[-73.706641,-70.635156],[-74.400977,-70.575879],[-74.589697,-70.791992],[-74.953613,-70.590234],[-76.421484,-71.09043],[-74.205029,-70.924121],[-73.706641,-70.635156]]],[[[-74.987109,-69.727832],[-75.80415,-70.038184],[-74.848828,-70.179297],[-74.437988,-69.949609],[-74.987109,-69.727832]]],[[[-74.354443,-73.098438],[-75.376855,-72.82041],[-76.096387,-73.150488],[-74.574658,-73.611328],[-74.354443,-73.098438]]],[[[-91.160693,-73.182227],[-90.76333,-72.681055],[-91.303516,-72.547363],[-91.160693,-73.182227]]],[[[167.642773,-78.141406],[166.111133,-78.089648],[166.121875,-78.274609],[167.642773,-78.141406]]],[[[100.981445,-65.677539],[101.220605,-65.472266],[100.545117,-65.408984],[100.292578,-65.65127],[100.981445,-65.677539]]],[[[26.857227,-70.381152],[26.301074,-70.072461],[26.005371,-70.372949],[26.857227,-70.381152]]],[[[-66.173633,-80.077832],[-65.579248,-79.770801],[-67.687939,-79.528418],[-66.173633,-80.077832]]],[[[-67.261914,-79.452637],[-68.032568,-79.227148],[-68.548926,-79.437402],[-67.261914,-79.452637]]],[[[-55.16543,-61.22041],[-54.670996,-61.116992],[-55.387012,-61.072656],[-55.16543,-61.22041]]],[[[-61.997607,-69.721875],[-61.815967,-69.376172],[-62.442139,-69.145996],[-61.997607,-69.721875]]],[[[-60.625,-62.560059],[-61.149805,-62.63418],[-59.849561,-62.614941],[-60.625,-62.560059]]],[[[96.612695,-66.03584],[96.307031,-66.18584],[96.933984,-66.200781],[96.612695,-66.03584]]],[[[16.222656,-70.007617],[16.573438,-69.723242],[15.596875,-69.828027],[16.222656,-70.007617]]],[[[3.036914,-70.597363],[3.072168,-70.381641],[2.584668,-70.53457],[3.036914,-70.597363]]],[[[-2.532812,-70.767773],[-3.574658,-70.703125],[-2.783496,-71.16748],[-2.092285,-70.820898],[-2.532812,-70.767773]]],[[[-20.607422,-73.886621],[-20.580225,-73.619238],[-22.035352,-74.106543],[-20.489014,-74.492676],[-20.607422,-73.886621]]],[[[-67.348926,-67.766211],[-67.246729,-67.59873],[-67.743262,-67.66123],[-67.348926,-67.766211]]],[[[-116.738623,-74.165039],[-116.381299,-73.865527],[-117.376465,-74.082813],[-116.738623,-74.165039]]],[[[-131.066699,-74.583789],[-130.981104,-74.414062],[-132.162646,-74.425781],[-131.066699,-74.583789]]],[[[-149.230664,-77.120508],[-149.870605,-76.875],[-150.788525,-76.981641],[-149.230664,-77.120508]]]]}}, - {"type":"Feature","properties":{"name":"荷属圣马丁","full_name":"荷属圣马丁岛","iso_a2":"SX","iso_a3":"SXM","iso_n3":"534"},"geometry":{"type":"Polygon","coordinates":[[[-63.123047,18.068945],[-63.023047,18.019189],[-63.011182,18.068945],[-63.123047,18.068945]]]}}, - {"type":"Feature","properties":{"name":"图瓦卢","full_name":"图瓦卢","iso_a2":"TV","iso_a3":"TUV","iso_n3":"798"},"geometry":{"type":"Polygon","coordinates":[[[179.213672,-8.524219],[179.203027,-8.466309],[179.195703,-8.534766],[179.213672,-8.524219]]]}} - ] -} + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { name: '津巴布韦', full_name: '津巴布韦共和国', iso_a2: 'ZW', iso_a3: 'ZWE', iso_n3: '716' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [31.287891, -22.402051], + [32.429785, -21.29707], + [32.492383, -20.659766], + [32.992773, -19.984863], + [32.699707, -18.940918], + [32.993066, -18.35957], + [32.948047, -16.712305], + [31.23623, -16.023633], + [30.437793, -15.995313], + [30.396094, -15.643066], + [29.487305, -15.696777], + [28.913086, -15.987793], + [28.760547, -16.532129], + [27.932227, -16.896191], + [27.020801, -17.958398], + [25.258789, -17.793555], + [26.168066, -19.538281], + [27.178223, -20.100977], + [27.280762, -20.478711], + [27.679297, -20.503027], + [27.669434, -21.064258], + [28.014063, -21.554199], + [29.025586, -21.796875], + [29.364844, -22.193945], + [31.287891, -22.402051], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '赞比亚', full_name: '赞比亚共和国', iso_a2: 'ZM', iso_a3: 'ZMB', iso_n3: '894' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [30.396094, -15.643066], + [30.231836, -14.990332], + [33.201758, -14.013379], + [32.67041, -13.59043], + [33.021582, -12.630469], + [33.512305, -12.347754], + [33.252344, -12.112598], + [33.261328, -10.893359], + [33.661523, -10.553125], + [32.919922, -9.407422], + [31.033398, -8.597656], + [30.751172, -8.193652], + [28.898145, -8.485449], + [28.400684, -9.224805], + [28.645508, -10.550195], + [28.383398, -11.566699], + [29.064355, -12.348828], + [29.485547, -12.418457], + [29.795117, -12.155469], + [29.775195, -13.438086], + [29.554199, -13.248926], + [29.014258, -13.368848], + [28.412891, -12.518066], + [27.573828, -12.227051], + [27.15918, -11.579199], + [26.824023, -11.965234], + [26.025977, -11.890137], + [25.349414, -11.623047], + [25.28877, -11.212402], + [24.37793, -11.41709], + [24.365723, -11.129883], + [23.966504, -10.871777], + [23.962988, -12.988477], + [21.978906, -13.000977], + [22.040234, -16.262793], + [23.380664, -17.640625], + [24.73291, -17.517773], + [25.258789, -17.793555], + [27.020801, -17.958398], + [27.932227, -16.896191], + [28.760547, -16.532129], + [28.913086, -15.987793], + [29.487305, -15.696777], + [30.396094, -15.643066], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '也门', full_name: '也门共和国', iso_a2: 'YE', iso_a3: 'YEM', iso_n3: '887' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [53.085645, 16.648389], + [51.977637, 18.996143], + [49.041992, 18.581787], + [48.172168, 18.156934], + [47.143555, 16.94668], + [46.727637, 17.265576], + [43.916992, 17.324707], + [43.417969, 17.51626], + [43.190918, 17.359375], + [43.165039, 16.689404], + [42.799316, 16.371777], + [42.657812, 15.232812], + [42.936426, 14.938574], + [43.231934, 13.26709], + [43.487598, 12.698828], + [44.005859, 12.607666], + [45.038672, 12.815869], + [45.657324, 13.338721], + [46.788867, 13.465576], + [47.989941, 14.048096], + [48.668359, 14.050146], + [49.349902, 14.637793], + [52.21748, 15.655518], + [52.327734, 16.293555], + [53.085645, 16.648389], + ], + ], + [ + [ + [53.763184, 12.636816], + [53.31582, 12.533154], + [53.718848, 12.318994], + [54.511133, 12.552783], + [53.763184, 12.636816], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '越南', full_name: '越南社会主义共和国', iso_a2: 'VN', iso_a3: 'VNM', iso_n3: '704' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [104.063965, 10.39082], + [103.849512, 10.371094], + [104.018457, 10.029199], + [104.063965, 10.39082], + ], + ], + [ + [ + [107.972656, 21.507959], + [106.663574, 21.978906], + [106.550391, 22.501367], + [106.780273, 22.778906], + [105.842969, 22.922803], + [105.275391, 23.345215], + [103.941504, 22.540088], + [103.32666, 22.769775], + [102.981934, 22.448242], + [102.470898, 22.750928], + [102.127441, 22.379199], + [102.662012, 21.676025], + [102.949609, 21.681348], + [102.851172, 21.265918], + [103.104492, 20.89165], + [103.635059, 20.69707], + [104.101367, 20.945508], + [104.583203, 20.64668], + [104.367773, 20.441406], + [104.92793, 20.018115], + [104.587891, 19.61875], + [104.032031, 19.675146], + [103.891602, 19.30498], + [105.146484, 18.650977], + [105.114551, 18.405273], + [106.502246, 16.954102], + [106.656445, 16.492627], + [107.396387, 16.043018], + [107.165918, 15.80249], + [107.653125, 15.255225], + [107.519434, 14.705078], + [107.331445, 14.126611], + [107.605469, 13.437793], + [107.506445, 12.364551], + [106.413867, 11.948438], + [106.399219, 11.687012], + [105.851465, 11.63501], + [106.163965, 10.794922], + [105.755078, 10.98999], + [105.045703, 10.911377], + [104.850586, 10.534473], + [104.426367, 10.41123], + [105.084473, 9.995703], + [104.77041, 8.597656], + [105.114355, 8.629199], + [106.168359, 9.396729], + [105.830957, 10.000732], + [106.484082, 9.559424], + [106.136426, 10.22168], + [106.595605, 9.859863], + [106.785254, 10.116455], + [106.464063, 10.298291], + [106.757422, 10.295801], + [106.605859, 10.464941], + [106.947461, 10.400342], + [107.006641, 10.660547], + [107.261523, 10.398389], + [108.001367, 10.720361], + [108.986719, 11.336377], + [109.198633, 11.724854], + [109.423926, 12.955957], + [108.821289, 15.37793], + [106.370508, 17.746875], + [106.499023, 17.946436], + [105.621777, 18.966309], + [105.984082, 19.939062], + [106.572852, 20.392188], + [106.683398, 21.000293], + [107.164746, 20.94873], + [107.972656, 21.507959], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '委内瑞拉', full_name: '委内瑞拉玻利瓦尔共和国', iso_a2: 'VE', iso_a3: 'VEN', iso_n3: '862' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-63.849365, 11.131006], + [-64.402344, 10.981592], + [-63.917627, 10.887549], + [-63.849365, 11.131006], + ], + ], + [ + [ + [-60.017529, 8.549316], + [-61.304004, 8.4104], + [-61.618701, 8.597461], + [-61.247266, 8.600342], + [-60.79248, 9.360742], + [-61.588867, 9.894531], + [-61.735938, 9.631201], + [-62.0771, 9.975049], + [-62.32041, 9.783057], + [-62.550342, 10.200439], + [-62.740576, 10.056152], + [-62.913574, 10.531494], + [-61.879492, 10.741016], + [-64.298193, 10.635156], + [-63.731885, 10.503418], + [-65.129102, 10.070068], + [-65.851758, 10.257764], + [-66.247217, 10.632227], + [-68.139941, 10.492725], + [-68.398633, 11.160986], + [-68.827979, 11.431738], + [-69.631592, 11.479932], + [-70.003955, 12.177881], + [-70.286523, 11.886035], + [-69.804785, 11.474219], + [-71.469531, 10.96416], + [-71.494238, 10.533203], + [-71.052686, 9.705811], + [-71.241406, 9.160449], + [-71.619531, 9.047949], + [-72.112842, 9.815576], + [-71.594336, 10.657373], + [-71.956934, 11.569922], + [-71.319727, 11.861914], + [-71.958105, 11.666406], + [-72.690088, 10.83584], + [-73.366211, 9.194141], + [-72.796387, 9.108984], + [-72.390332, 8.287061], + [-72.471973, 7.524268], + [-72.006641, 7.032617], + [-70.129199, 6.953613], + [-69.427148, 6.123975], + [-67.481982, 6.180273], + [-67.855273, 4.506885], + [-67.311133, 3.415869], + [-67.859082, 2.793604], + [-67.21084, 2.390137], + [-66.876025, 1.223047], + [-66.347119, 0.767188], + [-65.681445, 0.983447], + [-65.473389, 0.69126], + [-64.205029, 1.529492], + [-64.008496, 1.931592], + [-63.43252, 2.155566], + [-63.389258, 2.411914], + [-64.046582, 2.502393], + [-64.221094, 3.587402], + [-64.788672, 4.276025], + [-64.021484, 3.929102], + [-63.338672, 3.943896], + [-62.856982, 3.593457], + [-62.712109, 4.01792], + [-61.002832, 4.535254], + [-60.603857, 4.949365], + [-60.742139, 5.202051], + [-61.39082, 5.93877], + [-61.128711, 6.214307], + [-61.145605, 6.694531], + [-60.3521, 7.002881], + [-60.718652, 7.535937], + [-59.849072, 8.248682], + [-60.017529, 8.549316], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '梵蒂冈', full_name: '梵蒂冈城国', iso_a2: 'VA', iso_a3: 'VAT', iso_n3: '336' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [12.43916, 41.898389], + [12.430566, 41.905469], + [12.430566, 41.897559], + [12.43916, 41.898389], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '瓦努阿图', full_name: '瓦努阿图共和国', iso_a2: 'VU', iso_a3: 'VUT', iso_n3: '548' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [166.745801, -14.826855], + [166.567383, -14.641797], + [166.631055, -15.406055], + [166.758301, -15.631152], + [167.093945, -15.580859], + [167.075586, -14.935645], + [166.810156, -15.157422], + [166.745801, -14.826855], + ], + ], + [ + [ + [167.4125, -16.095898], + [167.199512, -15.885059], + [167.449316, -16.55498], + [167.836621, -16.449707], + [167.4125, -16.095898], + ], + ], + [ + [ + [168.29668, -16.336523], + [168.163867, -16.081641], + [167.929004, -16.228711], + [168.29668, -16.336523], + ], + ], + [ + [ + [168.445801, -17.542188], + [168.158203, -17.710547], + [168.524609, -17.798047], + [168.445801, -17.542188], + ], + ], + [ + [ + [167.911328, -15.435938], + [168.002539, -15.283203], + [167.674219, -15.451562], + [167.911328, -15.435938], + ], + ], + [ + [ + [169.491309, -19.540137], + [169.247461, -19.344727], + [169.347266, -19.623535], + [169.491309, -19.540137], + ], + ], + [ + [ + [169.334375, -18.940234], + [169.01582, -18.64375], + [168.986914, -18.871289], + [169.334375, -18.940234], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '乌兹别克斯坦', full_name: '乌兹别克斯坦共和国', iso_a2: 'UZ', iso_a3: 'UZB', iso_n3: '860' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [70.946777, 42.248682], + [69.153613, 41.425244], + [68.572656, 40.622656], + [68.291895, 40.656104], + [67.935742, 41.196582], + [66.709668, 41.17915], + [66.498633, 41.994873], + [66.00957, 42.004883], + [66.100293, 42.99082], + [65.803027, 42.876953], + [64.905469, 43.714697], + [61.990234, 43.492139], + [61.00791, 44.393799], + [58.555273, 45.555371], + [55.975684, 44.994922], + [55.977441, 41.322217], + [57.017969, 41.263477], + [56.964063, 41.856543], + [58.028906, 42.487646], + [58.474414, 42.299365], + [58.151563, 42.628076], + [58.589063, 42.778467], + [59.985156, 42.211719], + [60.089648, 41.399414], + [61.902832, 41.093701], + [62.483203, 39.975635], + [63.763672, 39.160547], + [65.612891, 38.238574], + [66.60625, 37.986719], + [66.522266, 37.348486], + [67.758984, 37.172217], + [68.350293, 38.211035], + [68.13252, 38.927637], + [67.357617, 39.216699], + [67.426172, 39.465576], + [68.463281, 39.536719], + [68.97207, 40.089941], + [68.630664, 40.16709], + [69.274902, 40.198096], + [69.357227, 40.767383], + [69.712891, 40.656982], + [70.401953, 41.035107], + [70.751074, 40.721777], + [70.371582, 40.384131], + [70.958008, 40.238867], + [71.69248, 40.152344], + [73.136914, 40.810645], + [72.187305, 41.025928], + [71.664941, 41.541211], + [71.393066, 41.123389], + [70.200879, 41.514453], + [71.228516, 42.162891], + [70.946777, 42.248682], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '乌拉圭', full_name: '乌拉圭东岸共和国', iso_a2: 'UY', iso_a3: 'URY', iso_n3: '858' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-53.370605, -33.742188], + [-53.531348, -33.170898], + [-53.125586, -32.736719], + [-53.761719, -32.056836], + [-55.603027, -30.850781], + [-56.004687, -31.079199], + [-56.044824, -30.777637], + [-56.832715, -30.107227], + [-57.608887, -30.187793], + [-58.201172, -32.47168], + [-58.092676, -32.967383], + [-58.363525, -33.182324], + [-58.438135, -33.719141], + [-57.829102, -34.477344], + [-57.170703, -34.452344], + [-56.249951, -34.90127], + [-54.902295, -34.932813], + [-53.785303, -34.380371], + [-53.370605, -33.742188], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '密克罗尼西亚', full_name: '密克罗尼西亚联邦', iso_a2: 'FM', iso_a3: 'FSM', iso_n3: '583' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [158.314844, 6.813672], + [158.294629, 6.951074], + [158.134766, 6.944824], + [158.314844, 6.813672], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马绍尔群岛', full_name: '马绍尔群岛共和国', iso_a2: 'MH', iso_a3: 'MHL', iso_n3: '584' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [171.101953, 7.138232], + [171.035742, 7.156104], + [171.235352, 7.06875], + [171.39375, 7.110938], + [171.101953, 7.138232], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '北马里亚纳群岛', + full_name: '北马里亚纳群岛联邦(美国)', + iso_a2: 'MP', + iso_a3: 'MNP', + iso_n3: '580', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [145.751953, 15.133154], + [145.821875, 15.265381], + [145.713184, 15.215283], + [145.751953, 15.133154], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '美属维尔京群岛', full_name: '美属维尔京群岛', iso_a2: 'VI', iso_a3: 'VIR', iso_n3: '850' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-64.765625, 17.794336], + [-64.889111, 17.701709], + [-64.580469, 17.750195], + [-64.765625, 17.794336], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '关岛', full_name: '关岛', iso_a2: 'GU', iso_a3: 'GUM', iso_n3: '316' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [144.741797, 13.259277], + [144.875391, 13.614648], + [144.649316, 13.428711], + [144.741797, 13.259277], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '美属萨摩亚', full_name: '美属萨摩亚', iso_a2: 'AS', iso_a3: 'ASM', iso_n3: '016' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-170.72627, -14.351172], + [-170.568115, -14.266797], + [-170.820508, -14.312109], + [-170.72627, -14.351172], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '波多黎各', full_name: '波多黎各自由邦', iso_a2: 'PR', iso_a3: 'PRI', iso_n3: '630' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-66.129395, 18.444922], + [-67.158643, 18.499219], + [-67.196875, 17.994189], + [-65.970801, 17.974365], + [-65.62085, 18.242334], + [-66.129395, 18.444922], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '美国', full_name: '美利坚合众国', iso_a2: 'US', iso_a3: 'USA', iso_n3: '840' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-171.463037, 63.640039], + [-171.631836, 63.351221], + [-170.848389, 63.444385], + [-169.676367, 62.956104], + [-168.761328, 63.21377], + [-170.299365, 63.680615], + [-171.463037, 63.640039], + ], + ], + [ + [ + [-166.135449, 60.383545], + [-167.436426, 60.206641], + [-166.14873, 59.764111], + [-165.591797, 59.913135], + [-166.135449, 60.383545], + ], + ], + [ + [ + [-152.898047, 57.823926], + [-153.841553, 57.862842], + [-153.687695, 57.305127], + [-154.116162, 57.651221], + [-154.673193, 57.446094], + [-154.338965, 56.920898], + [-154.24375, 57.143018], + [-153.793213, 56.989502], + [-154.027344, 56.777979], + [-152.679053, 57.345117], + [-152.940771, 57.498096], + [-152.216211, 57.577002], + [-152.898047, 57.823926], + ], + ], + [ + [ + [-130.025098, 55.888232], + [-130.097852, 56.109277], + [-131.824268, 56.58999], + [-133.401123, 58.410889], + [-135.475928, 59.793262], + [-137.438574, 58.903125], + [-139.185156, 60.083594], + [-139.079248, 60.343701], + [-141.002148, 60.300244], + [-141.002148, 64.975537], + [-141.002148, 69.650781], + [-143.218311, 70.11626], + [-145.197363, 70.008691], + [-149.269434, 70.500781], + [-151.944678, 70.4521], + [-152.491211, 70.880957], + [-154.195215, 70.801123], + [-155.166846, 71.099219], + [-155.973535, 70.841992], + [-155.579443, 71.121094], + [-156.470215, 71.407666], + [-157.909375, 70.860107], + [-159.680908, 70.786768], + [-159.865674, 70.278857], + [-160.117139, 70.591211], + [-162.073877, 70.161963], + [-161.880957, 70.331738], + [-163.86792, 69.03667], + [-166.209082, 68.885352], + [-166.786279, 68.359619], + [-164.125195, 67.606738], + [-163.531836, 67.102588], + [-161.719922, 67.020557], + [-161.856689, 66.700342], + [-160.360889, 66.6125], + [-160.231689, 66.420264], + [-161.591016, 66.459521], + [-162.361621, 66.947314], + [-161.916895, 66.411816], + [-161.034277, 66.188818], + [-163.695361, 66.083838], + [-163.638232, 66.574658], + [-164.460498, 66.588428], + [-168.088379, 65.657764], + [-166.157031, 65.28584], + [-166.928418, 65.15708], + [-166.142773, 64.582764], + [-163.144336, 64.423828], + [-163.203906, 64.652002], + [-162.807031, 64.374219], + [-161.759375, 64.81626], + [-160.855908, 64.755615], + [-161.490723, 64.433789], + [-160.778564, 63.818945], + [-161.099707, 63.55791], + [-162.282812, 63.529199], + [-163.287842, 63.046436], + [-164.409033, 63.215039], + [-164.757861, 62.496729], + [-166.078809, 61.803125], + [-165.565869, 61.102344], + [-165.273633, 61.274854], + [-164.868994, 61.111768], + [-165.114844, 60.932812], + [-163.749023, 60.969727], + [-163.420947, 60.757422], + [-164.372363, 60.591846], + [-164.805176, 60.892041], + [-165.353809, 60.541211], + [-163.680371, 59.801514], + [-162.570752, 59.989746], + [-162.684961, 60.268945], + [-161.962012, 60.695361], + [-162.421338, 60.283984], + [-161.828711, 59.588623], + [-161.981055, 59.146143], + [-161.644385, 59.109668], + [-162.144922, 58.644238], + [-160.363135, 59.051172], + [-158.950684, 58.404541], + [-158.809473, 58.973877], + [-158.080518, 58.977441], + [-158.503174, 58.850342], + [-158.190918, 58.614258], + [-156.808887, 59.134277], + [-157.523633, 58.421338], + [-157.193701, 58.194189], + [-157.610889, 58.05083], + [-157.461914, 57.506201], + [-158.320947, 57.2979], + [-158.675146, 56.794873], + [-160.302051, 56.314111], + [-160.291699, 55.805078], + [-161.697314, 55.907227], + [-163.278809, 55.121826], + [-163.335303, 54.83916], + [-163.131104, 54.916553], + [-163.119629, 55.064697], + [-162.674365, 54.996582], + [-162.630371, 55.24668], + [-162.073975, 55.139307], + [-161.516943, 55.618408], + [-161.381934, 55.371289], + [-159.771387, 55.841113], + [-159.659668, 55.625928], + [-158.275635, 56.19624], + [-158.414404, 56.43584], + [-156.629004, 57.009961], + [-156.435889, 57.359961], + [-154.247021, 58.159424], + [-153.437598, 58.754834], + [-154.17832, 59.155566], + [-152.660107, 59.997217], + [-153.025, 60.295654], + [-152.540918, 60.26543], + [-151.593506, 60.979639], + [-149.433545, 61.500781], + [-150.053271, 61.171094], + [-149.075098, 60.876416], + [-150.44126, 61.023584], + [-151.356445, 60.722949], + [-151.853223, 59.78208], + [-151.046484, 59.771826], + [-151.884619, 59.386328], + [-151.738184, 59.188525], + [-150.934521, 59.249121], + [-149.713867, 59.91958], + [-149.598047, 59.770459], + [-149.395264, 60.105762], + [-148.430713, 59.989111], + [-147.964111, 60.484863], + [-148.640137, 60.489453], + [-148.256738, 60.675293], + [-148.556152, 60.827002], + [-147.751855, 61.218945], + [-147.891113, 60.889893], + [-146.284912, 61.112646], + [-146.570459, 60.72915], + [-145.674902, 60.651123], + [-145.898877, 60.478174], + [-145.248291, 60.380127], + [-144.691113, 60.669092], + [-144.901318, 60.335156], + [-144.147217, 60.016406], + [-141.40874, 60.117676], + [-141.408301, 59.902783], + [-140.419824, 59.710742], + [-139.431445, 60.012256], + [-138.988086, 59.83501], + [-139.286719, 59.610938], + [-139.512305, 59.953564], + [-139.773291, 59.527295], + [-136.607422, 58.243994], + [-136.061475, 58.452734], + [-136.989014, 59.034473], + [-136.22583, 58.765479], + [-136.150049, 59.048096], + [-135.897559, 58.400195], + [-135.090234, 58.24585], + [-135.363672, 59.419434], + [-134.776123, 58.453857], + [-134.208838, 58.232959], + [-133.876758, 58.518164], + [-134.031104, 58.072168], + [-133.194336, 57.877686], + [-133.535205, 57.832959], + [-133.117041, 57.566211], + [-133.64873, 57.642285], + [-133.465869, 57.172168], + [-131.551367, 56.206787], + [-132.15542, 55.599561], + [-131.032764, 56.088086], + [-130.748193, 55.318018], + [-131.047852, 55.157666], + [-130.575342, 54.769678], + [-130.214062, 55.025879], + [-130.025098, 55.888232], + ], + ], + [ + [ + [-163.476025, 54.980713], + [-164.478613, 54.906836], + [-164.823438, 54.419092], + [-163.083252, 54.668994], + [-163.378955, 54.815527], + [-163.476025, 54.980713], + ], + ], + [ + [ + [-133.305078, 55.54375], + [-133.737109, 55.496924], + [-133.650195, 55.269287], + [-133.305078, 55.54375], + ], + ], + [ + [ + [-131.339746, 55.079834], + [-131.56543, 55.264111], + [-131.329541, 54.887744], + [-131.339746, 55.079834], + ], + ], + [ + [ + [-132.112354, 56.109375], + [-132.379834, 56.498779], + [-132.659912, 56.078174], + [-132.287305, 55.929395], + [-132.112354, 56.109375], + ], + ], + [ + [ + [-130.97915, 55.48916], + [-131.269238, 55.955371], + [-131.7625, 55.16582], + [-131.447559, 55.408789], + [-131.187891, 55.206299], + [-130.97915, 55.48916], + ], + ], + [ + [ + [-133.366211, 57.003516], + [-133.979443, 57.00957], + [-133.48418, 56.451758], + [-133.158154, 56.495166], + [-133.328955, 56.830078], + [-132.95918, 56.677051], + [-133.366211, 57.003516], + ], + ], + [ + [ + [-132.862256, 54.894434], + [-133.429053, 55.303809], + [-132.705811, 54.68418], + [-132.862256, 54.894434], + ], + ], + [ + [ + [-160.684912, 55.314795], + [-160.795068, 55.145215], + [-160.487549, 55.184863], + [-160.684912, 55.314795], + ], + ], + [ + [ + [-177.148193, 51.716748], + [-177.110059, 51.92876], + [-177.670215, 51.701074], + [-177.148193, 51.716748], + ], + ], + [ + [ + [-134.969775, 57.351416], + [-135.448682, 57.534375], + [-135.812305, 57.009521], + [-135.454932, 57.249414], + [-134.681885, 56.216162], + [-134.969775, 57.351416], + ], + ], + [ + [ + [-134.680273, 58.16167], + [-134.923486, 58.354639], + [-134.516016, 57.042578], + [-133.911133, 57.352539], + [-134.292334, 58.044727], + [-133.822754, 57.628662], + [-134.240088, 58.143994], + [-134.680273, 58.16167], + ], + ], + [ + [ + [-135.730371, 58.244238], + [-136.568604, 57.972168], + [-135.910791, 57.446582], + [-135.564209, 57.666406], + [-134.931494, 57.481152], + [-135.338477, 57.768652], + [-134.954688, 58.015332], + [-135.613232, 57.991846], + [-135.730371, 58.244238], + ], + ], + [ + [ + [-133.566113, 56.339209], + [-133.742529, 55.964844], + [-133.241504, 55.920801], + [-133.680176, 55.785156], + [-133.033398, 55.589697], + [-133.118555, 55.327637], + [-132.064746, 54.713135], + [-131.976416, 55.208594], + [-132.631299, 55.473193], + [-132.172705, 55.480615], + [-133.566113, 56.339209], + ], + ], + [ + [ + [-133.9896, 56.844971], + [-134.373682, 56.838672], + [-134.1896, 56.076953], + [-133.738379, 56.650439], + [-133.9896, 56.844971], + ], + ], + [ + [ + [-152.416943, 58.360205], + [-152.841113, 58.416406], + [-153.381348, 58.087207], + [-152.068896, 58.17793], + [-152.416943, 58.360205], + ], + ], + [ + [ + [-167.964355, 53.345117], + [-167.828076, 53.507959], + [-168.287695, 53.500146], + [-169.088916, 52.832031], + [-167.964355, 53.345117], + ], + ], + [ + [ + [-166.615332, 53.900928], + [-167.038086, 53.942188], + [-166.808984, 53.646143], + [-167.780859, 53.300244], + [-166.354541, 53.673535], + [-166.230859, 53.932617], + [-166.615332, 53.900928], + ], + ], + [ + [ + [-173.55332, 52.136279], + [-173.99248, 52.12334], + [-173.0229, 52.07915], + [-173.55332, 52.136279], + ], + ], + [ + [ + [-174.677393, 52.03501], + [-174.045605, 52.367236], + [-175.295557, 52.022168], + [-174.677393, 52.03501], + ], + ], + [ + [ + [-176.593311, 51.866699], + [-176.961621, 51.603662], + [-176.452344, 51.735693], + [-176.593311, 51.866699], + ], + ], + [ + [ + [-177.879053, 51.649707], + [-177.644482, 51.82627], + [-178.168262, 51.903027], + [-177.879053, 51.649707], + ], + ], + [ + [ + [172.811816, 53.012988], + [172.494824, 52.937891], + [172.935156, 52.7521], + [173.436035, 52.852051], + [172.811816, 53.012988], + ], + ], + [ + [ + [-155.581348, 19.012012], + [-154.804199, 19.524463], + [-155.831641, 20.27583], + [-156.048682, 19.749951], + [-155.881299, 19.070508], + [-155.581348, 19.012012], + ], + ], + [ + [ + [-156.486816, 20.932568], + [-156.697754, 20.949072], + [-156.408789, 20.605176], + [-155.989844, 20.757129], + [-156.486816, 20.932568], + ], + ], + [ + [ + [-157.799365, 21.456641], + [-157.9625, 21.701367], + [-158.273145, 21.585254], + [-158.110352, 21.318604], + [-157.6354, 21.307617], + [-157.799365, 21.456641], + ], + ], + [ + [ + [-159.372754, 21.932373], + [-159.352051, 22.21958], + [-159.78916, 22.041797], + [-159.372754, 21.932373], + ], + ], + [ + [ + [-74.708887, 45.003857], + [-76.151172, 44.303955], + [-76.819971, 43.628809], + [-78.72041, 43.624951], + [-79.171875, 43.466553], + [-79.036719, 42.802344], + [-82.690039, 41.675195], + [-83.149658, 42.141943], + [-82.545312, 42.624707], + [-82.137842, 43.570898], + [-82.551074, 45.347363], + [-83.592676, 45.817139], + [-83.615967, 46.116846], + [-83.977783, 46.084912], + [-84.149463, 46.542773], + [-84.561768, 46.457373], + [-84.875977, 46.899902], + [-88.378174, 48.303076], + [-89.455664, 47.99624], + [-90.916064, 48.209131], + [-91.518311, 48.058301], + [-92.99624, 48.611816], + [-94.620898, 48.742627], + [-95.155273, 49.369678], + [-95.162061, 48.991748], + [-97.529834, 48.993164], + [-106.483838, 48.993115], + [-114.585107, 48.993066], + [-122.78877, 48.993018], + [-122.241992, 48.010742], + [-122.353809, 47.371582], + [-122.701953, 47.110889], + [-123.027588, 47.138916], + [-122.577881, 47.293164], + [-122.532812, 47.919727], + [-123.139062, 47.386084], + [-122.656641, 47.881152], + [-122.778613, 48.137598], + [-124.709961, 48.380371], + [-124.139258, 46.954688], + [-123.842871, 46.963184], + [-124.112549, 46.862695], + [-124.072754, 46.279443], + [-123.220605, 46.153613], + [-123.989307, 46.219385], + [-124.14873, 43.691748], + [-124.539648, 42.812891], + [-124.071924, 41.459521], + [-124.324023, 40.251953], + [-122.998779, 37.988623], + [-122.521338, 37.826416], + [-122.393359, 38.144824], + [-121.525342, 38.055908], + [-122.314258, 38.007324], + [-122.070508, 37.478271], + [-122.445605, 37.797998], + [-122.499219, 37.542627], + [-121.807422, 36.851221], + [-121.877393, 36.331055], + [-120.659082, 35.122412], + [-120.644678, 34.57998], + [-118.506201, 34.017383], + [-118.410449, 33.743945], + [-117.467432, 33.295508], + [-117.128271, 32.53335], + [-114.724756, 32.715332], + [-114.835938, 32.508301], + [-111.041992, 31.324219], + [-108.214453, 31.329443], + [-108.211816, 31.779346], + [-106.44541, 31.768408], + [-104.978809, 30.645947], + [-104.110596, 29.386133], + [-103.168311, 28.998193], + [-102.614941, 29.752344], + [-101.440381, 29.776855], + [-99.505322, 27.54834], + [-99.107764, 26.446924], + [-97.14624, 25.961475], + [-97.485107, 27.237402], + [-97.768457, 27.45752], + [-97.439111, 27.328271], + [-97.156494, 28.144336], + [-96.421094, 28.457324], + [-96.640039, 28.708789], + [-96.011035, 28.631934], + [-96.234521, 28.488965], + [-95.273486, 28.963867], + [-94.888281, 29.370557], + [-95.022852, 29.702344], + [-94.52627, 29.547949], + [-94.759619, 29.384277], + [-93.890479, 29.689355], + [-93.841455, 29.979736], + [-93.826465, 29.725146], + [-92.26084, 29.556836], + [-91.893164, 29.836035], + [-91.248828, 29.564209], + [-91.290137, 29.288965], + [-90.751025, 29.130859], + [-90.379199, 29.295117], + [-90.212793, 29.104932], + [-90.159082, 29.537158], + [-89.376123, 28.981348], + [-89.015723, 29.202881], + [-89.720898, 29.619287], + [-89.354443, 29.820215], + [-89.400732, 30.046045], + [-90.175342, 30.029102], + [-90.331982, 30.277588], + [-88.135449, 30.366602], + [-88.011328, 30.694189], + [-87.790283, 30.291797], + [-88.005957, 30.230908], + [-87.281055, 30.339258], + [-86.997559, 30.570312], + [-87.201172, 30.339258], + [-86.257373, 30.493018], + [-86.454443, 30.399121], + [-85.603516, 30.286768], + [-85.318945, 29.680225], + [-84.309668, 30.064746], + [-83.694385, 29.925977], + [-82.651465, 28.8875], + [-82.843506, 27.845996], + [-82.405762, 27.862891], + [-82.7146, 27.499609], + [-82.441357, 27.059668], + [-82.242871, 26.848877], + [-82.013281, 26.961572], + [-81.715479, 25.983154], + [-80.94043, 25.264209], + [-81.110498, 25.138037], + [-80.484668, 25.229834], + [-80.126367, 25.833496], + [-80.050049, 26.807715], + [-80.838184, 28.757666], + [-80.456885, 27.900684], + [-80.524121, 28.486084], + [-81.249512, 29.793799], + [-81.516211, 30.801807], + [-81.380957, 31.353271], + [-80.694238, 32.215723], + [-80.802539, 32.448047], + [-80.579346, 32.287305], + [-80.63418, 32.511719], + [-79.940723, 32.667139], + [-78.841455, 33.724072], + [-78.01333, 33.911816], + [-77.953271, 34.168994], + [-77.927832, 33.939746], + [-77.412256, 34.730811], + [-76.439795, 34.84292], + [-76.974951, 35.025195], + [-77.070264, 35.154639], + [-76.77915, 34.990332], + [-76.512939, 35.27041], + [-77.03999, 35.527393], + [-76.173828, 35.35415], + [-75.758838, 35.843262], + [-76.083594, 35.690527], + [-76.069775, 35.970312], + [-76.726221, 35.957617], + [-76.733643, 36.22915], + [-76.559375, 36.015332], + [-76.147852, 36.279297], + [-75.820068, 36.112842], + [-75.946484, 36.659082], + [-75.53418, 35.819092], + [-75.999414, 36.912646], + [-76.487842, 36.897021], + [-77.250879, 37.329199], + [-76.283301, 37.052686], + [-76.757715, 37.50542], + [-76.453906, 37.273535], + [-76.305566, 37.571484], + [-76.549463, 37.669141], + [-77.111084, 38.165674], + [-76.49248, 37.682227], + [-76.344141, 37.675684], + [-76.264258, 37.893555], + [-77.273242, 38.351758], + [-77.2604, 38.6], + [-77.030371, 38.889258], + [-77.23252, 38.407715], + [-76.341162, 38.087012], + [-76.668555, 38.5375], + [-76.394092, 38.368994], + [-76.57041, 39.269336], + [-75.958936, 39.585059], + [-76.341162, 38.709668], + [-76.016943, 38.625098], + [-76.264648, 38.436426], + [-75.858691, 38.362061], + [-75.659277, 37.953955], + [-75.934375, 37.151904], + [-75.03877, 38.426367], + [-75.187109, 38.591113], + [-75.088672, 38.777539], + [-75.392188, 39.092773], + [-75.587598, 39.640771], + [-75.07417, 39.983496], + [-75.524219, 39.490186], + [-74.897021, 39.145459], + [-74.954297, 38.949951], + [-74.794482, 39.001904], + [-74.0646, 39.993115], + [-74.079932, 39.788135], + [-73.972266, 40.400342], + [-74.264209, 40.528613], + [-73.969922, 41.249707], + [-73.987109, 40.751367], + [-72.924707, 41.285156], + [-71.522852, 41.378955], + [-71.390137, 41.795312], + [-71.168555, 41.489404], + [-69.948633, 41.677148], + [-69.977881, 41.961279], + [-70.108936, 42.07832], + [-70.001416, 41.826172], + [-70.42666, 41.757275], + [-71.046191, 42.331104], + [-70.612939, 42.623242], + [-70.733105, 43.07002], + [-70.178809, 43.766357], + [-69.226074, 43.986475], + [-68.762695, 44.570752], + [-68.53252, 44.258643], + [-68.450586, 44.507617], + [-68.056641, 44.384326], + [-67.19126, 44.675586], + [-67.124854, 45.169434], + [-67.802246, 45.727539], + [-67.806787, 47.082812], + [-68.235498, 47.345947], + [-68.937207, 47.21123], + [-69.242871, 47.462988], + [-70.865039, 45.270703], + [-71.327295, 45.290088], + [-71.517529, 45.007568], + [-74.708887, 45.003857], + ], + [ + [-122.572754, 48.156641], + [-122.628613, 48.384229], + [-122.383154, 47.923193], + [-122.572754, 48.156641], + ], + ], + [ + [ + [-72.509766, 40.986035], + [-72.274121, 41.153027], + [-73.573828, 40.919629], + [-74.014893, 40.581201], + [-71.903223, 41.060693], + [-72.509766, 40.986035], + ], + ], + [ + [ + [-80.381836, 25.142285], + [-80.25708, 25.347607], + [-80.580566, 24.954248], + [-80.381836, 25.142285], + ], + ], + [ + [ + [-84.90791, 29.642627], + [-84.737158, 29.732422], + [-85.116748, 29.632812], + [-84.90791, 29.642627], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '南乔治亚和南桑威奇群岛', + full_name: '南乔治亚和南桑威奇群岛(英国)', + iso_a2: 'GS', + iso_a3: 'SGS', + iso_n3: '239', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-37.10332, -54.065625], + [-38.017432, -54.008008], + [-36.085498, -54.866797], + [-35.798584, -54.763477], + [-36.326465, -54.251172], + [-37.10332, -54.065625], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '英属印度洋领地', full_name: '英属印度洋领地', iso_a2: 'IO', iso_a3: 'IOT', iso_n3: '086' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [72.491992, -7.377441], + [72.445605, -7.22041], + [72.447266, -7.395703], + [72.349707, -7.263379], + [72.429102, -7.435352], + [72.491992, -7.377441], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圣赫勒拿', full_name: '圣赫勒拿岛(英国)', iso_a2: 'SH', iso_a3: 'SHN', iso_n3: '654' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-5.692139, -15.997754], + [-5.707861, -15.906152], + [-5.78252, -16.004004], + [-5.692139, -15.997754], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '皮特凯恩群岛', + full_name: '皮特凯恩群岛(英国)', + iso_a2: 'PN', + iso_a3: 'PCN', + iso_n3: '612', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-128.290088, -24.397363], + [-128.330127, -24.323242], + [-128.342188, -24.370703], + [-128.290088, -24.397363], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '安圭拉', full_name: '安圭拉', iso_a2: 'AI', iso_a3: 'AIA', iso_n3: '660' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-63.001221, 18.221777], + [-63.026025, 18.269727], + [-63.16001, 18.171387], + [-63.001221, 18.221777], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '马尔维纳斯群岛(福克兰)', + full_name: '马尔维纳斯群岛(福克兰)', + iso_a2: 'FK', + iso_a3: 'FLK', + iso_n3: '238', + }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-58.850195, -51.269922], + [-59.570801, -51.925391], + [-59.395654, -52.308008], + [-59.19585, -52.017676], + [-58.652783, -52.099219], + [-57.791797, -51.636133], + [-57.976514, -51.384375], + [-58.271582, -51.574707], + [-58.850195, -51.269922], + ], + ], + [ + [ + [-60.28623, -51.461914], + [-60.568457, -51.357812], + [-60.245166, -51.638867], + [-60.58252, -51.712695], + [-60.238477, -51.771973], + [-60.961426, -52.057324], + [-60.686377, -52.188379], + [-59.268066, -51.427539], + [-60.28623, -51.461914], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '开曼群岛', full_name: '开曼群岛', iso_a2: 'KY', iso_a3: 'CYM', iso_n3: '136' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-81.369531, 19.348877], + [-81.404785, 19.278418], + [-81.107129, 19.305176], + [-81.369531, 19.348877], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '百慕大', full_name: '百慕大', iso_a2: 'BM', iso_a3: 'BMU', iso_n3: '060' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-64.730273, 32.293457], + [-64.668311, 32.381934], + [-64.862842, 32.273877], + [-64.730273, 32.293457], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '英属维尔京群岛', full_name: '英属维尔京群岛', iso_a2: 'VG', iso_a3: 'VGB', iso_n3: '092' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-64.395215, 18.4646], + [-64.324658, 18.51748], + [-64.426074, 18.513086], + [-64.395215, 18.4646], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '特克斯和凯科斯群岛', + full_name: '特克斯和凯科斯群岛', + iso_a2: 'TC', + iso_a3: 'TCA', + iso_n3: '796', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-71.661426, 21.765234], + [-71.668359, 21.833447], + [-71.847656, 21.843457], + [-71.661426, 21.765234], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '蒙特塞拉特', full_name: '蒙特塞拉特', iso_a2: 'MS', iso_a3: 'MSR', iso_n3: '500' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-62.148438, 16.740332], + [-62.191357, 16.804395], + [-62.221631, 16.699512], + [-62.148438, 16.740332], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '泽西岛', full_name: '泽西岛', iso_a2: 'JE', iso_a3: 'JEY', iso_n3: '832' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-2.018652, 49.23125], + [-2.220508, 49.266357], + [-2.23584, 49.176367], + [-2.018652, 49.23125], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '根西岛', full_name: '根西岛', iso_a2: 'GG', iso_a3: 'GGY', iso_n3: '831' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-2.512305, 49.494531], + [-2.639014, 49.450928], + [-2.547363, 49.428711], + [-2.512305, 49.494531], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马恩岛', full_name: '马恩岛', iso_a2: 'IM', iso_a3: 'IMN', iso_n3: '833' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-4.412061, 54.185352], + [-4.424707, 54.407178], + [-4.785352, 54.073047], + [-4.412061, 54.185352], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '英国', full_name: '大不列颠及北爱尔兰联合王国', iso_a2: 'GB', iso_a3: 'GBR', iso_n3: '826' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-2.667676, 51.622998], + [-2.433057, 51.740723], + [-3.135986, 51.205029], + [-4.188184, 51.188525], + [-5.622119, 50.050684], + [-4.19458, 50.393311], + [-3.679785, 50.239941], + [-2.999414, 50.716602], + [-2.03584, 50.603076], + [-1.416455, 50.896875], + [0.205078, 50.763037], + [0.960156, 50.925879], + [1.414941, 51.363281], + [0.424512, 51.465625], + [1.274414, 51.845361], + [1.743359, 52.578516], + [1.271289, 52.924561], + [0.045898, 52.905615], + [0.270996, 53.335498], + [-0.659912, 53.724023], + [0.115332, 53.609277], + [-0.084375, 54.118066], + [-1.232422, 54.703711], + [-1.655371, 55.570361], + [-2.599316, 56.027295], + [-3.789062, 56.095215], + [-2.674268, 56.253418], + [-3.309961, 56.363477], + [-2.592676, 56.561572], + [-1.77793, 57.49375], + [-2.074072, 57.702393], + [-4.134521, 57.577734], + [-3.053076, 58.634814], + [-4.924658, 58.588379], + [-5.413184, 58.069727], + [-5.157227, 57.881348], + [-5.744922, 57.668311], + [-5.561914, 57.232715], + [-5.730615, 56.853076], + [-6.132764, 56.718018], + [-5.652441, 56.531982], + [-5.188379, 56.758057], + [-5.768213, 55.362646], + [-4.996973, 56.23335], + [-5.228223, 55.886328], + [-4.800293, 56.15835], + [-4.584082, 55.938672], + [-5.135498, 54.85752], + [-4.91123, 54.689453], + [-3.03623, 54.953076], + [-3.592041, 54.564355], + [-3.165967, 54.12793], + [-2.867578, 54.177246], + [-3.0646, 53.512842], + [-2.749512, 53.310205], + [-4.268555, 53.144531], + [-4.683057, 52.806152], + [-4.101465, 52.915479], + [-4.149365, 52.32627], + [-5.262305, 51.880176], + [-3.562354, 51.413818], + [-2.667676, 51.622998], + ], + ], + [ + [ + [-4.196777, 53.321436], + [-4.567871, 53.386475], + [-4.373047, 53.13418], + [-4.196777, 53.321436], + ], + ], + [ + [ + [-1.308105, 60.5375], + [-1.66377, 60.28252], + [-1.299463, 59.878662], + [-1.308105, 60.5375], + ], + ], + [ + [ + [-3.057422, 59.029639], + [-3.310352, 59.130811], + [-2.793018, 58.906934], + [-3.057422, 59.029639], + ], + ], + [ + [ + [-5.777881, 56.344336], + [-6.286328, 56.611865], + [-6.313428, 56.293652], + [-5.777881, 56.344336], + ], + ], + [ + [ + [-6.198682, 58.363281], + [-7.085254, 58.182178], + [-6.983105, 57.75], + [-6.198682, 58.363281], + ], + ], + [ + [ + [-6.144727, 57.50498], + [-6.305957, 57.671973], + [-6.761133, 57.442383], + [-5.949072, 57.045166], + [-5.672461, 57.252686], + [-6.135547, 57.314258], + [-6.144727, 57.50498], + ], + ], + [ + [ + [-6.218018, 54.088721], + [-5.47041, 54.500195], + [-6.035791, 55.144531], + [-7.218652, 55.091992], + [-8.118262, 54.414258], + [-7.606543, 54.143848], + [-7.007715, 54.406689], + [-6.649805, 54.058643], + [-6.218018, 54.088721], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿联酋', full_name: '阿拉伯联合酋长国', iso_a2: 'AE', iso_a3: 'ARE', iso_n3: '784' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [56.297852, 25.650684], + [56.080469, 26.062646], + [54.147949, 24.171191], + [52.118555, 23.971094], + [51.568359, 24.286182], + [52.555078, 22.932812], + [55.18584, 22.704102], + [55.468457, 23.941113], + [55.985156, 24.063379], + [55.76084, 24.242676], + [55.795703, 24.868115], + [56.000586, 24.953223], + [56.063867, 24.73877], + [56.387988, 24.979199], + [56.297852, 25.650684], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '乌克兰', full_name: '乌克兰', iso_a2: 'UA', iso_a3: 'UKR', iso_n3: '804' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [38.214355, 47.091455], + [38.368848, 47.609961], + [39.778711, 47.887549], + [39.95791, 48.268896], + [39.644727, 48.591211], + [40.003613, 48.82207], + [39.686523, 49.00791], + [40.108789, 49.251562], + [40.080664, 49.576855], + [38.258594, 50.052344], + [38.046875, 49.92002], + [37.422852, 50.411475], + [36.619434, 50.209229], + [35.591113, 50.36875], + [35.311914, 51.043896], + [34.213867, 51.255371], + [34.397852, 51.78042], + [33.735254, 52.344775], + [31.763379, 52.101074], + [30.755273, 51.895166], + [30.544531, 51.265039], + [30.160742, 51.477881], + [29.346484, 51.382568], + [29.102051, 51.627539], + [28.73125, 51.433398], + [27.7, 51.477979], + [27.141992, 51.752051], + [25.267188, 51.937744], + [23.605273, 51.51792], + [24.089941, 50.530469], + [22.706152, 49.606201], + [22.852051, 49.062744], + [22.538672, 49.072705], + [22.131836, 48.405322], + [22.87666, 47.947266], + [23.202637, 48.084521], + [24.979102, 47.724121], + [26.618945, 48.259863], + [27.549219, 48.477734], + [29.125391, 47.964551], + [29.134863, 47.489697], + [30.131055, 46.423096], + [28.958398, 46.458496], + [28.947754, 46.049951], + [28.2125, 45.450439], + [29.705859, 45.259912], + [29.628418, 45.722461], + [30.219043, 45.866748], + [30.796289, 46.552002], + [31.563379, 46.777295], + [31.872852, 46.649756], + [31.75918, 47.212842], + [32.044336, 46.64248], + [32.578027, 46.615625], + [31.554883, 46.554297], + [32.008496, 46.42998], + [31.83125, 46.281689], + [33.594141, 46.09624], + [33.806667, 46.208288], + [35.001674, 45.733383], + [34.849609, 46.189893], + [35.230371, 46.440625], + [35.014551, 46.106006], + [35.827148, 46.624316], + [38.214355, 47.091455], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '乌干达', full_name: '乌干达共和国', iso_a2: 'UG', iso_a3: 'UGA', iso_n3: '800' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [33.903223, -1.002051], + [33.943164, 0.173779], + [34.978223, 1.773633], + [34.437695, 3.650586], + [33.976074, 4.220215], + [33.489355, 3.755078], + [32.997266, 3.880176], + [32.135938, 3.519727], + [31.798047, 3.802637], + [31.152344, 3.785596], + [30.838574, 3.490723], + [30.728613, 2.455371], + [31.252734, 2.04458], + [29.942871, 0.819238], + [29.576953, -1.387891], + [29.930078, -1.469922], + [30.509961, -1.067285], + [33.903223, -1.002051], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '土库曼斯坦', full_name: '土库曼斯坦', iso_a2: 'TM', iso_a3: 'TKM', iso_n3: '795' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [55.977441, 41.322217], + [55.434375, 41.296289], + [54.120996, 42.335205], + [53.055859, 42.147754], + [52.493848, 41.780371], + [52.850391, 41.200293], + [52.97002, 41.976221], + [53.804688, 42.117627], + [54.703711, 41.071143], + [54.377344, 40.693262], + [53.87002, 40.648682], + [52.943457, 41.038086], + [52.733691, 40.39873], + [53.035547, 39.774414], + [52.9875, 39.987598], + [53.487305, 39.909375], + [53.603125, 39.546973], + [53.235645, 39.608545], + [53.156641, 39.26499], + [53.70459, 39.20957], + [53.868652, 38.949268], + [53.91416, 37.343555], + [54.699414, 37.470166], + [55.380859, 38.051123], + [57.193555, 38.216406], + [58.261621, 37.66582], + [59.301758, 37.510645], + [60.341309, 36.637646], + [61.119629, 36.642578], + [61.262012, 35.61958], + [62.307813, 35.170801], + [62.688086, 35.255322], + [63.056641, 35.445801], + [63.12998, 35.846191], + [64.511035, 36.340674], + [64.816309, 37.13208], + [65.55498, 37.251172], + [65.765039, 37.569141], + [66.522266, 37.348486], + [66.60625, 37.986719], + [65.612891, 38.238574], + [63.763672, 39.160547], + [62.483203, 39.975635], + [61.902832, 41.093701], + [60.089648, 41.399414], + [59.985156, 42.211719], + [58.589063, 42.778467], + [58.151563, 42.628076], + [58.474414, 42.299365], + [58.028906, 42.487646], + [56.964063, 41.856543], + [57.017969, 41.263477], + [55.977441, 41.322217], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '土耳其', full_name: '土耳其共和国', iso_a2: 'TR', iso_a3: 'TUR', iso_n3: '792' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [41.510059, 41.51748], + [40.265234, 40.961328], + [39.426367, 41.106445], + [38.381055, 40.924512], + [36.405371, 41.274609], + [36.051758, 41.682568], + [35.297754, 41.728516], + [35.006445, 42.063281], + [33.381348, 42.017578], + [31.254883, 41.107617], + [29.148145, 41.221045], + [29.113867, 40.937842], + [29.849219, 40.760107], + [28.958008, 40.630566], + [29.007129, 40.389746], + [26.738086, 40.400244], + [26.181348, 39.990088], + [26.113086, 39.467383], + [26.899219, 39.549658], + [26.681836, 39.292236], + [27.013672, 38.886865], + [26.763672, 38.709619], + [27.144238, 38.451953], + [26.674219, 38.335742], + [26.441309, 38.641211], + [26.290723, 38.277197], + [27.232422, 37.978662], + [27.067969, 37.65791], + [27.535059, 37.163867], + [27.262988, 36.976562], + [28.242383, 37.029053], + [27.453906, 36.712158], + [28.969629, 36.715332], + [29.689062, 36.156689], + [30.446094, 36.269873], + [30.644043, 36.865674], + [31.240625, 36.821729], + [32.794824, 36.035889], + [33.694727, 36.181982], + [34.703613, 36.816797], + [35.393164, 36.575195], + [36.048926, 36.910596], + [35.892676, 35.916553], + [36.153613, 35.833887], + [36.636719, 36.233984], + [36.658594, 36.802539], + [37.436328, 36.643311], + [38.191699, 36.901562], + [39.356641, 36.681592], + [40.705664, 37.097705], + [42.202734, 37.297266], + [42.358984, 37.108594], + [42.774609, 37.371875], + [44.114453, 37.301855], + [44.281836, 36.978027], + [44.765137, 37.142432], + [44.589941, 37.710352], + [44.211328, 37.908057], + [44.449902, 38.334229], + [44.023242, 39.377441], + [44.389355, 39.422119], + [44.587109, 39.768555], + [44.817188, 39.650439], + [44.768262, 39.703516], + [43.666211, 40.126367], + [43.439453, 41.107129], + [42.754102, 41.578906], + [41.510059, 41.51748], + ], + ], + [ + [ + [28.014453, 41.969043], + [27.011719, 42.058643], + [26.320898, 41.716553], + [26.624902, 41.401758], + [26.038965, 40.726758], + [26.79209, 40.626611], + [26.202734, 40.075391], + [27.499414, 40.973145], + [28.95625, 41.008203], + [29.057227, 41.229736], + [28.197852, 41.554492], + [28.014453, 41.969043], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '突尼斯', full_name: '突尼斯共和国', iso_a2: 'TN', iso_a3: 'TUN', iso_n3: '788' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [11.50459, 33.181934], + [10.049023, 34.056299], + [11.120117, 35.240283], + [10.476562, 36.175146], + [11.053906, 37.07251], + [10.412305, 36.731836], + [10.196387, 37.205859], + [9.830273, 37.135352], + [9.687988, 37.340381], + [8.576563, 36.937207], + [8.207617, 36.518945], + [8.245605, 34.734082], + [7.500195, 33.832471], + [8.333398, 32.543604], + [9.044043, 32.072363], + [9.51875, 30.229395], + [10.216406, 30.783203], + [10.274609, 31.684961], + [11.50498, 32.413672], + [11.50459, 33.181934], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '特立尼达和多巴哥', + full_name: '特立尼达和多巴哥共和国', + iso_a2: 'TT', + iso_a3: 'TTO', + iso_n3: '780', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-61.012109, 10.134326], + [-60.917627, 10.840234], + [-61.651172, 10.718066], + [-61.499316, 10.268555], + [-61.906104, 10.069141], + [-61.012109, 10.134326], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '汤加', full_name: '汤加王国', iso_a2: 'TO', iso_a3: 'TON', iso_n3: '776' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-175.161914, -21.169336], + [-175.362354, -21.106836], + [-175.156592, -21.263672], + [-175.161914, -21.169336], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '多哥', full_name: '多哥共和国', iso_a2: 'TG', iso_a3: 'TGO', iso_n3: '768' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [0.900488, 10.993262], + [-0.068604, 11.115625], + [-0.086328, 10.673047], + [0.380859, 10.291846], + [0.233398, 9.463525], + [0.525684, 9.398486], + [0.372559, 8.759277], + [0.686328, 8.354883], + [0.525586, 6.850928], + [1.187207, 6.089404], + [1.622656, 6.216797], + [1.77793, 6.294629], + [1.530957, 6.992432], + [1.600195, 9.050049], + [1.330078, 9.996973], + [0.763379, 10.38667], + [0.900488, 10.993262], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '东帝汶', full_name: '东帝汶民主共和国', iso_a2: 'TL', iso_a3: 'TLS', iso_n3: '626' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [125.068164, -9.511914], + [127.296094, -8.424512], + [125.381836, -8.575391], + [124.922266, -8.94248], + [125.149023, -9.042578], + [124.960156, -9.21377], + [125.068164, -9.511914], + ], + ], + [ + [ + [124.036328, -9.341602], + [124.282324, -9.42793], + [124.444434, -9.190332], + [124.036328, -9.341602], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '泰国', full_name: '泰王国', iso_a2: 'TH', iso_a3: 'THA', iso_n3: '764' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [98.409082, 7.902051], + [98.32207, 8.166309], + [98.296289, 7.776074], + [98.409082, 7.902051], + ], + ], + [ + [ + [100.122461, 20.31665], + [99.458887, 20.363037], + [99.485938, 20.149854], + [99.074219, 20.099365], + [98.916699, 19.7729], + [98.015039, 19.749512], + [97.745898, 18.588184], + [97.373926, 18.517969], + [98.660742, 16.33042], + [98.888281, 16.351904], + [98.202148, 14.975928], + [99.136816, 13.716699], + [99.123926, 13.030762], + [99.614746, 11.781201], + [98.757227, 10.660937], + [98.702539, 10.190381], + [98.241797, 8.767871], + [98.305469, 8.226221], + [98.703516, 8.256738], + [100.119141, 6.441992], + [100.261426, 6.682715], + [101.053516, 6.242578], + [101.113965, 5.636768], + [101.556055, 5.907764], + [101.873633, 5.825293], + [102.101074, 6.242236], + [101.497949, 6.865283], + [100.423535, 7.187842], + [100.160742, 7.599268], + [100.256641, 7.774902], + [100.545215, 7.226904], + [100.279395, 8.268506], + [99.835547, 9.288379], + [99.253906, 9.265234], + [99.165039, 10.319824], + [99.989062, 12.170801], + [99.990527, 13.243457], + [100.235645, 13.484473], + [100.962695, 13.431982], + [100.897754, 12.653809], + [101.835742, 12.640381], + [102.594141, 12.203027], + [102.933887, 11.706689], + [102.336328, 13.560303], + [103.199414, 14.332617], + [105.183301, 14.34624], + [105.497363, 14.590674], + [105.641016, 15.656543], + [104.819336, 16.466064], + [104.739648, 17.46167], + [103.949609, 18.318994], + [103.366992, 18.42334], + [102.680078, 17.824121], + [102.101465, 18.210645], + [100.955859, 17.541113], + [101.211914, 19.54834], + [100.513574, 19.553467], + [100.519531, 20.17793], + [100.122461, 20.31665], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '坦桑尼亚', full_name: '坦桑尼亚联合共和国', iso_a2: 'TZ', iso_a3: 'TZA', iso_n3: '834' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [39.496484, -6.174609], + [39.308984, -5.721973], + [39.182324, -6.172559], + [39.480957, -6.453711], + [39.496484, -6.174609], + ], + ], + [ + [ + [39.865039, -4.906152], + [39.673438, -4.927051], + [39.749316, -5.443848], + [39.865039, -4.906152], + ], + ], + [ + [ + [32.919922, -9.407422], + [33.888867, -9.670117], + [33.995605, -9.49541], + [34.320898, -9.731543], + [34.60791, -11.080469], + [34.959473, -11.578125], + [35.911328, -11.454688], + [36.305664, -11.706348], + [37.372852, -11.710449], + [37.920215, -11.294727], + [38.491797, -11.413281], + [40.463574, -10.464355], + [39.725195, -10.000488], + [39.304004, -8.443848], + [39.288477, -7.517871], + [39.546094, -7.024023], + [38.804688, -6.070117], + [39.221777, -4.692383], + [37.608203, -3.49707], + [37.643848, -3.04541], + [33.903223, -1.002051], + [30.509961, -1.067285], + [30.876562, -2.143359], + [30.553613, -2.400098], + [30.433496, -2.874512], + [30.780273, -2.984863], + [30.790234, -3.274609], + [29.947266, -4.307324], + [29.403223, -4.449316], + [29.54082, -6.313867], + [30.212695, -7.037891], + [30.751172, -8.193652], + [31.033398, -8.597656], + [32.919922, -9.407422], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塔吉克斯坦', full_name: '塔吉克斯坦共和国', iso_a2: 'TJ', iso_a3: 'TJK', iso_n3: '762' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [67.758984, 37.172217], + [68.067773, 36.949805], + [68.911816, 37.333936], + [69.303906, 37.116943], + [69.49209, 37.553076], + [70.188672, 37.582471], + [70.214648, 37.924414], + [70.878906, 38.456396], + [71.255859, 38.306982], + [71.278516, 37.918408], + [71.582227, 37.910107], + [71.43291, 37.127539], + [71.665625, 36.696924], + [73.38291, 37.462256], + [73.720605, 37.41875], + [73.653516, 37.239355], + [74.349023, 37.41875], + [74.891309, 37.231641], + [75.11875, 37.385693], + [74.812305, 38.460303], + [73.80166, 38.606885], + [73.631641, 39.448877], + [72.22998, 39.20752], + [71.470312, 39.603662], + [70.799316, 39.394727], + [70.501172, 39.587354], + [69.297656, 39.524805], + [69.530273, 40.097314], + [69.966797, 40.202246], + [70.515137, 39.949902], + [70.958008, 40.238867], + [70.371582, 40.384131], + [70.751074, 40.721777], + [70.401953, 41.035107], + [69.712891, 40.656982], + [69.357227, 40.767383], + [69.274902, 40.198096], + [68.630664, 40.16709], + [68.97207, 40.089941], + [68.463281, 39.536719], + [67.426172, 39.465576], + [67.357617, 39.216699], + [68.13252, 38.927637], + [68.350293, 38.211035], + [67.758984, 37.172217], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '叙利亚', full_name: '阿拉伯叙利亚共和国', iso_a2: 'SY', iso_a3: 'SYR', iso_n3: '760' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35.892676, 35.916553], + [35.97627, 34.629199], + [36.383887, 34.65791], + [36.584961, 34.22124], + [35.869141, 33.431738], + [35.816125, 33.361879], + [35.787305, 32.734912], + [36.818359, 32.317285], + [38.773535, 33.372217], + [40.987012, 34.429053], + [41.295996, 36.38335], + [42.358984, 37.108594], + [42.202734, 37.297266], + [40.705664, 37.097705], + [39.356641, 36.681592], + [38.191699, 36.901562], + [37.436328, 36.643311], + [36.658594, 36.802539], + [36.636719, 36.233984], + [36.153613, 35.833887], + [35.892676, 35.916553], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '瑞士', full_name: '瑞士联邦', iso_a2: 'CH', iso_a3: 'CHE', iso_n3: '756' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [9.524023, 47.524219], + [8.572656, 47.775635], + [8.454004, 47.596191], + [7.615625, 47.592725], + [6.968359, 47.453223], + [5.97002, 46.214697], + [6.758105, 46.415771], + [7.021094, 45.925781], + [7.787891, 45.921826], + [8.422559, 46.446045], + [9.02373, 45.845703], + [9.260156, 46.475195], + [10.12832, 46.238232], + [10.452832, 46.864941], + [9.580273, 47.057373], + [9.487695, 47.062256], + [9.527539, 47.270752], + [9.524023, 47.524219], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '瑞典', full_name: '瑞典', iso_a2: 'SE', iso_a3: 'SWE', iso_n3: '752' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [19.076465, 57.835938], + [18.136523, 57.556641], + [18.146387, 56.920508], + [19.076465, 57.835938], + ], + ], + [ + [ + [11.388281, 59.036523], + [11.248242, 58.369141], + [12.883691, 56.617725], + [12.80166, 56.263916], + [12.471191, 56.290527], + [12.88584, 55.411377], + [14.17373, 55.396631], + [14.401953, 55.976758], + [15.82666, 56.124951], + [16.34873, 56.709277], + [16.923828, 58.492578], + [16.214258, 58.63667], + [18.285352, 59.109375], + [18.560254, 59.394482], + [17.964258, 59.359375], + [18.970508, 59.757227], + [17.955762, 60.589795], + [17.250977, 60.700781], + [17.410254, 62.508398], + [20.762695, 63.867822], + [21.519629, 64.463086], + [21.410352, 65.317432], + [22.400977, 65.862109], + [24.155469, 65.805273], + [23.638867, 67.954395], + [20.622168, 69.036865], + [20.116699, 69.020898], + [19.969824, 68.356396], + [18.303027, 68.55542], + [17.916699, 67.964893], + [17.324609, 68.103809], + [16.783594, 67.89502], + [15.483789, 66.305957], + [14.543262, 66.129346], + [14.479688, 65.301465], + [13.650293, 64.581543], + [14.141211, 64.173535], + [12.792773, 64], + [12.175195, 63.595947], + [12.155371, 61.720752], + [12.880762, 61.352295], + [12.294141, 61.002686], + [12.486133, 60.106787], + [11.680762, 59.592285], + [11.642773, 58.926074], + [11.388281, 59.036523], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '斯威士兰', full_name: '斯威士兰王国', iso_a2: 'SZ', iso_a3: 'SWZ', iso_n3: '748' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [31.948242, -25.957617], + [31.207324, -25.843359], + [30.794336, -26.764258], + [31.469531, -27.295508], + [31.958398, -27.305859], + [32.112891, -26.839453], + [31.948242, -25.957617], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '苏里南', full_name: '苏里南共和国', iso_a2: 'SR', iso_a3: 'SUR', iso_n3: '740' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-54.155957, 5.358984], + [-54.054199, 5.80791], + [-54.833691, 5.98833], + [-55.828174, 5.96167], + [-55.897607, 5.699316], + [-56.969824, 5.992871], + [-57.194775, 5.548438], + [-57.331006, 5.020166], + [-57.917041, 4.82041], + [-58.054297, 4.10166], + [-57.646729, 3.394531], + [-57.303662, 3.3771], + [-57.197363, 2.853271], + [-56.482812, 1.942139], + [-55.929639, 1.8875], + [-56.137695, 2.259033], + [-55.957471, 2.520459], + [-54.978662, 2.597656], + [-54.61626, 2.326758], + [-54.00957, 3.448535], + [-54.479688, 4.836523], + [-54.155957, 5.358984], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '南苏丹', full_name: '南苏丹共和国', iso_a2: 'SS', iso_a3: 'SSD', iso_n3: '728' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [33.976074, 4.220215], + [35.268359, 5.492285], + [34.710645, 6.660303], + [33.902441, 7.509521], + [32.998926, 7.899512], + [33.281055, 8.437256], + [34.072754, 8.545264], + [34.078125, 9.461523], + [33.871484, 9.506152], + [33.907031, 10.181445], + [33.130078, 10.745947], + [33.199316, 12.217285], + [32.721875, 12.223096], + [32.736719, 12.009668], + [32.072266, 12.006738], + [32.420801, 11.089111], + [31.224902, 9.799268], + [30.755371, 9.731201], + [30.003027, 10.277393], + [28.844531, 9.326074], + [28.048926, 9.328613], + [27.880859, 9.601611], + [26.658691, 9.484131], + [25.858203, 10.406494], + [25.211719, 10.329932], + [24.531934, 8.886914], + [24.147363, 8.665625], + [24.291406, 8.291406], + [24.85332, 8.137549], + [25.278906, 7.42749], + [26.361816, 6.635303], + [26.514258, 6.069238], + [27.143945, 5.722949], + [27.40332, 5.10918], + [28.19209, 4.350244], + [29.676855, 4.586914], + [30.838574, 3.490723], + [31.152344, 3.785596], + [31.798047, 3.802637], + [32.135938, 3.519727], + [32.997266, 3.880176], + [33.489355, 3.755078], + [33.976074, 4.220215], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '苏丹', full_name: '苏丹共和国', iso_a2: 'SD', iso_a3: 'SDN', iso_n3: '729' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [34.078125, 9.461523], + [34.343945, 10.658643], + [34.931445, 10.864795], + [35.112305, 11.816553], + [35.670215, 12.62373], + [36.125195, 12.757031], + [36.524316, 14.256836], + [36.426758, 15.13208], + [37.008984, 17.058887], + [37.411035, 17.061719], + [38.609473, 18.005078], + [37.471289, 18.820117], + [37.258594, 21.108545], + [36.871387, 21.996729], + [31.434473, 21.99585], + [31.400293, 22.202441], + [31.092676, 21.994873], + [24.980273, 21.99585], + [24.979492, 20.002588], + [23.980273, 19.995947], + [23.980273, 19.496631], + [23.970801, 15.721533], + [22.933887, 15.533105], + [22.932324, 15.162109], + [22.381543, 14.550488], + [22.538574, 14.161865], + [22.106445, 13.799805], + [22.228125, 13.32959], + [21.825293, 12.790527], + [22.352344, 12.660449], + [22.591113, 11.579883], + [22.922656, 11.344873], + [22.860059, 10.919678], + [23.646289, 9.8229], + [23.537305, 8.81582], + [24.147363, 8.665625], + [24.531934, 8.886914], + [25.211719, 10.329932], + [25.858203, 10.406494], + [26.658691, 9.484131], + [27.880859, 9.601611], + [28.048926, 9.328613], + [28.844531, 9.326074], + [30.003027, 10.277393], + [30.755371, 9.731201], + [31.224902, 9.799268], + [32.420801, 11.089111], + [32.072266, 12.006738], + [32.736719, 12.009668], + [32.721875, 12.223096], + [33.199316, 12.217285], + [33.130078, 10.745947], + [33.907031, 10.181445], + [33.871484, 9.506152], + [34.078125, 9.461523], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '斯里兰卡', + full_name: '斯里兰卡民主社会主义共和国', + iso_a2: 'LK', + iso_a3: 'LKA', + iso_n3: '144', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [79.982324, 9.812695], + [80.42832, 9.480957], + [80.086328, 9.577832], + [79.783496, 8.018457], + [79.712988, 8.182324], + [79.859375, 6.829297], + [80.095313, 6.153174], + [80.724121, 5.979053], + [81.637402, 6.425146], + [81.874121, 7.28833], + [80.711133, 9.366357], + [79.982324, 9.812695], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '西班牙', full_name: '西班牙王国', iso_a2: 'ES', iso_a3: 'ESP', iso_n3: '724' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [3.145313, 39.790088], + [3.158691, 39.970508], + [2.37002, 39.57207], + [3.072852, 39.30127], + [3.461816, 39.697754], + [3.145313, 39.790088], + ], + ], + [ + [ + [1.445215, 38.918701], + [1.564453, 39.121045], + [1.22334, 38.903857], + [1.445215, 38.918701], + ], + ], + [ + [ + [-1.794043, 43.407324], + [-4.523047, 43.415723], + [-8.004687, 43.694385], + [-9.178076, 43.174023], + [-8.777148, 41.941064], + [-8.266064, 42.137402], + [-8.15249, 41.811963], + [-6.618262, 41.942383], + [-6.2125, 41.532031], + [-6.928467, 41.009131], + [-6.975391, 39.798389], + [-7.535693, 39.661572], + [-6.997949, 39.056445], + [-7.343018, 38.457422], + [-6.957568, 38.187891], + [-7.443945, 37.728271], + [-7.406152, 37.179443], + [-6.86377, 37.278906], + [-6.216797, 36.913574], + [-6.384131, 36.637012], + [-5.625488, 36.025928], + [-4.366846, 36.718115], + [-2.111523, 36.77666], + [-1.640967, 37.386963], + [-0.721582, 37.631055], + [-0.520801, 38.317285], + [0.201563, 38.75918], + [-0.327002, 39.519873], + [0.891113, 40.722363], + [0.714648, 40.822852], + [3.004883, 41.767432], + [3.211426, 42.431152], + [1.706055, 42.50332], + [1.448828, 42.437451], + [1.42832, 42.595898], + [0.696875, 42.845117], + [0.631641, 42.6896], + [-0.586426, 42.798975], + [-1.46084, 43.051758], + [-1.794043, 43.407324], + ], + ], + [ + [ + [-16.334473, 28.379932], + [-16.123633, 28.575977], + [-16.905322, 28.3396], + [-16.658008, 28.007178], + [-16.334473, 28.379932], + ], + ], + [ + [ + [-14.196777, 28.169287], + [-13.928027, 28.253467], + [-13.857227, 28.738037], + [-14.491797, 28.100928], + [-14.196777, 28.169287], + ], + ], + [ + [ + [-15.400586, 28.147363], + [-15.682764, 28.154053], + [-15.710303, 27.784082], + [-15.436768, 27.810693], + [-15.400586, 28.147363], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '韩国', full_name: '大韩民国', iso_a2: 'KR', iso_a3: 'KOR', iso_n3: '410' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [126.633887, 37.781836], + [126.976855, 36.939404], + [126.487012, 37.007471], + [126.160547, 36.771924], + [126.487695, 36.693799], + [126.753027, 35.871973], + [126.291113, 35.15415], + [126.593359, 34.824365], + [126.264453, 34.673242], + [126.531445, 34.314258], + [127.24707, 34.755127], + [127.324609, 34.463281], + [127.714844, 34.954688], + [128.443945, 34.870361], + [128.510938, 35.100977], + [129.076758, 35.122705], + [129.419141, 35.497852], + [129.418262, 37.059033], + [128.374609, 38.623438], + [128.038965, 38.308545], + [127.090332, 38.283887], + [126.633887, 37.781836], + ], + ], + [ + [ + [128.741016, 34.798535], + [128.667969, 35.008789], + [128.489258, 34.865283], + [128.741016, 34.798535], + ], + ], + [ + [ + [126.326953, 33.223633], + [126.901172, 33.515137], + [126.337695, 33.4604], + [126.326953, 33.223633], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '南非', full_name: '南非共和国', iso_a2: 'ZA', iso_a3: 'ZAF', iso_n3: '710' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [29.364844, -22.193945], + [28.210156, -22.693652], + [27.085547, -23.57793], + [26.835059, -24.24082], + [25.912109, -24.747461], + [25.443652, -25.714453], + [24.748145, -25.817383], + [23.05752, -25.312305], + [22.597656, -26.132715], + [21.646289, -26.854199], + [20.685059, -26.822461], + [20.793164, -25.915625], + [19.980469, -24.776758], + [19.980469, -28.45127], + [19.161719, -28.93877], + [18.102734, -28.87168], + [17.447949, -28.698145], + [17.05625, -28.031055], + [16.447559, -28.617578], + [18.21084, -31.74248], + [18.325293, -32.50498], + [17.851074, -32.827441], + [18.433008, -33.717285], + [18.410352, -34.295605], + [18.752148, -34.082617], + [18.831348, -34.364062], + [20.020605, -34.785742], + [20.529883, -34.463086], + [21.788965, -34.372656], + [22.553809, -34.010059], + [25.574219, -34.035352], + [25.805859, -33.737109], + [26.613672, -33.707422], + [27.860645, -33.053906], + [29.971191, -31.32207], + [31.335156, -29.378125], + [32.285742, -28.621484], + [32.886133, -26.849316], + [32.112891, -26.839453], + [31.958398, -27.305859], + [31.469531, -27.295508], + [30.794336, -26.764258], + [31.207324, -25.843359], + [31.948242, -25.957617], + [31.98584, -24.460645], + [31.287891, -22.402051], + [29.364844, -22.193945], + ], + [ + [28.736914, -30.101953], + [28.39209, -30.147559], + [28.056836, -30.631055], + [27.753125, -30.6], + [27.056934, -29.625586], + [27.735547, -28.940039], + [28.625781, -28.581738], + [29.390723, -29.269727], + [29.098047, -29.919043], + [28.736914, -30.101953], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '索马里', full_name: '索马里联邦共和国', iso_a2: 'SO', iso_a3: 'SOM', iso_n3: '706' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [41.532715, -1.695312], + [43.717578, 0.857861], + [46.878809, 3.285645], + [47.975293, 4.497021], + [49.049316, 6.173633], + [49.852051, 7.962549], + [50.825, 9.428174], + [50.930078, 10.335547], + [51.390234, 10.422607], + [51.031836, 10.444775], + [51.254883, 11.830713], + [50.792285, 11.983691], + [50.110059, 11.529297], + [48.938574, 11.258447], + [48.938086, 9.451758], + [47.978223, 7.99707], + [44.940527, 4.912012], + [43.583496, 4.85498], + [41.883984, 3.977734], + [40.964453, 2.814648], + [40.978711, -0.870313], + [41.532715, -1.695312], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '索马里兰', full_name: '索马里兰', iso_a2: '-99', iso_a3: '-99', iso_n3: '-99' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [48.938574, 11.258447], + [47.40498, 11.174023], + [46.565039, 10.745996], + [45.816699, 10.835889], + [44.386523, 10.430225], + [43.245996, 11.499805], + [42.922754, 10.999316], + [42.656445, 10.6], + [42.841602, 10.203076], + [43.983789, 9.008838], + [46.978223, 7.99707], + [47.978223, 7.99707], + [48.938086, 9.451758], + [48.938574, 11.258447], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '所罗门群岛', full_name: '所罗门群岛', iso_a2: 'SB', iso_a3: 'SLB', iso_n3: '090' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [157.763477, -8.242188], + [157.490625, -7.965723], + [157.217578, -8.262793], + [157.558008, -8.269922], + [157.819336, -8.612012], + [157.763477, -8.242188], + ], + ], + [ + [ + [157.388965, -8.713477], + [157.379492, -8.420898], + [157.212305, -8.565039], + [157.388965, -8.713477], + ], + ], + [ + [ + [156.687891, -7.923047], + [156.809082, -7.722852], + [156.560938, -7.574023], + [156.687891, -7.923047], + ], + ], + [ + [ + [159.750391, -9.272656], + [159.612305, -9.470703], + [159.802734, -9.763477], + [160.818945, -9.862793], + [160.35459, -9.421582], + [159.750391, -9.272656], + ], + ], + [ + [ + [159.879102, -8.534277], + [159.431445, -8.029004], + [158.457422, -7.544727], + [159.879102, -8.534277], + ], + ], + [ + [ + [157.486719, -7.330371], + [156.452539, -6.638281], + [157.101562, -7.323633], + [157.486719, -7.330371], + ], + ], + [ + [ + [160.749414, -8.313965], + [160.59043, -8.372754], + [160.77207, -8.963867], + [161.367383, -9.61123], + [160.749414, -8.313965], + ], + ], + [ + [ + [161.715332, -10.387305], + [161.304785, -10.204395], + [161.786816, -10.716895], + [162.37334, -10.823242], + [162.105371, -10.453809], + [161.715332, -10.387305], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '斯洛伐克', full_name: '斯洛伐克共和国', iso_a2: 'SK', iso_a3: 'SVK', iso_n3: '703' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [22.538672, 49.072705], + [21.639648, 49.411963], + [20.362988, 49.385254], + [20.057617, 49.181299], + [19.441602, 49.597705], + [19.149414, 49.4], + [18.832227, 49.510791], + [16.953125, 48.598828], + [17.147363, 48.005957], + [17.761914, 47.770166], + [18.724219, 47.787158], + [18.791895, 48.000293], + [19.950391, 48.146631], + [20.490039, 48.526904], + [22.131836, 48.405322], + [22.538672, 49.072705], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '斯洛文尼亚', full_name: '斯洛文尼亚共和国', iso_a2: 'SI', iso_a3: 'SVN', iso_n3: '705' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [16.516211, 46.499902], + [16.093066, 46.863281], + [14.549805, 46.399707], + [13.7, 46.520264], + [13.378223, 46.261621], + [13.719824, 45.587598], + [13.57793, 45.516895], + [14.568848, 45.657227], + [15.339453, 45.467041], + [15.635938, 46.200732], + [16.516211, 46.499902], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '新加坡', full_name: '新加坡共和国', iso_a2: 'SG', iso_a3: 'SGP', iso_n3: '702' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [103.969727, 1.331445], + [103.817969, 1.44707], + [103.650195, 1.325537], + [103.969727, 1.331445], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塞拉利昂', full_name: '塞拉利昂共和国', iso_a2: 'SL', iso_a3: 'SLE', iso_n3: '694' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-10.283203, 8.485156], + [-10.712109, 8.335254], + [-10.500537, 8.687549], + [-10.690527, 9.314258], + [-11.273633, 9.996533], + [-12.427979, 9.898145], + [-13.292676, 9.049219], + [-13.059473, 8.881152], + [-13.181836, 8.576904], + [-12.894092, 8.629785], + [-13.272754, 8.429736], + [-12.850879, 7.818701], + [-12.480273, 7.753271], + [-12.485645, 7.386279], + [-11.50752, 6.906543], + [-10.647461, 7.759375], + [-10.283203, 8.485156], + ], + ], + [ + [ + [-12.526074, 7.436328], + [-12.615234, 7.637207], + [-12.951611, 7.57085], + [-12.526074, 7.436328], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塞舌尔', full_name: '塞舌尔共和国', iso_a2: 'SC', iso_a3: 'SYC', iso_n3: '690' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [55.540332, -4.693066], + [55.383398, -4.609277], + [55.542969, -4.785547], + [55.540332, -4.693066], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塞尔维亚', full_name: '塞尔维亚共和国', iso_a2: 'RS', iso_a3: 'SRB', iso_n3: '688' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [22.705078, 44.237793], + [22.64209, 44.650977], + [22.093066, 44.541943], + [21.360059, 44.82666], + [21.490234, 45.1479], + [20.241797, 46.108594], + [18.905371, 45.931738], + [19.004688, 45.399512], + [19.4, 45.2125], + [19.007129, 44.869189], + [19.348633, 44.880908], + [19.118457, 44.359961], + [19.583789, 44.043457], + [19.24502, 43.965039], + [19.495117, 43.642871], + [19.194336, 43.533301], + [20.344336, 42.82793], + [20.800586, 43.261084], + [21.75293, 42.669824], + [21.5625, 42.24751], + [22.344043, 42.313965], + [22.466797, 42.84248], + [22.967969, 43.142041], + [22.369629, 43.781299], + [22.705078, 44.237793], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塞内加尔', full_name: '塞内加尔共和国', iso_a2: 'SN', iso_a3: 'SEN', iso_n3: '686' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-12.280615, 14.809033], + [-13.409668, 16.05918], + [-14.53374, 16.655957], + [-16.239014, 16.531299], + [-16.535254, 15.838379], + [-17.147168, 14.922021], + [-17.535645, 14.755127], + [-16.618115, 14.040527], + [-16.766943, 13.904932], + [-16.562305, 13.587305], + [-15.509668, 13.58623], + [-15.10835, 13.812109], + [-13.826709, 13.407812], + [-14.246777, 13.23584], + [-15.151123, 13.556494], + [-15.834277, 13.156445], + [-16.76333, 13.06416], + [-16.743896, 12.585449], + [-16.442871, 12.609473], + [-16.760303, 12.525781], + [-16.711816, 12.354834], + [-15.196094, 12.679932], + [-13.729248, 12.673926], + [-12.399072, 12.340088], + [-11.389404, 12.404395], + [-11.390381, 12.941992], + [-12.054199, 13.633057], + [-12.280615, 14.809033], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '沙特阿拉伯', full_name: '沙特阿拉伯王国', iso_a2: 'SA', iso_a3: 'SAU', iso_n3: '682' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [41.987695, 16.715625], + [41.860449, 17.002539], + [41.801563, 16.77876], + [42.157812, 16.570703], + [41.987695, 16.715625], + ], + ], + [ + [ + [51.977637, 18.996143], + [54.977344, 19.995947], + [55.641016, 22.001855], + [55.18584, 22.704102], + [52.555078, 22.932812], + [51.568359, 24.286182], + [51.267969, 24.607227], + [50.804395, 24.789258], + [50.081055, 25.961377], + [50.149805, 26.662646], + [48.797168, 27.724316], + [48.44248, 28.54292], + [47.671289, 28.533154], + [47.433203, 28.989551], + [46.531445, 29.09624], + [44.69082, 29.202344], + [42.074414, 31.080371], + [40.369336, 31.938965], + [39.14541, 32.124512], + [36.958594, 31.491504], + [37.980078, 30.5], + [37.469238, 29.995068], + [36.755273, 29.866016], + [36.068457, 29.200537], + [34.950781, 29.353516], + [34.625, 28.064502], + [35.180469, 28.034863], + [37.148828, 25.291113], + [37.543066, 24.29165], + [38.46416, 23.711865], + [39.062012, 22.592188], + [38.987891, 21.881738], + [39.276074, 20.973975], + [39.72832, 20.390332], + [40.75918, 19.755469], + [41.229492, 18.678418], + [42.293945, 17.434961], + [42.799316, 16.371777], + [43.165039, 16.689404], + [43.190918, 17.359375], + [43.417969, 17.51626], + [43.916992, 17.324707], + [46.727637, 17.265576], + [47.143555, 16.94668], + [48.172168, 18.156934], + [49.041992, 18.581787], + [51.977637, 18.996143], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '圣多美和普林西比', + full_name: '圣多美和普林西比民主共和国', + iso_a2: 'ST', + iso_a3: 'STP', + iso_n3: '678', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [6.659961, 0.120654], + [6.686914, 0.404395], + [6.468164, 0.227344], + [6.659961, 0.120654], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圣马力诺', full_name: '圣马力诺共和国', iso_a2: 'SM', iso_a3: 'SMR', iso_n3: '674' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [12.485254, 43.901416], + [12.503711, 43.989746], + [12.396875, 43.93457], + [12.485254, 43.901416], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '萨摩亚', full_name: '萨摩亚独立国', iso_a2: 'WS', iso_a3: 'WSM', iso_n3: '882' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-172.333496, -13.465234], + [-172.778516, -13.516797], + [-172.224951, -13.804297], + [-172.333496, -13.465234], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '圣文森特和格林纳丁斯', + full_name: '圣文森特和格林纳丁斯', + iso_a2: 'VC', + iso_a3: 'VCT', + iso_n3: '670', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-61.174512, 13.158105], + [-61.138965, 13.35874], + [-61.268457, 13.287695], + [-61.174512, 13.158105], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圣卢西亚', full_name: '圣卢西亚', iso_a2: 'LC', iso_a3: 'LCA', iso_n3: '662' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-60.895215, 13.821973], + [-60.908105, 14.093359], + [-61.073145, 13.865576], + [-60.895215, 13.821973], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '圣基茨和尼维斯', + full_name: '圣基茨和尼维斯联邦', + iso_a2: 'KN', + iso_a3: 'KNA', + iso_n3: '659', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-62.630664, 17.23999], + [-62.827051, 17.386426], + [-62.838916, 17.339258], + [-62.630664, 17.23999], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '卢旺达', full_name: '卢旺达共和国', iso_a2: 'RW', iso_a3: 'RWA', iso_n3: '646' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [29.576953, -1.387891], + [28.876367, -2.400293], + [29.014355, -2.720215], + [29.698047, -2.794727], + [29.930176, -2.339551], + [30.553613, -2.400098], + [30.876562, -2.143359], + [30.509961, -1.067285], + [29.930078, -1.469922], + [29.576953, -1.387891], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '俄罗斯', full_name: '俄罗斯联邦', iso_a2: 'RU', iso_a3: 'RUS', iso_n3: '643' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-179.798535, 68.94043], + [-180, 68.738672], + [-180, 68.493896], + [-180, 68.249121], + [-180, 68.004395], + [-180, 67.759619], + [-180, 67.514844], + [-180, 67.270117], + [-180, 67.025342], + [-180, 66.780566], + [-180, 66.53584], + [-180, 66.291064], + [-180, 66.046289], + [-180, 65.801562], + [-180, 65.556787], + [-180, 65.311963], + [-180, 65.067236], + [-179.3521, 65.516748], + [-179.683301, 66.184131], + [-178.526562, 66.401562], + [-178.939062, 66.032764], + [-178.4125, 65.495557], + [-176.093262, 65.471045], + [-175.395117, 64.802393], + [-173.729736, 64.364502], + [-173.32749, 64.539551], + [-173.275488, 64.289648], + [-172.903174, 64.526074], + [-172.401465, 64.413916], + [-173.085791, 64.817334], + [-172.213184, 65.048145], + [-172.783301, 65.681055], + [-171.105859, 65.511035], + [-171.421533, 65.810352], + [-170.666309, 65.621533], + [-169.777881, 66.143115], + [-170.604443, 66.248926], + [-171.795557, 66.931738], + [-173.00752, 67.064893], + [-172.520117, 66.95249], + [-173.258936, 66.840088], + [-173.679688, 67.144775], + [-174.550098, 67.090625], + [-173.773975, 66.434668], + [-174.065039, 66.22959], + [-174.084766, 66.473096], + [-174.394092, 66.344238], + [-174.924902, 66.623145], + [-175.345215, 67.678076], + [-178.373047, 68.565674], + [-178.055811, 68.264893], + [-179.798535, 68.94043], + ], + ], + [ + [ + [130.687305, 42.302539], + [130.709375, 42.656396], + [131.158301, 42.626025], + [131.938965, 43.301953], + [131.866602, 43.095166], + [132.30957, 43.313525], + [132.303809, 42.883301], + [133.159961, 42.696973], + [135.131055, 43.525732], + [137.685449, 45.818359], + [140.170605, 48.523682], + [140.520898, 50.800195], + [141.485254, 52.178516], + [140.839648, 53.087891], + [141.18125, 53.015283], + [141.37373, 53.292773], + [139.707422, 54.277148], + [138.657227, 54.29834], + [138.450684, 53.537012], + [138.52793, 53.959863], + [137.950488, 53.603564], + [137.253711, 53.546143], + [137.834766, 53.946729], + [137.339258, 54.100537], + [137.666016, 54.283301], + [137.141602, 54.182227], + [137.155371, 53.82168], + [136.718848, 53.804102], + [136.797266, 54.620996], + [135.851562, 54.583936], + [135.2625, 54.943311], + [137.691504, 56.139355], + [142.580273, 59.240137], + [145.55459, 59.413525], + [146.049512, 59.170557], + [146.537207, 59.456982], + [148.72666, 59.25791], + [148.79707, 59.532324], + [149.642578, 59.77041], + [152.260645, 59.223584], + [151.121094, 59.08252], + [151.326758, 58.875098], + [152.817871, 58.92627], + [153.361133, 59.214795], + [155.160449, 59.190137], + [154.971289, 59.449609], + [154.149805, 59.528516], + [154.293066, 59.83335], + [157.08418, 61.675684], + [160.309375, 61.894385], + [159.79043, 60.956641], + [160.378906, 61.025488], + [160.173633, 60.638428], + [162.392578, 61.662109], + [163.085254, 61.570557], + [163.331738, 62.550928], + [164.418359, 62.704639], + [165.417383, 62.44707], + [164.207227, 62.292236], + [163.709961, 60.916797], + [162.068164, 60.466406], + [158.275195, 58.008984], + [156.829883, 57.779639], + [156.848828, 57.290186], + [155.98252, 56.695215], + [155.620312, 54.864551], + [156.847461, 51.006592], + [158.103516, 51.809619], + [158.47207, 53.032373], + [160.025098, 53.12959], + [160.074414, 54.18916], + [162.105566, 54.752148], + [161.723926, 55.496143], + [162.084961, 56.089648], + [162.671484, 56.490088], + [163.038379, 56.521875], + [162.628125, 56.232275], + [162.840332, 56.065625], + [163.335547, 56.23252], + [163.256543, 56.688037], + [162.791113, 56.875391], + [163.225781, 57.790381], + [162.654297, 57.948242], + [162.391406, 57.717236], + [161.960059, 58.076904], + [163.743848, 60.028027], + [164.953711, 59.843604], + [166.352148, 60.484814], + [166.273047, 59.85625], + [167.226758, 60.406299], + [169.226758, 60.595947], + [170.350977, 59.965527], + [170.608203, 60.434912], + [172.856543, 61.469189], + [177.159473, 62.560986], + [177.023535, 62.777246], + [179.120703, 62.320361], + [179.570508, 62.6875], + [178.44043, 63.605566], + [178.381445, 64.260889], + [177.6875, 64.304736], + [177.427441, 64.763379], + [176.140918, 64.58584], + [176.056543, 64.904736], + [174.548828, 64.683887], + [176.061133, 64.960889], + [177.06875, 64.78667], + [177.037305, 64.999658], + [176.341016, 65.047314], + [176.880859, 65.081934], + [178.519531, 64.602979], + [180, 65.067236], + [180, 68.983447], + [179.272656, 69.259668], + [175.921484, 69.895312], + [173.277441, 69.823828], + [170.486816, 70.107568], + [170.160938, 69.626562], + [170.99541, 69.045312], + [169.609863, 68.786035], + [167.856836, 69.728223], + [166.820312, 69.499561], + [163.201367, 69.714746], + [161.536914, 69.379541], + [161.565625, 68.905176], + [160.856055, 68.53833], + [161.309863, 68.982275], + [160.910742, 69.606348], + [159.729395, 69.870215], + [160.006445, 70.309668], + [159.350684, 70.790723], + [156.68457, 71.09375], + [152.508789, 70.834473], + [151.582422, 71.286963], + [150.097656, 71.226562], + [150.599805, 71.520117], + [148.968164, 71.690479], + [150.016895, 71.895654], + [149.501563, 72.164307], + [147.261816, 72.327881], + [146.073242, 71.80835], + [145.188574, 71.695801], + [145.758594, 72.225879], + [146.113281, 71.944971], + [146.831836, 72.29541], + [144.294922, 72.192627], + [146.25293, 72.442236], + [140.808203, 72.890967], + [141.079297, 72.586914], + [139.14082, 72.329736], + [140.187695, 72.191309], + [139.359277, 71.951367], + [139.98418, 71.491504], + [138.23418, 71.596338], + [137.939648, 71.133398], + [136.090332, 71.61958], + [133.688867, 71.434229], + [132.653906, 71.925977], + [131.021582, 70.746094], + [129.761914, 71.119531], + [128.843262, 71.663477], + [129.210254, 71.916943], + [128.911426, 71.755322], + [127.841406, 72.308252], + [129.410645, 72.166309], + [128.418262, 72.535156], + [129.250391, 72.705176], + [128.599023, 72.895166], + [129.100586, 73.112354], + [127.740332, 73.481543], + [126.552539, 73.334912], + [124.541211, 73.75127], + [123.491113, 73.666357], + [123.622266, 73.193262], + [122.260156, 72.880566], + [119.750391, 72.979102], + [118.430273, 73.246533], + [118.870898, 73.537891], + [115.337695, 73.702588], + [113.510352, 73.50498], + [113.664551, 72.634521], + [113.032813, 73.913867], + [112.147266, 73.708936], + [111.550586, 74.028516], + [110.261426, 74.017432], + [109.706738, 73.74375], + [110.868164, 73.730713], + [109.855273, 73.472461], + [105.143945, 72.777051], + [112.924902, 75.015039], + [113.726172, 75.450635], + [112.453027, 75.830176], + [113.567578, 75.568408], + [113.272656, 76.25166], + [112.65625, 76.053564], + [111.39248, 76.68667], + [106.413574, 76.512256], + [107.429785, 76.926562], + [104.202441, 77.101807], + [106.05957, 77.390527], + [104.014551, 77.73042], + [100.989941, 76.990479], + [101.597754, 76.439209], + [98.805664, 76.480664], + [99.540723, 75.798584], + [98.662012, 76.242676], + [96.49707, 75.891211], + [95.65332, 75.892188], + [95.578711, 76.137305], + [93.259277, 76.098779], + [92.89043, 75.909961], + [94.075195, 75.912891], + [87.005957, 75.169824], + [87.041797, 74.778857], + [85.791016, 74.645117], + [87.229688, 74.363867], + [86.001367, 74.316016], + [87.571191, 73.810742], + [85.938965, 73.456494], + [86.677051, 73.106787], + [85.792578, 73.43833], + [86.892969, 73.887109], + [80.583203, 73.568457], + [80.827051, 72.488281], + [83.534375, 71.683936], + [83.15127, 71.103613], + [83.735938, 70.546484], + [83.080762, 70.093018], + [82.869141, 70.954834], + [82.221191, 70.395703], + [82.163184, 70.598145], + [83.233594, 71.668164], + [81.661621, 71.715967], + [79.42207, 72.380762], + [77.471582, 72.192139], + [78.232422, 71.952295], + [77.777539, 71.836426], + [76.871387, 72.033008], + [76.032422, 71.9104], + [76.433398, 71.55249], + [78.942187, 70.933789], + [75.332031, 71.341748], + [75.741406, 72.29624], + [75.152441, 72.852734], + [74.992188, 72.144824], + [73.08623, 71.444922], + [74.343359, 70.578711], + [73.578125, 69.802979], + [73.836035, 69.143213], + [76.000977, 69.235059], + [77.650684, 68.903027], + [77.588281, 67.751904], + [78.922461, 67.589111], + [77.174414, 67.778516], + [77.238477, 68.46958], + [76.10752, 68.975732], + [74.57959, 68.751221], + [74.769531, 67.766357], + [72.321582, 66.332129], + [70.339453, 66.342383], + [69.194336, 66.578662], + [69.217773, 66.828613], + [70.690723, 66.745312], + [70.724902, 66.519434], + [71.539551, 66.683105], + [71.365234, 66.961523], + [73.066797, 67.766943], + [73.591699, 68.481885], + [72.576758, 68.968701], + [72.704492, 70.963232], + [71.867285, 71.457373], + [72.812109, 72.691406], + [69.611816, 72.981934], + [68.269238, 71.682812], + [66.639648, 71.081396], + [67.284766, 70.738721], + [66.89668, 69.553809], + [67.624121, 69.584424], + [68.542773, 68.96709], + [69.140527, 68.950635], + [68.371191, 68.314258], + [64.19043, 69.534668], + [60.909082, 69.847119], + [60.170605, 69.590918], + [60.933594, 68.986768], + [59.725684, 68.351611], + [59.099023, 68.444336], + [59.057324, 69.006055], + [57.126855, 68.554004], + [55.418066, 68.567822], + [54.861328, 68.201855], + [53.260547, 68.26748], + [53.930859, 68.435547], + [53.797656, 68.907471], + [54.491211, 68.992334], + [53.801953, 68.995898], + [52.344043, 68.608154], + [52.39668, 68.351709], + [51.994727, 68.53877], + [48.754297, 67.895947], + [48.833203, 67.681494], + [47.874707, 67.58418], + [47.655859, 66.975928], + [46.492383, 66.800195], + [44.902148, 67.413135], + [45.528711, 67.757568], + [46.69043, 67.848828], + [45.891992, 68.479688], + [43.333203, 68.673389], + [44.204688, 68.25376], + [44.104395, 66.008594], + [42.210547, 66.519678], + [39.816504, 65.597949], + [40.444922, 64.778711], + [39.758008, 64.577051], + [36.882812, 65.172363], + [36.624219, 64.750537], + [38.062207, 64.091016], + [37.442188, 63.813379], + [35.035352, 64.440234], + [34.406445, 65.395752], + [34.691797, 65.951855], + [31.895313, 67.161426], + [34.482617, 66.550342], + [38.653906, 66.069043], + [40.10332, 66.299951], + [41.188965, 66.826172], + [40.966406, 67.713477], + [35.85791, 69.191748], + [33.684375, 69.310254], + [33.141211, 69.068701], + [33.454297, 69.428174], + [32.377734, 69.479102], + [32.176758, 69.674023], + [33.007812, 69.722119], + [31.98457, 69.953662], + [31.546973, 69.696924], + [30.869727, 69.783447], + [30.860742, 69.538428], + [30.180176, 69.63584], + [28.96582, 69.021973], + [28.414062, 68.90415], + [28.685156, 68.189795], + [29.988086, 67.668262], + [29.066211, 66.891748], + [30.102734, 65.72627], + [29.604199, 64.968408], + [30.51377, 64.2], + [29.991504, 63.735156], + [31.533984, 62.8854], + [27.797656, 60.536133], + [28.512793, 60.677295], + [29.069141, 60.191455], + [30.172656, 59.957129], + [28.058008, 59.781543], + [28.0125, 59.484277], + [27.43418, 58.787256], + [27.778516, 57.870703], + [27.351953, 57.528125], + [27.828613, 57.293311], + [27.639453, 56.845654], + [28.147949, 56.14292], + [29.375, 55.938721], + [29.482227, 55.68457], + [30.233594, 55.845215], + [30.906836, 55.57002], + [30.798828, 54.783252], + [31.754199, 53.810449], + [32.706445, 53.419434], + [32.141992, 53.091162], + [31.417871, 53.196045], + [31.258789, 53.016699], + [31.763379, 52.101074], + [33.735254, 52.344775], + [34.397852, 51.78042], + [34.213867, 51.255371], + [35.311914, 51.043896], + [35.591113, 50.36875], + [36.619434, 50.209229], + [37.422852, 50.411475], + [38.046875, 49.92002], + [38.258594, 50.052344], + [40.080664, 49.576855], + [40.108789, 49.251562], + [39.686523, 49.00791], + [40.003613, 48.82207], + [39.644727, 48.591211], + [39.95791, 48.268896], + [39.778711, 47.887549], + [38.368848, 47.609961], + [38.214355, 47.091455], + [39.02373, 47.272217], + [39.293457, 47.105762], + [38.500977, 46.663672], + [37.766504, 46.636133], + [38.492285, 46.090527], + [37.933105, 46.001709], + [37.647168, 45.377197], + [36.865918, 45.427051], + [36.627637, 45.151318], + [38.717285, 44.288086], + [39.97832, 43.419824], + [40.648047, 43.533887], + [41.580566, 43.219238], + [42.760645, 43.16958], + [43.825977, 42.571533], + [44.870996, 42.756396], + [45.705273, 42.498096], + [45.638574, 42.205078], + [46.429883, 41.890967], + [47.791016, 41.199268], + [48.572852, 41.844482], + [47.463184, 43.035059], + [47.646484, 43.884619], + [47.462793, 43.555029], + [47.307031, 44.103125], + [46.707227, 44.50332], + [47.463281, 45.679688], + [48.72959, 45.896826], + [49.232227, 46.337158], + [48.541211, 46.605615], + [48.959375, 46.774609], + [48.166992, 47.708789], + [47.292383, 47.740918], + [46.660938, 48.412256], + [47.031348, 49.150293], + [46.889551, 49.696973], + [47.429199, 50.357959], + [48.334961, 49.858252], + [48.758984, 49.92832], + [48.625098, 50.612695], + [50.793945, 51.729199], + [51.344531, 51.475342], + [52.219141, 51.709375], + [53.338086, 51.482373], + [54.555273, 50.535791], + [54.641602, 51.011572], + [55.68623, 50.582861], + [56.491406, 51.019531], + [57.442188, 50.888867], + [57.838867, 51.09165], + [59.523047, 50.492871], + [60.058594, 50.850293], + [60.942285, 50.695508], + [61.389453, 50.861035], + [61.554688, 51.324609], + [60.030273, 51.933252], + [60.994531, 52.336865], + [60.774414, 52.675781], + [61.047461, 52.972461], + [62.082715, 53.00542], + [61.199219, 53.287158], + [61.534961, 53.523291], + [60.979492, 53.621729], + [61.231055, 54.019482], + [65.088379, 54.340186], + [65.476953, 54.623291], + [68.155859, 54.976709], + [68.977246, 55.3896], + [70.182422, 55.162451], + [70.738086, 55.305176], + [71.093164, 54.212207], + [72.186035, 54.325635], + [72.446777, 53.941846], + [72.622266, 54.134326], + [73.712402, 54.042383], + [73.406934, 53.447559], + [73.858984, 53.619727], + [74.351562, 53.487646], + [76.837305, 54.442383], + [76.484766, 54.022559], + [77.859961, 53.269189], + [79.98623, 50.774561], + [80.735254, 51.293408], + [81.465918, 50.739844], + [82.493945, 50.727588], + [83.357324, 50.99458], + [84.323242, 50.23916], + [84.989453, 50.061426], + [85.232617, 49.61582], + [86.180859, 49.499316], + [86.675488, 49.777295], + [87.322852, 49.085791], + [87.814258, 49.162305], + [88.192578, 49.451709], + [89.395605, 49.611523], + [90.053711, 50.09375], + [92.354785, 50.86416], + [94.251074, 50.556396], + [94.614746, 50.02373], + [97.359766, 49.741455], + [98.250293, 50.302441], + [97.835742, 51.05166], + [98.893164, 52.117285], + [102.111523, 51.353467], + [102.288379, 50.585107], + [103.304395, 50.200293], + [105.383594, 50.47373], + [106.711133, 50.312598], + [107.233301, 49.989404], + [107.916602, 49.947803], + [108.613672, 49.322803], + [110.709766, 49.142969], + [112.806445, 49.523584], + [114.29707, 50.274414], + [115.429199, 49.896484], + [116.216797, 50.009277], + [116.683301, 49.823779], + [117.873438, 49.513477], + [119.259863, 50.066406], + [119.163672, 50.406006], + [120.749805, 52.096533], + [120.656152, 52.56665], + [120.094531, 52.787207], + [120.985449, 53.28457], + [123.607813, 53.546533], + [125.649023, 53.042285], + [127.590234, 50.208984], + [127.550781, 49.801807], + [129.498145, 49.388818], + [130.553125, 48.861182], + [130.961914, 47.709326], + [132.47627, 47.71499], + [133.144043, 48.105664], + [134.293359, 48.373438], + [135.083406, 48.436324], + [134.665234, 48.253906], + [134.752344, 47.71543], + [134.167676, 47.302197], + [133.113477, 45.130713], + [131.851855, 45.326855], + [130.981641, 44.844336], + [131.257324, 43.378076], + [131.068555, 42.902246], + [130.424805, 42.727051], + [130.526953, 42.5354], + [130.687305, 42.302539], + ], + ], + [ + [ + [47.441992, 80.853662], + [44.90498, 80.611279], + [46.141406, 80.446729], + [47.705273, 80.765186], + [48.683594, 80.633252], + [47.441992, 80.853662], + ], + ], + [ + [ + [50.278125, 80.927246], + [49.087793, 80.515771], + [46.644434, 80.300342], + [47.737305, 80.081689], + [51.703613, 80.687646], + [50.278125, 80.927246], + ], + ], + [ + [ + [67.765332, 76.237598], + [68.941699, 76.707666], + [67.651855, 77.011572], + [64.463477, 76.378174], + [61.20166, 76.282031], + [58.88125, 75.854785], + [57.606836, 75.34126], + [55.810059, 75.124902], + [56.49873, 74.95708], + [55.582227, 74.627686], + [56.137109, 74.496094], + [53.762891, 73.766162], + [54.299902, 73.350977], + [56.963867, 73.366553], + [58.534668, 74.498926], + [59.674023, 74.610156], + [61.355957, 75.314844], + [67.765332, 76.237598], + ], + ], + [ + [ + [55.319824, 73.308301], + [53.251172, 73.182959], + [51.511328, 71.648096], + [53.411621, 71.530127], + [54.155664, 71.125488], + [53.383594, 70.873535], + [57.145898, 70.589111], + [57.625391, 70.728809], + [55.297852, 71.935352], + [56.42959, 73.201172], + [55.319824, 73.308301], + ], + ], + [ + [ + [96.526563, 81.075586], + [95.15957, 81.270996], + [92.710352, 80.872168], + [91.523828, 80.358545], + [93.654688, 80.009619], + [97.298438, 80.272754], + [97.869922, 80.763281], + [96.526563, 81.075586], + ], + ], + [ + [ + [97.674512, 80.158252], + [94.987305, 80.096826], + [93.070801, 79.495312], + [95.02041, 79.052686], + [98.411133, 78.787793], + [99.929297, 78.961426], + [99.041797, 79.293018], + [100.06123, 79.7771], + [98.596484, 80.052197], + [97.65166, 79.760645], + [97.674512, 80.158252], + ], + ], + [ + [ + [102.884766, 79.253955], + [101.590625, 79.350439], + [99.500293, 77.976074], + [105.312598, 78.499902], + [103.800781, 79.149268], + [102.412305, 78.835449], + [102.884766, 79.253955], + ], + ], + [ + [ + [140.04873, 75.828955], + [138.919531, 76.196729], + [138.207617, 76.114941], + [136.947656, 75.325537], + [139.099121, 74.656543], + [139.68125, 74.964062], + [142.472754, 74.82041], + [143.12793, 74.970312], + [142.30791, 75.691699], + [144.019727, 75.044678], + [145.359961, 75.530469], + [141.485449, 76.137158], + [140.815918, 75.630713], + [140.04873, 75.828955], + ], + ], + [ + [ + [146.795215, 75.370752], + [146.5375, 75.581787], + [146.148535, 75.198291], + [148.296875, 74.800439], + [150.646289, 74.94458], + [150.822363, 75.156543], + [146.795215, 75.370752], + ], + ], + [ + [ + [178.861523, 70.826416], + [180, 70.993018], + [180, 71.537744], + [178.683887, 71.105664], + [178.861523, 70.826416], + ], + ], + [ + [ + [142.761035, 54.393945], + [142.334961, 54.280713], + [142.705957, 53.895703], + [142.526172, 53.447461], + [141.823535, 53.339502], + [141.66084, 52.272949], + [142.206738, 51.222559], + [141.866309, 48.750098], + [142.181738, 48.013379], + [141.830371, 46.451074], + [142.077148, 45.917041], + [142.578027, 46.700781], + [143.282324, 46.558984], + [143.431641, 46.028662], + [143.580664, 46.360693], + [142.556934, 47.737891], + [143.10498, 49.198828], + [144.04873, 49.24917], + [144.71377, 48.640283], + [143.299512, 51.632373], + [143.324707, 52.963086], + [142.761035, 54.393945], + ], + ], + [ + [ + [-178.876465, 71.577051], + [-179.999951, 71.537744], + [-179.999951, 70.993018], + [-177.523584, 71.166895], + [-178.876465, 71.577051], + ], + ], + [ + [ + [52.90332, 71.36499], + [52.249609, 71.284912], + [53.022656, 70.968701], + [52.90332, 71.36499], + ], + ], + [ + [ + [96.285449, 77.02666], + [96.528418, 77.205518], + [95.270312, 77.018848], + [96.285449, 77.02666], + ], + ], + [ + [ + [74.660547, 72.873438], + [74.961523, 73.0625], + [74.198535, 73.109082], + [74.660547, 72.873438], + ], + ], + [ + [ + [58.622363, 81.04165], + [57.210938, 81.01709], + [58.285645, 80.764893], + [58.622363, 81.04165], + ], + ], + [ + [ + [50.265234, 69.185596], + [48.844922, 69.494727], + [48.439062, 68.804883], + [49.62627, 68.859717], + [50.265234, 69.185596], + ], + ], + [ + [ + [63.373828, 80.700098], + [65.437402, 80.930713], + [64.802051, 81.197266], + [62.592578, 80.853027], + [63.373828, 80.700098], + ], + ], + [ + [ + [57.95625, 80.123242], + [59.255469, 80.343213], + [57.075, 80.493945], + [57.95625, 80.123242], + ], + ], + [ + [ + [62.167773, 80.834766], + [59.592285, 80.816504], + [59.649805, 80.43125], + [61.05127, 80.418604], + [62.167773, 80.834766], + ], + ], + [ + [ + [61.14082, 80.950342], + [61.457422, 81.103955], + [60.07832, 80.99917], + [61.14082, 80.950342], + ], + ], + [ + [ + [53.521387, 80.185205], + [52.853906, 80.402393], + [52.343555, 80.213232], + [53.521387, 80.185205], + ], + ], + [ + [ + [57.078711, 80.350928], + [55.811621, 80.087158], + [56.986914, 80.071484], + [57.078711, 80.350928], + ], + ], + [ + [ + [57.810254, 81.546045], + [55.716699, 81.188477], + [57.769727, 81.169727], + [57.810254, 81.546045], + ], + ], + [ + [ + [54.718945, 81.115967], + [54.668164, 80.738672], + [57.580371, 80.755469], + [54.718945, 81.115967], + ], + ], + [ + [ + [92.683496, 79.685205], + [93.803125, 79.904541], + [91.229297, 80.030713], + [92.683496, 79.685205], + ], + ], + [ + [ + [141.010254, 73.999463], + [141.038574, 74.242725], + [140.193555, 74.236719], + [140.409473, 73.92168], + [141.010254, 73.999463], + ], + ], + [ + [ + [142.184863, 73.895898], + [141.084766, 73.865869], + [139.785547, 73.355225], + [143.451465, 73.231299], + [142.184863, 73.895898], + ], + ], + [ + [ + [137.940527, 55.092627], + [137.577344, 55.197021], + [137.23291, 54.790576], + [137.721484, 54.663232], + [138.206152, 55.033545], + [137.940527, 55.092627], + ], + ], + [ + [ + [169.200781, 69.580469], + [169.374805, 69.882617], + [167.864746, 69.901074], + [169.200781, 69.580469], + ], + ], + [ + [ + [163.635156, 58.603369], + [164.615723, 58.885596], + [164.572656, 59.221143], + [163.760938, 59.015039], + [163.635156, 58.603369], + ], + ], + [ + [ + [166.650293, 54.839062], + [166.275781, 55.311963], + [165.751074, 55.294531], + [166.650293, 54.839062], + ], + ], + [ + [ + [155.921094, 50.302197], + [156.096875, 50.771875], + [155.243066, 50.094629], + [155.921094, 50.302197], + ], + ], + [ + [ + [152.002051, 46.897168], + [152.288867, 47.142188], + [151.723438, 46.828809], + [152.002051, 46.897168], + ], + ], + [ + [ + [149.687695, 45.642041], + [150.553125, 46.208545], + [149.44707, 45.593359], + [149.687695, 45.642041], + ], + ], + [ + [ + [148.599512, 45.317627], + [148.812207, 45.51001], + [148.324219, 45.282422], + [147.924023, 45.383301], + [146.897461, 44.404297], + [148.599512, 45.317627], + ], + ], + [ + [ + [146.207617, 44.497656], + [145.461719, 43.870898], + [145.555859, 43.6646], + [146.567773, 44.44043], + [146.207617, 44.497656], + ], + ], + [ + [ + [113.387207, 74.400439], + [112.084473, 74.548975], + [111.503418, 74.353076], + [112.782422, 74.095068], + [113.387207, 74.400439], + ], + ], + [ + [ + [70.673926, 73.09502], + [71.626172, 73.173975], + [70.940234, 73.514404], + [69.995898, 73.359375], + [70.040723, 73.037158], + [70.673926, 73.09502], + ], + ], + [ + [ + [77.63252, 72.29126], + [78.365137, 72.482422], + [77.748535, 72.631201], + [76.871094, 72.317041], + [77.63252, 72.29126], + ], + ], + [ + [ + [79.501465, 72.721924], + [79.164258, 73.094336], + [78.633203, 72.850732], + [79.501465, 72.721924], + ], + ], + [ + [ + [60.450488, 69.934863], + [59.048047, 70.460498], + [58.519922, 70.318311], + [59.637012, 69.721045], + [60.440234, 69.725928], + [60.450488, 69.934863], + ], + ], + [ + [ + [20.957813, 55.278906], + [20.899805, 55.28667], + [19.604395, 54.45918], + [22.766211, 54.356787], + [22.567285, 55.059131], + [21.235742, 55.264111], + [20.995898, 54.902686], + [20.594824, 54.982373], + [20.957813, 55.278906], + ], + ], + [ + [ + [33.594141, 46.09624], + [32.508008, 45.403809], + [33.555176, 45.097656], + [33.450684, 44.553662], + [33.755664, 44.398926], + [35.472559, 45.098486], + [36.393359, 45.065381], + [36.575, 45.393555], + [35.45752, 45.316309], + [35.001674, 45.733383], + [33.806667, 46.208288], + [33.594141, 46.09624], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '罗马尼亚', full_name: '罗马尼亚', iso_a2: 'RO', iso_a3: 'ROU', iso_n3: '642' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [28.2125, 45.450439], + [28.071777, 46.978418], + [26.618945, 48.259863], + [24.979102, 47.724121], + [23.202637, 48.084521], + [22.87666, 47.947266], + [21.999707, 47.505029], + [21.12168, 46.282422], + [20.241797, 46.108594], + [21.490234, 45.1479], + [21.360059, 44.82666], + [22.093066, 44.541943], + [22.64209, 44.650977], + [22.705078, 44.237793], + [23.028516, 44.077979], + [22.919043, 43.834473], + [25.49707, 43.670801], + [27.086914, 44.167383], + [28.585352, 43.742236], + [28.891504, 44.918652], + [29.55752, 44.843408], + [29.705859, 45.259912], + [28.2125, 45.450439], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '卡塔尔', full_name: '卡塔尔国', iso_a2: 'QA', iso_a3: 'QAT', iso_n3: '634' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [51.267969, 24.607227], + [51.608887, 25.052881], + [51.543066, 25.902393], + [51.262305, 26.153271], + [50.762891, 25.444727], + [50.804395, 24.789258], + [51.267969, 24.607227], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '葡萄牙', full_name: '葡萄牙共和国', iso_a2: 'PT', iso_a3: 'PRT', iso_n3: '620' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-17.190869, 32.868604], + [-17.018262, 32.662793], + [-16.693262, 32.758008], + [-17.190869, 32.868604], + ], + ], + [ + [ + [-8.777148, 41.941064], + [-8.684619, 40.752539], + [-9.479736, 38.798779], + [-9.356738, 38.6979], + [-9.135791, 38.742773], + [-8.791602, 39.078174], + [-9.021484, 38.746875], + [-9.250391, 38.656738], + [-9.213281, 38.448096], + [-8.798877, 38.518164], + [-8.668311, 38.424316], + [-8.881104, 38.44668], + [-8.81416, 37.430811], + [-8.997803, 37.032275], + [-7.406152, 37.179443], + [-7.443945, 37.728271], + [-6.957568, 38.187891], + [-7.343018, 38.457422], + [-6.997949, 39.056445], + [-7.535693, 39.661572], + [-6.975391, 39.798389], + [-6.928467, 41.009131], + [-6.2125, 41.532031], + [-6.618262, 41.942383], + [-8.15249, 41.811963], + [-8.266064, 42.137402], + [-8.777148, 41.941064], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '波兰', full_name: '波兰共和国', iso_a2: 'PL', iso_a3: 'POL', iso_n3: '616' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [23.605273, 51.51792], + [23.652441, 52.040381], + [23.175098, 52.286621], + [23.91543, 52.770264], + [23.484668, 53.939795], + [22.766211, 54.356787], + [19.604395, 54.45918], + [18.836426, 54.36958], + [18.43623, 54.744727], + [18.759277, 54.68457], + [18.323438, 54.838184], + [17.842969, 54.816699], + [14.211426, 53.950342], + [14.213672, 53.870752], + [14.583496, 53.639355], + [14.258887, 53.729639], + [14.128613, 52.878223], + [14.619434, 52.528516], + [15.016602, 51.252734], + [14.809375, 50.858984], + [14.99375, 51.014355], + [16.282227, 50.655615], + [16.63916, 50.102148], + [16.880078, 50.427051], + [17.702246, 50.307178], + [17.627051, 50.116406], + [18.562402, 49.879346], + [18.832227, 49.510791], + [19.149414, 49.4], + [19.441602, 49.597705], + [20.057617, 49.181299], + [20.362988, 49.385254], + [21.639648, 49.411963], + [22.538672, 49.072705], + [22.852051, 49.062744], + [22.706152, 49.606201], + [24.089941, 50.530469], + [23.605273, 51.51792], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '菲律宾', full_name: '菲律宾共和国', iso_a2: 'PH', iso_a3: 'PHL', iso_n3: '608' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [121.101562, 18.615283], + [120.599707, 18.507861], + [120.36875, 16.10957], + [119.772559, 16.255127], + [120.082129, 14.851074], + [120.43877, 14.453369], + [120.583691, 14.88125], + [120.941309, 14.645068], + [120.637109, 13.804492], + [121.344141, 13.649121], + [121.77793, 13.937646], + [122.599902, 13.194141], + [122.595215, 13.907617], + [123.310938, 13.044092], + [123.948535, 12.916406], + [124.059766, 12.56709], + [124.142773, 13.035791], + [123.785156, 13.110547], + [123.549609, 13.645752], + [123.815723, 13.837109], + [123.320312, 14.06167], + [123.101953, 13.750244], + [122.627148, 14.317529], + [122.199707, 14.148047], + [122.211719, 13.930176], + [121.766602, 14.168066], + [121.392285, 15.324414], + [121.595313, 15.933252], + [122.135156, 16.184814], + [122.519141, 17.124854], + [122.152344, 17.664404], + [122.265527, 18.458838], + [121.845605, 18.29541], + [121.101562, 18.615283], + ], + ], + [ + [ + [117.311133, 8.4396], + [119.686914, 10.500342], + [119.55332, 11.313525], + [119.223828, 10.477295], + [117.349902, 8.713574], + [117.311133, 8.4396], + ], + ], + [ + [ + [122.496191, 11.615088], + [121.916016, 11.854346], + [122.103516, 11.64292], + [121.954004, 10.444385], + [122.769922, 10.823828], + [123.119531, 11.286816], + [123.158301, 11.535547], + [122.496191, 11.615088], + ], + ], + [ + [ + [123.130859, 9.064111], + [123.320508, 9.272949], + [123.162012, 9.864258], + [123.567578, 10.780762], + [123.256641, 10.993945], + [122.983301, 10.886621], + [122.855566, 10.086914], + [122.399512, 9.823047], + [123.130859, 9.064111], + ], + ], + [ + [ + [124.574609, 11.343066], + [124.330664, 11.535205], + [124.445508, 10.923584], + [124.786719, 10.781396], + [124.780762, 10.168066], + [125.026563, 10.033105], + [124.9875, 10.367578], + [125.268457, 10.307715], + [125.026563, 11.211719], + [124.574609, 11.343066], + ], + ], + [ + [ + [125.239551, 12.527881], + [124.294727, 12.569336], + [124.445703, 12.152783], + [124.99502, 11.764941], + [125.034277, 11.34126], + [125.735645, 11.049609], + [125.535645, 12.191406], + [125.239551, 12.527881], + ], + ], + [ + [ + [120.704395, 13.479492], + [120.338477, 13.412354], + [121.236719, 12.218799], + [121.540625, 12.638184], + [121.522754, 13.131201], + [121.202734, 13.432324], + [120.704395, 13.479492], + ], + ], + [ + [ + [126.005957, 9.320947], + [125.471289, 9.756787], + [125.49873, 9.014746], + [124.868945, 8.972266], + [124.731152, 8.562988], + [124.404883, 8.599854], + [123.799414, 8.049121], + [123.849219, 8.432715], + [123.43457, 8.70332], + [122.911133, 8.156445], + [122.243359, 7.945117], + [121.964258, 6.968213], + [122.616211, 7.763135], + [123.390918, 7.40752], + [123.66582, 7.817773], + [124.206641, 7.396436], + [123.985254, 6.993701], + [124.078125, 6.404443], + [124.927344, 5.875342], + [125.231543, 6.069531], + [125.346484, 5.598975], + [125.667969, 5.978662], + [125.380664, 6.689941], + [125.689258, 7.263037], + [125.824414, 7.333301], + [126.189355, 6.309668], + [126.19209, 6.852539], + [126.581543, 7.247754], + [126.458691, 8.202832], + [126.139551, 8.595654], + [126.30459, 8.952051], + [126.005957, 9.320947], + ], + ], + [ + [ + [123.370313, 9.449609], + [124.00498, 10.400098], + [124.038867, 11.273535], + [123.38623, 9.96709], + [123.370313, 9.449609], + ], + ], + [ + [ + [121.159375, 6.075635], + [120.876367, 5.952637], + [121.411035, 5.939844], + [121.159375, 6.075635], + ], + ], + [ + [ + [120.1, 12.167676], + [119.885742, 12.299854], + [120.010547, 12.008252], + [120.314551, 12.012402], + [120.1, 12.167676], + ], + ], + [ + [ + [124.593848, 9.787207], + [124.335742, 10.159912], + [123.817187, 9.817383], + [124.122461, 9.599316], + [124.593848, 9.787207], + ], + ], + [ + [ + [122.092871, 6.42832], + [122.323535, 6.602246], + [121.832031, 6.664062], + [122.092871, 6.42832], + ], + ], + [ + [ + [125.690234, 9.914453], + [125.666797, 10.440137], + [125.494824, 10.118701], + [125.690234, 9.914453], + ], + ], + [ + [ + [121.914844, 13.540332], + [122.004883, 13.20498], + [122.114551, 13.463184], + [121.914844, 13.540332], + ], + ], + [ + [ + [122.094043, 12.354883], + [122.14502, 12.652637], + [122.013965, 12.105615], + [122.094043, 12.354883], + ], + ], + [ + [ + [123.281836, 12.853418], + [122.95752, 13.107178], + [123.367188, 12.70083], + [123.281836, 12.853418], + ], + ], + [ + [ + [123.716602, 12.287354], + [123.236426, 12.583496], + [123.157813, 11.925635], + [123.47373, 12.21665], + [124.045508, 11.752441], + [123.716602, 12.287354], + ], + ], + [ + [ + [124.353613, 13.632227], + [124.224902, 14.077588], + [124.038867, 13.663135], + [124.353613, 13.632227], + ], + ], + [ + [ + [122.033496, 15.005029], + [121.839844, 15.038135], + [121.933008, 14.656055], + [122.033496, 15.005029], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '秘鲁', full_name: '秘鲁共和国', iso_a2: 'PE', iso_a3: 'PER', iso_n3: '604' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-69.965918, -4.235938], + [-70.339502, -3.814355], + [-70.735107, -3.781543], + [-70.09585, -2.658203], + [-70.968555, -2.206836], + [-72.941113, -2.394043], + [-73.664307, -1.248828], + [-74.246387, -0.970605], + [-74.801758, -0.200098], + [-75.284473, -0.106543], + [-75.62627, -0.122852], + [-75.259375, -0.590137], + [-75.570557, -1.53125], + [-76.679102, -2.562598], + [-77.860596, -2.981641], + [-78.158496, -3.465137], + [-78.345361, -3.397363], + [-78.686035, -4.562402], + [-79.033301, -4.969141], + [-79.330957, -4.927832], + [-79.638525, -4.454883], + [-80.478564, -4.430078], + [-80.490137, -4.010059], + [-80.179248, -3.877734], + [-80.324658, -3.387891], + [-81.283203, -4.322266], + [-81.336621, -4.669531], + [-80.881934, -5.635059], + [-81.142041, -6.056738], + [-79.994971, -6.768945], + [-78.762256, -8.616992], + [-77.633203, -11.287793], + [-76.223633, -13.371191], + [-76.289014, -14.133105], + [-75.104248, -15.411914], + [-72.467676, -16.708105], + [-70.418262, -18.345605], + [-69.926367, -18.206055], + [-69.8521, -17.703809], + [-69.510938, -17.506055], + [-69.624854, -17.200195], + [-68.842773, -16.337891], + [-69.217578, -16.149121], + [-69.420898, -15.640625], + [-69.172461, -15.236621], + [-69.359473, -14.795312], + [-68.870898, -14.169727], + [-69.074121, -13.682813], + [-68.978613, -12.880078], + [-68.685254, -12.501953], + [-69.578613, -10.951758], + [-70.642334, -11.010254], + [-70.541113, -9.4375], + [-71.237939, -9.966016], + [-72.142969, -10.005176], + [-72.379053, -9.510156], + [-73.209424, -9.411426], + [-72.974023, -8.993164], + [-74.002051, -7.556055], + [-73.72041, -7.309277], + [-73.758105, -6.905762], + [-73.137354, -6.46582], + [-73.235547, -6.098438], + [-72.887061, -5.122754], + [-70.799512, -4.17334], + [-69.965918, -4.235938], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴拉圭', full_name: '巴拉圭共和国', iso_a2: 'PY', iso_a3: 'PRY', iso_n3: '600' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-58.159766, -20.164648], + [-58.180176, -19.817871], + [-59.090527, -19.28623], + [-61.756836, -19.645312], + [-62.276318, -20.5625], + [-62.650977, -22.233691], + [-61.03291, -23.755664], + [-59.89248, -24.093555], + [-57.643896, -25.328418], + [-58.604834, -27.314355], + [-56.437158, -27.553809], + [-56.164062, -27.321484], + [-55.714648, -27.414844], + [-54.825488, -26.652246], + [-54.615869, -25.576074], + [-54.241797, -24.047266], + [-54.625488, -23.8125], + [-55.415918, -23.951367], + [-55.84917, -22.307617], + [-56.447803, -22.076172], + [-56.937256, -22.271289], + [-57.955908, -22.10918], + [-57.830225, -20.997949], + [-58.159766, -20.164648], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '巴布亚新几内亚', + full_name: '巴布亚新几内亚独立国', + iso_a2: 'PG', + iso_a3: 'PNG', + iso_n3: '598', + }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [152.96582, -4.756348], + [153.016797, -4.105664], + [152.03291, -3.251367], + [150.825391, -2.572949], + [150.746094, -2.738867], + [152.279395, -3.582422], + [152.96582, -4.756348], + ], + ], + [ + [ + [151.915625, -4.296777], + [151.593066, -4.200781], + [151.671191, -4.883301], + [150.900293, -5.447168], + [150.183105, -5.523633], + [150.090039, -5.011816], + [149.831445, -5.524121], + [148.432031, -5.471777], + [148.337207, -5.669434], + [149.652539, -6.29043], + [150.473535, -6.263379], + [151.515137, -5.552344], + [152.077051, -5.458301], + [151.983691, -5.074414], + [152.351172, -4.822168], + [152.405664, -4.340723], + [151.915625, -4.296777], + ], + ], + [ + [ + [140.976172, -9.11875], + [142.647168, -9.327832], + [143.366211, -8.961035], + [143.111816, -8.474512], + [142.206836, -8.195801], + [143.61377, -8.200391], + [143.518164, -8.000684], + [143.942285, -7.944238], + [143.654883, -7.460352], + [144.142871, -7.757227], + [144.509863, -7.567383], + [146.033203, -8.076367], + [147.768652, -10.070117], + [149.754102, -10.353027], + [150.319922, -10.654883], + [150.647168, -10.517969], + [150.446094, -10.307324], + [150.849512, -10.236035], + [149.874414, -10.012988], + [149.76123, -9.805859], + [150.011035, -9.688184], + [149.263184, -9.497852], + [149.19834, -9.03125], + [148.583105, -9.051758], + [148.126758, -8.103613], + [147.190039, -7.378125], + [146.953613, -6.834082], + [147.845508, -6.662402], + [147.566699, -6.056934], + [145.745215, -5.402441], + [145.766992, -4.823047], + [144.477734, -3.825293], + [140.973438, -2.609766], + [140.976172, -9.11875], + ], + ], + [ + [ + [151.080957, -10.020117], + [150.776074, -9.709082], + [151.230859, -10.194727], + [151.296484, -9.956738], + [151.080957, -10.020117], + ], + ], + [ + [ + [150.34541, -9.493848], + [150.208301, -9.206348], + [150.109766, -9.361914], + [150.34541, -9.493848], + ], + ], + [ + [ + [147.067578, -1.960156], + [146.65625, -1.974023], + [146.546484, -2.208594], + [147.438086, -2.058984], + [147.067578, -1.960156], + ], + ], + [ + [ + [150.436621, -2.661816], + [150.227148, -2.38418], + [149.961621, -2.473828], + [150.436621, -2.661816], + ], + ], + [ + [ + [150.528418, -9.346582], + [150.43623, -9.624609], + [150.894043, -9.66748], + [150.528418, -9.346582], + ], + ], + [ + [ + [153.536133, -11.476172], + [153.203613, -11.324121], + [153.759863, -11.586328], + [153.536133, -11.476172], + ], + ], + [ + [ + [155.957617, -6.686816], + [154.729297, -5.444434], + [154.759277, -5.931348], + [155.344043, -6.72168], + [155.719336, -6.862793], + [155.957617, -6.686816], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴拿马', full_name: '巴拿马共和国', iso_a2: 'PA', iso_a3: 'PAN', iso_n3: '591' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-77.374219, 8.658301], + [-78.082764, 9.236279], + [-79.577295, 9.597852], + [-81.354785, 8.780566], + [-81.894482, 9.14043], + [-81.780225, 8.957227], + [-82.244189, 9.031494], + [-82.563574, 9.57666], + [-82.939844, 9.44917], + [-82.727832, 8.916064], + [-83.027344, 8.337744], + [-82.879346, 8.070654], + [-82.781152, 8.303516], + [-82.235449, 8.311035], + [-81.727637, 8.137549], + [-81.268408, 7.625488], + [-81.063867, 7.899756], + [-80.845557, 7.220068], + [-80.438867, 7.274951], + [-80.01123, 7.500049], + [-80.458984, 8.213867], + [-79.50708, 8.970068], + [-79.086377, 8.997168], + [-78.409863, 8.355322], + [-78.099463, 8.496973], + [-77.760547, 8.133252], + [-78.141895, 8.386084], + [-78.421582, 8.060986], + [-77.901172, 7.229346], + [-77.761914, 7.698828], + [-77.538281, 7.56626], + [-77.195996, 7.972461], + [-77.374219, 8.658301], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '帕劳', full_name: '帕劳共和国', iso_a2: 'PW', iso_a3: 'PLW', iso_n3: '585' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [134.59541, 7.382031], + [134.651172, 7.712109], + [134.515723, 7.525781], + [134.59541, 7.382031], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴基斯坦', full_name: '巴基斯坦伊斯兰共和国', iso_a2: 'PK', iso_a3: 'PAK', iso_n3: '586' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [76.766895, 35.661719], + [75.912305, 36.048975], + [75.772168, 36.694922], + [74.541406, 37.022168], + [72.249805, 36.734717], + [71.23291, 36.121777], + [71.620508, 35.183008], + [70.965625, 34.530371], + [71.051563, 34.049707], + [69.889648, 34.007275], + [70.261133, 33.289014], + [69.501562, 33.020068], + [69.279297, 31.936816], + [68.868945, 31.634229], + [68.161035, 31.802979], + [67.452832, 31.234619], + [66.829297, 31.263672], + [66.346875, 30.802783], + [66.23125, 29.865723], + [64.09873, 29.391943], + [62.476562, 29.40835], + [60.843359, 29.858691], + [61.889844, 28.546533], + [62.758008, 28.243555], + [62.762988, 27.250195], + [63.301563, 27.151465], + [63.157812, 26.649756], + [61.842383, 26.225928], + [61.587891, 25.202344], + [64.059375, 25.40293], + [64.658984, 25.184082], + [66.467676, 25.445312], + [66.131152, 25.493262], + [66.324219, 25.601807], + [66.698633, 25.226318], + [66.703027, 24.860938], + [67.171484, 24.756104], + [67.563086, 23.881836], + [68.165039, 23.857324], + [68.724121, 23.964697], + [68.781152, 24.313721], + [69.805176, 24.165234], + [71.044043, 24.400098], + [70.648438, 25.666943], + [70.100195, 25.910059], + [70.147656, 26.506445], + [69.506934, 26.742676], + [69.537012, 27.122949], + [70.403711, 28.025049], + [70.797949, 27.709619], + [71.870313, 27.9625], + [72.341895, 28.751904], + [72.90332, 29.02876], + [73.381641, 29.934375], + [73.80918, 30.093359], + [74.632812, 31.034668], + [74.555566, 31.818555], + [75.333496, 32.279199], + [74.685742, 32.493799], + [74.663281, 32.757666], + [74.35459, 32.768701], + [74.003809, 33.189453], + [73.976465, 33.721289], + [74.250879, 33.946094], + [73.904102, 34.075684], + [73.96123, 34.653467], + [75.70918, 34.503076], + [77.048633, 35.109912], + [76.766895, 35.661719], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿曼', full_name: '阿曼苏丹国', iso_a2: 'OM', iso_a3: 'OMN', iso_n3: '512' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [53.085645, 16.648389], + [54.068164, 17.005518], + [55.06416, 17.038916], + [55.479102, 17.843262], + [56.383496, 17.987988], + [56.825977, 18.753516], + [57.811621, 19.01709], + [57.861816, 20.244141], + [58.169434, 20.589502], + [58.474219, 20.406885], + [59.8, 22.219922], + [59.823242, 22.508984], + [59.429395, 22.66084], + [58.773047, 23.517188], + [57.123047, 23.980713], + [56.387988, 24.979199], + [56.063867, 24.73877], + [56.000586, 24.953223], + [55.795703, 24.868115], + [55.76084, 24.242676], + [55.985156, 24.063379], + [55.468457, 23.941113], + [55.18584, 22.704102], + [55.641016, 22.001855], + [54.977344, 19.995947], + [51.977637, 18.996143], + [53.085645, 16.648389], + ], + ], + [ + [ + [56.297852, 25.650684], + [56.413086, 26.351172], + [56.080469, 26.062646], + [56.297852, 25.650684], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '挪威', full_name: '挪威王国', iso_a2: 'NO', iso_a3: 'NOR', iso_n3: '578' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [20.622168, 69.036865], + [21.59375, 69.273584], + [22.410938, 68.719873], + [23.854004, 68.805908], + [24.941406, 68.593262], + [26.072461, 69.691553], + [27.747852, 70.064844], + [29.141602, 69.671436], + [28.96582, 69.021973], + [30.180176, 69.63584], + [30.860742, 69.538428], + [30.869727, 69.783447], + [29.79209, 69.727881], + [28.804297, 70.092529], + [30.944141, 70.274414], + [30.065137, 70.702979], + [28.831543, 70.863965], + [28.192969, 70.248584], + [28.392285, 70.975293], + [27.59707, 71.091309], + [26.989355, 70.511377], + [26.585059, 70.41001], + [26.661328, 70.939746], + [25.043848, 70.109033], + [25.768164, 70.853174], + [24.658008, 71.001025], + [23.353906, 69.983398], + [22.68457, 70.374756], + [21.355762, 70.233398], + [21.974707, 69.83457], + [20.62207, 69.913916], + [20.739453, 69.520508], + [20.054492, 69.332666], + [20.324219, 69.945312], + [19.641504, 69.424023], + [19.722461, 69.781641], + [19.197266, 69.747852], + [18.915918, 69.335596], + [18.259766, 69.470605], + [18.101465, 69.156299], + [16.514355, 68.532568], + [17.552832, 68.42627], + [16.203809, 68.316748], + [16.312305, 67.881445], + [16.00791, 68.228711], + [14.798926, 67.809326], + [15.594434, 67.348535], + [14.961914, 67.574268], + [14.441699, 67.271387], + [15.415723, 67.202441], + [14.108789, 67.119238], + [13.211426, 66.64082], + [13.118848, 66.230664], + [14.03418, 66.297559], + [12.783789, 66.100439], + [12.133887, 65.27915], + [12.915527, 65.339258], + [11.489355, 64.97583], + [9.567285, 63.706152], + [10.055078, 63.512695], + [11.306641, 64.048877], + [11.370703, 63.804834], + [10.020996, 63.39082], + [9.696875, 63.624561], + [9.156055, 63.459326], + [8.576172, 63.601172], + [8.158008, 63.161523], + [8.623145, 62.84624], + [7.571875, 63.099512], + [6.734961, 62.720703], + [8.045508, 62.77124], + [7.653125, 62.564014], + [6.35293, 62.611133], + [6.136133, 62.407471], + [6.580078, 62.407275], + [5.143164, 62.159912], + [5.266895, 61.935596], + [6.730762, 61.869775], + [4.930078, 61.87832], + [5.106738, 61.187549], + [7.173535, 61.165967], + [7.442578, 61.434619], + [7.604492, 61.210547], + [7.038672, 60.95293], + [6.777832, 61.142432], + [5.008594, 61.038184], + [5.11582, 60.635986], + [5.64834, 60.687988], + [5.137109, 60.445605], + [5.688574, 60.123193], + [5.205664, 60.087939], + [5.145801, 59.638818], + [6.15332, 60.34624], + [6.995703, 60.511963], + [5.730469, 59.863086], + [6.216602, 59.818359], + [5.242188, 59.564307], + [5.173242, 59.162549], + [6.415332, 59.547119], + [5.88916, 59.097949], + [6.363281, 59.000928], + [5.555566, 58.975195], + [5.706836, 58.523633], + [7.004883, 58.024219], + [8.166113, 58.145312], + [9.557227, 59.112695], + [10.179395, 59.009277], + [10.595312, 59.764551], + [10.834473, 59.183936], + [11.388281, 59.036523], + [11.642773, 58.926074], + [11.680762, 59.592285], + [12.486133, 60.106787], + [12.294141, 61.002686], + [12.880762, 61.352295], + [12.155371, 61.720752], + [12.175195, 63.595947], + [12.792773, 64], + [14.141211, 64.173535], + [13.650293, 64.581543], + [14.479688, 65.301465], + [14.543262, 66.129346], + [15.483789, 66.305957], + [16.783594, 67.89502], + [17.324609, 68.103809], + [17.916699, 67.964893], + [18.303027, 68.55542], + [19.969824, 68.356396], + [20.116699, 69.020898], + [20.622168, 69.036865], + ], + ], + [ + [ + [23.440527, 70.815771], + [21.994531, 70.657129], + [22.829102, 70.541553], + [23.440527, 70.815771], + ], + ], + [ + [ + [15.207129, 68.943115], + [14.404688, 68.663232], + [15.22207, 68.616309], + [15.207129, 68.943115], + ], + ], + [ + [ + [19.255078, 70.066406], + [19.132715, 70.244141], + [18.129883, 69.557861], + [19.334766, 69.820264], + [19.255078, 70.066406], + ], + ], + [ + [ + [17.503027, 69.59624], + [17.08252, 69.013672], + [17.950684, 69.198145], + [17.503027, 69.59624], + ], + ], + [ + [ + [15.760352, 68.56123], + [16.048047, 69.302051], + [15.412598, 68.61582], + [14.25752, 68.190771], + [15.975293, 68.40249], + [16.328906, 68.876318], + [15.760352, 68.56123], + ], + ], + [ + [ + [32.525977, 80.119141], + [33.629297, 80.217432], + [31.481934, 80.10791], + [32.525977, 80.119141], + ], + ], + [ + [ + [21.608105, 78.595703], + [20.22793, 78.477832], + [21.653125, 77.923535], + [20.928125, 77.459668], + [22.685352, 77.553516], + [22.553711, 77.26665], + [24.901855, 77.756592], + [23.116699, 77.991504], + [21.608105, 78.595703], + ], + ], + [ + [ + [20.897852, 80.249951], + [19.733301, 80.477832], + [19.343359, 80.116406], + [17.916895, 80.143115], + [18.725, 79.760742], + [20.784082, 79.748584], + [20.128223, 79.4896], + [23.947754, 79.194287], + [25.641211, 79.403027], + [26.86084, 80.16001], + [24.297559, 80.3604], + [23.114551, 80.186963], + [23.008008, 80.473975], + [22.289746, 80.049219], + [20.897852, 80.249951], + ], + ], + [ + [ + [16.786719, 79.906738], + [16.245703, 80.049463], + [16.34375, 78.976123], + [14.593652, 79.79873], + [14.02959, 79.344141], + [12.555371, 79.569482], + [13.692871, 79.860986], + [10.804004, 79.798779], + [10.737598, 79.520166], + [13.150195, 78.2375], + [14.638281, 78.4146], + [14.689258, 78.720947], + [15.417383, 78.473242], + [16.782617, 78.663623], + [17.00293, 78.369385], + [13.680566, 78.028125], + [14.089941, 77.771387], + [16.914062, 77.897998], + [13.995703, 77.508203], + [16.700488, 76.579297], + [19.676758, 78.60957], + [21.38877, 78.74043], + [18.677832, 79.261719], + [18.397363, 79.605176], + [17.66875, 79.385938], + [16.786719, 79.906738], + ], + ], + [ + [ + [11.250293, 78.610693], + [10.558203, 78.90293], + [12.116406, 78.232568], + [11.250293, 78.610693], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '朝鲜', full_name: '朝鲜民主主义人民共和国', iso_a2: 'KP', iso_a3: 'PRK', iso_n3: '408' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [128.374609, 38.623438], + [127.394531, 39.20791], + [127.568164, 39.781982], + [129.708691, 40.857324], + [129.756348, 41.712256], + [130.687305, 42.302539], + [130.526953, 42.5354], + [129.898242, 42.998145], + [129.697852, 42.448145], + [128.923438, 42.038232], + [128.045215, 41.9875], + [128.149414, 41.387744], + [126.743066, 41.724854], + [125.989062, 40.904639], + [124.362109, 40.004053], + [124.638281, 39.615088], + [124.775293, 39.758057], + [125.36084, 39.526611], + [125.168848, 38.805518], + [125.554492, 38.68623], + [124.690918, 38.129199], + [125.206738, 38.081543], + [124.98877, 37.931445], + [125.357813, 37.724805], + [125.769141, 37.985352], + [126.116699, 37.74292], + [126.633887, 37.781836], + [127.090332, 38.283887], + [128.038965, 38.308545], + [128.374609, 38.623438], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '尼日利亚', full_name: '尼日利亚联邦共和国', iso_a2: 'NG', iso_a3: 'NGA', iso_n3: '566' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [13.606348, 13.70459], + [12.463184, 13.09375], + [10.475879, 13.330225], + [9.615918, 12.810645], + [8.750586, 12.908154], + [7.830469, 13.340918], + [7.005078, 12.995557], + [6.299805, 13.658789], + [5.491992, 13.872852], + [4.664844, 13.733203], + [4.147559, 13.457715], + [4.03877, 12.934668], + [3.64668, 12.52998], + [3.59541, 11.696289], + [3.487793, 11.39541], + [3.834473, 10.607422], + [3.044922, 9.083838], + [2.774805, 9.048535], + [2.706445, 6.369238], + [3.716992, 6.597949], + [3.450781, 6.427051], + [4.431348, 6.348584], + [5.112402, 5.641553], + [5.456641, 5.611719], + [5.199219, 5.533545], + [5.549707, 5.474219], + [5.367969, 5.337744], + [5.493262, 4.83877], + [6.076563, 4.290625], + [6.860352, 4.37334], + [6.767676, 4.724707], + [6.923242, 4.390674], + [7.154688, 4.514404], + [7.076563, 4.716162], + [7.800781, 4.522266], + [8.293066, 4.557617], + [8.252734, 4.923975], + [8.555859, 4.755225], + [8.997168, 5.917725], + [9.779883, 6.760156], + [10.60625, 7.063086], + [11.237305, 6.450537], + [11.861426, 7.116406], + [12.233398, 8.282324], + [12.782227, 8.817871], + [13.699902, 10.873145], + [14.575391, 11.532422], + [14.619727, 12.150977], + [14.197461, 12.383789], + [14.063965, 13.078516], + [13.606348, 13.70459], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '尼日尔', full_name: '尼日尔共和国', iso_a2: 'NE', iso_a3: 'NER', iso_n3: '562' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [13.606348, 13.70459], + [13.448242, 14.380664], + [15.474316, 16.908398], + [15.735059, 19.904053], + [15.963184, 20.346191], + [15.181836, 21.523389], + [14.979004, 22.996191], + [14.215527, 22.619678], + [13.48125, 23.180176], + [11.967871, 23.517871], + [7.481738, 20.873096], + [5.836621, 19.47915], + [4.227637, 19.142773], + [4.234668, 16.996387], + [3.842969, 15.701709], + [3.504297, 15.356348], + [1.300195, 15.272266], + [0.947461, 14.982129], + [0.21748, 14.911475], + [0.429199, 13.972119], + [1.201172, 13.35752], + [0.988477, 13.364844], + [0.987305, 13.041895], + [1.564941, 12.6354], + [2.10459, 12.70127], + [2.072949, 12.309375], + [2.38916, 11.89707], + [2.366016, 12.221924], + [2.805273, 12.383838], + [3.59541, 11.696289], + [3.64668, 12.52998], + [4.03877, 12.934668], + [4.147559, 13.457715], + [4.664844, 13.733203], + [5.491992, 13.872852], + [6.299805, 13.658789], + [7.005078, 12.995557], + [7.830469, 13.340918], + [8.750586, 12.908154], + [9.615918, 12.810645], + [10.475879, 13.330225], + [12.463184, 13.09375], + [13.606348, 13.70459], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '尼加拉瓜', full_name: '尼加拉瓜共和国', iso_a2: 'NI', iso_a3: 'NIC', iso_n3: '558' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-83.15752, 14.993066], + [-84.453564, 14.643701], + [-84.985156, 14.752441], + [-85.733936, 13.858691], + [-86.040381, 14.050146], + [-86.733643, 13.763477], + [-86.729297, 13.284375], + [-87.337256, 12.979248], + [-87.667529, 12.903564], + [-85.744336, 11.062109], + [-84.63418, 11.045605], + [-83.919287, 10.735352], + [-83.641992, 10.917236], + [-83.867871, 11.300049], + [-83.651758, 11.642041], + [-83.754248, 12.501953], + [-83.593359, 12.713086], + [-83.510938, 12.411816], + [-83.567334, 13.320312], + [-83.187744, 14.340088], + [-83.413721, 14.825342], + [-83.15752, 14.993066], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '新西兰', full_name: '新西兰', iso_a2: 'NZ', iso_a3: 'NZL', iso_n3: '554' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [173.115332, -41.279297], + [172.704395, -40.667773], + [172.943652, -40.51875], + [172.640625, -40.518262], + [171.48623, -41.794727], + [171.011719, -42.885059], + [170.969922, -42.718359], + [169.178906, -43.913086], + [168.457422, -44.030566], + [167.908984, -44.664746], + [167.194531, -44.963477], + [167.155664, -45.410938], + [166.743066, -45.468457], + [167.00332, -45.712109], + [166.488281, -45.831836], + [166.916699, -45.957227], + [166.731543, -46.197852], + [167.539453, -46.148535], + [168.382129, -46.605371], + [169.342285, -46.620508], + [170.77627, -45.870898], + [171.240723, -44.26416], + [172.179785, -43.895996], + [172.035547, -43.701758], + [173.065625, -43.874609], + [172.52666, -43.464746], + [172.624023, -43.272461], + [173.221191, -42.976562], + [174.283105, -41.740625], + [174.069336, -41.429492], + [174.370117, -41.103711], + [174.038574, -41.241895], + [174.302539, -41.019531], + [173.797852, -41.271973], + [173.947168, -40.924121], + [173.115332, -41.279297], + ], + ], + [ + [ + [168.144922, -46.862207], + [167.783984, -46.699805], + [167.521973, -47.258691], + [168.240918, -47.07002], + [168.144922, -46.862207], + ], + ], + [ + [ + [173.269434, -34.934766], + [173.043945, -34.429102], + [172.705957, -34.455176], + [173.313965, -35.443359], + [173.626172, -35.319141], + [173.412207, -35.542578], + [174.054688, -36.359766], + [173.914453, -35.908691], + [174.392773, -36.240039], + [174.401562, -36.601953], + [174.188867, -36.492285], + [174.475586, -36.941895], + [174.928906, -37.084766], + [174.58584, -37.097754], + [174.928027, -37.804492], + [174.597363, -38.785059], + [173.763672, -39.31875], + [175.155957, -40.114941], + [175.1625, -40.621582], + [174.635352, -41.289453], + [175.309766, -41.610645], + [176.842188, -40.157812], + [177.076758, -39.221777], + [177.522949, -39.073828], + [177.908789, -39.239551], + [178.53623, -37.69209], + [178.00918, -37.554883], + [177.274023, -37.993457], + [176.108398, -37.645117], + [175.46084, -36.475684], + [175.54248, -37.201367], + [174.722461, -36.841211], + [174.802148, -36.309473], + [174.320312, -35.24668], + [173.269434, -34.934766], + ], + ], + [ + [ + [166.221094, -50.761523], + [166.101367, -50.538965], + [165.88916, -50.807715], + [166.221094, -50.761523], + ], + ], + [ + [ + [-176.177637, -43.740332], + [-176.847656, -43.823926], + [-176.515527, -44.116602], + [-176.516553, -43.784766], + [-176.177637, -43.740332], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '纽埃', full_name: '纽埃', iso_a2: 'NU', iso_a3: 'NIU', iso_n3: '570' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-169.803418, -19.083008], + [-169.834033, -18.966016], + [-169.94834, -19.072852], + [-169.803418, -19.083008], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '库克群岛', full_name: '库克群岛', iso_a2: 'CK', iso_a3: 'COK', iso_n3: '184' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-159.740527, -21.249219], + [-159.768359, -21.188477], + [-159.832031, -21.200488], + [-159.740527, -21.249219], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '荷兰', full_name: '荷兰王国', iso_a2: 'NL', iso_a3: 'NLD', iso_n3: '528' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [5.993945, 50.750439], + [5.94873, 51.802686], + [6.800391, 51.967383], + [7.035156, 52.380225], + [6.710742, 52.617871], + [7.033008, 52.651367], + [7.197266, 53.282275], + [6.062207, 53.40708], + [4.76875, 52.941309], + [3.946875, 51.810547], + [4.274121, 51.471631], + [3.448926, 51.540771], + [4.226172, 51.386475], + [5.030957, 51.469092], + [5.796484, 51.153076], + [5.639453, 50.843604], + [5.993945, 50.750439], + ], + ], + [ + [ + [4.226172, 51.386475], + [3.350098, 51.377686], + [3.902051, 51.207666], + [4.226172, 51.386475], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿鲁巴', full_name: '阿鲁巴(荷兰)', iso_a2: 'AW', iso_a3: 'ABW', iso_n3: '533' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-69.899121, 12.452002], + [-70.035107, 12.614111], + [-70.066113, 12.546973], + [-69.899121, 12.452002], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '库拉索', full_name: '库拉索岛(荷兰)', iso_a2: 'CW', iso_a3: 'CUW', iso_n3: '531' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-68.751074, 12.059766], + [-69.158887, 12.380273], + [-68.995117, 12.141846], + [-68.751074, 12.059766], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '尼泊尔', full_name: '尼泊尔', iso_a2: 'NP', iso_a3: 'NPL', iso_n3: '524' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [88.109766, 27.870605], + [87.141406, 27.83833], + [86.137012, 28.114355], + [85.994531, 27.9104], + [85.67832, 28.277441], + [85.122461, 28.315967], + [85.159082, 28.592236], + [84.228711, 28.911768], + [84.101367, 29.219971], + [83.583496, 29.183594], + [82.043359, 30.326758], + [81.010254, 30.164502], + [80.401855, 29.730273], + [80.070703, 28.830176], + [82.733398, 27.518994], + [84.091016, 27.491357], + [85.794531, 26.60415], + [87.995117, 26.382373], + [88.109766, 27.870605], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '瑙鲁', full_name: '瑙鲁共和国', iso_a2: 'NR', iso_a3: 'NRU', iso_n3: '520' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [166.958398, -0.516602], + [166.907031, -0.52373], + [166.938965, -0.550781], + [166.958398, -0.516602], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '纳米比亚', full_name: '纳米比亚共和国', iso_a2: 'NA', iso_a3: 'NAM', iso_n3: '516' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [23.380664, -17.640625], + [20.745508, -18.019727], + [18.955273, -17.803516], + [18.396387, -17.399414], + [14.01748, -17.408887], + [13.101172, -16.967676], + [11.743066, -17.249219], + [11.775879, -18.001758], + [14.462793, -22.449121], + [14.501562, -24.201953], + [14.967773, -26.318066], + [15.341504, -27.386523], + [16.447559, -28.617578], + [17.05625, -28.031055], + [17.447949, -28.698145], + [18.102734, -28.87168], + [19.161719, -28.93877], + [19.980469, -28.45127], + [19.980469, -24.776758], + [19.977344, -22.000195], + [20.979492, -21.961914], + [20.974121, -18.318848], + [23.219336, -17.999707], + [23.599707, -18.459961], + [24.243945, -18.023438], + [25.258789, -17.793555], + [24.73291, -17.517773], + [23.380664, -17.640625], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '莫桑比克', full_name: '莫桑比克共和国', iso_a2: 'MZ', iso_a3: 'MOZ', iso_n3: '508' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [31.287891, -22.402051], + [31.98584, -24.460645], + [31.948242, -25.957617], + [32.112891, -26.839453], + [32.886133, -26.849316], + [32.954883, -26.083594], + [32.848828, -26.268066], + [32.59043, -26.004102], + [32.792188, -25.644336], + [34.99209, -24.650586], + [35.489648, -24.065527], + [35.530078, -22.248145], + [35.315723, -22.396875], + [34.649414, -19.701367], + [34.947852, -19.812695], + [36.403711, -18.769727], + [37.244531, -17.739941], + [39.844629, -16.435645], + [40.558984, -15.473438], + [40.844531, -14.718652], + [40.436816, -12.983105], + [40.463574, -10.464355], + [38.491797, -11.413281], + [37.920215, -11.294727], + [37.372852, -11.710449], + [36.305664, -11.706348], + [35.911328, -11.454688], + [34.959473, -11.578125], + [34.618555, -11.620215], + [34.357813, -12.164746], + [34.563672, -13.360156], + [35.247461, -13.896875], + [35.892773, -14.891797], + [35.755273, -16.058301], + [35.358496, -16.160547], + [35.167188, -16.560254], + [35.272559, -17.118457], + [34.248242, -15.8875], + [34.54082, -15.297266], + [34.505273, -14.598145], + [34.33252, -14.408594], + [33.636426, -14.568164], + [33.201758, -14.013379], + [30.231836, -14.990332], + [30.396094, -15.643066], + [30.437793, -15.995313], + [31.23623, -16.023633], + [32.948047, -16.712305], + [32.993066, -18.35957], + [32.699707, -18.940918], + [32.992773, -19.984863], + [32.492383, -20.659766], + [32.429785, -21.29707], + [31.287891, -22.402051], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '摩洛哥', full_name: '摩洛哥王国', iso_a2: 'MA', iso_a3: 'MAR', iso_n3: '504' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-2.219629, 35.104199], + [-2.839941, 35.127832], + [-2.972217, 35.407275], + [-4.62832, 35.206396], + [-5.252686, 35.614746], + [-5.277832, 35.902734], + [-5.924805, 35.785791], + [-6.900977, 33.969043], + [-8.512842, 33.252441], + [-9.24585, 32.572461], + [-9.808691, 31.424609], + [-9.66709, 30.109277], + [-10.200586, 29.380371], + [-11.552686, 28.310107], + [-12.948926, 27.91416], + [-13.575781, 26.735107], + [-14.413867, 26.253711], + [-14.904297, 24.719775], + [-15.899316, 23.844434], + [-17.003076, 21.420703], + [-14.750977, 21.500586], + [-14.221191, 22.310156], + [-13.891113, 23.691016], + [-12.431152, 24.830664], + [-12.060986, 25.99082], + [-11.718213, 26.104102], + [-11.392578, 26.883398], + [-9.817871, 26.850195], + [-8.794873, 27.120703], + [-8.68335, 27.656445], + [-8.678418, 28.689404], + [-7.685156, 29.349512], + [-5.448779, 29.956934], + [-4.968262, 30.465381], + [-3.666797, 30.964014], + [-3.826758, 31.661914], + [-3.017383, 31.834277], + [-2.887207, 32.068848], + [-1.225928, 32.107227], + [-1.065527, 32.468311], + [-1.679199, 33.318652], + [-1.795605, 34.751904], + [-2.219629, 35.104199], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '西撒哈拉', full_name: '西撒哈拉', iso_a2: 'EH', iso_a3: 'ESH', iso_n3: '732' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-8.68335, 27.656445], + [-8.794873, 27.120703], + [-9.817871, 26.850195], + [-11.392578, 26.883398], + [-11.718213, 26.104102], + [-12.060986, 25.99082], + [-12.431152, 24.830664], + [-13.891113, 23.691016], + [-14.221191, 22.310156], + [-14.750977, 21.500586], + [-17.003076, 21.420703], + [-17.048047, 20.806152], + [-16.964551, 21.329248], + [-13.016211, 21.333936], + [-13.153271, 22.820508], + [-12.023438, 23.467578], + [-12.016309, 25.99541], + [-8.682227, 25.995508], + [-8.68335, 27.285938], + [-8.68335, 27.656445], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '黑山', full_name: '黑山', iso_a2: 'ME', iso_a3: 'MNE', iso_n3: '499' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [19.194336, 43.533301], + [18.460156, 42.9979], + [18.436328, 42.559717], + [18.51748, 42.43291], + [19.342383, 41.869092], + [19.654492, 42.628564], + [20.063965, 42.547266], + [20.344336, 42.82793], + [19.194336, 43.533301], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '蒙古', full_name: '蒙古国', iso_a2: 'MN', iso_a3: 'MNG', iso_n3: '496' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [87.814258, 49.162305], + [87.979688, 48.555127], + [89.047656, 48.002539], + [90.02793, 47.877686], + [90.869922, 46.954492], + [90.877246, 45.196094], + [93.516211, 44.944482], + [95.350293, 44.278076], + [96.385449, 42.720361], + [101.495313, 42.53877], + [102.156641, 42.158105], + [103.711133, 41.751318], + [104.498242, 41.877002], + [104.498242, 41.658691], + [104.982031, 41.595508], + [106.77002, 42.288721], + [109.339844, 42.438379], + [110.400391, 42.773682], + [111.933203, 43.711426], + [111.402246, 44.367285], + [111.898047, 45.064062], + [113.587012, 44.745703], + [114.560156, 45.38999], + [115.681055, 45.458252], + [116.562598, 46.289795], + [117.333398, 46.362012], + [117.438086, 46.58623], + [119.867188, 46.672168], + [119.711133, 47.15], + [118.498438, 47.983984], + [117.768359, 47.987891], + [117.350781, 47.652197], + [116.760547, 47.869775], + [115.898242, 47.686914], + [115.616406, 47.874805], + [116.683301, 49.823779], + [116.216797, 50.009277], + [115.429199, 49.896484], + [114.29707, 50.274414], + [112.806445, 49.523584], + [110.709766, 49.142969], + [108.613672, 49.322803], + [107.916602, 49.947803], + [107.233301, 49.989404], + [106.711133, 50.312598], + [105.383594, 50.47373], + [103.304395, 50.200293], + [102.288379, 50.585107], + [102.111523, 51.353467], + [98.893164, 52.117285], + [97.835742, 51.05166], + [98.250293, 50.302441], + [97.359766, 49.741455], + [94.614746, 50.02373], + [94.251074, 50.556396], + [92.354785, 50.86416], + [90.053711, 50.09375], + [89.395605, 49.611523], + [88.192578, 49.451709], + [87.814258, 49.162305], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '摩尔多瓦', full_name: '摩尔多瓦共和国', iso_a2: 'MD', iso_a3: 'MDA', iso_n3: '498' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [26.618945, 48.259863], + [28.071777, 46.978418], + [28.2125, 45.450439], + [28.947754, 46.049951], + [28.958398, 46.458496], + [30.131055, 46.423096], + [29.134863, 47.489697], + [29.125391, 47.964551], + [27.549219, 48.477734], + [26.618945, 48.259863], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '摩纳哥', full_name: '摩纳哥公国', iso_a2: 'MC', iso_a3: 'MCO', iso_n3: '492' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [7.438672, 43.750439], + [7.39502, 43.765332], + [7.377734, 43.731738], + [7.438672, 43.750439], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '墨西哥', full_name: '墨西哥合众国', iso_a2: 'MX', iso_a3: 'MEX', iso_n3: '484' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-117.128271, 32.53335], + [-115.673828, 29.756396], + [-114.048486, 28.426172], + [-114.265869, 27.934473], + [-114.069336, 27.675684], + [-114.300586, 27.872998], + [-114.993506, 27.736035], + [-113.598535, 26.721289], + [-113.155811, 26.94624], + [-113.020752, 26.583252], + [-112.377246, 26.213916], + [-112.069873, 25.572852], + [-112.072559, 24.840039], + [-110.362695, 23.604932], + [-110.00625, 22.894043], + [-109.495703, 23.159814], + [-109.42085, 23.480127], + [-110.262891, 24.344531], + [-110.367432, 24.100488], + [-110.659326, 24.341455], + [-111.569678, 26.707617], + [-111.795264, 26.879688], + [-111.699414, 26.580957], + [-111.862646, 26.678516], + [-112.734033, 27.825977], + [-112.87085, 28.424219], + [-114.550488, 30.022266], + [-114.933594, 31.900732], + [-113.046729, 31.179248], + [-113.057666, 30.651025], + [-112.161768, 29.018896], + [-111.121387, 27.966992], + [-110.529883, 27.864209], + [-110.377295, 27.233301], + [-109.27627, 26.533887], + [-109.116699, 26.252734], + [-109.425635, 26.032568], + [-109.384961, 25.727148], + [-108.886572, 25.733447], + [-109.028809, 25.480469], + [-108.051465, 25.067041], + [-108.280762, 25.081543], + [-107.951172, 24.614893], + [-107.511914, 24.48916], + [-107.764941, 24.471924], + [-105.791797, 22.62749], + [-105.649121, 21.988086], + [-105.208691, 21.49082], + [-105.51084, 20.80874], + [-105.260156, 20.579053], + [-105.669434, 20.385596], + [-104.938477, 19.309375], + [-103.912451, 18.828467], + [-103.441602, 18.325391], + [-101.918701, 17.959766], + [-100.847803, 17.200488], + [-97.754785, 15.966846], + [-96.213574, 15.693066], + [-94.799414, 16.209668], + [-95.02085, 16.277637], + [-94.858691, 16.419727], + [-94.587109, 16.31582], + [-94.661523, 16.201904], + [-94.00127, 16.018945], + [-94.37417, 16.284766], + [-93.166895, 15.448047], + [-92.235156, 14.54541], + [-92.204248, 15.275], + [-91.736572, 16.070166], + [-90.447168, 16.072705], + [-90.416992, 16.391016], + [-91.409619, 17.255859], + [-90.992969, 17.252441], + [-90.98916, 17.816406], + [-89.161475, 17.814844], + [-88.295654, 18.472412], + [-88.031738, 18.838916], + [-87.881982, 18.273877], + [-87.761816, 18.446143], + [-87.424756, 19.58335], + [-87.687695, 19.637109], + [-86.771777, 21.150537], + [-87.034766, 21.592236], + [-88.466699, 21.569385], + [-90.353125, 21.009424], + [-90.739258, 19.352246], + [-91.43667, 18.889795], + [-91.275244, 18.624463], + [-91.533984, 18.456543], + [-92.441016, 18.675293], + [-94.459766, 18.16665], + [-95.181836, 18.700732], + [-95.920361, 18.81958], + [-97.186328, 20.717041], + [-97.753809, 22.02666], + [-97.40918, 21.272559], + [-97.314502, 21.564209], + [-97.84248, 22.510303], + [-97.667676, 24.38999], + [-97.14624, 25.961475], + [-99.107764, 26.446924], + [-99.505322, 27.54834], + [-101.440381, 29.776855], + [-102.614941, 29.752344], + [-103.168311, 28.998193], + [-104.110596, 29.386133], + [-104.978809, 30.645947], + [-106.44541, 31.768408], + [-108.211816, 31.779346], + [-108.214453, 31.329443], + [-111.041992, 31.324219], + [-114.835938, 32.508301], + [-114.724756, 32.715332], + [-117.128271, 32.53335], + ], + ], + [ + [ + [-115.170605, 28.069385], + [-115.233545, 28.368359], + [-115.35293, 28.103955], + [-115.170605, 28.069385], + ], + ], + [ + [ + [-112.203076, 29.005322], + [-112.423535, 29.203662], + [-112.514062, 28.847607], + [-112.278418, 28.769336], + [-112.203076, 29.005322], + ], + ], + [ + [ + [-112.057275, 24.545703], + [-112.159424, 25.285645], + [-112.296777, 24.789648], + [-112.057275, 24.545703], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '毛里求斯', full_name: '毛里求斯共和国', iso_a2: 'MU', iso_a3: 'MUS', iso_n3: '480' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [57.65127, -20.484863], + [57.656543, -19.989941], + [57.317676, -20.427637], + [57.65127, -20.484863], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '毛里塔尼亚', + full_name: '毛里塔尼亚伊斯兰共和国', + iso_a2: 'MR', + iso_a3: 'MRT', + iso_n3: '478', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-12.280615, 14.809033], + [-11.940918, 14.886914], + [-11.502686, 15.636816], + [-10.895605, 15.150488], + [-10.696582, 15.422656], + [-9.446924, 15.458203], + [-9.350586, 15.677393], + [-9.176807, 15.496094], + [-5.5125, 15.496289], + [-5.359912, 16.282861], + [-5.628662, 16.568652], + [-5.941016, 19.296191], + [-6.594092, 24.994629], + [-4.822607, 24.995605], + [-8.68335, 27.285938], + [-8.682227, 25.995508], + [-12.016309, 25.99541], + [-12.023438, 23.467578], + [-13.153271, 22.820508], + [-13.016211, 21.333936], + [-16.964551, 21.329248], + [-17.048047, 20.806152], + [-16.92793, 21.114795], + [-16.210449, 20.22793], + [-16.514453, 19.361963], + [-16.213086, 19.00332], + [-16.030322, 17.887939], + [-16.535254, 15.838379], + [-16.239014, 16.531299], + [-14.53374, 16.655957], + [-13.409668, 16.05918], + [-12.280615, 14.809033], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马耳他', full_name: '马耳他共和国', iso_a2: 'MT', iso_a3: 'MLT', iso_n3: '470' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [14.566211, 35.852734], + [14.35127, 35.978418], + [14.436426, 35.82168], + [14.566211, 35.852734], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马里', full_name: '马里共和国', iso_a2: 'ML', iso_a3: 'MLI', iso_n3: '466' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-11.389404, 12.404395], + [-11.502197, 12.198633], + [-11.305176, 12.01543], + [-10.933203, 12.205176], + [-10.709229, 11.89873], + [-10.274854, 12.212646], + [-9.754004, 12.029932], + [-9.358105, 12.25542], + [-9.395361, 12.464648], + [-9.043066, 12.402344], + [-8.822021, 11.673242], + [-8.398535, 11.366553], + [-8.666699, 11.009473], + [-8.337402, 10.990625], + [-7.990625, 10.1625], + [-7.497949, 10.439795], + [-7.01709, 10.143262], + [-6.65415, 10.656445], + [-6.261133, 10.724072], + [-6.196875, 10.232129], + [-5.523535, 10.426025], + [-5.288135, 11.82793], + [-4.428711, 12.337598], + [-4.151025, 13.306201], + [-3.301758, 13.280762], + [-3.248633, 13.65835], + [-2.95083, 13.648438], + [-2.586719, 14.227588], + [-2.113232, 14.168457], + [-1.973047, 14.456543], + [-0.760449, 15.047754], + [0.21748, 14.911475], + [0.947461, 14.982129], + [1.300195, 15.272266], + [3.504297, 15.356348], + [3.842969, 15.701709], + [4.234668, 16.996387], + [4.227637, 19.142773], + [3.119727, 19.103174], + [3.130273, 19.850195], + [1.685449, 20.378369], + [1.145508, 21.102246], + [-4.822607, 24.995605], + [-6.594092, 24.994629], + [-5.941016, 19.296191], + [-5.628662, 16.568652], + [-5.359912, 16.282861], + [-5.5125, 15.496289], + [-9.176807, 15.496094], + [-9.350586, 15.677393], + [-9.446924, 15.458203], + [-10.696582, 15.422656], + [-10.895605, 15.150488], + [-11.502686, 15.636816], + [-11.940918, 14.886914], + [-12.280615, 14.809033], + [-12.054199, 13.633057], + [-11.390381, 12.941992], + [-11.389404, 12.404395], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马尔代夫', full_name: '马尔代夫共和国', iso_a2: 'MV', iso_a3: 'MDV', iso_n3: '462' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [73.512207, 4.164551], + [73.517773, 4.247656], + [73.473047, 4.170703], + [73.512207, 4.164551], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马来西亚', full_name: '马来西亚', iso_a2: 'MY', iso_a3: 'MYS', iso_n3: '458' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [100.119141, 6.441992], + [100.71543, 3.966211], + [101.299902, 3.253271], + [101.295508, 2.885205], + [103.480273, 1.329492], + [103.694531, 1.449658], + [103.991504, 1.454785], + [103.981445, 1.623633], + [104.250098, 1.388574], + [103.812207, 2.580469], + [103.439453, 2.933105], + [103.41582, 4.850293], + [103.09707, 5.408447], + [102.101074, 6.242236], + [101.873633, 5.825293], + [101.556055, 5.907764], + [101.113965, 5.636768], + [101.053516, 6.242578], + [100.261426, 6.682715], + [100.119141, 6.441992], + ], + ], + [ + [ + [117.574414, 4.170605], + [118.54834, 4.379248], + [118.260547, 4.988867], + [118.9125, 5.0229], + [119.266309, 5.308105], + [118.353125, 5.806055], + [117.973633, 5.70625], + [118.003809, 6.05332], + [117.501172, 5.884668], + [117.69375, 6.35], + [117.229883, 6.93999], + [116.788086, 6.606104], + [116.749805, 6.9771], + [115.877148, 5.613525], + [115.419043, 5.413184], + [115.554492, 5.093555], + [115.140039, 4.899756], + [115.290625, 4.352588], + [115.026758, 4.899707], + [114.74668, 4.718066], + [114.654102, 4.037646], + [114.063867, 4.592676], + [112.987891, 3.161914], + [111.5125, 2.743018], + [111.028711, 1.557812], + [111.223242, 1.39585], + [109.864844, 1.764453], + [109.628906, 2.027539], + [109.654004, 1.614893], + [110.505762, 0.861963], + [111.808984, 1.01167], + [112.476172, 1.559082], + [113.622266, 1.235938], + [114.5125, 1.452002], + [114.786426, 2.250488], + [115.179102, 2.523193], + [115.117578, 2.894873], + [115.454395, 3.034326], + [115.678809, 4.193018], + [116.514746, 4.370801], + [117.574414, 4.170605], + ], + ], + [ + [ + [99.848047, 6.465723], + [99.646289, 6.418359], + [99.74375, 6.263281], + [99.848047, 6.465723], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马拉维', full_name: '马拉维共和国', iso_a2: 'MW', iso_a3: 'MWI', iso_n3: '454' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [33.201758, -14.013379], + [33.636426, -14.568164], + [34.33252, -14.408594], + [34.505273, -14.598145], + [34.54082, -15.297266], + [34.248242, -15.8875], + [35.272559, -17.118457], + [35.167188, -16.560254], + [35.358496, -16.160547], + [35.755273, -16.058301], + [35.892773, -14.891797], + [35.247461, -13.896875], + [34.563672, -13.360156], + [34.357813, -12.164746], + [34.618555, -11.620215], + [34.959473, -11.578125], + [34.60791, -11.080469], + [34.320898, -9.731543], + [33.995605, -9.49541], + [33.888867, -9.670117], + [32.919922, -9.407422], + [33.661523, -10.553125], + [33.261328, -10.893359], + [33.252344, -12.112598], + [33.512305, -12.347754], + [33.021582, -12.630469], + [32.67041, -13.59043], + [33.201758, -14.013379], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '马达加斯加', full_name: '马达加斯加共和国', iso_a2: 'MG', iso_a3: 'MDG', iso_n3: '450' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [49.538281, -12.432129], + [49.207031, -12.07959], + [48.786328, -12.470898], + [48.796484, -13.26748], + [48.255273, -13.719336], + [47.941016, -13.662402], + [47.77334, -14.369922], + [47.96416, -14.672559], + [47.47832, -15.009375], + [47.351953, -14.766113], + [47.099219, -15.43418], + [46.942285, -15.219043], + [46.475098, -15.513477], + [46.399609, -15.924609], + [46.15752, -15.738281], + [44.476172, -16.217285], + [43.979395, -17.391602], + [44.404688, -19.92207], + [43.501855, -21.356445], + [43.264844, -22.383594], + [44.035352, -24.995703], + [45.205762, -25.570508], + [46.728516, -25.149902], + [47.177344, -24.787207], + [49.362891, -18.336328], + [49.449316, -17.240625], + [49.839063, -16.486523], + [49.664355, -15.521582], + [49.892578, -15.457715], + [50.208984, -15.960449], + [50.482715, -15.385645], + [49.9375, -13.072266], + [49.538281, -12.432129], + ], + ], + [ + [ + [49.936426, -16.90293], + [50.023047, -16.695312], + [49.824023, -17.086523], + [49.936426, -16.90293], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '北马其顿', full_name: '北马其顿共和国', iso_a2: 'MK', iso_a3: 'MKD', iso_n3: '807' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [21.5625, 42.24751], + [20.566211, 41.873682], + [20.488965, 41.272607], + [20.964258, 40.849902], + [22.916016, 41.336279], + [23.003613, 41.739844], + [22.344043, 42.313965], + [21.5625, 42.24751], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '卢森堡', full_name: '卢森堡大公国', iso_a2: 'LU', iso_a3: 'LUX', iso_n3: '442' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [6.116504, 50.120996], + [5.744043, 49.919629], + [5.789746, 49.538281], + [6.344336, 49.452734], + [6.487305, 49.798486], + [6.116504, 50.120996], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '立陶宛', full_name: '立陶宛共和国', iso_a2: 'LT', iso_a3: 'LTU', iso_n3: '440' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [20.957813, 55.278906], + [21.114844, 55.616504], + [20.899805, 55.28667], + [20.957813, 55.278906], + ], + ], + [ + [ + [22.766211, 54.356787], + [23.484668, 53.939795], + [24.317969, 53.892969], + [25.461133, 54.292773], + [25.749219, 54.156982], + [25.780859, 54.833252], + [26.775684, 55.273096], + [26.457617, 55.34248], + [26.593555, 55.667529], + [24.841016, 56.411182], + [24.120703, 56.264258], + [22.08457, 56.406738], + [21.046094, 56.070068], + [21.235742, 55.264111], + [22.567285, 55.059131], + [22.766211, 54.356787], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '列支敦士登', full_name: '列支敦士登公国', iso_a2: 'LI', iso_a3: 'LIE', iso_n3: '438' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [9.580273, 47.057373], + [9.527539, 47.270752], + [9.487695, 47.062256], + [9.580273, 47.057373], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '利比亚', full_name: '利比亚国', iso_a2: 'LY', iso_a3: 'LBY', iso_n3: '434' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [9.51875, 30.229395], + [9.310254, 30.115234], + [9.805273, 29.176953], + [9.916016, 27.785693], + [9.883203, 26.630811], + [9.448242, 26.067139], + [10.255859, 24.591016], + [11.507617, 24.314355], + [11.967871, 23.517871], + [13.48125, 23.180176], + [14.215527, 22.619678], + [14.979004, 22.996191], + [15.984082, 23.445215], + [20.147656, 21.389258], + [23.980273, 19.496631], + [23.980273, 19.995947], + [24.979492, 20.002588], + [24.980273, 21.99585], + [24.980273, 29.181885], + [24.703223, 30.201074], + [24.961426, 30.678516], + [24.852734, 31.334814], + [25.150488, 31.65498], + [24.878516, 31.984277], + [23.286328, 32.213818], + [23.090625, 32.61875], + [21.635938, 32.937305], + [20.121484, 32.21875], + [19.926367, 31.817529], + [20.111523, 30.963721], + [19.713281, 30.488379], + [19.12373, 30.266113], + [17.830469, 30.927588], + [15.705957, 31.426416], + [15.176563, 32.391162], + [13.283496, 32.914648], + [12.279883, 32.858545], + [11.50459, 33.181934], + [11.50498, 32.413672], + [10.274609, 31.684961], + [10.216406, 30.783203], + [9.51875, 30.229395], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '利比里亚', full_name: '利比里亚共和国', iso_a2: 'LR', iso_a3: 'LBR', iso_n3: '430' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-11.50752, 6.906543], + [-9.132178, 5.054639], + [-7.544971, 4.351318], + [-7.454395, 5.841309], + [-8.603564, 6.507812], + [-8.302344, 6.980957], + [-8.486426, 7.558496], + [-8.659766, 7.688379], + [-9.117578, 7.215918], + [-9.463818, 7.415869], + [-9.518262, 8.346094], + [-10.283203, 8.485156], + [-10.647461, 7.759375], + [-11.50752, 6.906543], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '莱索托', full_name: '莱索托王国', iso_a2: 'LS', iso_a3: 'LSO', iso_n3: '426' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [28.736914, -30.101953], + [29.098047, -29.919043], + [29.390723, -29.269727], + [28.625781, -28.581738], + [27.735547, -28.940039], + [27.056934, -29.625586], + [27.753125, -30.6], + [28.056836, -30.631055], + [28.39209, -30.147559], + [28.736914, -30.101953], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '黎巴嫩', full_name: '黎巴嫩共和国', iso_a2: 'LB', iso_a3: 'LBN', iso_n3: '422' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35.97627, 34.629199], + [35.108594, 33.083691], + [35.840723, 33.415674], + [35.869141, 33.431738], + [36.584961, 34.22124], + [36.383887, 34.65791], + [35.97627, 34.629199], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '拉脱维亚', full_name: '拉脱维亚共和国', iso_a2: 'LV', iso_a3: 'LVA', iso_n3: '428' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [26.593555, 55.667529], + [27.576758, 55.798779], + [28.147949, 56.14292], + [27.639453, 56.845654], + [27.828613, 57.293311], + [27.351953, 57.528125], + [26.462109, 57.544482], + [25.282617, 58.048486], + [24.322559, 57.870605], + [24.382617, 57.250049], + [23.647754, 56.971045], + [22.55459, 57.724268], + [21.728711, 57.570996], + [21.071289, 56.82373], + [21.046094, 56.070068], + [22.08457, 56.406738], + [24.120703, 56.264258], + [24.841016, 56.411182], + [26.593555, 55.667529], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '老挝', full_name: '老挝人民民主共和国', iso_a2: 'LA', iso_a3: 'LAO', iso_n3: '418' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [102.127441, 22.379199], + [101.73877, 22.495264], + [101.524512, 22.253662], + [101.800586, 21.212598], + [101.247852, 21.197314], + [101.138867, 21.56748], + [100.703125, 21.251367], + [100.622949, 20.85957], + [100.249316, 20.730273], + [100.122461, 20.31665], + [100.519531, 20.17793], + [100.513574, 19.553467], + [101.211914, 19.54834], + [100.955859, 17.541113], + [102.101465, 18.210645], + [102.680078, 17.824121], + [103.366992, 18.42334], + [103.949609, 18.318994], + [104.739648, 17.46167], + [104.819336, 16.466064], + [105.641016, 15.656543], + [105.497363, 14.590674], + [105.183301, 14.34624], + [106.066797, 13.921191], + [105.978906, 14.343018], + [106.501465, 14.578223], + [106.938086, 14.327344], + [107.519434, 14.705078], + [107.653125, 15.255225], + [107.165918, 15.80249], + [107.396387, 16.043018], + [106.656445, 16.492627], + [106.502246, 16.954102], + [105.114551, 18.405273], + [105.146484, 18.650977], + [103.891602, 19.30498], + [104.032031, 19.675146], + [104.587891, 19.61875], + [104.92793, 20.018115], + [104.367773, 20.441406], + [104.583203, 20.64668], + [104.101367, 20.945508], + [103.635059, 20.69707], + [103.104492, 20.89165], + [102.851172, 21.265918], + [102.949609, 21.681348], + [102.662012, 21.676025], + [102.127441, 22.379199], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '吉尔吉斯斯坦', full_name: '吉尔吉斯共和国', iso_a2: 'KG', iso_a3: 'KGZ', iso_n3: '417' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [70.958008, 40.238867], + [70.515137, 39.949902], + [69.966797, 40.202246], + [69.530273, 40.097314], + [69.297656, 39.524805], + [70.501172, 39.587354], + [70.799316, 39.394727], + [71.470312, 39.603662], + [72.22998, 39.20752], + [73.631641, 39.448877], + [73.991602, 40.043115], + [74.835156, 40.482617], + [75.555566, 40.625195], + [75.677148, 40.305811], + [76.318555, 40.352246], + [76.907715, 41.02417], + [78.123438, 41.075635], + [80.209375, 42.190039], + [79.12666, 42.775732], + [75.366211, 42.836963], + [74.209082, 43.240381], + [73.55625, 43.002783], + [73.492969, 42.409033], + [71.760547, 42.821484], + [71.256641, 42.733545], + [70.946777, 42.248682], + [71.228516, 42.162891], + [70.200879, 41.514453], + [71.393066, 41.123389], + [71.664941, 41.541211], + [72.187305, 41.025928], + [73.136914, 40.810645], + [71.69248, 40.152344], + [70.958008, 40.238867], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '科威特', full_name: '科威特国', iso_a2: 'KW', iso_a3: 'KWT', iso_n3: '414' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [48.44248, 28.54292], + [48.051465, 29.355371], + [47.722656, 29.393018], + [48.143457, 29.572461], + [47.978711, 29.982812], + [47.148242, 30.000977], + [46.531445, 29.09624], + [47.433203, 28.989551], + [47.671289, 28.533154], + [48.44248, 28.54292], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '科索沃', full_name: '科索沃', iso_a2: 'XK', iso_a3: '-99', iso_n3: '-99' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [20.344336, 42.82793], + [20.063965, 42.547266], + [20.566211, 41.873682], + [21.5625, 42.24751], + [21.75293, 42.669824], + [20.800586, 43.261084], + [20.344336, 42.82793], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '基里巴斯', full_name: '基里巴斯共和国', iso_a2: 'KI', iso_a3: 'KIR', iso_n3: '296' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-157.342139, 1.855566], + [-157.578955, 1.902051], + [-157.175781, 1.739844], + [-157.342139, 1.855566], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '肯尼亚', full_name: '肯尼亚共和国', iso_a2: 'KE', iso_a3: 'KEN', iso_n3: '404' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [33.903223, -1.002051], + [37.643848, -3.04541], + [37.608203, -3.49707], + [39.221777, -4.692383], + [40.11543, -3.250586], + [40.222461, -2.688379], + [41.532715, -1.695312], + [40.978711, -0.870313], + [40.964453, 2.814648], + [41.883984, 3.977734], + [41.14043, 3.962988], + [40.765234, 4.273047], + [39.494434, 3.456104], + [38.086133, 3.648828], + [36.905566, 4.411475], + [36.021973, 4.468115], + [35.74502, 5.343994], + [35.268359, 5.492285], + [33.976074, 4.220215], + [34.437695, 3.650586], + [34.978223, 1.773633], + [33.943164, 0.173779], + [33.903223, -1.002051], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '哈萨克斯坦', full_name: '哈萨克斯坦共和国', iso_a2: 'KZ', iso_a3: 'KAZ', iso_n3: '398' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [70.946777, 42.248682], + [71.256641, 42.733545], + [71.760547, 42.821484], + [73.492969, 42.409033], + [73.55625, 43.002783], + [74.209082, 43.240381], + [75.366211, 42.836963], + [79.12666, 42.775732], + [80.209375, 42.190039], + [80.202246, 42.734473], + [80.785742, 43.161572], + [80.355273, 44.097266], + [80.481543, 44.714648], + [79.871875, 44.883789], + [81.691992, 45.349365], + [82.521484, 45.125488], + [82.315234, 45.594922], + [83.029492, 47.185938], + [84.786133, 46.830713], + [85.484766, 47.063525], + [85.749414, 48.385059], + [86.549414, 48.528613], + [86.808301, 49.049707], + [87.322852, 49.085791], + [86.675488, 49.777295], + [86.180859, 49.499316], + [85.232617, 49.61582], + [84.989453, 50.061426], + [84.323242, 50.23916], + [83.357324, 50.99458], + [82.493945, 50.727588], + [81.465918, 50.739844], + [80.735254, 51.293408], + [79.98623, 50.774561], + [77.859961, 53.269189], + [76.484766, 54.022559], + [76.837305, 54.442383], + [74.351562, 53.487646], + [73.858984, 53.619727], + [73.406934, 53.447559], + [73.712402, 54.042383], + [72.622266, 54.134326], + [72.446777, 53.941846], + [72.186035, 54.325635], + [71.093164, 54.212207], + [70.738086, 55.305176], + [70.182422, 55.162451], + [68.977246, 55.3896], + [68.155859, 54.976709], + [65.476953, 54.623291], + [65.088379, 54.340186], + [61.231055, 54.019482], + [60.979492, 53.621729], + [61.534961, 53.523291], + [61.199219, 53.287158], + [62.082715, 53.00542], + [61.047461, 52.972461], + [60.774414, 52.675781], + [60.994531, 52.336865], + [60.030273, 51.933252], + [61.554688, 51.324609], + [61.389453, 50.861035], + [60.942285, 50.695508], + [60.058594, 50.850293], + [59.523047, 50.492871], + [57.838867, 51.09165], + [57.442188, 50.888867], + [56.491406, 51.019531], + [55.68623, 50.582861], + [54.641602, 51.011572], + [54.555273, 50.535791], + [53.338086, 51.482373], + [52.219141, 51.709375], + [51.344531, 51.475342], + [50.793945, 51.729199], + [48.625098, 50.612695], + [48.758984, 49.92832], + [48.334961, 49.858252], + [47.429199, 50.357959], + [46.889551, 49.696973], + [47.031348, 49.150293], + [46.660938, 48.412256], + [47.292383, 47.740918], + [48.166992, 47.708789], + [48.959375, 46.774609], + [48.541211, 46.605615], + [49.232227, 46.337158], + [51.178027, 47.110156], + [52.138281, 46.828613], + [52.483203, 46.990674], + [53.069434, 46.856055], + [53.135254, 46.19165], + [52.773828, 45.572754], + [53.200391, 45.331982], + [51.415723, 45.357861], + [51.009375, 44.921826], + [51.543555, 44.531006], + [50.409473, 44.624023], + [50.25293, 44.461523], + [50.830762, 44.192773], + [51.29541, 43.174121], + [52.596582, 42.760156], + [52.493848, 41.780371], + [53.055859, 42.147754], + [54.120996, 42.335205], + [55.434375, 41.296289], + [55.977441, 41.322217], + [55.975684, 44.994922], + [58.555273, 45.555371], + [61.00791, 44.393799], + [61.990234, 43.492139], + [64.905469, 43.714697], + [65.803027, 42.876953], + [66.100293, 42.99082], + [66.00957, 42.004883], + [66.498633, 41.994873], + [66.709668, 41.17915], + [67.935742, 41.196582], + [68.291895, 40.656104], + [68.572656, 40.622656], + [69.153613, 41.425244], + [70.946777, 42.248682], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '约旦', full_name: '约旦哈希姆王国', iso_a2: 'JO', iso_a3: 'JOR', iso_n3: '400' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35.787305, 32.734912], + [35.551465, 32.395508], + [35.450586, 31.479297], + [34.973438, 29.555029], + [34.950781, 29.353516], + [36.068457, 29.200537], + [36.755273, 29.866016], + [37.469238, 29.995068], + [37.980078, 30.5], + [36.958594, 31.491504], + [39.14541, 32.124512], + [38.773535, 33.372217], + [36.818359, 32.317285], + [35.787305, 32.734912], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '日本', full_name: '日本国', iso_a2: 'JP', iso_a3: 'JPN', iso_n3: '392' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [134.932812, 34.288135], + [135.004688, 34.544043], + [134.667871, 34.294141], + [134.932812, 34.288135], + ], + ], + [ + [ + [143.824316, 44.116992], + [141.937695, 45.509521], + [141.667969, 45.40127], + [141.760938, 44.48252], + [141.374121, 43.279639], + [140.392383, 43.303125], + [140.432227, 42.954102], + [139.860156, 42.581738], + [140.085156, 41.434082], + [140.659863, 41.815576], + [141.150977, 41.805078], + [140.32666, 42.293359], + [140.480469, 42.559375], + [140.986133, 42.342139], + [141.851367, 42.579053], + [143.236523, 42.000195], + [143.969336, 42.881396], + [145.833008, 43.385938], + [145.34082, 43.302539], + [145.139648, 43.6625], + [145.369531, 44.327393], + [144.715234, 43.927979], + [143.824316, 44.116992], + ], + ], + [ + [ + [131.174609, 33.602588], + [130.715625, 33.927783], + [129.580078, 33.236279], + [129.991699, 32.851562], + [129.679102, 33.059961], + [129.768555, 32.570996], + [130.34043, 32.701855], + [130.2375, 33.177637], + [130.547266, 32.831592], + [130.147266, 31.408496], + [130.58877, 31.178516], + [130.77627, 31.706299], + [130.685742, 31.015137], + [131.070801, 31.436865], + [131.337207, 31.404688], + [132.002148, 32.882373], + [131.896582, 33.25459], + [131.537402, 33.274072], + [131.696289, 33.602832], + [131.174609, 33.602588], + ], + ], + [ + [ + [134.357422, 34.256348], + [133.94834, 34.348047], + [133.193066, 33.933203], + [132.935156, 34.095312], + [132.032617, 33.33999], + [132.412793, 33.430469], + [132.495117, 32.916602], + [132.804297, 32.752002], + [133.632031, 33.510986], + [134.181641, 33.247217], + [134.738867, 33.820508], + [134.6375, 34.226611], + [134.357422, 34.256348], + ], + ], + [ + [ + [141.229297, 41.372656], + [140.936914, 41.505566], + [140.801855, 41.253662], + [141.244238, 41.205615], + [141.118555, 40.882275], + [140.748633, 40.830322], + [140.627637, 41.19541], + [140.344434, 41.20332], + [139.741504, 39.92085], + [140.064746, 39.624414], + [139.801953, 38.881592], + [139.363867, 38.099023], + [138.319922, 37.218408], + [137.246289, 36.753174], + [136.899902, 37.117676], + [137.322656, 37.52207], + [136.843457, 37.382129], + [135.903125, 35.606885], + [135.326953, 35.525537], + [135.174316, 35.74707], + [132.922949, 35.511279], + [131.354395, 34.413184], + [131.004199, 34.392578], + [130.918848, 33.975732], + [131.740527, 34.052051], + [132.146484, 33.83877], + [132.312598, 34.324951], + [133.142383, 34.302441], + [134.740039, 34.765234], + [135.415918, 34.61748], + [135.12793, 34.006982], + [135.695312, 33.486963], + [136.329883, 34.176855], + [136.853711, 34.324072], + [136.533008, 34.678369], + [136.804199, 35.050293], + [136.871289, 34.733105], + [137.275195, 34.77251], + [137.061719, 34.582812], + [138.189063, 34.596338], + [138.719629, 35.124072], + [138.8375, 34.619238], + [139.249414, 35.278027], + [139.675, 35.149268], + [139.834766, 35.658057], + [140.096875, 35.585156], + [139.843945, 34.914893], + [140.354688, 35.181445], + [140.874023, 35.724951], + [140.573535, 36.231348], + [141.00166, 37.114648], + [140.962109, 38.148877], + [141.46748, 38.40415], + [141.976953, 39.428809], + [141.430469, 40.72334], + [141.455469, 41.404736], + [141.229297, 41.372656], + ], + ], + [ + [ + [128.258789, 26.652783], + [128.254883, 26.881885], + [127.907227, 26.693604], + [127.653125, 26.094727], + [128.258789, 26.652783], + ], + ], + [ + [ + [129.452539, 28.208984], + [129.689551, 28.51748], + [129.164648, 28.249756], + [129.452539, 28.208984], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '牙买加', full_name: '牙买加', iso_a2: 'JM', iso_a3: 'JAM', iso_n3: '388' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-77.261475, 18.457422], + [-77.873438, 18.522217], + [-78.339502, 18.287207], + [-77.20498, 17.714941], + [-76.853223, 17.97373], + [-76.210791, 17.913525], + [-77.261475, 18.457422], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '意大利', full_name: '意大利共和国', iso_a2: 'IT', iso_a3: 'ITA', iso_n3: '380' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [7.021094, 45.925781], + [6.790918, 45.740869], + [7.146387, 45.381738], + [6.634766, 45.068164], + [6.992676, 44.827295], + [6.900195, 44.335742], + [7.637207, 44.164844], + [7.493164, 43.767139], + [8.76582, 44.422314], + [10.188086, 43.94751], + [10.514844, 42.967529], + [11.141211, 42.389893], + [15.692773, 39.990186], + [16.209961, 38.941113], + [15.645801, 38.034229], + [16.056836, 37.941846], + [16.616699, 38.800146], + [17.174609, 38.998096], + [17.114551, 39.380615], + [16.521875, 39.747559], + [16.928223, 40.458057], + [17.865039, 40.280176], + [18.34375, 39.821387], + [18.460645, 40.221045], + [15.900488, 41.512061], + [16.164648, 41.896191], + [15.16875, 41.934033], + [14.540723, 42.244287], + [13.56416, 43.571289], + [12.396289, 44.223877], + [12.274316, 45.446045], + [13.206348, 45.771387], + [13.719824, 45.587598], + [13.378223, 46.261621], + [13.7, 46.520264], + [12.388281, 46.702637], + [12.169434, 47.082129], + [10.993262, 46.777002], + [10.452832, 46.864941], + [10.12832, 46.238232], + [9.260156, 46.475195], + [9.02373, 45.845703], + [8.422559, 46.446045], + [7.787891, 45.921826], + [7.021094, 45.925781], + ], + [ + [12.43916, 41.898389], + [12.430566, 41.897559], + [12.430566, 41.905469], + [12.43916, 41.898389], + ], + [ + [12.485254, 43.901416], + [12.396875, 43.93457], + [12.503711, 43.989746], + [12.485254, 43.901416], + ], + ], + [ + [ + [15.576563, 38.220312], + [13.788867, 37.981201], + [12.734375, 38.183057], + [12.435547, 37.819775], + [15.112598, 36.687842], + [15.295703, 37.055176], + [15.099512, 37.458594], + [15.576563, 38.220312], + ], + ], + [ + [ + [9.632031, 40.882031], + [9.228418, 41.25708], + [8.571875, 40.850195], + [8.224219, 40.91333], + [8.648535, 38.926562], + [9.056348, 39.23916], + [9.5625, 39.166016], + [9.632031, 40.882031], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '以色列', full_name: '以色列国', iso_a2: 'IL', iso_a3: 'ISR', iso_n3: '376' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35.840723, 33.415674], + [35.108594, 33.083691], + [34.477344, 31.584863], + [34.245313, 31.208301], + [34.904297, 29.477344], + [34.973438, 29.555029], + [35.450586, 31.479297], + [34.872754, 31.396875], + [35.203711, 31.75], + [34.953809, 31.84126], + [35.065039, 32.460449], + [35.551465, 32.395508], + [35.787305, 32.734912], + [35.816125, 33.361879], + [35.840723, 33.415674], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴勒斯坦', full_name: '巴勒斯坦国', iso_a2: 'PS', iso_a3: 'PSE', iso_n3: '275' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [34.477344, 31.584863], + [34.198145, 31.322607], + [34.245313, 31.208301], + [34.477344, 31.584863], + ], + ], + [ + [ + [35.551465, 32.395508], + [35.065039, 32.460449], + [34.953809, 31.84126], + [35.203711, 31.75], + [34.872754, 31.396875], + [35.450586, 31.479297], + [35.551465, 32.395508], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '爱尔兰', full_name: '爱尔兰', iso_a2: 'IE', iso_a3: 'IRL', iso_n3: '372' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-7.218652, 55.091992], + [-6.96167, 55.237891], + [-7.308789, 55.36582], + [-7.65874, 54.970947], + [-7.66709, 55.256494], + [-8.274609, 55.146289], + [-8.763916, 54.681201], + [-8.133447, 54.64082], + [-8.545557, 54.241211], + [-10.056396, 54.257812], + [-9.578223, 53.80542], + [-10.09126, 53.412842], + [-8.930127, 53.20708], + [-9.916602, 52.569727], + [-8.783447, 52.679639], + [-10.356689, 52.206934], + [-9.909668, 52.122949], + [-10.341064, 51.798926], + [-9.598828, 51.874414], + [-10.120752, 51.600684], + [-8.813428, 51.584912], + [-8.40918, 51.88877], + [-6.325, 52.24668], + [-6.027393, 52.9271], + [-6.218018, 54.088721], + [-6.649805, 54.058643], + [-7.007715, 54.406689], + [-7.606543, 54.143848], + [-8.118262, 54.414258], + [-7.218652, 55.091992], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '伊拉克', full_name: '伊拉克共和国', iso_a2: 'IQ', iso_a3: 'IRQ', iso_n3: '368' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [42.358984, 37.108594], + [41.295996, 36.38335], + [40.987012, 34.429053], + [38.773535, 33.372217], + [39.14541, 32.124512], + [40.369336, 31.938965], + [42.074414, 31.080371], + [44.69082, 29.202344], + [46.531445, 29.09624], + [47.148242, 30.000977], + [47.978711, 29.982812], + [48.546484, 29.962354], + [48.014941, 30.465625], + [48.010645, 30.989795], + [47.679492, 31.002393], + [47.82998, 31.794434], + [47.371289, 32.42373], + [46.112793, 32.957666], + [46.019922, 33.415723], + [45.39707, 33.97085], + [46.273438, 35.773242], + [45.361621, 36.015332], + [44.765137, 37.142432], + [44.281836, 36.978027], + [44.114453, 37.301855], + [42.774609, 37.371875], + [42.358984, 37.108594], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '伊朗', full_name: '伊朗伊斯兰共和国', iso_a2: 'IR', iso_a3: 'IRN', iso_n3: '364' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [56.187988, 26.921143], + [55.757617, 26.947656], + [55.762598, 26.811963], + [55.311523, 26.592627], + [56.187988, 26.921143], + ], + ], + [ + [ + [53.91416, 37.343555], + [53.970117, 36.818311], + [52.190137, 36.621729], + [51.118555, 36.742578], + [50.130469, 37.407129], + [49.171191, 37.600586], + [48.86875, 38.435498], + [47.996484, 38.85376], + [48.322168, 39.399072], + [47.995898, 39.683936], + [46.490625, 38.906689], + [46.114453, 38.877783], + [45.479688, 39.00625], + [44.817188, 39.650439], + [44.587109, 39.768555], + [44.389355, 39.422119], + [44.023242, 39.377441], + [44.449902, 38.334229], + [44.211328, 37.908057], + [44.589941, 37.710352], + [44.765137, 37.142432], + [45.361621, 36.015332], + [46.273438, 35.773242], + [45.39707, 33.97085], + [46.019922, 33.415723], + [46.112793, 32.957666], + [47.371289, 32.42373], + [47.82998, 31.794434], + [47.679492, 31.002393], + [48.010645, 30.989795], + [48.014941, 30.465625], + [48.546484, 29.962354], + [48.919141, 30.120898], + [49.001953, 30.506543], + [49.554883, 30.028955], + [50.071582, 30.198535], + [51.278906, 28.131348], + [53.705762, 26.725586], + [54.759277, 26.505078], + [55.424023, 26.770557], + [55.650293, 26.977539], + [56.356152, 27.200244], + [56.982227, 26.905469], + [57.33457, 25.791553], + [59.046094, 25.417285], + [61.587891, 25.202344], + [61.842383, 26.225928], + [63.157812, 26.649756], + [63.301563, 27.151465], + [62.762988, 27.250195], + [62.758008, 28.243555], + [61.889844, 28.546533], + [60.843359, 29.858691], + [61.81084, 30.913281], + [61.660156, 31.382422], + [60.820703, 31.495166], + [60.561914, 33.058789], + [60.916992, 33.505225], + [60.51084, 33.638916], + [60.485742, 34.094775], + [60.889453, 34.319434], + [60.72627, 34.518262], + [61.262012, 35.61958], + [61.119629, 36.642578], + [60.341309, 36.637646], + [59.301758, 37.510645], + [58.261621, 37.66582], + [57.193555, 38.216406], + [55.380859, 38.051123], + [54.699414, 37.470166], + [53.91416, 37.343555], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '印度尼西亚', full_name: '印度尼西亚共和国', iso_a2: 'ID', iso_a3: 'IDN', iso_n3: '360' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [97.481543, 1.465088], + [97.079199, 1.425488], + [97.82041, 0.564453], + [97.931934, 0.973926], + [97.481543, 1.465088], + ], + ], + [ + [ + [99.163867, -1.77793], + [98.932617, -0.954004], + [98.676074, -0.970508], + [98.827734, -1.609961], + [99.163867, -1.77793], + ], + ], + [ + [ + [116.64082, -8.613867], + [116.718945, -8.336035], + [116.401563, -8.204199], + [115.857324, -8.787891], + [116.586523, -8.886133], + [116.64082, -8.613867], + ], + ], + [ + [ + [115.447852, -8.155176], + [114.467578, -8.166309], + [115.055078, -8.573047], + [115.144922, -8.849023], + [115.704297, -8.407129], + [115.447852, -8.155176], + ], + ], + [ + [ + [106.045703, -1.669434], + [105.45957, -1.574707], + [105.133398, -2.042578], + [105.78584, -2.181348], + [105.99873, -2.824902], + [106.667188, -3.071777], + [106.818457, -2.57334], + [106.365918, -2.464844], + [106.045703, -1.669434], + ], + ], + [ + [ + [123.179785, -4.551172], + [123.074609, -4.386914], + [122.85332, -4.618359], + [122.64502, -5.663379], + [123.187305, -5.333008], + [122.97168, -5.138477], + [123.179785, -4.551172], + ], + ], + [ + [ + [122.645117, -5.269434], + [122.701953, -4.618652], + [122.368945, -4.767188], + [122.283105, -5.319531], + [122.645117, -5.269434], + ], + ], + [ + [ + [108.316016, 3.689648], + [108.24834, 4.217139], + [108.002344, 3.982861], + [108.316016, 3.689648], + ], + ], + [ + [ + [108.207227, -2.997656], + [108.215137, -2.696973], + [107.666309, -2.566309], + [107.614453, -3.209375], + [108.055273, -3.226855], + [108.207227, -2.997656], + ], + ], + [ + [ + [135.383008, -0.651367], + [135.915039, -1.178418], + [136.375293, -1.094043], + [135.383008, -0.651367], + ], + ], + [ + [ + [135.474219, -1.591797], + [136.228125, -1.893652], + [136.892578, -1.799707], + [135.474219, -1.591797], + ], + ], + [ + [ + [130.813281, -0.004102], + [130.236621, -0.209668], + [130.750195, -0.443848], + [130.899219, -0.344434], + [130.622168, -0.085938], + [131.005371, -0.360742], + [131.339746, -0.290332], + [130.813281, -0.004102], + ], + ], + [ + [ + [128.453906, 2.051758], + [128.602148, 2.597607], + [128.217969, 2.297461], + [128.453906, 2.051758], + ], + ], + [ + [ + [128.153027, -1.660547], + [127.64668, -1.332422], + [127.39502, -1.589844], + [128.153027, -1.660547], + ], + ], + [ + [ + [126.024219, -1.789746], + [125.387207, -1.843066], + [126.331738, -1.822852], + [126.024219, -1.789746], + ], + ], + [ + [ + [124.969531, -1.705469], + [124.417578, -1.659277], + [124.329688, -1.858887], + [125.314063, -1.877148], + [124.969531, -1.705469], + ], + ], + [ + [ + [131.325586, -7.999512], + [131.624414, -7.626172], + [131.643457, -7.112793], + [131.137793, -7.684863], + [131.11377, -7.997363], + [131.325586, -7.999512], + ], + ], + [ + [ + [126.800977, -7.667871], + [125.975293, -7.663379], + [125.798242, -7.98457], + [126.47207, -7.950391], + [126.800977, -7.667871], + ], + ], + [ + [ + [124.575586, -8.14082], + [124.380664, -8.415137], + [125.131738, -8.326465], + [124.575586, -8.14082], + ], + ], + [ + [ + [131.001855, -1.315527], + [131.033008, -0.917578], + [130.672949, -0.959766], + [131.001855, -1.315527], + ], + ], + [ + [ + [96.492578, 5.229346], + [95.227832, 5.564795], + [95.578613, 4.661963], + [96.968945, 3.575146], + [97.59082, 2.846582], + [97.700781, 2.358545], + [98.595313, 1.8646], + [99.15918, 0.351758], + [99.669824, 0.045068], + [100.308203, -0.82666], + [100.889551, -2.248535], + [101.578613, -3.166992], + [104.601562, -5.90459], + [104.639551, -5.52041], + [105.081348, -5.745508], + [105.349414, -5.549512], + [105.74834, -5.818262], + [106.044336, -3.10625], + [105.396973, -2.380176], + [104.650781, -2.595215], + [104.845215, -2.092969], + [104.515918, -1.819434], + [104.360547, -1.038379], + [103.721094, -0.886719], + [103.438574, -0.575586], + [103.428516, -0.191797], + [103.786719, 0.046973], + [103.672656, 0.288916], + [103.338965, 0.513721], + [102.55, 0.216455], + [103.031836, 0.578906], + [102.389941, 0.841992], + [102.019922, 1.442139], + [101.47666, 1.693066], + [101.046191, 2.257471], + [100.828223, 2.242578], + [100.887891, 1.948242], + [100.523828, 2.18916], + [99.732324, 3.183057], + [98.307324, 4.092871], + [97.547168, 5.205859], + [96.492578, 5.229346], + ], + ], + [ + [ + [122.78291, -8.611719], + [122.916992, -8.105566], + [122.323242, -8.62832], + [120.610254, -8.24043], + [119.874805, -8.419824], + [119.80791, -8.697656], + [121.035254, -8.935449], + [122.78291, -8.611719], + ], + ], + [ + [ + [120.0125, -9.374707], + [118.958789, -9.519336], + [120.144824, -10.200098], + [120.698047, -10.206641], + [120.784473, -9.957031], + [120.0125, -9.374707], + ], + ], + [ + [ + [118.242383, -8.317773], + [117.755273, -8.149512], + [118.234863, -8.591895], + [117.969531, -8.728027], + [117.164844, -8.367188], + [116.835059, -8.532422], + [116.788477, -9.006348], + [119.129687, -8.668164], + [118.926172, -8.297656], + [118.242383, -8.317773], + ], + ], + [ + [ + [124.888867, 0.995312], + [125.233789, 1.502295], + [125.110938, 1.685693], + [123.930762, 0.850439], + [122.838281, 0.845703], + [121.081738, 1.327637], + [120.602539, 0.854395], + [120.269531, 0.970801], + [119.721875, -0.088477], + [119.844336, -0.861914], + [119.711328, -0.680762], + [119.508203, -0.906738], + [119.321875, -1.929688], + [118.783301, -2.720801], + [118.867676, -3.398047], + [119.46748, -3.512988], + [119.623633, -4.034375], + [119.360352, -5.31416], + [119.557422, -5.611035], + [120.430371, -5.591016], + [120.261035, -2.949316], + [120.653613, -2.667578], + [121.052148, -2.75166], + [120.891797, -3.520605], + [121.618066, -4.092676], + [121.588672, -4.75957], + [122.038086, -4.832422], + [122.114258, -4.540234], + [122.872266, -4.391992], + [122.847949, -4.064551], + [122.25293, -3.62041], + [122.291699, -2.907617], + [121.355469, -1.878223], + [122.250684, -1.555273], + [122.902832, -0.900977], + [123.37793, -1.004102], + [123.43418, -0.778223], + [123.171484, -0.570703], + [121.969629, -0.933301], + [121.575586, -0.828516], + [121.148535, -1.339453], + [120.667383, -1.370117], + [120.062891, -0.555566], + [120.192285, 0.268506], + [120.579004, 0.52832], + [123.753809, 0.305518], + [124.427539, 0.470605], + [124.888867, 0.995312], + ], + ], + [ + [ + [107.373926, -6.007617], + [106.075, -5.91416], + [105.255469, -6.835254], + [106.198242, -6.927832], + [106.519727, -7.053711], + [106.455273, -7.368652], + [107.91748, -7.724121], + [109.281641, -7.704883], + [111.509961, -8.305078], + [113.25332, -8.286719], + [114.583789, -8.769629], + [114.386914, -8.405176], + [114.409277, -7.79248], + [113.013574, -7.657715], + [112.539258, -6.926465], + [111.181543, -6.686719], + [110.834766, -6.424219], + [110.42627, -6.947266], + [108.677832, -6.790527], + [108.330176, -6.286035], + [107.373926, -6.007617], + ], + ], + [ + [ + [109.628906, 2.027539], + [109.075879, 1.495898], + [108.944531, 0.355664], + [109.25752, 0.031152], + [109.258789, -0.807422], + [109.983301, -1.274805], + [110.256055, -2.966113], + [111.694727, -2.889453], + [111.858105, -3.551855], + [112.758008, -3.322168], + [113.033984, -2.933496], + [113.705078, -3.455273], + [114.344336, -3.235156], + [114.693555, -4.169727], + [115.956152, -3.59502], + [116.257227, -3.126367], + [116.166309, -2.93457], + [116.56543, -2.299707], + [116.275488, -1.784863], + [116.753418, -1.327344], + [116.739844, -1.044238], + [116.913965, -1.223633], + [117.5625, -0.770898], + [117.522168, 0.235889], + [117.911621, 1.098682], + [118.534766, 0.813525], + [118.984961, 0.982129], + [117.789258, 2.026855], + [118.066602, 2.317822], + [117.055957, 3.622656], + [117.777246, 3.689258], + [117.574414, 4.170605], + [116.514746, 4.370801], + [115.678809, 4.193018], + [115.454395, 3.034326], + [115.117578, 2.894873], + [115.179102, 2.523193], + [114.786426, 2.250488], + [114.5125, 1.452002], + [113.622266, 1.235938], + [112.476172, 1.559082], + [111.808984, 1.01167], + [110.505762, 0.861963], + [109.654004, 1.614893], + [109.628906, 2.027539], + ], + ], + [ + [ + [127.732715, 0.848145], + [127.652832, 1.013867], + [128.011719, 1.331738], + [128.036426, 2.199023], + [127.631738, 1.843701], + [127.428516, 1.13999], + [127.691602, -0.241895], + [128.425488, -0.892676], + [127.887402, 0.29834], + [127.983105, 0.471875], + [128.899609, 0.21626], + [128.260645, 0.733789], + [128.702637, 1.106396], + [128.688379, 1.572559], + [127.732715, 0.848145], + ], + ], + [ + [ + [129.754688, -2.86582], + [128.198535, -2.865918], + [127.902344, -3.496289], + [128.132031, -3.157422], + [128.516602, -3.449121], + [128.8625, -3.234961], + [129.467676, -3.453223], + [129.844141, -3.327148], + [130.805078, -3.857715], + [130.569922, -3.130859], + [129.754688, -2.86582], + ], + ], + [ + [ + [126.861133, -3.087891], + [126.088281, -3.105469], + [126.056543, -3.420996], + [126.686328, -3.823633], + [127.22959, -3.633008], + [126.861133, -3.087891], + ], + ], + [ + [ + [124.922266, -8.94248], + [124.444434, -9.190332], + [124.282324, -9.42793], + [124.036328, -9.341602], + [123.589258, -9.966797], + [123.747266, -10.347168], + [124.427539, -10.148633], + [125.068164, -9.511914], + [124.960156, -9.21377], + [125.149023, -9.042578], + [124.922266, -8.94248], + ], + ], + [ + [ + [134.746973, -5.707031], + [134.570801, -5.427344], + [134.205371, -5.707227], + [134.343066, -5.833008], + [134.154883, -6.062891], + [134.441113, -6.334863], + [134.71416, -6.295117], + [134.746973, -5.707031], + ], + ], + [ + [ + [134.536816, -6.442285], + [134.114648, -6.19082], + [134.09082, -6.833789], + [134.322754, -6.84873], + [134.536816, -6.442285], + ], + ], + [ + [ + [138.535352, -8.273633], + [138.989063, -7.696094], + [138.769824, -7.39043], + [138.081836, -7.566211], + [137.650391, -8.386133], + [138.535352, -8.273633], + ], + ], + [ + [ + [140.973438, -2.609766], + [139.789551, -2.348242], + [137.80625, -1.483203], + [137.123438, -1.840918], + [137.07207, -2.105078], + [136.389941, -2.27334], + [135.85918, -2.995313], + [135.251563, -3.368555], + [134.886816, -3.209863], + [134.627441, -2.536719], + [134.459961, -2.832324], + [134.194824, -2.309082], + [134.25957, -1.362988], + [133.974512, -0.744336], + [132.39375, -0.355469], + [131.257227, -0.855469], + [130.995898, -1.424707], + [131.930371, -1.559668], + [132.307617, -2.242285], + [133.921582, -2.102051], + [133.700098, -2.624609], + [133.191016, -2.437793], + [132.725, -2.789062], + [131.971191, -2.788574], + [132.751367, -3.294629], + [132.914453, -4.056934], + [133.400879, -3.899023], + [133.841504, -3.054785], + [133.67832, -3.479492], + [133.973828, -3.817969], + [134.886523, -3.938477], + [134.679688, -4.079102], + [135.195605, -4.450684], + [137.279785, -4.94541], + [138.06084, -5.465234], + [138.087109, -5.70918], + [138.339648, -5.675684], + [138.199609, -5.807031], + [138.864551, -6.858398], + [138.601367, -6.936523], + [139.176855, -7.19043], + [138.747949, -7.251465], + [139.087988, -7.587207], + [138.890625, -8.237793], + [139.248828, -7.982422], + [139.385645, -8.189062], + [140.116992, -7.92373], + [140.00293, -8.195508], + [140.976172, -9.11875], + [140.973438, -2.609766], + ], + ], + [ + [ + [138.895117, -8.388672], + [138.796191, -8.173633], + [138.563379, -8.309082], + [138.895117, -8.388672], + ], + ], + [ + [ + [101.708105, 2.078418], + [101.409668, 2.02168], + [101.500781, 1.733203], + [101.719434, 1.78916], + [101.708105, 2.078418], + ], + ], + [ + [ + [103.027539, 0.746631], + [102.506641, 1.08877], + [102.49043, 0.856641], + [103.027539, 0.746631], + ], + ], + [ + [ + [104.585352, 1.216113], + [104.251953, 1.014893], + [104.575195, 0.831934], + [104.585352, 1.216113], + ], + ], + [ + [ + [104.778613, -0.175977], + [104.542676, 0.017725], + [104.44707, -0.18916], + [105.005371, -0.282812], + [104.778613, -0.175977], + ], + ], + [ + [ + [104.474219, -0.334668], + [104.257129, -0.463281], + [104.363184, -0.658594], + [104.590137, -0.466602], + [104.474219, -0.334668], + ], + ], + [ + [ + [109.710254, -1.180664], + [109.475977, -0.985352], + [109.463672, -1.277539], + [109.710254, -1.180664], + ], + ], + [ + [ + [113.844531, -7.105371], + [114.073633, -6.960156], + [113.067383, -6.87998], + [112.725879, -7.072754], + [113.844531, -7.105371], + ], + ], + [ + [ + [127.566992, -0.318945], + [127.3, -0.500293], + [127.761133, -0.883691], + [127.566992, -0.318945], + ], + ], + [ + [ + [128.275586, -3.674609], + [128.329102, -3.515918], + [127.925, -3.699316], + [128.275586, -3.674609], + ], + ], + [ + [ + [130.35332, -1.690527], + [129.737695, -1.866895], + [130.248047, -2.047754], + [130.35332, -1.690527], + ], + ], + [ + [ + [100.425098, -3.18291], + [100.198535, -2.785547], + [100.465137, -3.328516], + [100.425098, -3.18291], + ], + ], + [ + [ + [100.204102, -2.741016], + [99.987891, -2.525391], + [100.014941, -2.819727], + [100.204102, -2.741016], + ], + ], + [ + [ + [98.459277, -0.530469], + [98.544141, -0.257617], + [98.322949, -0.000781], + [98.459277, -0.530469], + ], + ], + [ + [ + [122.042969, -5.437988], + [121.913672, -5.072266], + [121.808496, -5.256152], + [122.042969, -5.437988], + ], + ], + [ + [ + [123.212305, -1.171289], + [122.908008, -1.182227], + [122.89043, -1.587207], + [123.150391, -1.304492], + [123.172949, -1.616016], + [123.511914, -1.447363], + [123.212305, -1.171289], + ], + ], + [ + [ + [120.52832, -6.298438], + [120.477344, -5.775293], + [120.487305, -6.464844], + [120.52832, -6.298438], + ], + ], + [ + [ + [115.377051, -6.970801], + [115.546094, -6.938672], + [115.240527, -6.86123], + [115.377051, -6.970801], + ], + ], + [ + [ + [116.30332, -3.868164], + [116.269727, -3.251074], + [116.058789, -4.006934], + [116.30332, -3.868164], + ], + ], + [ + [ + [122.948926, -10.909277], + [123.418164, -10.65127], + [123.371094, -10.474902], + [122.845703, -10.761816], + [122.948926, -10.909277], + ], + ], + [ + [ + [123.924805, -8.272461], + [123.391211, -8.280469], + [123.230078, -8.530664], + [123.924805, -8.272461], + ], + ], + [ + [ + [123.242383, -4.112988], + [122.969043, -4.02998], + [123.076172, -4.227148], + [123.242383, -4.112988], + ], + ], + [ + [ + [117.649023, 4.168994], + [117.736816, 4.004004], + [117.884766, 4.186133], + [117.649023, 4.168994], + ], + ], + [ + [ + [121.883008, -10.590332], + [121.99834, -10.446973], + [121.704688, -10.555664], + [121.883008, -10.590332], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '印度', full_name: '印度共和国', iso_a2: 'IN', iso_a3: 'IND', iso_n3: '356' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [68.165039, 23.857324], + [68.234961, 23.596973], + [68.776758, 23.8521], + [68.41748, 23.571484], + [69.235937, 22.848535], + [69.664648, 22.759082], + [70.489258, 23.089502], + [70.177246, 22.572754], + [68.969922, 22.290283], + [70.485059, 20.840186], + [71.024609, 20.738867], + [72.015234, 21.155713], + [72.254004, 21.531006], + [72.037207, 21.823047], + [72.182813, 22.269727], + [72.80918, 22.233301], + [72.553027, 22.159961], + [72.543066, 21.696582], + [73.1125, 21.750439], + [72.613281, 21.461816], + [72.89375, 20.672754], + [72.667773, 19.830957], + [72.987207, 19.277441], + [72.803027, 19.079297], + [72.97207, 19.15332], + [72.875488, 18.642822], + [73.476074, 16.054248], + [74.382227, 14.494727], + [74.945508, 12.564551], + [75.723828, 11.361768], + [76.553418, 8.902783], + [77.517578, 8.07832], + [78.060156, 8.38457], + [78.421484, 9.105029], + [79.411426, 9.192383], + [78.939941, 9.565771], + [79.314551, 10.256689], + [79.838184, 10.322559], + [79.693164, 11.312549], + [80.342383, 13.361328], + [80.062109, 13.60625], + [80.306543, 13.485059], + [80.053418, 15.074023], + [80.293457, 15.710742], + [80.646582, 15.89502], + [80.978711, 15.75835], + [81.286133, 16.337061], + [82.258789, 16.559863], + [82.35957, 17.096191], + [84.104102, 18.292676], + [85.441602, 19.626562], + [85.180762, 19.594873], + [85.248633, 19.757666], + [86.279492, 19.919434], + [86.975488, 20.700146], + [86.954102, 21.365332], + [87.82373, 21.727344], + [88.159277, 22.121729], + [87.941406, 22.374316], + [88.196289, 22.139551], + [88.12207, 21.635791], + [88.584668, 21.659717], + [88.641602, 22.121973], + [88.74502, 21.584375], + [89.05166, 21.654102], + [89.051465, 22.093164], + [88.928125, 23.186621], + [88.567383, 23.674414], + [88.723535, 24.274902], + [88.023438, 24.627832], + [88.45625, 25.188428], + [88.95166, 25.259277], + [88.08457, 25.888232], + [88.418164, 26.571533], + [88.828027, 26.252197], + [89.018652, 26.410254], + [89.369727, 26.006104], + [89.670898, 26.213818], + [89.833301, 25.292773], + [92.049707, 25.169482], + [92.468359, 24.944141], + [91.876953, 24.195312], + [91.36709, 24.093506], + [91.160449, 23.660645], + [91.315234, 23.104395], + [91.619531, 22.979688], + [91.92959, 23.685986], + [92.246094, 23.683594], + [92.574902, 21.978076], + [93.151172, 22.230615], + [93.32627, 24.064209], + [94.127637, 23.876465], + [94.707617, 25.04873], + [94.579883, 25.319824], + [95.132422, 26.04126], + [95.128711, 26.597266], + [96.19082, 27.261279], + [96.731641, 27.331494], + [97.102051, 27.11543], + [96.876855, 27.586719], + [97.086778, 27.7475], + [96.275479, 28.228241], + [95.832003, 28.295186], + [95.39715, 28.142259], + [95.28628, 27.939955], + [94.88592, 27.743098], + [94.277372, 27.58143], + [93.817265, 27.025183], + [93.111399, 26.880082], + [92.64698, 26.952656], + [91.99834, 26.85498], + [89.609961, 26.719434], + [88.857617, 26.961475], + [88.891406, 27.316064], + [88.803711, 28.006934], + [88.109766, 27.870605], + [87.995117, 26.382373], + [85.794531, 26.60415], + [84.091016, 27.491357], + [82.733398, 27.518994], + [80.070703, 28.830176], + [80.401855, 29.730273], + [81.010254, 30.164502], + [79.707774, 31.013593], + [78.807678, 31.099982], + [78.389648, 32.519873], + [78.700879, 32.597021], + [78.918945, 32.358203], + [79.219336, 32.501074], + [78.72666, 34.013379], + [78.970117, 34.302637], + [78.326953, 34.606396], + [78.042676, 35.479785], + [77.799414, 35.495898], + [77.048633, 35.109912], + [75.70918, 34.503076], + [73.96123, 34.653467], + [73.904102, 34.075684], + [74.250879, 33.946094], + [73.976465, 33.721289], + [74.003809, 33.189453], + [74.35459, 32.768701], + [74.663281, 32.757666], + [74.685742, 32.493799], + [75.333496, 32.279199], + [74.555566, 31.818555], + [74.632812, 31.034668], + [73.80918, 30.093359], + [73.381641, 29.934375], + [72.90332, 29.02876], + [72.341895, 28.751904], + [71.870313, 27.9625], + [70.797949, 27.709619], + [70.403711, 28.025049], + [69.537012, 27.122949], + [69.506934, 26.742676], + [70.147656, 26.506445], + [70.100195, 25.910059], + [70.648438, 25.666943], + [71.044043, 24.400098], + [69.805176, 24.165234], + [68.781152, 24.313721], + [68.724121, 23.964697], + [68.165039, 23.857324], + ], + ], + [ + [ + [93.890039, 6.831055], + [93.822461, 7.236621], + [93.658008, 7.016064], + [93.890039, 6.831055], + ], + ], + [ + [ + [92.502832, 10.554883], + [92.510352, 10.897461], + [92.352832, 10.751123], + [92.502832, 10.554883], + ], + ], + [ + [ + [92.722754, 11.536084], + [93.062305, 13.545459], + [92.533887, 11.873389], + [92.722754, 11.536084], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '冰岛', full_name: '冰岛共和国', iso_a2: 'IS', iso_a3: 'ISL', iso_n3: '352' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-15.543115, 66.228516], + [-16.249316, 66.5229], + [-16.48501, 66.195947], + [-17.550439, 65.964404], + [-18.297168, 66.157422], + [-18.141943, 65.734082], + [-18.845898, 66.183936], + [-19.489697, 65.768066], + [-20.20752, 66.100098], + [-21.129687, 65.266602], + [-21.406885, 66.025586], + [-22.944336, 66.429443], + [-22.441699, 65.908301], + [-23.452539, 66.181006], + [-23.832617, 65.849219], + [-23.285352, 65.75], + [-24.092627, 65.776465], + [-23.856738, 65.538379], + [-24.475684, 65.525195], + [-21.844385, 65.447363], + [-22.509082, 65.196777], + [-21.892139, 65.048779], + [-24.026172, 64.863428], + [-21.590625, 64.626367], + [-22.053369, 64.313916], + [-21.46333, 64.37915], + [-22.701172, 64.083203], + [-22.652197, 63.827734], + [-21.152393, 63.944531], + [-20.198145, 63.555811], + [-18.653613, 63.406689], + [-14.927393, 64.319678], + [-13.599316, 65.035938], + [-13.617871, 65.519336], + [-14.8271, 65.764258], + [-15.117383, 66.125635], + [-14.59585, 66.381543], + [-15.543115, 66.228516], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '匈牙利', full_name: '匈牙利', iso_a2: 'HU', iso_a3: 'HUN', iso_n3: '348' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [22.131836, 48.405322], + [20.490039, 48.526904], + [19.950391, 48.146631], + [18.791895, 48.000293], + [18.724219, 47.787158], + [17.761914, 47.770166], + [17.147363, 48.005957], + [17.066602, 47.707568], + [16.421289, 47.674463], + [16.676563, 47.536035], + [16.093066, 46.863281], + [16.516211, 46.499902], + [17.807129, 45.79043], + [18.905371, 45.931738], + [20.241797, 46.108594], + [21.12168, 46.282422], + [21.999707, 47.505029], + [22.87666, 47.947266], + [22.131836, 48.405322], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '洪都拉斯', full_name: '洪都拉斯共和国', iso_a2: 'HN', iso_a3: 'HND', iso_n3: '340' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-83.15752, 14.993066], + [-84.095068, 15.400928], + [-83.765283, 15.405469], + [-84.261426, 15.822607], + [-84.97373, 15.989893], + [-88.22832, 15.729004], + [-89.142578, 15.072314], + [-89.362598, 14.416016], + [-88.482666, 13.854248], + [-87.802246, 13.88999], + [-87.814209, 13.39917], + [-87.489111, 13.35293], + [-87.337256, 12.979248], + [-86.729297, 13.284375], + [-86.733643, 13.763477], + [-86.040381, 14.050146], + [-85.733936, 13.858691], + [-84.985156, 14.752441], + [-84.453564, 14.643701], + [-83.15752, 14.993066], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '海地', full_name: '海地共和国', iso_a2: 'HT', iso_a3: 'HTI', iso_n3: '332' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-71.779248, 19.718164], + [-73.400537, 19.807422], + [-72.703223, 19.441064], + [-72.811084, 19.071582], + [-72.376074, 18.574463], + [-72.789355, 18.434814], + [-74.227734, 18.662695], + [-74.478125, 18.45], + [-73.884961, 18.041895], + [-73.385156, 18.251172], + [-71.768311, 18.03916], + [-72.000391, 18.5979], + [-71.645312, 19.163525], + [-71.779248, 19.718164], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圭亚那', full_name: '圭亚那合作共和国', iso_a2: 'GY', iso_a3: 'GUY', iso_n3: '328' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-60.742139, 5.202051], + [-60.142041, 5.238818], + [-59.990674, 5.082861], + [-60.148633, 4.533252], + [-59.703271, 4.381104], + [-59.551123, 3.933545], + [-59.854395, 3.5875], + [-59.994336, 2.68999], + [-59.666602, 1.746289], + [-59.231201, 1.376025], + [-58.821777, 1.201221], + [-57.31748, 1.963477], + [-56.482812, 1.942139], + [-57.197363, 2.853271], + [-57.303662, 3.3771], + [-57.646729, 3.394531], + [-58.054297, 4.10166], + [-57.917041, 4.82041], + [-57.331006, 5.020166], + [-57.194775, 5.548438], + [-57.227539, 6.178418], + [-57.982568, 6.785889], + [-58.41499, 6.851172], + [-58.672949, 6.390771], + [-58.511084, 7.398047], + [-60.017529, 8.549316], + [-59.849072, 8.248682], + [-60.718652, 7.535937], + [-60.3521, 7.002881], + [-61.145605, 6.694531], + [-61.128711, 6.214307], + [-61.39082, 5.93877], + [-60.742139, 5.202051], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '几内亚比绍', full_name: '几内亚比绍共和国', iso_a2: 'GW', iso_a3: 'GNB', iso_n3: '624' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-16.711816, 12.354834], + [-16.24458, 12.237109], + [-16.328076, 12.051611], + [-15.941748, 11.786621], + [-15.078271, 11.968994], + [-15.501904, 11.723779], + [-15.072656, 11.597803], + [-15.479492, 11.410303], + [-15.043018, 10.940137], + [-14.682959, 11.508496], + [-13.732764, 11.736035], + [-13.948877, 12.178174], + [-13.70791, 12.312695], + [-13.729248, 12.673926], + [-15.196094, 12.679932], + [-16.711816, 12.354834], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '几内亚', full_name: '几内亚共和国', iso_a2: 'GN', iso_a3: 'GIN', iso_n3: '324' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-10.283203, 8.485156], + [-9.518262, 8.346094], + [-9.463818, 7.415869], + [-9.117578, 7.215918], + [-8.659766, 7.688379], + [-8.486426, 7.558496], + [-8.231885, 7.556738], + [-8.009863, 8.078516], + [-8.236963, 8.455664], + [-7.681201, 8.410352], + [-7.950977, 8.786816], + [-7.896191, 9.415869], + [-8.136963, 9.495703], + [-7.990625, 10.1625], + [-8.337402, 10.990625], + [-8.666699, 11.009473], + [-8.398535, 11.366553], + [-8.822021, 11.673242], + [-9.043066, 12.402344], + [-9.395361, 12.464648], + [-9.358105, 12.25542], + [-9.754004, 12.029932], + [-10.274854, 12.212646], + [-10.709229, 11.89873], + [-10.933203, 12.205176], + [-11.305176, 12.01543], + [-11.502197, 12.198633], + [-11.389404, 12.404395], + [-12.399072, 12.340088], + [-13.729248, 12.673926], + [-13.70791, 12.312695], + [-13.948877, 12.178174], + [-13.732764, 11.736035], + [-14.682959, 11.508496], + [-15.043018, 10.940137], + [-14.593506, 10.766699], + [-14.426904, 10.24834], + [-13.689795, 9.927783], + [-13.691357, 9.535791], + [-13.292676, 9.049219], + [-12.427979, 9.898145], + [-11.273633, 9.996533], + [-10.690527, 9.314258], + [-10.500537, 8.687549], + [-10.712109, 8.335254], + [-10.283203, 8.485156], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '危地马拉', full_name: '危地马拉共和国', iso_a2: 'GT', iso_a3: 'GTM', iso_n3: '320' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-92.235156, 14.54541], + [-91.377344, 13.990186], + [-90.095215, 13.736523], + [-89.362598, 14.416016], + [-89.142578, 15.072314], + [-88.22832, 15.729004], + [-88.894043, 15.890625], + [-89.2375, 15.894434], + [-89.161475, 17.814844], + [-90.98916, 17.816406], + [-90.992969, 17.252441], + [-91.409619, 17.255859], + [-90.416992, 16.391016], + [-90.447168, 16.072705], + [-91.736572, 16.070166], + [-92.204248, 15.275], + [-92.235156, 14.54541], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '格林纳达', full_name: '格林纳达', iso_a2: 'GD', iso_a3: 'GRD', iso_n3: '308' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-61.715527, 12.012646], + [-61.607031, 12.223291], + [-61.71499, 12.185156], + [-61.715527, 12.012646], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '希腊', full_name: '希腊共和国', iso_a2: 'GR', iso_a3: 'GRC', iso_n3: '300' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [20.612305, 38.38335], + [20.352539, 38.179883], + [20.761328, 38.070557], + [20.612305, 38.38335], + ], + ], + [ + [ + [20.07793, 39.432715], + [19.926074, 39.77373], + [19.646484, 39.76709], + [20.07793, 39.432715], + ], + ], + [ + [ + [23.41543, 38.958643], + [22.870313, 38.870508], + [24.536523, 37.979736], + [24.127539, 38.648486], + [23.41543, 38.958643], + ], + ], + [ + [ + [26.824414, 37.811426], + [26.581055, 37.72373], + [27.055078, 37.709277], + [26.824414, 37.811426], + ], + ], + [ + [ + [26.094043, 38.218066], + [26.160352, 38.540723], + [25.846094, 38.574023], + [26.094043, 38.218066], + ], + ], + [ + [ + [26.410156, 39.329443], + [25.844141, 39.200049], + [26.46875, 38.972803], + [26.410156, 39.329443], + ], + ], + [ + [ + [27.842773, 35.929297], + [28.231836, 36.433643], + [27.716309, 36.171582], + [27.842773, 35.929297], + ], + ], + [ + [ + [23.852246, 35.535449], + [23.569824, 35.534766], + [23.592773, 35.257227], + [24.799805, 34.934473], + [26.165625, 35.018604], + [26.320215, 35.315137], + [25.791309, 35.122852], + [25.730176, 35.348584], + [23.852246, 35.535449], + ], + ], + [ + [ + [26.320898, 41.716553], + [25.92334, 41.311914], + [25.251172, 41.243555], + [24.487891, 41.555225], + [22.916016, 41.336279], + [20.964258, 40.849902], + [20.657422, 40.117383], + [20.00127, 39.709424], + [20.713379, 39.035156], + [21.118359, 39.02998], + [20.768555, 38.874414], + [21.182617, 38.345557], + [22.42168, 38.438525], + [23.148926, 38.176074], + [22.920313, 37.958301], + [21.824707, 38.328125], + [21.124707, 37.891602], + [21.678906, 37.387207], + [21.58291, 37.080957], + [21.892383, 36.737305], + [22.080469, 37.028955], + [22.427734, 36.475781], + [22.717188, 36.793945], + [23.160156, 36.448096], + [22.725391, 37.542139], + [23.489258, 37.440186], + [23.036328, 37.878369], + [23.501758, 38.034863], + [24.019727, 37.677734], + [24.024512, 38.139795], + [22.569141, 38.86748], + [23.066699, 39.037939], + [22.921387, 39.306348], + [23.327734, 39.174902], + [22.592188, 40.036914], + [22.629492, 40.495557], + [22.922266, 40.590869], + [23.627344, 39.924072], + [23.42627, 40.263965], + [23.94707, 39.965576], + [23.72793, 40.329736], + [24.343359, 40.147705], + [23.762793, 40.747803], + [25.104492, 40.994727], + [26.038965, 40.726758], + [26.624902, 41.401758], + [26.320898, 41.716553], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '加纳', full_name: '加纳共和国', iso_a2: 'GH', iso_a3: 'GHA', iso_n3: '288' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-0.068604, 11.115625], + [-0.627148, 10.927393], + [-2.829932, 10.998389], + [-2.69585, 9.481348], + [-2.505859, 8.20874], + [-3.235791, 6.807227], + [-2.75498, 5.43252], + [-3.019141, 5.130811], + [-3.086719, 5.12832], + [-3.114014, 5.088672], + [-2.001855, 4.762451], + [0.259668, 5.757324], + [0.949707, 5.810254], + [1.187207, 6.089404], + [0.525586, 6.850928], + [0.686328, 8.354883], + [0.372559, 8.759277], + [0.525684, 9.398486], + [0.233398, 9.463525], + [0.380859, 10.291846], + [-0.086328, 10.673047], + [-0.068604, 11.115625], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '德国', full_name: '德意志联邦共和国', iso_a2: 'DE', iso_a3: 'DEU', iso_n3: '276' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [9.524023, 47.524219], + [10.183008, 47.278809], + [10.439453, 47.551562], + [11.041992, 47.393115], + [12.209277, 47.718262], + [13.014355, 47.478076], + [12.760352, 48.106982], + [13.814746, 48.766943], + [12.681152, 49.414502], + [12.089844, 50.301758], + [12.942676, 50.406445], + [14.319727, 51.037793], + [14.809375, 50.858984], + [15.016602, 51.252734], + [14.619434, 52.528516], + [14.128613, 52.878223], + [14.258887, 53.729639], + [12.575391, 54.467383], + [11.399609, 53.944629], + [10.85459, 54.009814], + [11.013379, 54.37915], + [9.868652, 54.472461], + [9.739746, 54.825537], + [8.670313, 54.903418], + [8.92041, 53.965332], + [9.783984, 53.554639], + [8.618945, 53.875], + [8.495215, 53.394238], + [8.009277, 53.690723], + [7.285254, 53.681348], + [7.197266, 53.282275], + [7.033008, 52.651367], + [6.710742, 52.617871], + [7.035156, 52.380225], + [6.800391, 51.967383], + [5.94873, 51.802686], + [5.993945, 50.750439], + [6.340918, 50.451758], + [6.116504, 50.120996], + [6.487305, 49.798486], + [6.344336, 49.452734], + [6.735449, 49.160596], + [8.134863, 48.973584], + [7.615625, 47.592725], + [8.454004, 47.596191], + [8.572656, 47.775635], + [9.524023, 47.524219], + ], + ], + [ + [ + [13.70918, 54.382715], + [13.336816, 54.697119], + [13.190039, 54.325635], + [13.70918, 54.382715], + ], + ], + [ + [ + [14.211426, 53.950342], + [13.827734, 54.127246], + [14.213672, 53.870752], + [14.211426, 53.950342], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '格鲁吉亚', full_name: '格鲁吉亚', iso_a2: 'GE', iso_a3: 'GEO', iso_n3: '268' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [43.439453, 41.107129], + [45.001367, 41.290967], + [45.280957, 41.449561], + [46.534375, 41.088574], + [46.672559, 41.286816], + [46.182129, 41.65708], + [46.429883, 41.890967], + [45.638574, 42.205078], + [45.705273, 42.498096], + [44.870996, 42.756396], + [43.825977, 42.571533], + [42.760645, 43.16958], + [41.580566, 43.219238], + [40.648047, 43.533887], + [39.97832, 43.419824], + [41.48877, 42.659326], + [41.762988, 41.97002], + [41.510059, 41.51748], + [42.754102, 41.578906], + [43.439453, 41.107129], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '冈比亚', full_name: '冈比亚共和国', iso_a2: 'GM', iso_a3: 'GMB', iso_n3: '270' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-16.562305, 13.587305], + [-16.351807, 13.343359], + [-15.42749, 13.468359], + [-16.413379, 13.269727], + [-16.669336, 13.475], + [-16.76333, 13.06416], + [-15.834277, 13.156445], + [-15.151123, 13.556494], + [-14.246777, 13.23584], + [-13.826709, 13.407812], + [-15.10835, 13.812109], + [-15.509668, 13.58623], + [-16.562305, 13.587305], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '加蓬', full_name: '加蓬共和国', iso_a2: 'GA', iso_a3: 'GAB', iso_n3: '266' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [13.293555, 2.161572], + [11.328711, 2.167432], + [11.335352, 0.999707], + [9.59082, 1.031982], + [9.617969, 0.576514], + [9.324805, 0.5521], + [10.001465, 0.194971], + [9.796777, 0.044238], + [9.354883, 0.343604], + [9.29668, -0.35127], + [8.946387, -0.68877], + [8.703125, -0.591016], + [9.064648, -1.29834], + [9.501074, -1.555176], + [9.318848, -1.632031], + [9.036328, -1.308887], + [9.624609, -2.36709], + [10.062012, -2.549902], + [9.72207, -2.467578], + [11.130176, -3.916309], + [11.504297, -3.520312], + [11.879883, -3.665918], + [11.93418, -3.318555], + [11.537793, -2.836719], + [11.605469, -2.342578], + [12.446387, -2.32998], + [12.59043, -1.826855], + [12.991992, -2.313379], + [13.464941, -2.39541], + [13.733789, -2.138477], + [13.993848, -2.490625], + [14.383984, -1.890039], + [14.474121, -0.573438], + [13.860059, -0.20332], + [13.949609, 0.353809], + [14.429883, 0.901465], + [14.180859, 1.370215], + [13.216309, 1.248438], + [13.293555, 2.161572], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '法国', full_name: '法兰西共和国', iso_a2: 'FR', iso_a3: 'FRA', iso_n3: '250' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [9.480371, 42.80542], + [9.363184, 43.017383], + [9.313379, 42.713184], + [8.565625, 42.357715], + [8.80752, 41.588379], + [9.186133, 41.384912], + [9.550684, 42.129736], + [9.480371, 42.80542], + ], + ], + [ + [ + [7.615625, 47.592725], + [8.134863, 48.973584], + [6.735449, 49.160596], + [6.344336, 49.452734], + [5.789746, 49.538281], + [4.867578, 49.788135], + [4.818652, 50.153174], + [4.149316, 49.971582], + [4.174609, 50.246484], + [2.759375, 50.750635], + [2.524902, 51.097119], + [1.672266, 50.88501], + [1.592773, 50.252197], + [0.186719, 49.703027], + [0.416895, 49.448389], + [-1.138525, 49.387891], + [-1.258643, 49.680176], + [-1.856445, 49.683789], + [-1.376465, 48.652588], + [-2.692334, 48.536816], + [-3.231445, 48.84082], + [-4.7625, 48.450244], + [-4.241406, 48.303662], + [-4.678809, 48.039502], + [-4.312109, 47.8229], + [-1.742529, 47.215967], + [-2.19707, 47.162939], + [-2.059375, 46.810303], + [-1.146289, 46.311377], + [-1.195996, 45.714453], + [-0.548486, 45.000586], + [-1.081006, 45.532422], + [-1.076953, 44.689844], + [-1.484863, 43.56377], + [-1.794043, 43.407324], + [-1.46084, 43.051758], + [-0.586426, 42.798975], + [0.631641, 42.6896], + [0.696875, 42.845117], + [1.42832, 42.595898], + [1.706055, 42.50332], + [3.211426, 42.431152], + [3.051758, 42.915137], + [3.91084, 43.563086], + [6.115918, 43.072363], + [7.377734, 43.731738], + [7.39502, 43.765332], + [7.438672, 43.750439], + [7.493164, 43.767139], + [7.637207, 44.164844], + [6.900195, 44.335742], + [6.992676, 44.827295], + [6.634766, 45.068164], + [7.146387, 45.381738], + [6.790918, 45.740869], + [7.021094, 45.925781], + [6.758105, 46.415771], + [5.97002, 46.214697], + [6.968359, 47.453223], + [7.615625, 47.592725], + ], + ], + [ + [ + [55.797363, -21.339355], + [55.661914, -20.90625], + [55.311328, -20.904102], + [55.362695, -21.273633], + [55.797363, -21.339355], + ], + ], + [ + [ + [-60.82627, 14.494482], + [-61.21333, 14.848584], + [-61.063721, 14.46709], + [-60.82627, 14.494482], + ], + ], + [ + [ + [-61.327148, 16.23042], + [-61.172607, 16.256104], + [-61.471191, 16.506641], + [-61.522168, 16.228027], + [-61.327148, 16.23042], + ], + ], + [ + [ + [-54.61626, 2.326758], + [-54.130078, 2.121045], + [-53.767773, 2.354834], + [-52.903467, 2.211523], + [-51.652539, 4.061279], + [-51.827539, 4.635693], + [-52.00293, 4.352295], + [-52.058105, 4.717383], + [-52.899316, 5.425049], + [-53.847168, 5.782227], + [-54.155957, 5.358984], + [-54.479688, 4.836523], + [-54.00957, 3.448535], + [-54.61626, 2.326758], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '圣皮埃尔和密克隆群岛', + full_name: '圣皮埃尔和密克隆群岛(法国)', + iso_a2: 'PM', + iso_a3: 'SPM', + iso_n3: '666', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-56.26709, 46.838477], + [-56.364648, 47.098975], + [-56.384766, 46.819434], + [-56.26709, 46.838477], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '瓦利斯和富图纳群岛', + full_name: '瓦利斯和富图纳群岛', + iso_a2: 'WF', + iso_a3: 'WLF', + iso_n3: '876', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-178.04668, -14.318359], + [-178.194385, -14.255469], + [-178.158594, -14.311914], + [-178.04668, -14.318359], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '法属圣马丁', full_name: '法属圣马丁岛', iso_a2: 'MF', iso_a3: 'MAF', iso_n3: '663' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-63.011182, 18.068945], + [-63.063086, 18.115332], + [-63.123047, 18.068945], + [-63.011182, 18.068945], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圣巴泰勒米', full_name: '圣巴泰勒米(法国)', iso_a2: 'BL', iso_a3: 'BLM', iso_n3: '652' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-62.831934, 17.876465], + [-62.799707, 17.908691], + [-62.874219, 17.922266], + [-62.831934, 17.876465], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '法属波利尼西亚', full_name: '法属波利尼西亚', iso_a2: 'PF', iso_a3: 'PYF', iso_n3: '258' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-149.321533, -17.690039], + [-149.63501, -17.564258], + [-149.181787, -17.862305], + [-149.321533, -17.690039], + ], + ], + [ + [ + [-139.024316, -9.695215], + [-139.134082, -9.829492], + [-138.827344, -9.741602], + [-139.024316, -9.695215], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '新喀里多尼亚', full_name: '新喀里多尼亚', iso_a2: 'NC', iso_a3: 'NCL', iso_n3: '540' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [164.202344, -20.246094], + [164.059668, -20.141504], + [164.927441, -21.289844], + [166.467969, -22.256055], + [166.970313, -22.322852], + [166.942383, -22.090137], + [165.191797, -20.768848], + [164.202344, -20.246094], + ], + ], + [ + [ + [168.010938, -21.42998], + [167.81543, -21.392676], + [167.966797, -21.641602], + [168.010938, -21.42998], + ], + ], + [ + [ + [167.400879, -21.160645], + [167.297949, -20.73252], + [167.055762, -20.720215], + [167.072656, -20.997266], + [167.400879, -21.160645], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '法属南部和南极领地', + full_name: '法属南部和南极领地', + iso_a2: 'TF', + iso_a3: 'ATF', + iso_n3: '260', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [69.184863, -49.10957], + [69.002441, -48.66123], + [68.814746, -49.699609], + [69.153125, -49.529688], + [70.124316, -49.704395], + [70.247754, -49.530664], + [69.759961, -49.430176], + [70.386133, -49.433984], + [70.555469, -49.201465], + [70.320215, -49.058594], + [69.542383, -49.255664], + [69.592773, -48.970996], + [69.184863, -49.10957], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '奥兰群岛', full_name: '奥兰群岛(芬兰)', iso_a2: 'AX', iso_a3: 'ALA', iso_n3: '248' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [19.989551, 60.351172], + [19.799805, 60.081738], + [20.258887, 60.261279], + [19.989551, 60.351172], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '芬兰', full_name: '芬兰共和国', iso_a2: 'FI', iso_a3: 'FIN', iso_n3: '246' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [24.155469, 65.805273], + [24.628027, 65.85918], + [25.347852, 65.479248], + [25.288184, 64.860352], + [24.55791, 64.801025], + [21.143848, 62.73999], + [21.436035, 60.596387], + [22.584961, 60.380566], + [22.462695, 60.029199], + [22.911719, 60.209717], + [23.021289, 59.816016], + [25.758008, 60.267529], + [26.569336, 60.624561], + [26.534668, 60.412891], + [27.797656, 60.536133], + [31.533984, 62.8854], + [29.991504, 63.735156], + [30.51377, 64.2], + [29.604199, 64.968408], + [30.102734, 65.72627], + [29.066211, 66.891748], + [29.988086, 67.668262], + [28.685156, 68.189795], + [28.414062, 68.90415], + [28.96582, 69.021973], + [29.141602, 69.671436], + [27.747852, 70.064844], + [26.072461, 69.691553], + [24.941406, 68.593262], + [23.854004, 68.805908], + [22.410938, 68.719873], + [21.59375, 69.273584], + [20.622168, 69.036865], + [23.638867, 67.954395], + [24.155469, 65.805273], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '斐济', full_name: '斐济共和国', iso_a2: 'FJ', iso_a3: 'FJI', iso_n3: '242' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [179.999219, -16.168555], + [180, -16.15293], + [178.497461, -16.787891], + [178.706641, -16.976172], + [179.202344, -16.712695], + [179.92793, -16.744434], + [179.930371, -16.519434], + [179.56416, -16.636914], + [179.999219, -16.168555], + ], + ], + [ + [ + [178.280176, -17.371973], + [177.504492, -17.539551], + [177.321387, -18.077539], + [177.955469, -18.264062], + [178.667676, -18.080859], + [178.280176, -17.371973], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '埃塞俄比亚', + full_name: '埃塞俄比亚联邦民主共和国', + iso_a2: 'ET', + iso_a3: 'ETH', + iso_n3: '231', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35.268359, 5.492285], + [35.74502, 5.343994], + [36.021973, 4.468115], + [36.905566, 4.411475], + [38.086133, 3.648828], + [39.494434, 3.456104], + [40.765234, 4.273047], + [41.14043, 3.962988], + [41.883984, 3.977734], + [43.583496, 4.85498], + [44.940527, 4.912012], + [47.978223, 7.99707], + [46.978223, 7.99707], + [43.983789, 9.008838], + [42.841602, 10.203076], + [42.656445, 10.6], + [42.922754, 10.999316], + [41.798242, 10.980469], + [41.792676, 11.686035], + [42.378516, 12.466406], + [40.820117, 14.11167], + [40.140625, 14.456055], + [39.023828, 14.628223], + [38.431445, 14.428613], + [37.88418, 14.852295], + [37.571191, 14.149072], + [37.257227, 14.45376], + [36.524316, 14.256836], + [36.125195, 12.757031], + [35.670215, 12.62373], + [35.112305, 11.816553], + [34.931445, 10.864795], + [34.343945, 10.658643], + [34.078125, 9.461523], + [34.072754, 8.545264], + [33.281055, 8.437256], + [32.998926, 7.899512], + [33.902441, 7.509521], + [34.710645, 6.660303], + [35.268359, 5.492285], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '爱沙尼亚', full_name: '爱沙尼亚共和国', iso_a2: 'EE', iso_a3: 'EST', iso_n3: '233' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [27.351953, 57.528125], + [27.778516, 57.870703], + [27.43418, 58.787256], + [28.0125, 59.484277], + [25.509277, 59.639014], + [23.494434, 59.195654], + [23.767578, 58.36084], + [24.529102, 58.354248], + [24.322559, 57.870605], + [25.282617, 58.048486], + [26.462109, 57.544482], + [27.351953, 57.528125], + ], + ], + [ + [ + [22.617383, 58.62124], + [21.862305, 58.497168], + [21.996875, 57.931348], + [23.323242, 58.45083], + [22.617383, 58.62124], + ], + ], + [ + [ + [22.92373, 58.826904], + [22.649414, 59.087109], + [22.05625, 58.943604], + [22.542188, 58.68999], + [22.92373, 58.826904], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '厄立特里亚', full_name: '厄立特里亚国', iso_a2: 'ER', iso_a3: 'ERI', iso_n3: '232' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [36.524316, 14.256836], + [37.257227, 14.45376], + [37.571191, 14.149072], + [37.88418, 14.852295], + [38.431445, 14.428613], + [39.023828, 14.628223], + [40.140625, 14.456055], + [40.820117, 14.11167], + [42.378516, 12.466406], + [42.703711, 12.380322], + [43.116699, 12.708594], + [41.176465, 14.620312], + [40.204102, 15.014111], + [39.86377, 15.470312], + [39.785547, 15.124854], + [38.609473, 18.005078], + [37.411035, 17.061719], + [37.008984, 17.058887], + [36.426758, 15.13208], + [36.524316, 14.256836], + ], + ], + [ + [ + [40.141211, 15.696143], + [39.956738, 15.889404], + [39.975195, 15.612451], + [40.399023, 15.579883], + [40.141211, 15.696143], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '赤道几内亚', full_name: '赤道几内亚共和国', iso_a2: 'GQ', iso_a3: 'GNQ', iso_n3: '226' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [8.735742, 3.758301], + [8.474902, 3.264648], + [8.704004, 3.223633], + [8.946094, 3.627539], + [8.735742, 3.758301], + ], + ], + [ + [ + [11.328711, 2.167432], + [9.800781, 2.304443], + [9.385938, 1.139258], + [9.59082, 1.031982], + [11.335352, 0.999707], + [11.328711, 2.167432], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '萨尔瓦多', full_name: '萨尔瓦多共和国', iso_a2: 'SV', iso_a3: 'SLV', iso_n3: '222' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-89.362598, 14.416016], + [-90.095215, 13.736523], + [-88.512012, 13.183936], + [-88.685645, 13.281494], + [-87.930859, 13.180664], + [-87.814209, 13.39917], + [-87.802246, 13.88999], + [-88.482666, 13.854248], + [-89.362598, 14.416016], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '埃及', full_name: '阿拉伯埃及共和国', iso_a2: 'EG', iso_a3: 'EGY', iso_n3: '818' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [36.871387, 21.996729], + [35.697852, 22.946191], + [35.504395, 23.779297], + [35.783887, 23.937793], + [35.194141, 24.475146], + [33.959082, 26.649023], + [33.54707, 27.898145], + [32.359766, 29.630664], + [32.565723, 29.973975], + [33.247754, 28.567725], + [34.220117, 27.764307], + [34.904297, 29.477344], + [34.245313, 31.208301], + [34.198145, 31.322607], + [32.60332, 31.06875], + [32.216211, 31.29375], + [32.101758, 31.092822], + [31.771094, 31.292578], + [31.892188, 31.482471], + [32.136035, 31.341064], + [31.888965, 31.541406], + [31.08291, 31.60332], + [29.07207, 30.830273], + [25.150488, 31.65498], + [24.852734, 31.334814], + [24.961426, 30.678516], + [24.703223, 30.201074], + [24.980273, 29.181885], + [24.980273, 21.99585], + [31.092676, 21.994873], + [31.400293, 22.202441], + [31.434473, 21.99585], + [36.871387, 21.996729], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '厄瓜多尔', full_name: '厄瓜多尔共和国', iso_a2: 'EC', iso_a3: 'ECU', iso_n3: '218' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-75.284473, -0.106543], + [-76.270605, 0.439404], + [-76.494629, 0.235449], + [-77.396338, 0.393896], + [-77.702881, 0.837842], + [-78.859668, 1.455371], + [-78.899658, 1.20625], + [-80.088281, 0.784766], + [-80.046143, 0.155371], + [-80.482275, -0.368262], + [-80.282373, -0.620508], + [-80.902393, -1.078906], + [-80.760596, -1.93457], + [-80.932178, -2.269141], + [-80.284717, -2.706738], + [-80.006641, -2.353809], + [-79.925586, -2.548535], + [-79.842139, -2.067383], + [-79.729883, -2.579102], + [-79.96333, -3.157715], + [-80.324658, -3.387891], + [-80.179248, -3.877734], + [-80.490137, -4.010059], + [-80.478564, -4.430078], + [-79.638525, -4.454883], + [-79.330957, -4.927832], + [-79.033301, -4.969141], + [-78.686035, -4.562402], + [-78.345361, -3.397363], + [-78.158496, -3.465137], + [-77.860596, -2.981641], + [-76.679102, -2.562598], + [-75.570557, -1.53125], + [-75.259375, -0.590137], + [-75.62627, -0.122852], + [-75.284473, -0.106543], + ], + ], + [ + [ + [-80.131592, -2.973145], + [-79.909033, -2.725586], + [-80.223682, -2.753125], + [-80.131592, -2.973145], + ], + ], + [ + [ + [-90.334863, -0.771582], + [-90.269385, -0.484668], + [-90.531689, -0.581445], + [-90.334863, -0.771582], + ], + ], + [ + [ + [-89.418896, -0.911035], + [-89.287842, -0.689844], + [-89.608594, -0.888574], + [-89.418896, -0.911035], + ], + ], + [ + [ + [-91.272168, 0.025146], + [-91.596826, 0.0021], + [-91.120947, -0.559082], + [-91.49541, -0.860938], + [-91.131055, -1.019629], + [-90.799658, -0.752051], + [-91.272168, 0.025146], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '多米尼加', full_name: '多米尼加共和国', iso_a2: 'DO', iso_a3: 'DOM', iso_n3: '214' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-71.768311, 18.03916], + [-71.438965, 17.635596], + [-71.027832, 18.273193], + [-69.274512, 18.439844], + [-68.687402, 18.214941], + [-68.33916, 18.611523], + [-69.623633, 19.117822], + [-69.232471, 19.271826], + [-69.739404, 19.299219], + [-69.956836, 19.671875], + [-70.95415, 19.913965], + [-71.779248, 19.718164], + [-71.645312, 19.163525], + [-72.000391, 18.5979], + [-71.768311, 18.03916], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '多米尼克', full_name: '多米尼克国', iso_a2: 'DM', iso_a3: 'DMA', iso_n3: '212' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-61.281689, 15.249023], + [-61.277246, 15.526709], + [-61.458105, 15.633105], + [-61.281689, 15.249023], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '美国本土外小岛屿', + full_name: '美国本土外小岛屿', + iso_a2: 'UM', + iso_a3: 'UMI', + iso_n3: '581', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [166.61939537900003, 19.281642971000053], + [166.64421634200005, 19.27558014500005], + [166.63819420700008, 19.286444403000075], + [166.61939537900003, 19.281642971000053], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '吉布提', full_name: '吉布提共和国', iso_a2: 'DJ', iso_a3: 'DJI', iso_n3: '262' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [43.245996, 11.499805], + [42.521777, 11.572168], + [43.380273, 12.09126], + [43.116699, 12.708594], + [42.703711, 12.380322], + [42.378516, 12.466406], + [41.792676, 11.686035], + [41.798242, 10.980469], + [42.922754, 10.999316], + [43.245996, 11.499805], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '格陵兰', full_name: '格陵兰', iso_a2: 'GL', iso_a3: 'GRL', iso_n3: '304' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-29.952881, 83.564844], + [-37.72334, 83.497754], + [-38.15625, 82.998633], + [-40.356836, 83.332178], + [-41.300146, 83.100781], + [-43.009277, 83.2646], + [-46.169043, 83.063867], + [-46.136816, 82.858838], + [-41.369629, 82.75], + [-45.556543, 82.747021], + [-44.238867, 82.368164], + [-44.729492, 81.779834], + [-50.037109, 82.472412], + [-49.541064, 81.918066], + [-53.022559, 82.321729], + [-53.555664, 81.653271], + [-54.548877, 82.350635], + [-59.261816, 82.006641], + [-56.615137, 81.362891], + [-59.281934, 81.884033], + [-60.842871, 81.855371], + [-61.435986, 81.133594], + [-62.903369, 81.218359], + [-63.028662, 80.889551], + [-64.515527, 81], + [-67.050635, 80.384521], + [-66.843652, 80.076221], + [-64.17915, 80.099268], + [-65.825537, 79.17373], + [-71.651318, 78.623145], + [-72.818066, 78.194336], + [-69.351367, 77.467139], + [-66.691211, 77.681201], + [-66.389453, 77.280273], + [-68.135547, 77.37959], + [-71.141455, 77.028662], + [-68.114258, 76.650635], + [-69.484082, 76.39917], + [-68.14873, 76.067041], + [-66.674805, 75.977393], + [-66.992578, 76.212939], + [-63.291309, 76.352051], + [-58.516211, 75.689062], + [-58.565527, 75.352734], + [-56.255469, 74.526807], + [-57.230566, 74.125293], + [-56.225391, 74.129102], + [-55.288281, 73.3271], + [-55.668555, 73.00791], + [-54.737939, 72.87251], + [-55.601709, 72.453467], + [-55.581445, 72.178857], + [-54.840137, 72.356104], + [-55.594043, 71.553516], + [-53.962988, 71.458984], + [-53.652148, 72.362646], + [-53.440088, 71.579004], + [-51.769922, 71.671729], + [-53.007568, 71.17998], + [-51.018945, 71.001318], + [-51.774316, 71.010449], + [-50.872363, 70.364893], + [-52.801953, 70.750586], + [-54.530762, 70.699268], + [-52.254639, 70.058936], + [-50.291699, 70.014453], + [-50.945703, 68.682666], + [-51.623145, 68.534814], + [-52.60459, 68.70874], + [-53.383154, 68.297363], + [-51.210156, 68.419922], + [-51.456494, 68.116064], + [-53.151562, 68.207764], + [-53.735205, 67.549023], + [-52.344824, 67.836914], + [-50.968848, 67.806641], + [-50.613477, 67.52793], + [-52.666455, 67.749707], + [-53.884424, 67.135547], + [-52.386865, 66.881152], + [-53.41875, 66.648535], + [-53.53877, 66.139355], + [-51.225, 66.881543], + [-53.392041, 66.04834], + [-53.198975, 65.594043], + [-52.55127, 65.461377], + [-51.091895, 65.775781], + [-52.537695, 65.328809], + [-51.922607, 64.21875], + [-50.721582, 64.797607], + [-50.960645, 65.201123], + [-50.121631, 64.70376], + [-50.008984, 64.447266], + [-50.49209, 64.693164], + [-51.40376, 64.463184], + [-51.584912, 64.103174], + [-50.260693, 64.214258], + [-51.54751, 64.006104], + [-51.468848, 63.642285], + [-50.390088, 62.822021], + [-49.793115, 63.044629], + [-50.319238, 62.473193], + [-49.553467, 62.232715], + [-49.623779, 61.998584], + [-48.828711, 62.079688], + [-49.380273, 61.890186], + [-49.289062, 61.589941], + [-48.386426, 61.004736], + [-47.770312, 60.997754], + [-48.180811, 60.769238], + [-46.874463, 60.816406], + [-45.870215, 61.218311], + [-46.046631, 60.615723], + [-45.380518, 60.444922], + [-44.756738, 60.6646], + [-45.379248, 60.20293], + [-44.613281, 60.01665], + [-44.224365, 60.273535], + [-44.412939, 59.922607], + [-43.906543, 59.815479], + [-43.955029, 60.025488], + [-43.1229, 60.06123], + [-43.212988, 60.390674], + [-43.922705, 60.595361], + [-43.044092, 60.523682], + [-42.585303, 61.71748], + [-42.110205, 61.857227], + [-42.152979, 62.568457], + [-42.94165, 62.720215], + [-41.908984, 62.737109], + [-41.634473, 62.972461], + [-42.174512, 63.208789], + [-41.387891, 63.061865], + [-40.550391, 63.725244], + [-40.617773, 64.131738], + [-41.581006, 64.29834], + [-40.781738, 64.221777], + [-40.182227, 64.479932], + [-41.084424, 65.10083], + [-39.937256, 65.141602], + [-39.57793, 65.340771], + [-40.173535, 65.556152], + [-38.203369, 65.711719], + [-38.520361, 66.009668], + [-38.139941, 65.903516], + [-37.752344, 66.261523], + [-38.156641, 66.385596], + [-37.278711, 66.304395], + [-37.954785, 65.633594], + [-36.665186, 65.790088], + [-36.527246, 66.007715], + [-36.379199, 65.830811], + [-35.630078, 66.139941], + [-35.867236, 66.441406], + [-35.188574, 66.250293], + [-34.198242, 66.655078], + [-32.164551, 67.991113], + [-32.327441, 68.437305], + [-30.978564, 68.061328], + [-26.48291, 68.675928], + [-22.287061, 70.033398], + [-25.529883, 70.353174], + [-27.38418, 69.991602], + [-27.56084, 70.124463], + [-26.621777, 70.463379], + [-29.07207, 70.444971], + [-28.069873, 70.699023], + [-28.398438, 70.99292], + [-26.71792, 70.950488], + [-25.742236, 71.183594], + [-25.842725, 71.480176], + [-27.087207, 71.626562], + [-25.885156, 71.571924], + [-24.562207, 71.223535], + [-23.327832, 70.450977], + [-22.690674, 70.437305], + [-22.437012, 70.86001], + [-22.384131, 70.462402], + [-21.522656, 70.526221], + [-21.752246, 71.47832], + [-22.479639, 71.383447], + [-21.959668, 71.744678], + [-25.117871, 72.346973], + [-24.81333, 72.901514], + [-26.657617, 72.71582], + [-24.62998, 73.037646], + [-24.069043, 72.49873], + [-22.293213, 72.119531], + [-22.036328, 72.918457], + [-24.132666, 73.409375], + [-27.348047, 73.067822], + [-27.561621, 73.138477], + [-26.541846, 73.248975], + [-27.27041, 73.436279], + [-26.062305, 73.253027], + [-24.79126, 73.511279], + [-25.521289, 73.851611], + [-22.346875, 73.269238], + [-20.509668, 73.492871], + [-20.367285, 73.848242], + [-22.134814, 73.990479], + [-22.321582, 74.302539], + [-21.94292, 74.565723], + [-21.954932, 74.244287], + [-20.653125, 74.137354], + [-19.369141, 74.284033], + [-19.287012, 74.546387], + [-19.984912, 74.975195], + [-20.861572, 74.635938], + [-20.985791, 75.074365], + [-22.232861, 75.119727], + [-20.484961, 75.314258], + [-19.526367, 75.180225], + [-19.508984, 75.75752], + [-20.103613, 76.219092], + [-21.488232, 76.271875], + [-22.609326, 76.704297], + [-20.486719, 76.920801], + [-18.510303, 76.778174], + [-18.442627, 77.259375], + [-20.680811, 77.618994], + [-19.49043, 77.718896], + [-20.862598, 77.911865], + [-21.72959, 77.708545], + [-21.13374, 78.658643], + [-18.991992, 79.178369], + [-20.150146, 80.01123], + [-19.429199, 80.257715], + [-16.48877, 80.251953], + [-16.760596, 80.573389], + [-11.430664, 81.456836], + [-14.241992, 81.813867], + [-17.456055, 81.397705], + [-19.629932, 81.639893], + [-23.117725, 80.778174], + [-21.337988, 82.068701], + [-24.293066, 81.700977], + [-25.148828, 82.001123], + [-29.887402, 82.054834], + [-23.118066, 82.324707], + [-21.58252, 82.63418], + [-25.123389, 83.159619], + [-32.032715, 82.983447], + [-25.795068, 83.260986], + [-29.952881, 83.564844], + ], + ], + [ + [ + [-52.731152, 69.944727], + [-54.371631, 70.317285], + [-54.919141, 69.713623], + [-53.578418, 69.256641], + [-51.900195, 69.604785], + [-52.731152, 69.944727], + ], + ], + [ + [ + [-55.016895, 72.791113], + [-56.214795, 72.719189], + [-55.566602, 72.564355], + [-55.016895, 72.791113], + ], + ], + [ + [ + [-44.864551, 82.083643], + [-44.91748, 82.480518], + [-47.272266, 82.656934], + [-44.864551, 82.083643], + ], + ], + [ + [ + [-25.432324, 70.921338], + [-27.617236, 70.91377], + [-28.035254, 70.486816], + [-26.604687, 70.553369], + [-26.217871, 70.454053], + [-25.432324, 70.921338], + ], + ], + [ + [ + [-17.6125, 79.825879], + [-17.98291, 80.055176], + [-19.138281, 79.852344], + [-17.6125, 79.825879], + ], + ], + [ + [ + [-18.000537, 75.407324], + [-18.856055, 75.319141], + [-18.670801, 75.00166], + [-17.391992, 75.036914], + [-18.000537, 75.407324], + ], + ], + [ + [ + [-46.266699, 60.781396], + [-46.205225, 60.943506], + [-46.788086, 60.758398], + [-46.266699, 60.781396], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '法罗群岛', full_name: '法罗群岛(丹麦)', iso_a2: 'FO', iso_a3: 'FRO', iso_n3: '234' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-6.631055, 62.227881], + [-7.172168, 62.285596], + [-6.725195, 61.951465], + [-6.631055, 62.227881], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '丹麦', full_name: '丹麦王国', iso_a2: 'DK', iso_a3: 'DNK', iso_n3: '208' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [12.56875, 55.785059], + [12.218945, 56.118652], + [11.819727, 55.697656], + [11.627734, 55.956885], + [10.978906, 55.721533], + [11.862305, 54.772607], + [12.56875, 55.785059], + ], + ], + [ + [ + [9.739746, 54.825537], + [9.591113, 55.493213], + [10.926172, 56.443262], + [10.282715, 56.620508], + [10.609961, 57.736914], + [8.284082, 56.852344], + [8.468359, 56.664551], + [9.254883, 57.011719], + [8.67168, 56.495654], + [8.163965, 56.606885], + [8.132129, 55.599805], + [8.615918, 55.418213], + [8.670313, 54.903418], + [9.739746, 54.825537], + ], + ], + [ + [ + [10.645117, 55.609814], + [9.860645, 55.515479], + [9.98877, 55.163184], + [10.785254, 55.133398], + [10.645117, 55.609814], + ], + ], + [ + [ + [11.361426, 54.89165], + [11.035547, 54.773096], + [11.765918, 54.679443], + [11.361426, 54.89165], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '捷克', full_name: '捷克共和国', iso_a2: 'CZ', iso_a3: 'CZE', iso_n3: '203' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [18.832227, 49.510791], + [18.562402, 49.879346], + [17.627051, 50.116406], + [17.702246, 50.307178], + [16.880078, 50.427051], + [16.63916, 50.102148], + [16.282227, 50.655615], + [14.99375, 51.014355], + [14.809375, 50.858984], + [14.319727, 51.037793], + [12.942676, 50.406445], + [12.089844, 50.301758], + [12.681152, 49.414502], + [13.814746, 48.766943], + [14.691309, 48.599219], + [15.066797, 48.997852], + [16.953125, 48.598828], + [18.832227, 49.510791], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '北塞浦路斯', + full_name: '北塞浦路斯土耳其共和国', + iso_a2: '-99', + iso_a3: '-99', + iso_n3: '-99', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [34.004492, 35.065234], + [33.941992, 35.292041], + [34.556055, 35.662061], + [32.712695, 35.171045], + [34.004492, 35.065234], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '塞浦路斯', full_name: '塞浦路斯共和国', iso_a2: 'CY', iso_a3: 'CYP', iso_n3: '196' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [32.712695, 35.171045], + [32.317188, 34.95332], + [33.00791, 34.56958], + [34.004492, 35.065234], + [32.712695, 35.171045], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '古巴', full_name: '古巴共和国', iso_a2: 'CU', iso_a3: 'CUB', iso_n3: '192' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-81.837451, 23.163037], + [-84.044922, 22.666016], + [-84.361279, 22.378906], + [-84.326367, 22.074316], + [-84.887207, 21.856982], + [-84.030957, 21.943115], + [-82.738037, 22.689258], + [-81.838818, 22.672461], + [-81.710352, 22.49668], + [-82.077734, 22.387695], + [-81.849414, 22.213672], + [-81.185498, 22.267969], + [-79.357422, 21.585156], + [-78.727686, 21.592725], + [-78.116357, 20.761865], + [-77.22959, 20.64375], + [-77.103809, 20.40752], + [-77.715088, 19.855469], + [-75.116406, 19.901416], + [-74.153711, 20.168555], + [-74.882568, 20.650635], + [-75.724561, 20.714551], + [-75.722949, 21.111035], + [-77.252881, 21.483496], + [-77.366162, 21.612646], + [-77.144141, 21.643604], + [-77.497266, 21.871631], + [-79.275684, 22.407617], + [-79.820264, 22.887012], + [-81.837451, 23.163037], + ], + ], + [ + [ + [-82.561768, 21.57168], + [-82.991211, 21.942725], + [-82.973584, 21.592285], + [-83.183789, 21.593457], + [-82.561768, 21.57168], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '克罗地亚', full_name: '克罗地亚共和国', iso_a2: 'HR', iso_a3: 'HRV', iso_n3: '191' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [13.57793, 45.516895], + [13.860742, 44.837402], + [14.550488, 45.297705], + [15.470996, 44.271973], + [15.18584, 44.172119], + [15.985547, 43.519775], + [16.903125, 43.392432], + [17.585156, 42.938379], + [15.736621, 44.76582], + [15.788086, 45.178955], + [16.293359, 45.008838], + [16.918652, 45.276562], + [19.007129, 44.869189], + [19.4, 45.2125], + [19.004688, 45.399512], + [18.905371, 45.931738], + [17.807129, 45.79043], + [16.516211, 46.499902], + [15.635938, 46.200732], + [15.339453, 45.467041], + [14.568848, 45.657227], + [13.57793, 45.516895], + ], + ], + [ + [ + [14.810254, 44.977051], + [14.571094, 45.224756], + [14.450391, 45.079199], + [14.810254, 44.977051], + ], + ], + [ + [ + [18.436328, 42.559717], + [17.667578, 42.897119], + [17.04541, 43.014893], + [18.51748, 42.43291], + [18.436328, 42.559717], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '科特迪瓦', full_name: '科特迪瓦共和国', iso_a2: 'CI', iso_a3: 'CIV', iso_n3: '384' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-7.990625, 10.1625], + [-8.136963, 9.495703], + [-7.896191, 9.415869], + [-7.950977, 8.786816], + [-7.681201, 8.410352], + [-8.236963, 8.455664], + [-8.009863, 8.078516], + [-8.231885, 7.556738], + [-8.486426, 7.558496], + [-8.302344, 6.980957], + [-8.603564, 6.507812], + [-7.454395, 5.841309], + [-7.544971, 4.351318], + [-5.061816, 5.130664], + [-5.282373, 5.210254], + [-3.347559, 5.130664], + [-3.199951, 5.354492], + [-3.019141, 5.130811], + [-2.75498, 5.43252], + [-3.235791, 6.807227], + [-2.505859, 8.20874], + [-2.69585, 9.481348], + [-3.223535, 9.895459], + [-4.62583, 9.713574], + [-5.523535, 10.426025], + [-6.196875, 10.232129], + [-6.261133, 10.724072], + [-6.65415, 10.656445], + [-7.01709, 10.143262], + [-7.497949, 10.439795], + [-7.990625, 10.1625], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '哥斯达黎加', full_name: '哥斯达黎加共和国', iso_a2: 'CR', iso_a3: 'CRI', iso_n3: '188' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-82.563574, 9.57666], + [-83.641992, 10.917236], + [-83.919287, 10.735352], + [-84.63418, 11.045605], + [-85.744336, 11.062109], + [-85.908008, 10.897559], + [-85.667236, 10.74502], + [-85.849658, 10.292041], + [-85.681006, 9.958594], + [-85.114502, 9.581787], + [-84.886426, 9.820947], + [-85.263184, 10.256641], + [-85.025049, 10.115723], + [-84.581592, 9.568359], + [-83.637256, 9.035352], + [-83.604736, 8.480322], + [-83.291504, 8.406006], + [-83.469727, 8.706836], + [-83.285791, 8.664355], + [-82.879346, 8.070654], + [-83.027344, 8.337744], + [-82.727832, 8.916064], + [-82.939844, 9.44917], + [-82.563574, 9.57666], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '刚果(金)', full_name: '刚果民主共和国', iso_a2: 'CD', iso_a3: 'COD', iso_n3: '180' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [30.751172, -8.193652], + [30.212695, -7.037891], + [29.54082, -6.313867], + [29.403223, -4.449316], + [29.014355, -2.720215], + [28.876367, -2.400293], + [29.576953, -1.387891], + [29.942871, 0.819238], + [31.252734, 2.04458], + [30.728613, 2.455371], + [30.838574, 3.490723], + [29.676855, 4.586914], + [28.19209, 4.350244], + [27.40332, 5.10918], + [25.525098, 5.312109], + [25.065234, 4.967432], + [22.864551, 4.723877], + [22.422168, 4.134961], + [20.558105, 4.462695], + [19.500977, 5.12749], + [18.594141, 4.34624], + [18.610352, 3.478418], + [18.072168, 2.013281], + [17.752832, -0.549023], + [16.879883, -1.225879], + [16.215332, -2.177832], + [15.990039, -3.766211], + [14.70791, -4.881738], + [14.410742, -4.83125], + [14.358301, -4.299414], + [13.71709, -4.454492], + [13.414941, -4.837402], + [13.072754, -4.634766], + [12.451465, -5.071484], + [12.503711, -5.695801], + [12.213672, -5.758691], + [12.45293, -6.000488], + [13.068164, -5.864844], + [16.431445, -5.900195], + [17.57959, -8.099023], + [19.34082, -7.966602], + [19.527637, -7.144434], + [19.875195, -6.986328], + [20.590039, -6.919922], + [20.607813, -7.277734], + [21.781641, -7.314648], + [21.813184, -9.46875], + [22.274512, -10.259082], + [22.226172, -11.121973], + [23.966504, -10.871777], + [24.365723, -11.129883], + [24.37793, -11.41709], + [25.28877, -11.212402], + [25.349414, -11.623047], + [26.025977, -11.890137], + [26.824023, -11.965234], + [27.15918, -11.579199], + [27.573828, -12.227051], + [28.412891, -12.518066], + [29.014258, -13.368848], + [29.554199, -13.248926], + [29.775195, -13.438086], + [29.795117, -12.155469], + [29.485547, -12.418457], + [29.064355, -12.348828], + [28.383398, -11.566699], + [28.645508, -10.550195], + [28.400684, -9.224805], + [28.898145, -8.485449], + [30.751172, -8.193652], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '刚果(布)', full_name: '刚果共和国', iso_a2: 'CG', iso_a3: 'COG', iso_n3: '178' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [11.130176, -3.916309], + [12.018359, -5.004297], + [12.798242, -4.430566], + [13.072754, -4.634766], + [13.414941, -4.837402], + [13.71709, -4.454492], + [14.358301, -4.299414], + [14.410742, -4.83125], + [14.70791, -4.881738], + [15.990039, -3.766211], + [16.215332, -2.177832], + [16.879883, -1.225879], + [17.752832, -0.549023], + [18.072168, 2.013281], + [18.610352, 3.478418], + [17.491602, 3.687305], + [16.610742, 3.505371], + [16.183398, 2.270068], + [16.059375, 1.676221], + [14.578906, 2.199121], + [13.293555, 2.161572], + [13.216309, 1.248438], + [14.180859, 1.370215], + [14.429883, 0.901465], + [13.949609, 0.353809], + [13.860059, -0.20332], + [14.474121, -0.573438], + [14.383984, -1.890039], + [13.993848, -2.490625], + [13.733789, -2.138477], + [13.464941, -2.39541], + [12.991992, -2.313379], + [12.59043, -1.826855], + [12.446387, -2.32998], + [11.605469, -2.342578], + [11.537793, -2.836719], + [11.93418, -3.318555], + [11.879883, -3.665918], + [11.504297, -3.520312], + [11.130176, -3.916309], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '科摩罗', full_name: '科摩罗联盟', iso_a2: 'KM', iso_a3: 'COM', iso_n3: '174' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [44.476367, -12.081543], + [44.220117, -12.171387], + [44.50498, -12.356543], + [44.476367, -12.081543], + ], + ], + [ + [ + [43.46582, -11.90127], + [43.299023, -11.374512], + [43.22666, -11.751855], + [43.46582, -11.90127], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '哥伦比亚', full_name: '哥伦比亚共和国', iso_a2: 'CO', iso_a3: 'COL', iso_n3: '170' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-71.319727, 11.861914], + [-71.137305, 12.046338], + [-71.262109, 12.335303], + [-71.714551, 12.419971], + [-73.313379, 11.295752], + [-74.14292, 11.32085], + [-74.400879, 10.765234], + [-74.330225, 10.99668], + [-74.84458, 11.109717], + [-75.70835, 10.143408], + [-75.538574, 10.205176], + [-75.639355, 9.450439], + [-76.920459, 8.57373], + [-76.786572, 7.931592], + [-77.374219, 8.658301], + [-77.195996, 7.972461], + [-77.538281, 7.56626], + [-77.761914, 7.698828], + [-77.901172, 7.229346], + [-77.368799, 6.575586], + [-77.469434, 6.176758], + [-77.249268, 5.780176], + [-77.534424, 5.537109], + [-77.286328, 4.721729], + [-77.520703, 4.212793], + [-77.076807, 3.913281], + [-77.813574, 2.716357], + [-78.591699, 2.356641], + [-78.576904, 1.773779], + [-79.025439, 1.623682], + [-78.859668, 1.455371], + [-77.702881, 0.837842], + [-77.396338, 0.393896], + [-76.494629, 0.235449], + [-76.270605, 0.439404], + [-75.284473, -0.106543], + [-74.801758, -0.200098], + [-74.246387, -0.970605], + [-73.664307, -1.248828], + [-72.941113, -2.394043], + [-70.968555, -2.206836], + [-70.09585, -2.658203], + [-70.735107, -3.781543], + [-70.339502, -3.814355], + [-69.965918, -4.235938], + [-69.400244, -1.194922], + [-69.633984, -0.509277], + [-70.070508, -0.138867], + [-70.053906, 0.578613], + [-69.15332, 0.658789], + [-69.311816, 1.050488], + [-69.852148, 1.059521], + [-69.848584, 1.70874], + [-68.176562, 1.719824], + [-68.193799, 1.987012], + [-67.93623, 1.748486], + [-67.400439, 2.116699], + [-67.082275, 1.1854], + [-66.876025, 1.223047], + [-67.21084, 2.390137], + [-67.859082, 2.793604], + [-67.311133, 3.415869], + [-67.855273, 4.506885], + [-67.481982, 6.180273], + [-69.427148, 6.123975], + [-70.129199, 6.953613], + [-72.006641, 7.032617], + [-72.471973, 7.524268], + [-72.390332, 8.287061], + [-72.796387, 9.108984], + [-73.366211, 9.194141], + [-72.690088, 10.83584], + [-71.958105, 11.666406], + [-71.319727, 11.861914], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '中国', full_name: '中华人民共和国', iso_a2: 'CN', iso_a3: 'CHN', iso_n3: '156' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [107.972656, 21.507959], + [108.502148, 21.633447], + [108.479883, 21.904639], + [109.081543, 21.440283], + [109.521484, 21.693408], + [109.930762, 21.480566], + [109.662598, 20.916895], + [110.123145, 20.263721], + [110.517578, 20.46001], + [110.154004, 20.944629], + [110.410937, 21.338135], + [110.567187, 21.214062], + [111.602734, 21.559082], + [111.943945, 21.849658], + [112.30498, 21.741699], + [112.359668, 21.978027], + [112.586328, 21.776855], + [113.08877, 22.207959], + [113.548145, 22.222607], + [113.331055, 22.912012], + [113.519727, 23.1021], + [114.01543, 22.511914], + [113.937305, 22.36499], + [114.267969, 22.295557], + [114.266016, 22.540967], + [116.470703, 22.945898], + [116.910645, 23.64668], + [117.367676, 23.588623], + [118.056055, 24.246094], + [117.842676, 24.474316], + [118.657031, 24.621436], + [118.977539, 25.209277], + [119.285547, 25.232227], + [119.180078, 25.449805], + [119.622461, 25.391162], + [119.61875, 26.003564], + [119.139453, 26.121777], + [119.463086, 26.054688], + [119.881055, 26.33418], + [119.588184, 26.784961], + [120.086719, 26.671582], + [120.747656, 28.009961], + [121.145703, 28.32666], + [121.609961, 28.292139], + [121.487109, 29.193164], + [121.917773, 29.13501], + [121.941211, 29.605908], + [121.50625, 29.48457], + [122.08291, 29.870361], + [121.258008, 30.304102], + [120.194629, 30.241309], + [121.87793, 30.916992], + [120.715527, 31.98374], + [120.035937, 31.936279], + [120.520117, 32.105859], + [121.856348, 31.816455], + [120.853223, 32.661377], + [120.266699, 34.274023], + [119.165332, 34.848828], + [120.284766, 35.984424], + [120.183301, 36.202441], + [120.637891, 36.129932], + [120.81084, 36.632812], + [121.932715, 36.959473], + [122.340918, 36.832227], + [122.666992, 37.402832], + [121.640234, 37.460352], + [120.75, 37.833936], + [119.760547, 37.155078], + [119.287402, 37.138281], + [118.952637, 37.331152], + [118.940039, 38.042773], + [117.766699, 38.31167], + [117.616699, 38.852881], + [117.865723, 39.19126], + [118.976953, 39.182568], + [119.391113, 39.75249], + [120.479102, 40.230957], + [121.174512, 40.90127], + [121.834863, 40.974268], + [122.275, 40.541846], + [121.26748, 39.544678], + [121.818457, 39.386523], + [121.106738, 38.920801], + [121.163574, 38.731641], + [122.840039, 39.60083], + [124.362109, 40.004053], + [125.989062, 40.904639], + [126.743066, 41.724854], + [128.149414, 41.387744], + [128.045215, 41.9875], + [128.923438, 42.038232], + [129.697852, 42.448145], + [129.898242, 42.998145], + [130.526953, 42.5354], + [130.424805, 42.727051], + [131.068555, 42.902246], + [131.257324, 43.378076], + [130.981641, 44.844336], + [131.851855, 45.326855], + [133.113477, 45.130713], + [134.167676, 47.302197], + [134.752344, 47.71543], + [134.665234, 48.253906], + [135.083406, 48.436324], + [134.293359, 48.373438], + [133.144043, 48.105664], + [132.47627, 47.71499], + [130.961914, 47.709326], + [130.553125, 48.861182], + [129.498145, 49.388818], + [127.550781, 49.801807], + [127.590234, 50.208984], + [125.649023, 53.042285], + [123.607813, 53.546533], + [120.985449, 53.28457], + [120.094531, 52.787207], + [120.656152, 52.56665], + [120.749805, 52.096533], + [119.163672, 50.406006], + [119.259863, 50.066406], + [117.873438, 49.513477], + [116.683301, 49.823779], + [115.616406, 47.874805], + [115.898242, 47.686914], + [116.760547, 47.869775], + [117.350781, 47.652197], + [117.768359, 47.987891], + [118.498438, 47.983984], + [119.711133, 47.15], + [119.867188, 46.672168], + [117.438086, 46.58623], + [117.333398, 46.362012], + [116.562598, 46.289795], + [115.681055, 45.458252], + [114.560156, 45.38999], + [113.587012, 44.745703], + [111.898047, 45.064062], + [111.402246, 44.367285], + [111.933203, 43.711426], + [110.400391, 42.773682], + [109.339844, 42.438379], + [106.77002, 42.288721], + [104.982031, 41.595508], + [104.498242, 41.658691], + [104.498242, 41.877002], + [103.711133, 41.751318], + [102.156641, 42.158105], + [101.495313, 42.53877], + [96.385449, 42.720361], + [95.350293, 44.278076], + [93.516211, 44.944482], + [90.877246, 45.196094], + [90.869922, 46.954492], + [90.02793, 47.877686], + [89.047656, 48.002539], + [87.979688, 48.555127], + [87.814258, 49.162305], + [87.322852, 49.085791], + [86.808301, 49.049707], + [86.549414, 48.528613], + [85.749414, 48.385059], + [85.484766, 47.063525], + [84.786133, 46.830713], + [83.029492, 47.185938], + [82.315234, 45.594922], + [82.521484, 45.125488], + [81.691992, 45.349365], + [79.871875, 44.883789], + [80.481543, 44.714648], + [80.355273, 44.097266], + [80.785742, 43.161572], + [80.202246, 42.734473], + [80.209375, 42.190039], + [78.123438, 41.075635], + [76.907715, 41.02417], + [76.318555, 40.352246], + [75.677148, 40.305811], + [75.555566, 40.625195], + [74.835156, 40.482617], + [73.991602, 40.043115], + [73.631641, 39.448877], + [73.80166, 38.606885], + [74.812305, 38.460303], + [75.11875, 37.385693], + [74.891309, 37.231641], + [74.372168, 37.157715], + [74.541406, 37.022168], + [75.772168, 36.694922], + [75.912305, 36.048975], + [76.766895, 35.661719], + [77.799414, 35.495898], + [78.042676, 35.479785], + [78.326953, 34.606396], + [78.970117, 34.302637], + [78.72666, 34.013379], + [79.219336, 32.501074], + [78.918945, 32.358203], + [78.700879, 32.597021], + [78.389648, 32.519873], + [78.807678, 31.099982], + [79.707774, 31.013593], + [81.010254, 30.164502], + [82.043359, 30.326758], + [83.583496, 29.183594], + [84.101367, 29.219971], + [84.228711, 28.911768], + [85.159082, 28.592236], + [85.122461, 28.315967], + [85.67832, 28.277441], + [85.994531, 27.9104], + [86.137012, 28.114355], + [87.141406, 27.83833], + [88.109766, 27.870605], + [88.803711, 28.006934], + [88.891406, 27.316064], + [89.981055, 28.311182], + [90.352734, 28.080225], + [91.273047, 28.078369], + [91.631934, 27.759961], + [91.594727, 27.557666], + [92.083398, 27.290625], + [91.99834, 26.85498], + [92.64698, 26.952656], + [93.111399, 26.880082], + [93.817265, 27.025183], + [94.277372, 27.58143], + [94.88592, 27.743098], + [95.28628, 27.939955], + [95.39715, 28.142259], + [95.832003, 28.295186], + [96.275479, 28.228241], + [97.086778, 27.7475], + [97.335156, 27.937744], + [97.322461, 28.217969], + [97.599219, 28.517041], + [98.061621, 28.185889], + [98.298828, 27.550098], + [98.651172, 27.572461], + [98.738477, 26.785742], + [98.65625, 25.863574], + [97.819531, 25.251855], + [97.583301, 24.774805], + [97.564551, 23.911035], + [98.835059, 24.121191], + [98.676758, 23.905078], + [98.86377, 23.19126], + [99.507129, 22.959131], + [99.192969, 22.125977], + [99.917676, 22.028027], + [100.147656, 21.480518], + [101.079785, 21.755859], + [101.138867, 21.56748], + [101.247852, 21.197314], + [101.800586, 21.212598], + [101.524512, 22.253662], + [101.73877, 22.495264], + [102.127441, 22.379199], + [102.470898, 22.750928], + [102.981934, 22.448242], + [103.32666, 22.769775], + [103.941504, 22.540088], + [105.275391, 23.345215], + [105.842969, 22.922803], + [106.780273, 22.778906], + [106.550391, 22.501367], + [106.663574, 21.978906], + [107.972656, 21.507959], + ], + ], + [ + [ + [110.88877, 19.991943], + [110.651758, 20.137744], + [109.263477, 19.882666], + [108.665527, 19.304102], + [108.701563, 18.535254], + [109.519336, 18.218262], + [110.067383, 18.447559], + [111.013672, 19.655469], + [110.88877, 19.991943], + ], + ], + [ + [ + [121.008789, 22.620361], + [121.397461, 23.17251], + [121.929004, 24.97373], + [121.593652, 25.275342], + [121.040625, 25.032812], + [120.132129, 23.65293], + [120.232813, 22.71792], + [120.839844, 21.925], + [121.008789, 22.620361], + ], + ], + [ + [ + [116.769628, 20.771721], + [116.889736, 20.683284], + [116.749302, 20.600958], + [116.862635, 20.588633], + [116.925461, 20.726949], + [116.769628, 20.771721], + ], + ], + [ + [ + [113.896887, 7.607204], + [114.058879, 7.537794], + [114.368696, 7.638642], + [114.540543, 7.945783], + [113.896887, 7.607204], + ], + ], + [ + [ + [109.463972, 7.344339], + [109.948716, 7.522962], + [109.653065, 7.559745], + [109.463972, 7.344339], + ], + ], + [ + [ + [116.48876, 10.395686], + [116.467202, 10.309144], + [116.644592, 10.335051], + [116.48876, 10.395686], + ], + ], + [ + [ + [122.518653, 23.460785], + [122.798614, 24.573674], + [122.779218, 24.578553], + [122.499257, 23.465664], + [122.518653, 23.460785], + ], + ], + [ + [ + [121.172026, 20.805459], + [121.909388, 21.687433], + [121.894044, 21.700262], + [121.156682, 20.818287], + [121.172026, 20.805459], + ], + ], + [ + [ + [119.473662, 18.007073], + [120.025697, 19.024038], + [120.00812, 19.033579], + [119.456084, 18.016614], + [119.473662, 18.007073], + ], + ], + [ + [ + [119.072182, 15.007514], + [119.072676, 16.043885], + [119.052676, 16.043885], + [119.052184, 15.00781], + [119.072182, 15.007514], + ], + ], + [ + [ + [118.686467, 11.189592], + [118.524042, 10.912566], + [118.540436, 10.90292], + [118.704762, 11.181475], + [118.874599, 11.607472], + [118.98895, 11.985731], + [118.969805, 11.991519], + [118.855579, 11.613671], + [118.686467, 11.189592], + ], + ], + [ + [ + [115.544669, 7.146723], + [116.250486, 7.979279], + [116.23523, 7.992212], + [115.529413, 7.159656], + [115.544669, 7.146723], + ], + ], + [ + [ + [112.307052, 3.534873], + [111.786901, 3.416873], + [111.791326, 3.397368], + [112.312489, 3.515623], + [112.521474, 3.578591], + [112.852064, 3.732569], + [112.843614, 3.750696], + [112.515016, 3.597533], + [112.307052, 3.534873], + ], + ], + [ + [ + [108.290133, 6.012663], + [108.308786, 6.019877], + [108.279563, 6.095434], + [108.256117, 6.227526], + [108.2168, 6.538165], + [108.218763, 6.949641], + [108.244195, 7.073907], + [108.224601, 7.077917], + [108.198768, 6.950725], + [108.196797, 6.537606], + [108.236307, 6.224768], + [108.26004, 6.090984], + [108.290133, 6.012663], + ], + ], + [ + [ + [110.128228, 11.368945], + [110.055537, 11.253354], + [110.072467, 11.242707], + [110.145887, 11.359542], + [110.207005, 11.481288], + [110.259018, 11.604996], + [110.304569, 11.783642], + [110.328228, 11.945713], + [110.334243, 12.141598], + [110.332274, 12.240384], + [110.312278, 12.239982], + [110.314245, 12.141953], + [110.308355, 11.948035], + [110.284855, 11.787051], + [110.239823, 11.610665], + [110.188981, 11.489964], + [110.128228, 11.368945], + ], + ], + [ + [ + [109.845225, 15.153166], + [109.864809, 15.157224], + [109.848892, 15.233933], + [109.789745, 15.450683], + [109.690053, 15.675484], + [109.591475, 15.836774], + [109.532015, 15.922592], + [109.30888, 16.207258], + [109.29314, 16.194919], + [109.515744, 15.910958], + [109.57456, 15.826099], + [109.672646, 15.665615], + [109.77065, 15.444688], + [109.829516, 15.228968], + [109.845225, 15.153166], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '智利', full_name: '智利共和国', iso_a2: 'CL', iso_a3: 'CHL', iso_n3: '152' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-70.418262, -18.345605], + [-70.080029, -21.356836], + [-70.593359, -23.255469], + [-70.392334, -23.565918], + [-70.445361, -25.172656], + [-70.925781, -27.588672], + [-71.519238, -28.926465], + [-71.315723, -29.649707], + [-71.708936, -30.628027], + [-71.452246, -32.65957], + [-72.223779, -35.096191], + [-73.215967, -37.166895], + [-73.662402, -37.341016], + [-73.226465, -39.224414], + [-73.965869, -41.118262], + [-73.624023, -41.773633], + [-72.318262, -41.499023], + [-72.824072, -41.908789], + [-72.499414, -41.980859], + [-72.412354, -42.388184], + [-72.773242, -42.257715], + [-72.758008, -43.039453], + [-73.224463, -43.897949], + [-73.265088, -44.168652], + [-72.663867, -44.436426], + [-72.680078, -44.593945], + [-73.444971, -45.238184], + [-72.933838, -45.452344], + [-73.730762, -45.47998], + [-73.591846, -45.899121], + [-73.845361, -46.566016], + [-73.735254, -45.811719], + [-73.967578, -46.154102], + [-74.392969, -46.217383], + [-74.019922, -46.055859], + [-73.957178, -45.404395], + [-74.157861, -45.767188], + [-75.066699, -45.874902], + [-74.924463, -46.159668], + [-75.706396, -46.705273], + [-75.430371, -46.93457], + [-75.540332, -46.69873], + [-74.98418, -46.512109], + [-75.005957, -46.741113], + [-74.313574, -46.788184], + [-74.158398, -47.18252], + [-74.482666, -47.430469], + [-74.134082, -47.59082], + [-74.654932, -47.702246], + [-74.227051, -47.968945], + [-73.715869, -47.655469], + [-73.391064, -48.145898], + [-74.584668, -47.999023], + [-74.474414, -48.463965], + [-74.009082, -48.475], + [-74.341016, -48.595703], + [-74.366553, -49.400488], + [-73.934961, -49.020898], + [-73.836377, -49.609375], + [-74.29082, -49.604102], + [-73.958594, -49.994727], + [-74.62959, -50.194043], + [-73.950342, -50.510547], + [-74.185596, -50.485352], + [-73.978027, -50.827051], + [-73.654443, -50.492676], + [-73.806543, -50.938379], + [-74.139404, -50.817773], + [-74.365576, -50.487891], + [-74.644482, -50.360938], + [-74.685742, -50.662012], + [-75.094678, -50.68125], + [-74.814746, -51.062891], + [-73.939502, -51.266309], + [-74.19668, -51.680566], + [-73.518164, -52.041016], + [-72.600049, -51.799121], + [-73.16875, -51.453906], + [-72.76123, -51.573242], + [-72.489648, -51.763672], + [-72.52334, -52.255469], + [-72.677051, -52.384668], + [-72.79502, -51.949512], + [-73.834473, -52.233984], + [-74.264941, -52.104883], + [-74.014453, -52.639355], + [-73.123926, -52.487988], + [-73.645215, -52.837012], + [-73.122461, -53.073926], + [-72.712109, -52.535547], + [-71.511279, -52.605371], + [-72.727686, -52.762305], + [-73.052734, -53.243457], + [-72.548926, -53.460742], + [-72.278027, -53.132324], + [-71.227148, -52.810645], + [-71.791455, -53.48457], + [-71.941699, -53.234082], + [-72.412891, -53.350195], + [-71.297754, -53.883398], + [-70.795117, -52.76875], + [-69.241016, -52.205469], + [-68.443359, -52.356641], + [-69.960254, -52.008203], + [-71.918652, -51.989551], + [-72.407666, -51.54082], + [-72.340234, -50.681836], + [-73.15293, -50.738281], + [-73.50127, -50.125293], + [-73.554199, -49.463867], + [-72.354736, -48.36582], + [-72.51792, -47.876367], + [-71.699658, -46.651367], + [-71.746191, -45.578906], + [-71.349316, -45.331934], + [-72.063721, -44.771875], + [-71.261133, -44.763086], + [-71.159717, -44.560254], + [-71.82002, -44.383105], + [-71.750635, -43.237305], + [-72.146436, -42.990039], + [-72.108203, -42.251855], + [-71.75, -42.046777], + [-71.932129, -40.691699], + [-71.401562, -38.935059], + [-70.858643, -38.604492], + [-71.192187, -36.843652], + [-70.404785, -36.061719], + [-70.555176, -35.246875], + [-69.852441, -34.224316], + [-69.819629, -33.283789], + [-70.084863, -33.201758], + [-70.51958, -31.148438], + [-69.844287, -30.175], + [-70.026807, -29.324023], + [-69.656934, -28.413574], + [-68.846338, -27.153711], + [-68.318652, -26.973242], + [-68.591602, -26.47041], + [-68.384229, -25.091895], + [-68.562012, -24.747363], + [-67.356201, -24.033789], + [-67.008789, -23.001367], + [-67.194873, -22.82168], + [-67.879443, -22.822949], + [-68.197021, -21.300293], + [-68.760547, -20.416211], + [-68.462891, -19.432813], + [-68.968311, -18.967969], + [-69.093945, -18.050488], + [-69.510938, -17.506055], + [-69.8521, -17.703809], + [-69.926367, -18.206055], + [-70.418262, -18.345605], + ], + [ + [-74.385742, -52.922363], + [-74.712012, -52.74873], + [-73.135205, -53.353906], + [-74.385742, -52.922363], + ], + ], + [ + [ + [-68.629932, -52.652637], + [-69.414062, -52.48623], + [-69.935449, -52.821094], + [-70.380127, -52.751953], + [-70.329297, -53.377637], + [-69.355957, -53.416309], + [-70.151123, -53.888086], + [-69.044336, -54.406738], + [-69.253174, -54.557422], + [-70.535303, -54.136133], + [-70.531299, -53.627344], + [-70.863086, -54.110449], + [-70.310986, -54.528516], + [-70.797266, -54.327246], + [-71.927734, -54.528711], + [-68.653223, -54.853613], + [-68.629932, -52.652637], + ], + ], + [ + [ + [-67.079932, -55.153809], + [-68.301367, -54.980664], + [-68.07002, -55.221094], + [-67.079932, -55.153809], + ], + ], + [ + [ + [-73.773389, -43.345898], + [-73.436328, -42.936523], + [-73.789258, -42.585742], + [-73.4229, -42.192871], + [-73.527832, -41.896289], + [-74.03667, -41.795508], + [-74.387354, -43.231641], + [-73.773389, -43.345898], + ], + ], + [ + [ + [-74.476172, -49.147852], + [-74.546094, -48.766895], + [-74.89624, -48.733203], + [-75.549805, -49.791309], + [-75.066016, -49.852344], + [-74.723828, -49.423828], + [-74.594727, -50.006641], + [-74.476172, -49.147852], + ], + ], + [ + [ + [-75.510254, -48.763477], + [-75.158496, -48.622656], + [-75.391406, -48.019727], + [-75.510254, -48.763477], + ], + ], + [ + [ + [-74.567285, -48.591992], + [-74.895654, -47.839355], + [-75.212891, -48.141699], + [-74.923047, -48.626465], + [-74.567285, -48.591992], + ], + ], + [ + [ + [-72.923242, -53.481641], + [-73.845459, -53.545801], + [-73.210645, -53.98584], + [-72.76377, -53.864844], + [-72.870996, -54.126562], + [-72.20542, -53.807422], + [-72.923242, -53.481641], + ], + ], + [ + [ + [-74.822949, -51.630176], + [-75.105371, -51.788867], + [-74.694482, -52.279199], + [-74.822949, -51.630176], + ], + ], + [ + [ + [-69.702979, -54.919043], + [-69.979785, -55.147461], + [-69.411816, -55.444238], + [-69.192627, -55.171875], + [-68.04834, -55.643164], + [-68.458008, -54.959668], + [-69.702979, -54.919043], + ], + ], + [ + [ + [-71.390479, -54.032812], + [-71.996484, -53.884863], + [-72.210449, -54.047754], + [-71.948535, -54.300879], + [-71.143262, -54.374023], + [-71.021924, -54.111816], + [-71.390479, -54.032812], + ], + ], + [ + [ + [-73.735352, -44.394531], + [-73.994922, -44.140234], + [-74.617773, -44.647949], + [-74.01626, -45.344922], + [-73.728174, -45.195898], + [-74.002051, -44.590918], + [-73.735352, -44.394531], + ], + ], + [ + [ + [-72.986133, -44.780078], + [-72.776367, -44.508594], + [-73.207715, -44.334961], + [-73.39707, -44.774316], + [-72.986133, -44.780078], + ], + ], + [ + [ + [-75.302002, -50.67998], + [-75.115332, -50.510449], + [-75.427637, -50.480566], + [-75.302002, -50.67998], + ], + ], + [ + [ + [-75.106689, -48.836523], + [-75.583105, -48.858887], + [-75.641162, -49.19541], + [-75.106689, -48.836523], + ], + ], + [ + [ + [-74.558643, -51.277051], + [-75.153662, -51.278809], + [-75.289111, -51.625391], + [-74.558643, -51.277051], + ], + ], + [ + [ + [-75.054785, -50.296094], + [-74.875977, -50.109961], + [-75.32666, -50.011816], + [-75.449121, -50.343359], + [-75.054785, -50.296094], + ], + ], + [ + [ + [-74.312891, -45.691504], + [-74.310547, -45.172656], + [-74.689844, -45.662598], + [-74.312891, -45.691504], + ], + ], + [ + [ + [-70.991602, -54.867969], + [-71.437207, -54.889258], + [-70.297852, -55.11377], + [-70.991602, -54.867969], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '乍得', full_name: '乍得共和国', iso_a2: 'TD', iso_a3: 'TCD', iso_n3: '148' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [23.980273, 19.496631], + [20.147656, 21.389258], + [15.984082, 23.445215], + [14.979004, 22.996191], + [15.181836, 21.523389], + [15.963184, 20.346191], + [15.735059, 19.904053], + [15.474316, 16.908398], + [13.448242, 14.380664], + [13.606348, 13.70459], + [14.063965, 13.078516], + [14.461719, 13.021777], + [14.84707, 12.5021], + [15.132227, 10.648486], + [15.654883, 10.007812], + [14.243262, 9.979736], + [13.977246, 9.691553], + [15.116211, 8.557324], + [15.549805, 7.787891], + [15.480078, 7.523779], + [15.957617, 7.507568], + [16.545313, 7.865479], + [16.784766, 7.550977], + [17.649414, 7.983594], + [18.56416, 8.045898], + [19.108691, 8.656152], + [18.95625, 8.938867], + [20.34209, 9.1271], + [21.682715, 10.289844], + [21.771484, 10.642822], + [22.493848, 10.99624], + [22.860059, 10.919678], + [22.922656, 11.344873], + [22.591113, 11.579883], + [22.352344, 12.660449], + [21.825293, 12.790527], + [22.228125, 13.32959], + [22.106445, 13.799805], + [22.538574, 14.161865], + [22.381543, 14.550488], + [22.932324, 15.162109], + [22.933887, 15.533105], + [23.970801, 15.721533], + [23.980273, 19.496631], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '中非', full_name: '中非共和国', iso_a2: 'CF', iso_a3: 'CAF', iso_n3: '140' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [24.147363, 8.665625], + [23.537305, 8.81582], + [23.646289, 9.8229], + [22.860059, 10.919678], + [22.493848, 10.99624], + [21.771484, 10.642822], + [21.682715, 10.289844], + [20.34209, 9.1271], + [18.95625, 8.938867], + [19.108691, 8.656152], + [18.56416, 8.045898], + [17.649414, 7.983594], + [16.784766, 7.550977], + [16.545313, 7.865479], + [15.957617, 7.507568], + [15.480078, 7.523779], + [14.431152, 6.038721], + [14.73125, 4.602393], + [15.063574, 4.284863], + [15.128711, 3.826904], + [16.063477, 2.908594], + [16.183398, 2.270068], + [16.610742, 3.505371], + [17.491602, 3.687305], + [18.610352, 3.478418], + [18.594141, 4.34624], + [19.500977, 5.12749], + [20.558105, 4.462695], + [22.422168, 4.134961], + [22.864551, 4.723877], + [25.065234, 4.967432], + [25.525098, 5.312109], + [27.40332, 5.10918], + [27.143945, 5.722949], + [26.514258, 6.069238], + [26.361816, 6.635303], + [25.278906, 7.42749], + [24.85332, 8.137549], + [24.291406, 8.291406], + [24.147363, 8.665625], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '佛得角', full_name: '佛得角共和国', iso_a2: 'CV', iso_a3: 'CPV', iso_n3: '132' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-25.169824, 16.946484], + [-25.034668, 17.176465], + [-25.337109, 17.091016], + [-25.169824, 16.946484], + ], + ], + [ + [ + [-23.444238, 15.007959], + [-23.748096, 15.328516], + [-23.705371, 14.961328], + [-23.444238, 15.007959], + ], + ], + [ + [ + [-22.917725, 16.237256], + [-22.959277, 16.045117], + [-22.710107, 16.043359], + [-22.917725, 16.237256], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '加拿大', full_name: '加拿大', iso_a2: 'CA', iso_a3: 'CAN', iso_n3: '124' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-132.655518, 54.12749], + [-133.048389, 54.158936], + [-133.079492, 53.837012], + [-132.347266, 53.189209], + [-131.957422, 53.308691], + [-131.667627, 54.141357], + [-132.534668, 53.651709], + [-132.166113, 53.955225], + [-132.655518, 54.12749], + ], + ], + [ + [ + [-131.753711, 53.195557], + [-132.546777, 53.1375], + [-131.221533, 52.153613], + [-131.455225, 52.701709], + [-131.971777, 52.879834], + [-131.634668, 52.922168], + [-131.753711, 53.195557], + ], + ], + [ + [ + [-127.197314, 50.640381], + [-127.918066, 50.860547], + [-128.346045, 50.744238], + [-128.05835, 50.498486], + [-127.465918, 50.583105], + [-127.486523, 50.404639], + [-127.905859, 50.445215], + [-127.863916, 50.127734], + [-127.249805, 50.137988], + [-127.114307, 49.879736], + [-126.134082, 49.672314], + [-126.519141, 49.396777], + [-125.9354, 49.401465], + [-125.489453, 48.933789], + [-124.812646, 49.212646], + [-125.120703, 48.760791], + [-123.310645, 48.411035], + [-125.48208, 50.316797], + [-127.197314, 50.640381], + ], + ], + [ + [ + [-130.025098, 55.888232], + [-130.048486, 55.057275], + [-129.795166, 55.55957], + [-129.560645, 55.462549], + [-130.430273, 54.420996], + [-129.626025, 54.230273], + [-130.335254, 53.723926], + [-129.563721, 53.251465], + [-128.959375, 53.841455], + [-128.532129, 53.858105], + [-128.675537, 53.55459], + [-127.927832, 53.274707], + [-128.905615, 53.559326], + [-128.85459, 53.704541], + [-129.171582, 53.533594], + [-128.365039, 52.825781], + [-128.053271, 52.910693], + [-128.271533, 52.362988], + [-127.940234, 52.545166], + [-128.357617, 52.158887], + [-128.102246, 51.788428], + [-127.791895, 52.289355], + [-126.951367, 52.751025], + [-127.193994, 52.457666], + [-126.713965, 52.060693], + [-127.437939, 52.356152], + [-127.795361, 52.191016], + [-127.850537, 51.673193], + [-127.668701, 51.477588], + [-127.338721, 51.707373], + [-126.691455, 51.703418], + [-127.419678, 51.608057], + [-127.708105, 51.151172], + [-127.057568, 50.867529], + [-126.517334, 51.056836], + [-126.514355, 50.679395], + [-125.904102, 50.704932], + [-126.447461, 50.587744], + [-126.094336, 50.497607], + [-125.058789, 50.513867], + [-124.859863, 50.872412], + [-125.056689, 50.418652], + [-124.782373, 50.020117], + [-124.141602, 49.792676], + [-123.880127, 50.173633], + [-123.874414, 49.736816], + [-123.582471, 49.68125], + [-124.028613, 49.602881], + [-123.530566, 49.397314], + [-123.1875, 49.680322], + [-123.276758, 49.343945], + [-122.879102, 49.398926], + [-123.196338, 49.147705], + [-122.78877, 48.993018], + [-114.585107, 48.993066], + [-106.483838, 48.993115], + [-97.529834, 48.993164], + [-95.162061, 48.991748], + [-95.155273, 49.369678], + [-94.620898, 48.742627], + [-92.99624, 48.611816], + [-91.518311, 48.058301], + [-90.916064, 48.209131], + [-89.455664, 47.99624], + [-88.378174, 48.303076], + [-84.875977, 46.899902], + [-84.561768, 46.457373], + [-84.149463, 46.542773], + [-83.977783, 46.084912], + [-83.615967, 46.116846], + [-83.592676, 45.817139], + [-82.551074, 45.347363], + [-82.137842, 43.570898], + [-82.545312, 42.624707], + [-83.149658, 42.141943], + [-82.690039, 41.675195], + [-79.036719, 42.802344], + [-79.171875, 43.466553], + [-78.72041, 43.624951], + [-76.819971, 43.628809], + [-76.151172, 44.303955], + [-74.708887, 45.003857], + [-73.973828, 45.345117], + [-74.315088, 45.531055], + [-73.476611, 45.738232], + [-72.981006, 46.209717], + [-71.87959, 46.686816], + [-71.267773, 46.795947], + [-70.705859, 47.139795], + [-69.994434, 47.739893], + [-69.865527, 48.172266], + [-71.018262, 48.455615], + [-69.673877, 48.19917], + [-68.281934, 49.197168], + [-67.372021, 49.348438], + [-66.495508, 50.211865], + [-62.71543, 50.30166], + [-61.724854, 50.104053], + [-59.886328, 50.316406], + [-58.510352, 51.295068], + [-56.975977, 51.457666], + [-55.695215, 52.137793], + [-56.011719, 52.394482], + [-55.746484, 52.474561], + [-56.324902, 52.544531], + [-55.802832, 52.643164], + [-55.797949, 53.211963], + [-56.46499, 53.765039], + [-57.331738, 53.469092], + [-57.416064, 54.162744], + [-58.19209, 54.228174], + [-57.935986, 54.091162], + [-60.329492, 53.266113], + [-60.100293, 53.486963], + [-60.39541, 53.65332], + [-58.633203, 54.049561], + [-57.404492, 54.590869], + [-57.962451, 54.875732], + [-58.780176, 54.838379], + [-59.25957, 55.199951], + [-59.837793, 54.813965], + [-59.437891, 55.175928], + [-59.758789, 55.30957], + [-60.617139, 55.060205], + [-60.341016, 55.784668], + [-61.449512, 55.995703], + [-61.364697, 56.216016], + [-61.713086, 56.230957], + [-61.425293, 56.360645], + [-62.497266, 56.801709], + [-61.371631, 56.680811], + [-61.33374, 57.010596], + [-62.495557, 57.489209], + [-61.967969, 57.611914], + [-61.958643, 57.911768], + [-62.48623, 58.154053], + [-63.261523, 58.014697], + [-62.593848, 58.474023], + [-63.537061, 58.329932], + [-62.873877, 58.672461], + [-63.248437, 59.068311], + [-63.971143, 59.053809], + [-63.415137, 59.194385], + [-63.945459, 59.380176], + [-64.283496, 60.064062], + [-64.768457, 60.012109], + [-64.499414, 60.268262], + [-64.817334, 60.331055], + [-65.028174, 59.770703], + [-65.433398, 59.776514], + [-65.038232, 59.387891], + [-65.475098, 59.470312], + [-65.383545, 59.060205], + [-66.043066, 58.820654], + [-66.002393, 58.431201], + [-66.362402, 58.791162], + [-67.678271, 57.991113], + [-68.021045, 58.485303], + [-68.413574, 58.051758], + [-69.04082, 57.90249], + [-68.356543, 58.163232], + [-68.381152, 58.743506], + [-68.698193, 58.904541], + [-70.154346, 58.760596], + [-69.531641, 58.869238], + [-69.344043, 59.303076], + [-69.681885, 59.341748], + [-69.733936, 59.918018], + [-70.654834, 60.026221], + [-69.67373, 60.075879], + [-69.50332, 61.04043], + [-69.992432, 60.856494], + [-71.422705, 61.158936], + [-71.638281, 61.617188], + [-72.215869, 61.587256], + [-72.686963, 62.124561], + [-73.705078, 62.473145], + [-74.632568, 62.115674], + [-77.372412, 62.57251], + [-78.133398, 62.282275], + [-77.514355, 61.556299], + [-78.181348, 60.819141], + [-77.589551, 60.808594], + [-77.349072, 59.578955], + [-77.726172, 59.675879], + [-78.515088, 58.682373], + [-76.809814, 57.657959], + [-76.604053, 56.199561], + [-77.775293, 55.29126], + [-79.712354, 54.671826], + [-78.996045, 54.00249], + [-78.448096, 52.261377], + [-78.981641, 51.774561], + [-78.903174, 51.200293], + [-79.338672, 51.628174], + [-79.737451, 51.186279], + [-79.3479, 50.762646], + [-80.103564, 51.282861], + [-80.851221, 51.125], + [-80.443311, 51.388574], + [-80.588037, 51.667236], + [-81.827881, 52.224219], + [-81.599414, 52.432617], + [-82.291553, 53.030713], + [-82.393262, 55.067822], + [-83.910596, 55.314648], + [-85.365283, 55.079297], + [-85.559326, 55.540186], + [-87.482422, 56.021289], + [-88.948486, 56.851318], + [-90.897461, 57.256934], + [-92.798145, 56.921973], + [-92.432812, 57.320312], + [-93.17876, 58.725635], + [-94.123193, 58.736719], + [-94.332227, 58.297363], + [-94.287061, 58.716016], + [-94.957324, 59.068848], + [-94.761719, 60.498242], + [-93.312012, 61.767285], + [-93.581787, 61.942041], + [-92.905518, 62.215137], + [-93.205371, 62.364941], + [-92.527979, 62.168408], + [-92.551416, 62.546729], + [-91.93584, 62.592383], + [-92.361279, 62.819385], + [-90.698584, 63.063867], + [-90.970068, 63.442773], + [-91.841846, 63.697559], + [-92.465088, 63.555078], + [-92.156885, 63.691699], + [-93.559814, 63.865283], + [-93.696338, 64.147168], + [-90.811914, 63.580908], + [-90.154736, 63.689648], + [-90.04165, 64.140869], + [-89.131543, 63.968506], + [-88.105615, 64.183301], + [-87.027539, 65.198096], + [-88.974023, 65.348291], + [-89.924072, 65.780273], + [-91.427246, 65.9479], + [-89.749414, 65.936035], + [-87.452881, 65.338965], + [-85.95874, 66.119043], + [-86.708154, 66.523047], + [-85.603857, 66.568262], + [-83.869043, 66.213574], + [-84.223047, 66.682471], + [-85.113721, 66.906934], + [-84.538477, 66.972803], + [-83.406445, 66.37124], + [-81.467578, 67.069873], + [-81.294336, 67.497412], + [-82.552686, 68.446484], + [-81.281543, 68.657227], + [-81.95791, 68.883643], + [-81.377832, 69.185645], + [-82.227539, 69.248877], + [-82.618359, 69.691064], + [-85.507373, 69.845264], + [-84.867578, 68.77334], + [-85.643164, 68.699707], + [-86.560791, 67.482129], + [-87.359375, 67.177246], + [-88.313818, 67.950342], + [-88.346973, 68.288281], + [-87.813574, 68.345703], + [-88.223535, 68.915039], + [-89.279541, 69.255469], + [-90.204785, 68.257471], + [-91.237207, 69.285547], + [-90.415576, 69.456982], + [-92.887793, 69.668213], + [-91.976709, 70.038672], + [-92.320508, 70.235352], + [-91.564062, 70.178271], + [-94.734863, 71.982959], + [-95.872314, 71.573145], + [-95.564258, 71.336768], + [-96.446582, 71.239893], + [-95.878613, 70.548975], + [-96.551367, 70.210303], + [-95.964941, 69.802783], + [-93.532275, 69.480908], + [-94.600439, 68.803223], + [-93.852441, 69.000342], + [-93.448926, 68.618896], + [-95.460693, 68.021387], + [-95.25874, 67.262549], + [-95.399658, 66.949463], + [-96.036865, 66.9375], + [-95.787549, 66.616797], + [-96.422559, 67.051758], + [-95.418896, 67.013232], + [-96.369141, 67.509766], + [-95.970312, 68.249121], + [-96.72207, 68.03877], + [-96.430664, 68.310596], + [-97.410352, 68.496533], + [-98.650488, 68.363525], + [-98.192529, 67.922998], + [-97.206543, 67.855078], + [-97.454932, 67.616992], + [-98.631543, 68.072559], + [-98.412109, 67.807178], + [-98.920459, 67.725781], + [-102.320361, 67.735645], + [-103.474121, 68.115039], + [-104.486816, 68.063184], + [-106.164453, 68.919873], + [-108.313477, 68.610791], + [-108.718115, 68.297461], + [-107.73418, 68.17373], + [-105.750195, 68.592285], + [-107.958398, 67.818604], + [-107.259473, 66.398535], + [-108.496045, 67.092285], + [-107.988721, 67.256396], + [-110.073926, 67.99292], + [-112.503027, 67.681934], + [-115.133203, 67.819189], + [-115.127051, 68.132031], + [-113.964404, 68.399072], + [-115.442285, 68.940918], + [-117.226953, 68.913428], + [-122.070068, 69.816162], + [-123.025781, 69.81001], + [-123.528418, 69.389355], + [-124.338086, 69.364844], + [-124.555029, 70.151221], + [-125.524951, 69.351563], + [-127.991016, 70.573828], + [-127.683789, 70.260352], + [-128.853027, 69.751025], + [-128.898926, 69.966162], + [-130.117627, 69.720068], + [-130.970654, 69.209082], + [-131.063428, 69.450684], + [-131.788379, 69.431982], + [-133.196826, 68.739844], + [-132.81748, 69.205762], + [-129.648291, 69.997754], + [-129.675635, 70.192969], + [-134.174316, 69.252832], + [-134.408936, 69.681787], + [-135.691455, 69.311182], + [-135.939014, 68.97417], + [-135.258838, 68.684326], + [-141.002148, 69.650781], + [-141.002148, 64.975537], + [-141.002148, 60.300244], + [-139.079248, 60.343701], + [-139.185156, 60.083594], + [-137.438574, 58.903125], + [-135.475928, 59.793262], + [-133.401123, 58.410889], + [-131.824268, 56.58999], + [-130.097852, 56.109277], + [-130.025098, 55.888232], + ], + ], + [ + [ + [-109.815967, 78.650391], + [-110.877588, 78.735059], + [-113.223047, 78.2979], + [-109.484473, 78.316406], + [-109.815967, 78.650391], + ], + ], + [ + [ + [-110.458057, 78.103223], + [-113.215186, 77.903516], + [-112.372656, 77.364111], + [-110.198486, 77.524512], + [-110.865625, 77.834131], + [-109.622266, 78.074756], + [-110.458057, 78.103223], + ], + ], + [ + [ + [-115.55127, 77.363281], + [-116.511328, 77.547607], + [-116.843555, 77.339551], + [-119.090186, 77.305078], + [-122.519385, 76.353174], + [-122.533057, 75.950928], + [-120.848389, 76.182666], + [-119.912891, 75.858838], + [-117.880811, 76.805078], + [-117.233594, 76.281543], + [-115.55127, 77.363281], + ], + ], + [ + [ + [-108.292383, 76.057129], + [-109.098242, 76.811865], + [-110.314453, 76.369385], + [-108.947168, 75.541797], + [-111.052686, 75.548535], + [-112.697607, 76.201709], + [-114.998486, 76.497461], + [-115.822168, 76.27002], + [-114.778613, 76.172607], + [-116.209863, 76.194434], + [-116.664551, 75.957568], + [-114.991504, 75.896338], + [-117.163623, 75.644873], + [-115.141846, 75.678516], + [-117.565234, 75.23335], + [-115.728857, 74.968115], + [-114.451758, 75.087891], + [-114.016504, 75.434277], + [-113.711768, 75.068604], + [-111.093457, 75.256299], + [-114.312695, 74.715088], + [-112.519336, 74.416846], + [-108.831299, 75.064893], + [-107.153418, 74.927148], + [-106.092627, 75.089453], + [-105.632666, 75.945361], + [-106.677002, 76.02373], + [-106.913525, 75.679639], + [-108.292383, 76.057129], + ], + ], + [ + [ + [-114.521533, 72.59292], + [-114.051709, 73.070996], + [-114.638232, 73.372656], + [-118.133105, 72.632812], + [-118.987695, 71.764258], + [-117.742334, 71.659326], + [-117.935645, 71.39209], + [-115.303418, 71.493701], + [-118.269092, 71.034717], + [-117.587061, 70.629541], + [-113.757275, 70.690723], + [-111.632568, 70.308838], + [-117.19541, 70.054053], + [-116.513477, 69.424609], + [-113.694141, 69.19502], + [-113.127734, 68.494141], + [-109.472119, 68.676709], + [-107.439893, 69.002148], + [-106.659082, 69.4396], + [-104.571436, 68.872119], + [-101.857129, 69.023975], + [-102.045947, 69.464844], + [-103.120215, 69.20459], + [-103.464893, 69.644482], + [-102.621094, 69.551514], + [-102.182129, 69.845947], + [-100.982373, 69.679883], + [-101.042676, 70.110791], + [-103.58457, 70.630859], + [-104.514795, 71.064258], + [-105.415137, 72.78833], + [-106.482129, 73.196191], + [-108.029053, 73.34873], + [-108.237402, 73.149902], + [-107.306006, 71.894678], + [-107.812842, 71.626172], + [-110.008447, 72.983643], + [-110.66084, 73.008203], + [-110.205127, 72.661279], + [-111.675098, 72.300146], + [-111.269727, 72.713721], + [-112.753613, 72.986035], + [-114.521533, 72.59292], + ], + ], + [ + [ + [-119.736328, 74.112646], + [-121.50415, 74.545117], + [-124.69624, 74.348193], + [-123.797266, 73.768164], + [-125.845312, 71.978662], + [-124.007764, 71.677441], + [-123.095654, 71.093799], + [-120.619336, 71.505762], + [-120.179883, 72.212646], + [-115.446875, 73.438867], + [-117.514844, 74.231738], + [-119.736328, 74.112646], + ], + ], + [ + [ + [-69.488867, 83.016797], + [-72.81167, 83.081201], + [-72.658691, 82.721631], + [-74.41416, 83.013135], + [-77.124902, 83.008545], + [-76.009375, 82.535156], + [-77.618066, 82.89585], + [-80.154932, 82.911133], + [-78.748779, 82.679395], + [-81.010156, 82.779053], + [-82.447559, 82.39502], + [-79.465625, 81.851123], + [-84.896826, 82.449414], + [-86.615625, 82.218555], + [-85.044824, 81.982812], + [-88.063184, 82.096484], + [-91.647559, 81.683838], + [-89.82168, 81.634863], + [-90.416309, 81.405371], + [-87.597021, 81.52583], + [-89.673682, 81.328613], + [-89.623047, 81.032471], + [-84.941211, 81.28623], + [-89.166895, 80.941309], + [-88.003662, 80.675391], + [-83.288818, 81.147949], + [-86.250342, 80.565771], + [-81.007031, 80.654883], + [-76.885107, 81.430273], + [-78.716211, 80.95166], + [-76.862988, 80.864795], + [-82.987012, 80.322607], + [-80.475928, 79.60625], + [-83.723633, 80.228955], + [-86.498535, 80.258252], + [-86.420752, 79.845215], + [-83.575879, 79.053662], + [-84.412012, 78.996582], + [-81.750098, 78.975781], + [-83.271436, 78.770312], + [-86.80791, 78.774365], + [-87.551758, 78.176611], + [-85.920068, 78.342871], + [-85.585938, 78.10957], + [-84.783203, 78.527588], + [-84.222705, 78.176025], + [-85.547559, 77.927686], + [-85.289355, 77.559033], + [-83.779395, 77.532617], + [-82.710352, 77.849512], + [-83.721289, 77.414209], + [-84.738672, 77.361035], + [-86.755078, 77.863721], + [-88.016992, 77.784717], + [-86.812256, 77.184912], + [-89.499756, 76.826807], + [-89.369629, 76.474463], + [-88.545801, 76.420898], + [-88.49585, 76.772852], + [-88.395996, 76.405273], + [-87.497559, 76.386279], + [-87.489795, 76.58584], + [-86.680225, 76.376611], + [-86.453711, 76.584863], + [-85.14126, 76.30459], + [-84.275342, 76.356543], + [-84.223779, 76.675342], + [-83.885693, 76.453125], + [-82.233154, 76.46582], + [-82.529834, 76.723291], + [-80.799707, 76.173584], + [-78.284326, 76.57124], + [-78.288867, 76.977979], + [-82.056787, 77.296533], + [-81.659082, 77.525439], + [-78.708496, 77.342139], + [-78.012598, 77.946045], + [-75.865967, 78.009814], + [-75.193457, 78.327734], + [-76.416113, 78.511523], + [-74.486328, 78.750098], + [-78.581641, 79.075], + [-74.640918, 79.035547], + [-76.898828, 79.512305], + [-73.361523, 79.504004], + [-74.394482, 79.874072], + [-71.387842, 79.761768], + [-70.568408, 80.093701], + [-72.055957, 80.123242], + [-70.264893, 80.233594], + [-70.712598, 80.5396], + [-69.550684, 80.383252], + [-64.780078, 81.492871], + [-68.688525, 81.293311], + [-61.615381, 82.184424], + [-63.641016, 82.812598], + [-68.469336, 82.653369], + [-66.422559, 82.926855], + [-69.488867, 83.016797], + ], + ], + [ + [ + [-95.484375, 77.791992], + [-95.987061, 77.484131], + [-93.543945, 77.46665], + [-93.300977, 77.739795], + [-95.484375, 77.791992], + ], + ], + [ + [ + [-93.542578, 75.02793], + [-94.878174, 75.630029], + [-96.599609, 75.031787], + [-94.534521, 74.636719], + [-93.573096, 74.668848], + [-93.542578, 75.02793], + ], + ], + [ + [ + [-100.001904, 73.945898], + [-100.962988, 73.791406], + [-100.52168, 73.449316], + [-101.523193, 73.486377], + [-99.825146, 73.213867], + [-100.536377, 73.197852], + [-100.128125, 72.906689], + [-101.273193, 72.72168], + [-102.204004, 73.077295], + [-102.70874, 72.764502], + [-98.662891, 71.3021], + [-98.322705, 71.852344], + [-97.582275, 71.629688], + [-96.613428, 71.833838], + [-96.445605, 72.552441], + [-97.636328, 73.027637], + [-98.421777, 72.941016], + [-97.170508, 73.824854], + [-99.157959, 73.731592], + [-100.001904, 73.945898], + ], + ], + [ + [ + [-84.919629, 65.261084], + [-85.554688, 65.918652], + [-86.2521, 64.136865], + [-87.151904, 63.585645], + [-85.768945, 63.700342], + [-85.392627, 63.119678], + [-83.303955, 64.143799], + [-81.046387, 63.461572], + [-80.302051, 63.762207], + [-80.828955, 64.089941], + [-81.887109, 64.016406], + [-82.05, 64.644287], + [-84.501123, 65.458447], + [-84.919629, 65.261084], + ], + ], + [ + [ + [-97.700928, 76.466504], + [-98.71084, 76.693848], + [-98.890332, 76.465576], + [-100.829736, 76.523877], + [-99.541064, 76.146289], + [-99.865479, 75.924219], + [-101.415186, 76.424902], + [-102.104688, 76.331201], + [-100.972803, 75.798438], + [-102.144727, 75.875049], + [-102.587402, 75.513672], + [-99.19458, 75.698389], + [-100.711914, 75.406348], + [-100.234375, 75.007715], + [-97.674316, 75.127295], + [-97.700928, 76.466504], + ], + ], + [ + [ + [-103.426025, 79.315625], + [-105.514551, 79.24248], + [-104.895508, 78.808154], + [-104.151953, 78.989893], + [-103.371582, 78.736328], + [-104.763574, 78.35166], + [-102.731348, 78.371045], + [-100.274658, 77.832715], + [-99.166406, 77.856934], + [-99.609424, 78.583057], + [-101.703662, 79.078906], + [-102.576172, 78.879395], + [-103.426025, 79.315625], + ], + ], + [ + [ + [-91.885547, 81.132861], + [-94.220117, 81.330762], + [-93.286719, 81.100293], + [-95.514746, 80.838135], + [-93.92793, 80.55918], + [-95.926953, 80.720654], + [-96.394092, 80.315039], + [-94.262598, 80.194873], + [-96.773242, 80.135791], + [-96.589062, 79.91665], + [-95.739355, 79.660156], + [-94.401855, 79.736328], + [-95.662891, 79.527344], + [-95.103174, 79.289893], + [-91.299902, 79.372705], + [-94.1146, 78.928906], + [-91.866895, 78.542676], + [-92.35127, 78.312891], + [-89.525684, 78.159619], + [-90.037109, 78.606836], + [-88.822412, 78.185889], + [-88.040186, 78.995312], + [-87.617383, 78.676318], + [-85.042139, 79.28457], + [-85.647852, 79.611426], + [-87.295166, 79.580176], + [-87.675, 80.372119], + [-88.857324, 80.166211], + [-91.885547, 81.132861], + ], + ], + [ + [ + [-94.294971, 76.912451], + [-95.849512, 77.066211], + [-96.880713, 76.73833], + [-95.273877, 76.264404], + [-93.091748, 76.354004], + [-91.549121, 74.655566], + [-90.880225, 74.817773], + [-89.558691, 74.554736], + [-88.534961, 74.831738], + [-88.423047, 74.494141], + [-84.425537, 74.508105], + [-83.531885, 74.585693], + [-83.52207, 74.901465], + [-82.735791, 74.530273], + [-80.262744, 74.584473], + [-80.347754, 74.902979], + [-79.401416, 74.917627], + [-80.381982, 75.03418], + [-79.509082, 75.259814], + [-80.321973, 75.629102], + [-83.931982, 75.818945], + [-85.951465, 75.39502], + [-88.644971, 75.658447], + [-88.916699, 75.453955], + [-91.407324, 76.220068], + [-89.284521, 76.301611], + [-91.415088, 76.455859], + [-90.542627, 76.495752], + [-91.305029, 76.680762], + [-93.53457, 76.447705], + [-93.230029, 76.770264], + [-94.294971, 76.912451], + ], + ], + [ + [ + [-96.204492, 78.531299], + [-98.332617, 78.773535], + [-96.989648, 77.806006], + [-94.934277, 78.075635], + [-94.915381, 78.390527], + [-96.204492, 78.531299], + ], + ], + [ + [ + [-93.17085, 74.160986], + [-94.973535, 74.041406], + [-94.697607, 73.663574], + [-95.63291, 73.695459], + [-95.007861, 72.012793], + [-94.037549, 72.02876], + [-93.555176, 72.421143], + [-94.211328, 72.756934], + [-92.11792, 72.753809], + [-90.381396, 73.824756], + [-93.17085, 74.160986], + ], + ], + [ + [ + [-97.439453, 69.642676], + [-98.200488, 69.796973], + [-98.041357, 69.456641], + [-98.545996, 69.5729], + [-99.494678, 68.95957], + [-96.401562, 68.470703], + [-95.267773, 68.826074], + [-97.439453, 69.642676], + ], + ], + [ + [ + [-61.105176, 45.944727], + [-60.494531, 46.270264], + [-60.408203, 47.003516], + [-61.408643, 46.170361], + [-61.449805, 45.716211], + [-60.672949, 45.59082], + [-59.842188, 45.941553], + [-60.297949, 46.31123], + [-60.737891, 45.751416], + [-61.059033, 45.703369], + [-60.865234, 45.983496], + [-61.105176, 45.944727], + ], + ], + [ + [ + [-67.124854, 45.169434], + [-66.439844, 45.095898], + [-66.026562, 45.417578], + [-65.884473, 45.2229], + [-64.778516, 45.638428], + [-64.632715, 45.946631], + [-64.314648, 45.835693], + [-64.873145, 45.35459], + [-63.368018, 45.364795], + [-64.135498, 45.023047], + [-64.448145, 45.337451], + [-66.090625, 44.504932], + [-65.868018, 44.568799], + [-66.125732, 43.813818], + [-65.481689, 43.518066], + [-64.286084, 44.550342], + [-63.609766, 44.47998], + [-63.604004, 44.683203], + [-61.031543, 45.291748], + [-61.955518, 45.868164], + [-62.750098, 45.648242], + [-64.541504, 46.240332], + [-64.831396, 47.060791], + [-65.318896, 47.101221], + [-64.703223, 47.724854], + [-65.607227, 47.67002], + [-66.704395, 48.022461], + [-65.926709, 48.188867], + [-65.259424, 48.02124], + [-64.348828, 48.423193], + [-64.513721, 48.841113], + [-64.216211, 48.873633], + [-64.836328, 49.191748], + [-66.178174, 49.213135], + [-68.238184, 48.626416], + [-70.519482, 47.03252], + [-71.261182, 46.75625], + [-72.109277, 46.551221], + [-73.15957, 46.010059], + [-73.558105, 45.425098], + [-74.708887, 45.003857], + [-71.517529, 45.007568], + [-71.327295, 45.290088], + [-70.865039, 45.270703], + [-69.242871, 47.462988], + [-68.937207, 47.21123], + [-68.235498, 47.345947], + [-67.806787, 47.082812], + [-67.802246, 45.727539], + [-67.124854, 45.169434], + ], + ], + [ + [ + [-55.45874, 51.536523], + [-56.025586, 51.568359], + [-57.035937, 51.01084], + [-57.791309, 49.48999], + [-58.213379, 49.38667], + [-57.990527, 48.987939], + [-58.403662, 49.084326], + [-58.716455, 48.598047], + [-58.841797, 48.746436], + [-59.167676, 48.558496], + [-58.330225, 48.522119], + [-59.320654, 47.736914], + [-59.116943, 47.570703], + [-58.336865, 47.730859], + [-56.774121, 47.56499], + [-55.85791, 47.819189], + [-56.127246, 47.502832], + [-55.576123, 47.465234], + [-54.784619, 47.664746], + [-55.919238, 47.016895], + [-55.788525, 46.867236], + [-54.488135, 47.403857], + [-54.191846, 47.859814], + [-53.849512, 47.440332], + [-54.17373, 46.880371], + [-53.597363, 47.145996], + [-53.589795, 46.638867], + [-53.114844, 46.655811], + [-52.653662, 47.549414], + [-52.782422, 47.769434], + [-53.169824, 47.512109], + [-52.866016, 48.112988], + [-53.672363, 47.648242], + [-53.86958, 48.019678], + [-53.027588, 48.634717], + [-54.114453, 48.393604], + [-53.706348, 48.655518], + [-54.161279, 48.787695], + [-53.619434, 49.321631], + [-54.448242, 49.329443], + [-54.502197, 49.527344], + [-55.353174, 49.079443], + [-55.229541, 49.508154], + [-56.087305, 49.451953], + [-55.869824, 49.670166], + [-56.140186, 49.619141], + [-55.50293, 49.983154], + [-56.161279, 49.940137], + [-56.179395, 50.11499], + [-56.822168, 49.613477], + [-55.8, 51.033301], + [-56.031104, 51.328369], + [-55.45874, 51.536523], + ], + ], + [ + [ + [-86.589355, 71.010791], + [-85.023389, 71.353223], + [-86.218457, 71.899121], + [-86.656299, 72.724023], + [-84.946777, 73.721631], + [-85.950781, 73.850146], + [-88.705176, 73.403271], + [-89.861523, 72.411914], + [-89.805371, 71.462305], + [-87.140088, 71.011621], + [-89.455908, 71.061719], + [-88.782715, 70.494482], + [-87.838135, 70.246582], + [-86.396875, 70.465332], + [-85.780029, 70.03667], + [-81.564697, 69.942725], + [-80.921729, 69.730908], + [-81.651953, 70.094629], + [-78.889648, 69.97749], + [-79.066406, 70.603564], + [-75.647754, 69.212549], + [-76.557227, 69.009473], + [-76.585059, 68.69873], + [-74.716699, 69.045508], + [-74.892969, 68.808154], + [-74.270117, 68.541211], + [-73.822119, 68.685986], + [-72.22002, 67.254297], + [-74.416406, 66.16709], + [-73.550781, 65.485254], + [-75.798682, 65.29751], + [-75.4521, 64.841602], + [-75.82832, 65.227051], + [-77.326709, 65.453125], + [-78.095605, 64.939258], + [-78.045215, 64.499268], + [-76.856152, 64.237646], + [-74.694727, 64.496582], + [-74.681396, 64.830664], + [-74.064795, 64.424658], + [-73.271289, 64.58252], + [-72.498438, 63.823486], + [-71.380859, 63.580322], + [-71.992236, 63.416162], + [-71.347266, 63.066113], + [-69.604736, 62.767725], + [-68.535889, 62.255615], + [-66.123877, 61.893066], + [-65.980176, 62.208887], + [-68.911084, 63.703223], + [-67.722559, 63.422754], + [-67.893262, 63.73374], + [-66.697461, 63.069531], + [-66.65498, 63.264746], + [-65.108496, 62.626465], + [-65.162793, 62.932617], + [-64.672363, 62.921973], + [-65.191846, 63.764258], + [-64.664648, 63.245361], + [-64.410937, 63.706348], + [-65.580322, 64.293848], + [-65.074609, 64.43667], + [-65.529346, 64.504785], + [-65.274805, 64.631543], + [-66.635498, 65.000342], + [-66.697412, 64.815186], + [-67.117969, 65.440381], + [-67.936768, 65.564893], + [-68.748926, 66.200049], + [-67.350439, 65.929736], + [-67.883398, 66.467432], + [-67.225391, 66.310254], + [-66.986328, 66.62749], + [-66.063721, 66.132715], + [-65.656348, 66.204736], + [-65.825732, 65.996924], + [-64.445361, 66.317139], + [-65.401611, 65.764014], + [-64.555078, 65.116602], + [-64.269678, 65.400781], + [-63.606592, 64.928076], + [-63.45874, 65.853027], + [-62.658887, 65.639941], + [-62.624121, 66.01626], + [-61.991602, 66.035303], + [-62.553125, 66.406836], + [-61.570801, 66.3729], + [-62.12334, 66.643066], + [-61.353418, 66.689209], + [-62.123584, 67.046729], + [-63.701562, 66.822363], + [-63.040137, 67.23501], + [-64.699951, 67.350537], + [-63.850195, 67.566064], + [-65.021094, 67.787549], + [-64.922314, 68.031641], + [-65.40127, 67.674854], + [-65.942383, 68.070947], + [-66.443945, 67.833838], + [-66.212402, 68.28042], + [-66.923096, 68.065723], + [-66.742725, 68.457764], + [-69.319092, 68.856982], + [-67.883203, 68.783984], + [-67.832617, 69.065967], + [-69.040625, 69.097998], + [-68.406299, 69.232227], + [-66.707422, 69.168213], + [-67.236963, 69.460107], + [-69.250781, 69.511914], + [-67.221631, 69.730713], + [-67.363672, 70.034424], + [-68.059082, 70.317236], + [-68.744043, 69.941406], + [-68.778223, 70.203564], + [-70.057715, 70.042627], + [-68.363525, 70.48125], + [-69.949805, 70.84502], + [-71.429443, 70.127783], + [-71.275879, 70.500293], + [-71.890186, 70.431543], + [-70.672656, 71.052197], + [-72.632715, 70.830762], + [-71.229395, 71.33877], + [-72.901953, 71.677783], + [-73.180615, 71.282861], + [-73.712842, 71.587598], + [-74.197266, 71.40415], + [-73.814062, 71.771436], + [-74.996191, 71.218115], + [-74.700781, 71.675586], + [-75.204785, 71.709131], + [-74.209326, 71.978662], + [-74.903174, 72.100488], + [-75.922803, 71.717236], + [-75.052686, 72.226367], + [-75.704297, 72.571533], + [-77.753223, 72.724756], + [-78.484277, 72.470605], + [-77.516504, 72.177783], + [-78.699268, 72.351416], + [-78.585107, 71.880615], + [-79.831299, 72.446289], + [-80.925146, 71.907666], + [-80.611475, 72.45083], + [-81.229346, 72.311719], + [-80.277246, 72.770166], + [-81.946143, 73.729834], + [-85.454736, 73.105469], + [-84.256641, 72.796729], + [-85.262109, 72.954004], + [-85.649902, 72.722168], + [-84.28374, 72.044482], + [-85.321875, 72.233154], + [-85.911621, 71.986523], + [-84.699414, 71.631445], + [-84.82373, 71.028613], + [-86.589355, 71.010791], + ], + ], + [ + [ + [-61.801123, 49.093896], + [-62.858545, 49.705469], + [-64.485205, 49.886963], + [-63.041504, 49.224951], + [-61.801123, 49.093896], + ], + ], + [ + [ + [-63.811279, 46.468701], + [-63.993555, 47.061572], + [-64.388037, 46.640869], + [-63.641016, 46.230469], + [-62.978467, 46.316357], + [-63.02207, 46.066602], + [-62.531348, 45.977295], + [-62.02373, 46.421582], + [-63.811279, 46.468701], + ], + ], + [ + [ + [-82.000488, 62.954199], + [-83.376416, 62.904932], + [-83.910498, 62.45415], + [-83.698877, 62.160254], + [-82.568262, 62.403223], + [-82.000488, 62.954199], + ], + ], + [ + [ + [-79.545312, 62.411719], + [-80.260059, 62.109033], + [-79.816113, 61.594629], + [-79.323926, 62.026074], + [-79.545312, 62.411719], + ], + ], + [ + [ + [-75.675879, 68.32251], + [-76.595801, 68.278955], + [-77.125879, 67.94707], + [-76.944189, 67.250293], + [-75.201953, 67.45918], + [-75.078125, 68.173145], + [-75.675879, 68.32251], + ], + ], + [ + [ + [-79.537305, 73.654492], + [-80.848877, 73.72124], + [-79.820703, 72.826318], + [-76.183398, 72.843066], + [-77.206543, 73.499561], + [-79.537305, 73.654492], + ], + ], + [ + [ + [-80.731689, 52.747266], + [-81.135596, 53.205811], + [-82.039258, 53.049902], + [-80.731689, 52.747266], + ], + ], + [ + [ + [-78.935596, 56.266064], + [-79.272412, 56.600439], + [-79.536328, 56.180078], + [-79.458887, 56.539746], + [-79.9875, 55.892139], + [-79.544727, 56.128369], + [-79.764746, 55.806787], + [-79.495117, 55.874756], + [-79.182129, 56.212158], + [-79.175488, 55.885059], + [-78.935596, 56.266064], + ], + ], + [ + [ + [-89.833252, 77.267627], + [-91.019043, 77.643896], + [-90.993213, 77.329492], + [-89.833252, 77.267627], + ], + ], + [ + [ + [-98.791602, 79.981104], + [-100.053271, 80.093359], + [-98.945215, 79.724072], + [-98.791602, 79.981104], + ], + ], + [ + [ + [-105.288916, 72.919922], + [-104.5875, 73.578076], + [-106.613965, 73.695605], + [-106.921533, 73.479834], + [-105.288916, 72.919922], + ], + ], + [ + [ + [-102.227344, 76.014893], + [-102.728027, 76.307031], + [-104.350635, 76.182324], + [-103.314746, 75.764209], + [-102.227344, 76.014893], + ], + ], + [ + [ + [-104.022852, 76.583105], + [-104.35752, 76.334619], + [-103.311377, 76.347559], + [-104.022852, 76.583105], + ], + ], + [ + [ + [-118.328125, 75.579688], + [-117.633691, 76.115088], + [-119.39458, 75.617334], + [-118.328125, 75.579688], + ], + ], + [ + [ + [-113.832471, 77.754639], + [-114.330371, 78.077539], + [-114.98042, 77.91543], + [-113.832471, 77.754639], + ], + ], + [ + [ + [-130.236279, 53.958545], + [-130.447998, 54.089014], + [-130.703174, 53.892236], + [-130.236279, 53.958545], + ], + ], + [ + [ + [-128.552441, 52.939746], + [-129.033252, 53.279932], + [-129.175928, 52.964941], + [-128.746338, 52.763379], + [-128.678955, 52.289648], + [-128.552441, 52.939746], + ], + ], + [ + [ + [-73.566504, 45.469092], + [-73.476074, 45.704736], + [-73.960547, 45.441406], + [-73.566504, 45.469092], + ], + ], + [ + [ + [-68.233789, 60.240918], + [-67.818848, 60.449512], + [-68.087598, 60.587842], + [-68.233789, 60.240918], + ], + ], + [ + [ + [-64.832617, 61.366064], + [-64.789648, 61.662207], + [-65.432129, 61.649512], + [-64.832617, 61.366064], + ], + ], + [ + [ + [-79.063086, 75.925879], + [-79.009326, 76.145898], + [-79.63877, 75.84292], + [-79.063086, 75.925879], + ], + ], + [ + [ + [-79.384277, 51.951953], + [-79.271289, 52.086816], + [-79.64375, 52.010059], + [-79.384277, 51.951953], + ], + ], + [ + [ + [-73.621729, 67.783838], + [-73.49375, 68.000635], + [-74.706543, 68.06709], + [-74.573389, 67.828662], + [-73.621729, 67.783838], + ], + ], + [ + [ + [-77.876709, 63.470557], + [-78.536768, 63.42373], + [-77.942432, 63.114404], + [-77.532715, 63.233643], + [-77.876709, 63.470557], + ], + ], + [ + [ + [-98.270361, 73.868506], + [-97.698242, 74.108691], + [-99.416992, 73.89541], + [-98.270361, 73.868506], + ], + ], + [ + [ + [-79.430664, 69.787793], + [-80.794775, 69.689258], + [-79.977832, 69.509668], + [-79.430664, 69.787793], + ], + ], + [ + [ + [-76.995361, 69.14375], + [-76.668848, 69.366162], + [-77.187549, 69.440088], + [-76.995361, 69.14375], + ], + ], + [ + [ + [-83.725977, 65.796729], + [-84.407178, 66.131006], + [-84.118262, 65.771777], + [-83.332422, 65.631055], + [-83.725977, 65.796729], + ], + ], + [ + [ + [-101.693555, 77.696582], + [-101.193213, 77.829785], + [-102.447705, 77.880615], + [-101.693555, 77.696582], + ], + ], + [ + [ + [-96.078564, 75.510107], + [-96.367822, 75.654639], + [-96.915137, 75.379688], + [-96.078564, 75.510107], + ], + ], + [ + [ + [-104.119922, 75.036328], + [-104.346191, 75.429932], + [-104.887402, 75.147754], + [-104.119922, 75.036328], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '喀麦隆', full_name: '喀麦隆共和国', iso_a2: 'CM', iso_a3: 'CMR', iso_n3: '120' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [8.555859, 4.755225], + [8.574414, 4.526221], + [8.918262, 4.55376], + [9.000098, 4.091602], + [9.688867, 4.056396], + [9.556152, 3.798047], + [9.948438, 3.079053], + [9.800781, 2.304443], + [11.328711, 2.167432], + [13.293555, 2.161572], + [14.578906, 2.199121], + [16.059375, 1.676221], + [16.183398, 2.270068], + [16.063477, 2.908594], + [15.128711, 3.826904], + [15.063574, 4.284863], + [14.73125, 4.602393], + [14.431152, 6.038721], + [15.480078, 7.523779], + [15.549805, 7.787891], + [15.116211, 8.557324], + [13.977246, 9.691553], + [14.243262, 9.979736], + [15.654883, 10.007812], + [15.132227, 10.648486], + [14.84707, 12.5021], + [14.461719, 13.021777], + [14.063965, 13.078516], + [14.197461, 12.383789], + [14.619727, 12.150977], + [14.575391, 11.532422], + [13.699902, 10.873145], + [12.782227, 8.817871], + [12.233398, 8.282324], + [11.861426, 7.116406], + [11.237305, 6.450537], + [10.60625, 7.063086], + [9.779883, 6.760156], + [8.997168, 5.917725], + [8.555859, 4.755225], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '柬埔寨', full_name: '柬埔寨王国', iso_a2: 'KH', iso_a3: 'KHM', iso_n3: '116' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [107.519434, 14.705078], + [106.938086, 14.327344], + [106.501465, 14.578223], + [105.978906, 14.343018], + [106.066797, 13.921191], + [105.183301, 14.34624], + [103.199414, 14.332617], + [102.336328, 13.560303], + [102.933887, 11.706689], + [103.152832, 10.913721], + [103.532422, 11.14668], + [103.721875, 10.890137], + [103.587109, 10.552197], + [104.426367, 10.41123], + [104.850586, 10.534473], + [105.045703, 10.911377], + [105.755078, 10.98999], + [106.163965, 10.794922], + [105.851465, 11.63501], + [106.399219, 11.687012], + [106.413867, 11.948438], + [107.506445, 12.364551], + [107.605469, 13.437793], + [107.331445, 14.126611], + [107.519434, 14.705078], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '缅甸', full_name: '缅甸联邦共和国', iso_a2: 'MM', iso_a3: 'MMR', iso_n3: '104' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [100.122461, 20.31665], + [100.249316, 20.730273], + [100.622949, 20.85957], + [100.703125, 21.251367], + [101.138867, 21.56748], + [101.079785, 21.755859], + [100.147656, 21.480518], + [99.917676, 22.028027], + [99.192969, 22.125977], + [99.507129, 22.959131], + [98.86377, 23.19126], + [98.676758, 23.905078], + [98.835059, 24.121191], + [97.564551, 23.911035], + [97.583301, 24.774805], + [97.819531, 25.251855], + [98.65625, 25.863574], + [98.738477, 26.785742], + [98.651172, 27.572461], + [98.298828, 27.550098], + [98.061621, 28.185889], + [97.599219, 28.517041], + [97.322461, 28.217969], + [97.335156, 27.937744], + [96.876855, 27.586719], + [97.102051, 27.11543], + [96.731641, 27.331494], + [96.19082, 27.261279], + [95.128711, 26.597266], + [95.132422, 26.04126], + [94.579883, 25.319824], + [94.707617, 25.04873], + [94.127637, 23.876465], + [93.32627, 24.064209], + [93.151172, 22.230615], + [92.574902, 21.978076], + [92.631641, 21.306201], + [92.17959, 21.293115], + [92.324121, 20.791846], + [92.722852, 20.295605], + [92.735645, 20.562695], + [92.82832, 20.177588], + [93.066797, 20.377637], + [93.129492, 19.858008], + [93.25, 20.070117], + [93.707031, 19.912158], + [93.998145, 19.440869], + [93.824902, 19.238477], + [93.493066, 19.369482], + [93.929199, 18.899658], + [94.044922, 19.287402], + [94.588965, 17.569336], + [94.223828, 16.016455], + [94.70332, 16.511914], + [94.661523, 15.904395], + [94.893164, 16.182812], + [94.942578, 15.818262], + [95.176953, 15.825684], + [95.346777, 16.097607], + [95.389551, 15.722754], + [96.324316, 16.444434], + [96.189063, 16.768311], + [96.431152, 16.504932], + [96.76543, 16.710352], + [96.851465, 17.401025], + [97.375879, 16.522949], + [97.725977, 16.568555], + [97.584277, 16.01958], + [97.812305, 14.858936], + [98.110645, 13.712891], + [98.200391, 13.980176], + [98.575977, 13.161914], + [98.636328, 11.738379], + [98.875977, 11.719727], + [98.464941, 10.67583], + [98.562598, 10.034961], + [98.702539, 10.190381], + [98.757227, 10.660937], + [99.614746, 11.781201], + [99.123926, 13.030762], + [99.136816, 13.716699], + [98.202148, 14.975928], + [98.888281, 16.351904], + [98.660742, 16.33042], + [97.373926, 18.517969], + [97.745898, 18.588184], + [98.015039, 19.749512], + [98.916699, 19.7729], + [99.074219, 20.099365], + [99.485938, 20.149854], + [99.458887, 20.363037], + [100.122461, 20.31665], + ], + ], + [ + [ + [98.553809, 11.744873], + [98.376465, 11.791504], + [98.434766, 11.56709], + [98.553809, 11.744873], + ], + ], + [ + [ + [93.69082, 18.684277], + [93.744727, 18.865527], + [93.4875, 18.867529], + [93.69082, 18.684277], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '布隆迪', full_name: '布隆迪共和国', iso_a2: 'BI', iso_a3: 'BDI', iso_n3: '108' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [30.553613, -2.400098], + [29.930176, -2.339551], + [29.698047, -2.794727], + [29.014355, -2.720215], + [29.403223, -4.449316], + [29.947266, -4.307324], + [30.790234, -3.274609], + [30.780273, -2.984863], + [30.433496, -2.874512], + [30.553613, -2.400098], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '布基纳法索', full_name: '布基纳法索', iso_a2: 'BF', iso_a3: 'BFA', iso_n3: '854' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [0.900488, 10.993262], + [1.426758, 11.447119], + [1.980371, 11.418408], + [2.38916, 11.89707], + [2.072949, 12.309375], + [2.10459, 12.70127], + [1.564941, 12.6354], + [0.987305, 13.041895], + [0.988477, 13.364844], + [1.201172, 13.35752], + [0.429199, 13.972119], + [0.21748, 14.911475], + [-0.760449, 15.047754], + [-1.973047, 14.456543], + [-2.113232, 14.168457], + [-2.586719, 14.227588], + [-2.95083, 13.648438], + [-3.248633, 13.65835], + [-3.301758, 13.280762], + [-4.151025, 13.306201], + [-4.428711, 12.337598], + [-5.288135, 11.82793], + [-5.523535, 10.426025], + [-4.62583, 9.713574], + [-3.223535, 9.895459], + [-2.69585, 9.481348], + [-2.829932, 10.998389], + [-0.627148, 10.927393], + [-0.068604, 11.115625], + [0.900488, 10.993262], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '保加利亚', full_name: '保加利亚共和国', iso_a2: 'BG', iso_a3: 'BGR', iso_n3: '100' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [28.014453, 41.969043], + [27.484766, 42.468066], + [28.585352, 43.742236], + [27.086914, 44.167383], + [25.49707, 43.670801], + [22.919043, 43.834473], + [23.028516, 44.077979], + [22.705078, 44.237793], + [22.369629, 43.781299], + [22.967969, 43.142041], + [22.466797, 42.84248], + [22.344043, 42.313965], + [23.003613, 41.739844], + [22.916016, 41.336279], + [24.487891, 41.555225], + [25.251172, 41.243555], + [25.92334, 41.311914], + [26.320898, 41.716553], + [27.011719, 42.058643], + [28.014453, 41.969043], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '文莱', full_name: '文莱达鲁萨兰国', iso_a2: 'BN', iso_a3: 'BRN', iso_n3: '096' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [115.140039, 4.899756], + [115.026758, 4.899707], + [115.290625, 4.352588], + [115.140039, 4.899756], + ], + ], + [ + [ + [115.026758, 4.899707], + [114.063867, 4.592676], + [114.654102, 4.037646], + [114.74668, 4.718066], + [115.026758, 4.899707], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴西', full_name: '巴西联邦共和国', iso_a2: 'BR', iso_a3: 'BRA', iso_n3: '076' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-66.876025, 1.223047], + [-67.082275, 1.1854], + [-67.400439, 2.116699], + [-67.93623, 1.748486], + [-68.193799, 1.987012], + [-68.176562, 1.719824], + [-69.848584, 1.70874], + [-69.852148, 1.059521], + [-69.311816, 1.050488], + [-69.15332, 0.658789], + [-70.053906, 0.578613], + [-70.070508, -0.138867], + [-69.633984, -0.509277], + [-69.400244, -1.194922], + [-69.965918, -4.235938], + [-70.799512, -4.17334], + [-72.887061, -5.122754], + [-73.235547, -6.098438], + [-73.137354, -6.46582], + [-73.758105, -6.905762], + [-73.72041, -7.309277], + [-74.002051, -7.556055], + [-72.974023, -8.993164], + [-73.209424, -9.411426], + [-72.379053, -9.510156], + [-72.142969, -10.005176], + [-71.237939, -9.966016], + [-70.541113, -9.4375], + [-70.642334, -11.010254], + [-69.578613, -10.951758], + [-68.622656, -11.10918], + [-66.575342, -9.899902], + [-65.396143, -9.712402], + [-65.389893, -11.246289], + [-64.992529, -11.975195], + [-64.420508, -12.439746], + [-63.06748, -12.669141], + [-61.789941, -13.525586], + [-61.077002, -13.489746], + [-60.506592, -13.789844], + [-60.27334, -15.08877], + [-60.583203, -15.09834], + [-60.242334, -15.47959], + [-60.175586, -16.269336], + [-58.345605, -16.284375], + [-58.395996, -17.234277], + [-57.832471, -17.512109], + [-57.495654, -18.214648], + [-58.131494, -19.744531], + [-57.860742, -19.97959], + [-58.159766, -20.164648], + [-57.830225, -20.997949], + [-57.955908, -22.10918], + [-56.937256, -22.271289], + [-56.447803, -22.076172], + [-55.84917, -22.307617], + [-55.415918, -23.951367], + [-54.625488, -23.8125], + [-54.241797, -24.047266], + [-54.615869, -25.576074], + [-53.891162, -25.668848], + [-53.668555, -26.288184], + [-53.838184, -27.121094], + [-55.725488, -28.204102], + [-57.608887, -30.187793], + [-56.832715, -30.107227], + [-56.044824, -30.777637], + [-56.004687, -31.079199], + [-55.603027, -30.850781], + [-53.761719, -32.056836], + [-53.125586, -32.736719], + [-53.531348, -33.170898], + [-53.370605, -33.742188], + [-52.652246, -33.137793], + [-51.972461, -31.383789], + [-51.283057, -30.751562], + [-51.298047, -30.034863], + [-51.024951, -30.368652], + [-50.563525, -30.253613], + [-50.581934, -30.438867], + [-51.272168, -31.476953], + [-52.063232, -31.830371], + [-52.039209, -32.114844], + [-50.921387, -31.258398], + [-49.745996, -29.363184], + [-48.799658, -28.575293], + [-48.55415, -27.195996], + [-48.748291, -26.268652], + [-48.401172, -25.597363], + [-48.731738, -25.36875], + [-48.202734, -25.416504], + [-46.867285, -24.236328], + [-45.97207, -23.795508], + [-45.464307, -23.802539], + [-44.569678, -23.274023], + [-44.637256, -23.055469], + [-43.22417, -22.991211], + [-43.154297, -22.725195], + [-42.958301, -22.96709], + [-42.042383, -22.94707], + [-41.705518, -22.309668], + [-41.000293, -21.999023], + [-40.954541, -21.237891], + [-39.783301, -19.571777], + [-39.650781, -18.252344], + [-39.154004, -17.703906], + [-38.880615, -15.864258], + [-39.089355, -13.588184], + [-38.851758, -12.790137], + [-38.690967, -12.623926], + [-38.498926, -12.956641], + [-38.239746, -12.844238], + [-37.359229, -11.252539], + [-37.356006, -11.403906], + [-35.340869, -9.230664], + [-34.834668, -7.971484], + [-34.805469, -7.288379], + [-35.481689, -5.166016], + [-37.174658, -4.912402], + [-38.475781, -3.71748], + [-39.964697, -2.861523], + [-41.479932, -2.916504], + [-43.380078, -2.376074], + [-44.192676, -2.80957], + [-44.228613, -2.471289], + [-44.723047, -3.204785], + [-44.381836, -2.365527], + [-44.435449, -2.168066], + [-44.756348, -2.265527], + [-44.537793, -2.052734], + [-44.65127, -1.745801], + [-45.076367, -1.466406], + [-45.32915, -1.717285], + [-45.458594, -1.35625], + [-47.398096, -0.62666], + [-48.115088, -0.7375], + [-48.449805, -1.145508], + [-48.349805, -1.482129], + [-48.71001, -1.487695], + [-49.211035, -1.916504], + [-49.636523, -2.656934], + [-49.313672, -1.731738], + [-50.403223, -2.015527], + [-50.690039, -1.761719], + [-50.894922, -0.937598], + [-51.947559, -1.586719], + [-52.66416, -1.551758], + [-51.980811, -1.367969], + [-51.28291, -0.085205], + [-49.898877, 1.162988], + [-49.957129, 1.659863], + [-50.714404, 2.134033], + [-51.219922, 4.093604], + [-51.54707, 4.310889], + [-51.652539, 4.061279], + [-52.903467, 2.211523], + [-53.767773, 2.354834], + [-54.130078, 2.121045], + [-54.61626, 2.326758], + [-54.978662, 2.597656], + [-55.957471, 2.520459], + [-56.137695, 2.259033], + [-55.929639, 1.8875], + [-56.482812, 1.942139], + [-57.31748, 1.963477], + [-58.821777, 1.201221], + [-59.231201, 1.376025], + [-59.666602, 1.746289], + [-59.994336, 2.68999], + [-59.854395, 3.5875], + [-59.551123, 3.933545], + [-59.703271, 4.381104], + [-60.148633, 4.533252], + [-59.990674, 5.082861], + [-60.142041, 5.238818], + [-60.742139, 5.202051], + [-60.603857, 4.949365], + [-61.002832, 4.535254], + [-62.712109, 4.01792], + [-62.856982, 3.593457], + [-63.338672, 3.943896], + [-64.021484, 3.929102], + [-64.788672, 4.276025], + [-64.221094, 3.587402], + [-64.046582, 2.502393], + [-63.389258, 2.411914], + [-63.43252, 2.155566], + [-64.008496, 1.931592], + [-64.205029, 1.529492], + [-65.473389, 0.69126], + [-65.681445, 0.983447], + [-66.347119, 0.767188], + [-66.876025, 1.223047], + ], + ], + [ + [ + [-49.628662, -0.229199], + [-50.645508, -0.272852], + [-50.796094, -0.90625], + [-50.507617, -1.787988], + [-49.805127, -1.790234], + [-48.833594, -1.390039], + [-48.392676, -0.297363], + [-49.628662, -0.229199], + ], + ], + [ + [ + [-45.260254, -23.88916], + [-45.302344, -23.727539], + [-45.451416, -23.895605], + [-45.260254, -23.88916], + ], + ], + [ + [ + [-49.738232, 0.268164], + [-50.272656, 0.231738], + [-50.339453, 0.043359], + [-49.91709, -0.023193], + [-49.738232, 0.268164], + ], + ], + [ + [ + [-49.443896, -0.112402], + [-49.503467, 0.083691], + [-49.830078, -0.093896], + [-49.443896, -0.112402], + ], + ], + [ + [ + [-50.426123, 0.139258], + [-50.372754, 0.590869], + [-50.623926, 0.054395], + [-50.426123, 0.139258], + ], + ], + [ + [ + [-50.15293, 0.393018], + [-50.058838, 0.638037], + [-50.281689, 0.516504], + [-50.15293, 0.393018], + ], + ], + [ + [ + [-51.83252, -1.433789], + [-51.276318, -1.021777], + [-51.254004, -0.541406], + [-51.546045, -0.649609], + [-51.83252, -1.433789], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '博茨瓦纳', full_name: '博茨瓦纳共和国', iso_a2: 'BW', iso_a3: 'BWA', iso_n3: '072' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [25.258789, -17.793555], + [24.243945, -18.023438], + [23.599707, -18.459961], + [23.219336, -17.999707], + [20.974121, -18.318848], + [20.979492, -21.961914], + [19.977344, -22.000195], + [19.980469, -24.776758], + [20.793164, -25.915625], + [20.685059, -26.822461], + [21.646289, -26.854199], + [22.597656, -26.132715], + [23.05752, -25.312305], + [24.748145, -25.817383], + [25.443652, -25.714453], + [25.912109, -24.747461], + [26.835059, -24.24082], + [27.085547, -23.57793], + [28.210156, -22.693652], + [29.364844, -22.193945], + [29.025586, -21.796875], + [28.014063, -21.554199], + [27.669434, -21.064258], + [27.679297, -20.503027], + [27.280762, -20.478711], + [27.178223, -20.100977], + [26.168066, -19.538281], + [25.258789, -17.793555], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '波黑', full_name: '波斯尼亚和黑塞哥维那', iso_a2: 'BA', iso_a3: 'BIH', iso_n3: '070' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [19.194336, 43.533301], + [19.495117, 43.642871], + [19.24502, 43.965039], + [19.583789, 44.043457], + [19.118457, 44.359961], + [19.348633, 44.880908], + [19.007129, 44.869189], + [16.918652, 45.276562], + [16.293359, 45.008838], + [15.788086, 45.178955], + [15.736621, 44.76582], + [17.585156, 42.938379], + [17.667578, 42.897119], + [18.436328, 42.559717], + [18.460156, 42.9979], + [19.194336, 43.533301], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '玻利维亚', full_name: '多民族玻利维亚国', iso_a2: 'BO', iso_a3: 'BOL', iso_n3: '068' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-69.510938, -17.506055], + [-69.093945, -18.050488], + [-68.968311, -18.967969], + [-68.462891, -19.432813], + [-68.760547, -20.416211], + [-68.197021, -21.300293], + [-67.879443, -22.822949], + [-67.194873, -22.82168], + [-66.220166, -21.802539], + [-65.771045, -22.099609], + [-64.605518, -22.228809], + [-64.325293, -22.827637], + [-63.92168, -22.028613], + [-62.843359, -21.997266], + [-62.650977, -22.233691], + [-62.276318, -20.5625], + [-61.756836, -19.645312], + [-59.090527, -19.28623], + [-58.180176, -19.817871], + [-58.159766, -20.164648], + [-57.860742, -19.97959], + [-58.131494, -19.744531], + [-57.495654, -18.214648], + [-57.832471, -17.512109], + [-58.395996, -17.234277], + [-58.345605, -16.284375], + [-60.175586, -16.269336], + [-60.242334, -15.47959], + [-60.583203, -15.09834], + [-60.27334, -15.08877], + [-60.506592, -13.789844], + [-61.077002, -13.489746], + [-61.789941, -13.525586], + [-63.06748, -12.669141], + [-64.420508, -12.439746], + [-64.992529, -11.975195], + [-65.389893, -11.246289], + [-65.396143, -9.712402], + [-66.575342, -9.899902], + [-68.622656, -11.10918], + [-69.578613, -10.951758], + [-68.685254, -12.501953], + [-68.978613, -12.880078], + [-69.074121, -13.682813], + [-68.870898, -14.169727], + [-69.359473, -14.795312], + [-69.172461, -15.236621], + [-69.420898, -15.640625], + [-69.217578, -16.149121], + [-68.842773, -16.337891], + [-69.624854, -17.200195], + [-69.510938, -17.506055], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '不丹', full_name: '不丹王国', iso_a2: 'BT', iso_a3: 'BTN', iso_n3: '064' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [91.631934, 27.759961], + [91.273047, 28.078369], + [90.352734, 28.080225], + [89.981055, 28.311182], + [88.891406, 27.316064], + [88.857617, 26.961475], + [89.609961, 26.719434], + [91.99834, 26.85498], + [92.083398, 27.290625], + [91.594727, 27.557666], + [91.631934, 27.759961], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '贝宁', full_name: '贝宁共和国', iso_a2: 'BJ', iso_a3: 'BEN', iso_n3: '204' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [1.622656, 6.216797], + [2.706445, 6.369238], + [2.774805, 9.048535], + [3.044922, 9.083838], + [3.834473, 10.607422], + [3.487793, 11.39541], + [3.59541, 11.696289], + [2.805273, 12.383838], + [2.366016, 12.221924], + [2.38916, 11.89707], + [1.980371, 11.418408], + [1.426758, 11.447119], + [0.900488, 10.993262], + [0.763379, 10.38667], + [1.330078, 9.996973], + [1.600195, 9.050049], + [1.530957, 6.992432], + [1.77793, 6.294629], + [1.622656, 6.216797], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '伯利兹', full_name: '伯利兹', iso_a2: 'BZ', iso_a3: 'BLZ', iso_n3: '084' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-89.161475, 17.814844], + [-89.2375, 15.894434], + [-88.894043, 15.890625], + [-88.313428, 16.632764], + [-88.085254, 18.226123], + [-88.295654, 18.472412], + [-89.161475, 17.814844], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '比利时', full_name: '比利时王国', iso_a2: 'BE', iso_a3: 'BEL', iso_n3: '056' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [4.226172, 51.386475], + [3.902051, 51.207666], + [3.350098, 51.377686], + [2.524902, 51.097119], + [2.759375, 50.750635], + [4.174609, 50.246484], + [4.149316, 49.971582], + [4.818652, 50.153174], + [4.867578, 49.788135], + [5.789746, 49.538281], + [5.744043, 49.919629], + [6.116504, 50.120996], + [6.340918, 50.451758], + [5.993945, 50.750439], + [5.639453, 50.843604], + [5.796484, 51.153076], + [5.030957, 51.469092], + [4.226172, 51.386475], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '白俄罗斯', full_name: '白俄罗斯共和国', iso_a2: 'BY', iso_a3: 'BLR', iso_n3: '112' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [31.763379, 52.101074], + [31.258789, 53.016699], + [31.417871, 53.196045], + [32.141992, 53.091162], + [32.706445, 53.419434], + [31.754199, 53.810449], + [30.798828, 54.783252], + [30.906836, 55.57002], + [30.233594, 55.845215], + [29.482227, 55.68457], + [29.375, 55.938721], + [28.147949, 56.14292], + [27.576758, 55.798779], + [26.593555, 55.667529], + [26.457617, 55.34248], + [26.775684, 55.273096], + [25.780859, 54.833252], + [25.749219, 54.156982], + [25.461133, 54.292773], + [24.317969, 53.892969], + [23.484668, 53.939795], + [23.91543, 52.770264], + [23.175098, 52.286621], + [23.652441, 52.040381], + [23.605273, 51.51792], + [25.267188, 51.937744], + [27.141992, 51.752051], + [27.7, 51.477979], + [28.73125, 51.433398], + [29.102051, 51.627539], + [29.346484, 51.382568], + [30.160742, 51.477881], + [30.544531, 51.265039], + [30.755273, 51.895166], + [31.763379, 52.101074], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴巴多斯', full_name: '巴巴多斯', iso_a2: 'BB', iso_a3: 'BRB', iso_n3: '052' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-59.493311, 13.081982], + [-59.427637, 13.152783], + [-59.64668, 13.303125], + [-59.493311, 13.081982], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '孟加拉国', full_name: '孟加拉人民共和国', iso_a2: 'BD', iso_a3: 'BGD', iso_n3: '050' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [89.051465, 22.093164], + [89.353711, 21.721094], + [89.483203, 22.275537], + [89.568555, 21.767432], + [89.985156, 22.466406], + [89.918066, 22.116162], + [90.20957, 22.156592], + [90.230566, 21.829785], + [90.616113, 22.362158], + [90.435059, 22.751904], + [90.590918, 23.266406], + [90.269141, 23.455859], + [90.604004, 23.591357], + [90.945605, 22.597021], + [91.480078, 22.884814], + [91.863379, 22.350488], + [92.324121, 20.791846], + [92.17959, 21.293115], + [92.631641, 21.306201], + [92.574902, 21.978076], + [92.246094, 23.683594], + [91.92959, 23.685986], + [91.619531, 22.979688], + [91.315234, 23.104395], + [91.160449, 23.660645], + [91.36709, 24.093506], + [91.876953, 24.195312], + [92.468359, 24.944141], + [92.049707, 25.169482], + [89.833301, 25.292773], + [89.670898, 26.213818], + [89.369727, 26.006104], + [89.018652, 26.410254], + [88.828027, 26.252197], + [88.418164, 26.571533], + [88.08457, 25.888232], + [88.95166, 25.259277], + [88.45625, 25.188428], + [88.023438, 24.627832], + [88.723535, 24.274902], + [88.567383, 23.674414], + [88.928125, 23.186621], + [89.051465, 22.093164], + ], + ], + [ + [ + [90.777637, 22.089307], + [90.868164, 22.484863], + [90.596484, 22.863525], + [90.680469, 22.32749], + [90.515039, 22.065137], + [90.777637, 22.089307], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴林', full_name: '巴林王国', iso_a2: 'BH', iso_a3: 'BHR', iso_n3: '048' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [50.607227, 25.883105], + [50.585938, 26.240723], + [50.469922, 26.228955], + [50.607227, 25.883105], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '巴哈马', full_name: '巴哈马国', iso_a2: 'BS', iso_a3: 'BHS', iso_n3: '044' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-77.657715, 24.249463], + [-77.999902, 24.219824], + [-77.57373, 23.73916], + [-77.657715, 24.249463], + ], + ], + [ + [ + [-77.225635, 25.904199], + [-77.066357, 26.530176], + [-77.533887, 26.903418], + [-77.94375, 26.903564], + [-77.238623, 26.561133], + [-77.403174, 26.024707], + [-77.225635, 25.904199], + ], + ], + [ + [ + [-73.026855, 21.192383], + [-73.523096, 21.19082], + [-73.681152, 20.975586], + [-73.164551, 20.97915], + [-73.026855, 21.192383], + ], + ], + [ + [ + [-77.743848, 24.707422], + [-78.211377, 25.19126], + [-78.366504, 24.544189], + [-78.044922, 24.287451], + [-77.743848, 24.707422], + ], + ], + [ + [ + [-78.492871, 26.729053], + [-78.985645, 26.689502], + [-78.743652, 26.500684], + [-77.922461, 26.691113], + [-78.492871, 26.729053], + ], + ], + [ + [ + [-76.648828, 25.487402], + [-76.1604, 25.119336], + [-76.169531, 24.649414], + [-76.126611, 25.140527], + [-76.648828, 25.487402], + ], + ], + [ + [ + [-74.840479, 22.894336], + [-75.315967, 23.668359], + [-75.22334, 23.165332], + [-74.840479, 22.894336], + ], + ], + [ + [ + [-75.308398, 24.2], + [-75.72666, 24.689355], + [-75.503223, 24.139062], + [-75.308398, 24.2], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿塞拜疆', full_name: '阿塞拜疆共和国', iso_a2: 'AZ', iso_a3: 'AZE', iso_n3: '031' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [44.817188, 39.650439], + [45.479688, 39.00625], + [46.114453, 38.877783], + [45.784473, 39.545605], + [44.768262, 39.703516], + [44.817188, 39.650439], + ], + ], + [ + [ + [48.86875, 38.435498], + [49.551172, 40.194141], + [50.365918, 40.279492], + [49.456738, 40.799854], + [48.572852, 41.844482], + [47.791016, 41.199268], + [46.429883, 41.890967], + [46.182129, 41.65708], + [46.672559, 41.286816], + [46.534375, 41.088574], + [45.280957, 41.449561], + [45.001367, 41.290967], + [45.5875, 40.846924], + [45.454395, 40.532373], + [45.964648, 40.233789], + [45.579785, 39.977539], + [46.481445, 39.555176], + [46.490625, 38.906689], + [47.995898, 39.683936], + [48.322168, 39.399072], + [47.996484, 38.85376], + [48.86875, 38.435498], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '奥地利', full_name: '奥地利共和国', iso_a2: 'AT', iso_a3: 'AUT', iso_n3: '040' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [9.527539, 47.270752], + [9.580273, 47.057373], + [10.452832, 46.864941], + [10.993262, 46.777002], + [12.169434, 47.082129], + [12.388281, 46.702637], + [13.7, 46.520264], + [14.549805, 46.399707], + [16.093066, 46.863281], + [16.676563, 47.536035], + [16.421289, 47.674463], + [17.066602, 47.707568], + [17.147363, 48.005957], + [16.953125, 48.598828], + [15.066797, 48.997852], + [14.691309, 48.599219], + [13.814746, 48.766943], + [12.760352, 48.106982], + [13.014355, 47.478076], + [12.209277, 47.718262], + [11.041992, 47.393115], + [10.439453, 47.551562], + [10.183008, 47.278809], + [9.524023, 47.524219], + [9.527539, 47.270752], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '澳大利亚', full_name: '澳大利亚联邦', iso_a2: 'AU', iso_a3: 'AUS', iso_n3: '036' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [143.178906, -11.954492], + [142.872559, -11.821387], + [142.779688, -11.115332], + [142.456445, -10.707324], + [142.168359, -10.946582], + [141.961133, -12.054297], + [141.688574, -12.351074], + [141.929785, -12.739844], + [141.613574, -12.943457], + [141.625488, -15.056641], + [141.291406, -16.463477], + [140.830469, -17.414453], + [140.03584, -17.702637], + [139.248438, -17.328613], + [139.009863, -16.899316], + [138.24502, -16.718359], + [135.45332, -14.923145], + [135.954492, -13.934863], + [135.927344, -13.304297], + [136.461035, -13.225195], + [136.897461, -12.243555], + [136.443359, -11.951465], + [136.081836, -12.422461], + [136.008496, -12.191406], + [135.704395, -12.209863], + [135.922461, -11.825781], + [135.029688, -12.19375], + [131.961523, -11.180859], + [131.822461, -11.302441], + [132.072852, -11.474707], + [132.674219, -11.649023], + [132.712793, -12.123438], + [131.438281, -12.276953], + [131.291602, -12.067871], + [130.867383, -12.557813], + [130.622656, -12.431055], + [130.168164, -12.957422], + [130.259766, -13.302246], + [129.838867, -13.572949], + [129.378711, -14.39248], + [129.84873, -14.828906], + [129.637109, -14.850977], + [129.634766, -15.139746], + [129.267578, -14.871484], + [129.21582, -15.160254], + [129.058203, -14.884375], + [128.575781, -14.774512], + [128.069434, -15.329297], + [128.180469, -14.711621], + [127.457617, -14.031445], + [126.903223, -13.744141], + [126.569727, -14.160938], + [126.053906, -13.977246], + [126.020703, -14.494531], + [125.661621, -14.529492], + [125.627734, -14.256641], + [125.178711, -14.714746], + [125.355664, -15.119824], + [124.839063, -15.160742], + [125.062988, -15.442285], + [124.692578, -15.273633], + [124.439551, -15.493555], + [124.381641, -15.758203], + [124.648535, -15.870215], + [124.404883, -16.298926], + [124.771973, -16.402637], + [123.607031, -16.224023], + [123.49043, -16.490723], + [123.874414, -16.918652], + [123.831055, -17.120801], + [123.593555, -17.030371], + [123.563086, -17.520898], + [122.970703, -16.436816], + [122.260938, -17.135742], + [122.34541, -18.111914], + [120.997949, -19.604395], + [119.104492, -19.995313], + [117.40625, -20.721191], + [116.706738, -20.653809], + [114.709277, -21.823438], + [114.141602, -22.483105], + [114.022852, -21.881445], + [113.417676, -24.435645], + [114.214258, -25.851562], + [114.215723, -26.289453], + [113.72373, -26.129785], + [113.451367, -25.599121], + [113.836426, -26.500586], + [113.581641, -26.558105], + [113.356055, -26.080469], + [113.184766, -26.182227], + [114.028125, -27.347266], + [114.165137, -28.080664], + [114.856836, -29.142969], + [115.07793, -30.560449], + [115.698438, -31.694531], + [115.683008, -33.192871], + [115.358789, -33.639941], + [114.993848, -33.515332], + [115.008789, -34.255859], + [116.517188, -34.987891], + [117.581934, -35.097754], + [119.450586, -34.368262], + [119.854102, -33.974707], + [123.506836, -33.916211], + [124.24375, -33.015234], + [125.917188, -32.296973], + [127.319824, -32.264062], + [129.187695, -31.659961], + [131.143652, -31.495703], + [134.23418, -32.548535], + [134.173535, -32.979102], + [134.791016, -33.32832], + [135.45, -34.581055], + [135.123047, -34.585742], + [135.647559, -34.939648], + [135.969727, -34.981836], + [135.891016, -34.660938], + [136.430664, -34.02998], + [137.237305, -33.629492], + [137.783203, -32.578125], + [137.931836, -33.579102], + [137.493848, -34.161133], + [137.391016, -34.913281], + [137.014258, -34.91582], + [136.883594, -35.239746], + [137.691699, -35.142969], + [138.089258, -34.169824], + [138.511133, -35.024414], + [138.184375, -35.612695], + [139.28252, -35.375391], + [139.289453, -35.611328], + [138.968945, -35.580762], + [139.729004, -36.371387], + [139.784277, -37.245801], + [140.39043, -37.89668], + [141.424219, -38.363477], + [142.455859, -38.386328], + [143.538965, -38.820898], + [144.891309, -37.899805], + [145.119922, -38.091309], + [144.717773, -38.340332], + [144.95957, -38.500781], + [145.475781, -38.24375], + [145.397266, -38.535352], + [146.4, -39.145508], + [146.21748, -38.727441], + [146.856836, -38.663477], + [147.876758, -37.93418], + [149.932715, -37.528516], + [150.195312, -35.833594], + [150.80459, -35.012891], + [151.29209, -33.580957], + [152.47041, -32.439062], + [152.943945, -31.434863], + [153.616895, -28.673047], + [153.116797, -27.194434], + [153.164941, -25.96416], + [151.831641, -24.122949], + [150.843164, -23.458008], + [150.672461, -22.418164], + [150.541309, -22.559082], + [150.076172, -22.164453], + [149.974414, -22.550684], + [149.703906, -22.440527], + [149.454102, -21.578711], + [148.683691, -20.580176], + [148.884766, -20.480859], + [148.759375, -20.289551], + [147.418555, -19.378125], + [146.383398, -18.977051], + [146.032227, -18.272852], + [145.912109, -16.9125], + [145.426074, -16.406152], + [145.287695, -14.943164], + [144.473047, -14.231836], + [143.756348, -14.348828], + [143.178906, -11.954492], + ], + ], + [ + [ + [139.507812, -16.573047], + [139.587891, -16.395215], + [139.15957, -16.741699], + [139.507812, -16.573047], + ], + ], + [ + [ + [136.714648, -13.803906], + [136.424707, -13.864844], + [136.335449, -14.211816], + [136.894336, -14.293066], + [136.89082, -13.786621], + [136.714648, -13.803906], + ], + ], + [ + [ + [130.459277, -11.679297], + [130.294922, -11.336816], + [130.043262, -11.787305], + [130.60625, -11.816602], + [130.459277, -11.679297], + ], + ], + [ + [ + [130.618848, -11.376074], + [130.38457, -11.192188], + [130.511914, -11.617871], + [130.950977, -11.926465], + [131.538574, -11.436914], + [131.268262, -11.189844], + [130.618848, -11.376074], + ], + ], + [ + [ + [137.596484, -35.738672], + [137.334082, -35.59248], + [136.540625, -35.890137], + [137.448438, -36.074805], + [138.123438, -35.852344], + [137.596484, -35.738672], + ], + ], + [ + [ + [146.27832, -18.23125], + [146.098828, -18.251758], + [146.298828, -18.484766], + [146.27832, -18.23125], + ], + ], + [ + [ + [136.338672, -11.602344], + [136.479297, -11.465918], + [136.180273, -11.676758], + [136.338672, -11.602344], + ], + ], + [ + [ + [145.042969, -40.786719], + [144.718555, -40.672266], + [144.766113, -41.390039], + [145.516602, -42.354492], + [145.198828, -42.230859], + [145.487598, -42.92666], + [146.208008, -43.316211], + [146.043164, -43.547168], + [146.873926, -43.6125], + [147.297949, -42.790918], + [147.94541, -43.181836], + [148.213672, -41.97002], + [148.342578, -42.215332], + [148.215234, -40.854883], + [146.31748, -41.163477], + [145.042969, -40.786719], + ], + ], + [ + [ + [148.000391, -39.757617], + [147.767188, -39.870313], + [148.105664, -40.262109], + [148.297363, -39.985742], + [148.000391, -39.757617], + ], + ], + [ + [ + [148.32627, -40.306934], + [148.020117, -40.404199], + [148.404004, -40.486523], + [148.32627, -40.306934], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '圣诞岛', full_name: '圣诞岛(澳大利亚)', iso_a2: 'CX', iso_a3: 'CXR', iso_n3: '162' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [105.725391, -10.492969], + [105.584082, -10.5125], + [105.696875, -10.56416], + [105.725391, -10.492969], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '赫德岛和麦克唐纳群岛', + full_name: '赫德岛和麦克唐纳群岛', + iso_a2: 'HM', + iso_a3: 'HMD', + iso_n3: '334', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [73.707422, -53.137109], + [73.251172, -52.975781], + [73.465137, -53.18418], + [73.707422, -53.137109], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '诺福克岛', full_name: '诺福克岛(澳大利亚)', iso_a2: 'NF', iso_a3: 'NFK', iso_n3: '574' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [167.939453, -29.017676], + [167.960742, -29.096289], + [167.99043, -29.04209], + [167.939453, -29.017676], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '阿什莫尔和卡捷群岛', + full_name: '阿什莫尔和卡捷群岛(澳大利亚)', + iso_a2: 'AU', + iso_a3: 'AUS', + iso_n3: '036', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [123.594531, -12.425684], + [123.572461, -12.423926], + [123.573145, -12.43418], + [123.594531, -12.425684], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '亚美尼亚', full_name: '亚美尼亚共和国', iso_a2: 'AM', iso_a3: 'ARM', iso_n3: '051' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [44.768262, 39.703516], + [45.784473, 39.545605], + [46.114453, 38.877783], + [46.490625, 38.906689], + [46.481445, 39.555176], + [45.579785, 39.977539], + [45.964648, 40.233789], + [45.454395, 40.532373], + [45.5875, 40.846924], + [45.001367, 41.290967], + [43.439453, 41.107129], + [43.666211, 40.126367], + [44.768262, 39.703516], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿根廷', full_name: '阿根廷共和国', iso_a2: 'AR', iso_a3: 'ARG', iso_n3: '032' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-57.608887, -30.187793], + [-55.725488, -28.204102], + [-53.838184, -27.121094], + [-53.668555, -26.288184], + [-53.891162, -25.668848], + [-54.615869, -25.576074], + [-54.825488, -26.652246], + [-55.714648, -27.414844], + [-56.164062, -27.321484], + [-56.437158, -27.553809], + [-58.604834, -27.314355], + [-57.643896, -25.328418], + [-59.89248, -24.093555], + [-61.03291, -23.755664], + [-62.650977, -22.233691], + [-62.843359, -21.997266], + [-63.92168, -22.028613], + [-64.325293, -22.827637], + [-64.605518, -22.228809], + [-65.771045, -22.099609], + [-66.220166, -21.802539], + [-67.194873, -22.82168], + [-67.008789, -23.001367], + [-67.356201, -24.033789], + [-68.562012, -24.747363], + [-68.384229, -25.091895], + [-68.591602, -26.47041], + [-68.318652, -26.973242], + [-68.846338, -27.153711], + [-69.656934, -28.413574], + [-70.026807, -29.324023], + [-69.844287, -30.175], + [-70.51958, -31.148438], + [-70.084863, -33.201758], + [-69.819629, -33.283789], + [-69.852441, -34.224316], + [-70.555176, -35.246875], + [-70.404785, -36.061719], + [-71.192187, -36.843652], + [-70.858643, -38.604492], + [-71.401562, -38.935059], + [-71.932129, -40.691699], + [-71.75, -42.046777], + [-72.108203, -42.251855], + [-72.146436, -42.990039], + [-71.750635, -43.237305], + [-71.82002, -44.383105], + [-71.159717, -44.560254], + [-71.261133, -44.763086], + [-72.063721, -44.771875], + [-71.349316, -45.331934], + [-71.746191, -45.578906], + [-71.699658, -46.651367], + [-72.51792, -47.876367], + [-72.354736, -48.36582], + [-73.554199, -49.463867], + [-73.50127, -50.125293], + [-73.15293, -50.738281], + [-72.340234, -50.681836], + [-72.407666, -51.54082], + [-71.918652, -51.989551], + [-69.960254, -52.008203], + [-68.443359, -52.356641], + [-68.965332, -51.677148], + [-69.46543, -51.584473], + [-69.035303, -51.488965], + [-69.358594, -51.028125], + [-69.044775, -50.499121], + [-68.421875, -50.15791], + [-68.97959, -50.003027], + [-68.667578, -49.752539], + [-68.257227, -50.10459], + [-67.825977, -49.919629], + [-67.466309, -48.951758], + [-65.810059, -47.941113], + [-66.225244, -47.826758], + [-65.738086, -47.344922], + [-65.998535, -47.09375], + [-66.776855, -47.005859], + [-67.599561, -46.052539], + [-66.941406, -45.257324], + [-65.63877, -45.007812], + [-65.252344, -43.571875], + [-64.319141, -42.968945], + [-64.970703, -42.666309], + [-64.487842, -42.513477], + [-64.034766, -42.88125], + [-63.617334, -42.695801], + [-63.795557, -42.113867], + [-64.42041, -42.433789], + [-64.986377, -42.102051], + [-65.133398, -40.880664], + [-64.869482, -40.73584], + [-63.621777, -41.159766], + [-62.39502, -40.89082], + [-62.053662, -39.373828], + [-62.334766, -38.800098], + [-61.602539, -38.998828], + [-59.82832, -38.838184], + [-57.546973, -38.085645], + [-56.672021, -36.85127], + [-56.698096, -36.426465], + [-57.335449, -36.026758], + [-57.303662, -35.188477], + [-58.28335, -34.683496], + [-58.525488, -34.296191], + [-58.39248, -34.192969], + [-58.547217, -33.663477], + [-58.424463, -33.111523], + [-58.170996, -32.959277], + [-58.201172, -32.47168], + [-57.608887, -30.187793], + ], + ], + [ + [ + [-68.653223, -54.853613], + [-66.511133, -55.032129], + [-65.179004, -54.678125], + [-67.294238, -54.049805], + [-68.488525, -53.260938], + [-68.240137, -53.081836], + [-68.629932, -52.652637], + [-68.653223, -54.853613], + ], + ], + [ + [ + [-64.54917, -54.716211], + [-64.637354, -54.902539], + [-63.81543, -54.725098], + [-64.54917, -54.716211], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '安提瓜和巴布达', full_name: '安提瓜和巴布达', iso_a2: 'AG', iso_a3: 'ATG', iso_n3: '028' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-61.716064, 17.037012], + [-61.817285, 17.168945], + [-61.887109, 17.098145], + [-61.716064, 17.037012], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '安哥拉', full_name: '安哥拉共和国', iso_a2: 'AO', iso_a3: 'AGO', iso_n3: '024' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [13.072754, -4.634766], + [12.798242, -4.430566], + [12.018359, -5.004297], + [12.213672, -5.758691], + [12.503711, -5.695801], + [12.451465, -5.071484], + [13.072754, -4.634766], + ], + ], + [ + [ + [23.966504, -10.871777], + [22.226172, -11.121973], + [22.274512, -10.259082], + [21.813184, -9.46875], + [21.781641, -7.314648], + [20.607813, -7.277734], + [20.590039, -6.919922], + [19.875195, -6.986328], + [19.527637, -7.144434], + [19.34082, -7.966602], + [17.57959, -8.099023], + [16.431445, -5.900195], + [13.068164, -5.864844], + [12.283301, -6.124316], + [13.378516, -8.369727], + [12.998535, -9.048047], + [13.833594, -10.929688], + [13.785352, -11.812793], + [12.550488, -13.437793], + [11.750879, -15.831934], + [11.743066, -17.249219], + [13.101172, -16.967676], + [14.01748, -17.408887], + [18.396387, -17.399414], + [18.955273, -17.803516], + [20.745508, -18.019727], + [23.380664, -17.640625], + [22.040234, -16.262793], + [21.978906, -13.000977], + [23.962988, -12.988477], + [23.966504, -10.871777], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '安道尔', full_name: '安道尔公国', iso_a2: 'AD', iso_a3: 'AND', iso_n3: '020' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [1.706055, 42.50332], + [1.42832, 42.595898], + [1.448828, 42.437451], + [1.706055, 42.50332], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + name: '阿尔及利亚', + full_name: '阿尔及利亚民主人民共和国', + iso_a2: 'DZ', + iso_a3: 'DZA', + iso_n3: '012', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [8.576563, 36.937207], + [6.486523, 37.085742], + [5.29541, 36.648242], + [4.758105, 36.896338], + [3.779004, 36.896191], + [1.257227, 36.51958], + [-0.048242, 35.832812], + [-2.219629, 35.104199], + [-1.795605, 34.751904], + [-1.679199, 33.318652], + [-1.065527, 32.468311], + [-1.225928, 32.107227], + [-2.887207, 32.068848], + [-3.017383, 31.834277], + [-3.826758, 31.661914], + [-3.666797, 30.964014], + [-4.968262, 30.465381], + [-5.448779, 29.956934], + [-7.685156, 29.349512], + [-8.678418, 28.689404], + [-8.68335, 27.656445], + [-8.68335, 27.285938], + [-4.822607, 24.995605], + [1.145508, 21.102246], + [1.685449, 20.378369], + [3.130273, 19.850195], + [3.119727, 19.103174], + [4.227637, 19.142773], + [5.836621, 19.47915], + [7.481738, 20.873096], + [11.967871, 23.517871], + [11.507617, 24.314355], + [10.255859, 24.591016], + [9.448242, 26.067139], + [9.883203, 26.630811], + [9.916016, 27.785693], + [9.805273, 29.176953], + [9.310254, 30.115234], + [9.51875, 30.229395], + [9.044043, 32.072363], + [8.333398, 32.543604], + [7.500195, 33.832471], + [8.245605, 34.734082], + [8.207617, 36.518945], + [8.576563, 36.937207], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿尔巴尼亚', full_name: '阿尔巴尼亚共和国', iso_a2: 'AL', iso_a3: 'ALB', iso_n3: '008' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [19.342383, 41.869092], + [19.575684, 41.64043], + [19.322266, 40.40708], + [20.00127, 39.709424], + [20.657422, 40.117383], + [20.964258, 40.849902], + [20.488965, 41.272607], + [20.566211, 41.873682], + [20.063965, 42.547266], + [19.654492, 42.628564], + [19.342383, 41.869092], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '阿富汗', full_name: '阿富汗', iso_a2: 'AF', iso_a3: 'AFG', iso_n3: '004' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [66.522266, 37.348486], + [65.765039, 37.569141], + [65.55498, 37.251172], + [64.816309, 37.13208], + [64.511035, 36.340674], + [63.12998, 35.846191], + [63.056641, 35.445801], + [62.688086, 35.255322], + [62.307813, 35.170801], + [61.262012, 35.61958], + [60.72627, 34.518262], + [60.889453, 34.319434], + [60.485742, 34.094775], + [60.51084, 33.638916], + [60.916992, 33.505225], + [60.561914, 33.058789], + [60.820703, 31.495166], + [61.660156, 31.382422], + [61.81084, 30.913281], + [60.843359, 29.858691], + [62.476562, 29.40835], + [64.09873, 29.391943], + [66.23125, 29.865723], + [66.346875, 30.802783], + [66.829297, 31.263672], + [67.452832, 31.234619], + [68.161035, 31.802979], + [68.868945, 31.634229], + [69.279297, 31.936816], + [69.501562, 33.020068], + [70.261133, 33.289014], + [69.889648, 34.007275], + [71.051563, 34.049707], + [70.965625, 34.530371], + [71.620508, 35.183008], + [71.23291, 36.121777], + [72.249805, 36.734717], + [74.541406, 37.022168], + [74.372168, 37.157715], + [74.891309, 37.231641], + [74.349023, 37.41875], + [73.653516, 37.239355], + [73.720605, 37.41875], + [73.38291, 37.462256], + [71.665625, 36.696924], + [71.43291, 37.127539], + [71.582227, 37.910107], + [71.278516, 37.918408], + [71.255859, 38.306982], + [70.878906, 38.456396], + [70.214648, 37.924414], + [70.188672, 37.582471], + [69.49209, 37.553076], + [69.303906, 37.116943], + [68.911816, 37.333936], + [68.067773, 36.949805], + [67.758984, 37.172217], + [66.522266, 37.348486], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '锡亚琴冰川', full_name: '锡亚琴冰川', iso_a2: '-99', iso_a3: '-99', iso_n3: '-99' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [77.048633, 35.109912], + [77.799414, 35.495898], + [76.766895, 35.661719], + [77.048633, 35.109912], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '南极洲', full_name: '南极洲', iso_a2: 'AQ', iso_a3: 'ATA', iso_n3: '010' }, + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [-57.020654, -63.372852], + [-57.389648, -63.22627], + [-58.87207, -63.551855], + [-60.86416, -64.073438], + [-61.631787, -64.604688], + [-62.503467, -64.656445], + [-63.059082, -65.139355], + [-63.760254, -65.033496], + [-63.818115, -65.531543], + [-64.646582, -65.747852], + [-64.613525, -66.019043], + [-65.617285, -66.135254], + [-65.766406, -66.624902], + [-66.503613, -66.689844], + [-66.498682, -67.289062], + [-67.034473, -66.945117], + [-67.493359, -67.112793], + [-67.544531, -67.534668], + [-66.677246, -67.560254], + [-67.390527, -68.86123], + [-66.974902, -69.161035], + [-67.371777, -69.412305], + [-68.707959, -69.432227], + [-66.827734, -72.09043], + [-68.000342, -72.935547], + [-72.929199, -73.447949], + [-73.996045, -73.699805], + [-75.293066, -73.63877], + [-77.048926, -73.844141], + [-76.850488, -73.460449], + [-78.78623, -73.506738], + [-80.442236, -72.944531], + [-80.336377, -73.41416], + [-81.176416, -73.248828], + [-81.30874, -73.738281], + [-82.183496, -73.856836], + [-85.801416, -73.19209], + [-86.791016, -73.363672], + [-88.419385, -73.229004], + [-88.194092, -72.7875], + [-88.77998, -72.683008], + [-90.920947, -73.319141], + [-92.828369, -73.164648], + [-96.394238, -73.301172], + [-98.208594, -73.022266], + [-102.409277, -72.987402], + [-102.855859, -72.716211], + [-103.375, -72.818848], + [-102.908789, -73.285156], + [-98.896143, -73.611133], + [-102.862744, -73.783594], + [-101.251709, -74.485742], + [-100.118604, -74.515039], + [-100.47334, -74.872363], + [-98.752344, -75.31709], + [-101.039355, -75.421875], + [-101.708105, -75.127344], + [-103.121045, -75.095215], + [-106.618848, -75.343945], + [-111.358789, -75.219922], + [-110.229785, -74.536328], + [-111.180176, -74.188086], + [-111.69624, -74.792188], + [-114.110449, -74.981836], + [-113.508496, -74.088867], + [-114.62373, -73.90293], + [-115.222607, -74.487402], + [-118.655762, -74.392773], + [-121.543945, -74.75], + [-135.362061, -74.69043], + [-136.649854, -75.161719], + [-139.691162, -75.212793], + [-141.22334, -75.545898], + [-140.874316, -75.745898], + [-142.329834, -75.490918], + [-145.987744, -75.88877], + [-145.44209, -76.40918], + [-148.458984, -76.117969], + [-149.654248, -76.365332], + [-147.34043, -76.438379], + [-145.750488, -76.749023], + [-145.677148, -77.488086], + [-148.572412, -77.105078], + [-148.155713, -77.462305], + [-149.717725, -77.797461], + [-154.814941, -77.126953], + [-158.213574, -77.157129], + [-158.285889, -77.950781], + [-157.266797, -78.199805], + [-154.293018, -78.259082], + [-156.114551, -78.744629], + [-152.137695, -79.115918], + [-148.176514, -79.775879], + [-150.575391, -80.353711], + [-148.023438, -80.835742], + [-156.528223, -81.162305], + [-157.03252, -81.319141], + [-153.956641, -81.700195], + [-153.009863, -82.449609], + [-159.444385, -83.543164], + [-174.235938, -82.793457], + [-167.801221, -83.79082], + [-164.950879, -83.805859], + [-165.135352, -84.409863], + [-156.986328, -84.811133], + [-156.459131, -85.186035], + [-171.703662, -84.542383], + [-180, -84.351562], + [-180, -85.05752], + [-180, -85.763379], + [-180, -86.469336], + [-180, -87.175195], + [-180, -87.881055], + [-180, -88.587012], + [-180, -89.292969], + [-180, -89.58291], + [-180, -89.998926], + [-1.40625, -89.998926], + [180, -89.998926], + [180, -84.351562], + [171.035742, -83.448438], + [168.110059, -83.362012], + [168.607324, -83.065332], + [164.980078, -82.384961], + [161.283203, -82.489941], + [163.602344, -82.120605], + [160.469824, -81.340625], + [160.637305, -80.449902], + [158.573633, -80.423438], + [160.558789, -80.010547], + [159.975879, -79.585645], + [160.873535, -79.049707], + [161.951465, -79.02998], + [161.669238, -78.536133], + [162.639453, -78.897754], + [164.634766, -78.603223], + [167.130273, -78.606152], + [165.662988, -78.305664], + [165.417578, -78.042188], + [163.977637, -78.223828], + [164.420898, -77.883496], + [162.450293, -76.955664], + [162.815723, -75.846191], + [160.910742, -75.334668], + [162.410059, -75.237598], + [163.397852, -74.382129], + [165.408594, -74.558594], + [164.812988, -73.396777], + [165.733691, -73.866699], + [165.860156, -73.592676], + [167.709082, -73.394238], + [166.452832, -72.936035], + [167.155664, -73.147266], + [169.54502, -73.050391], + [169.828613, -72.728809], + [168.428418, -72.383398], + [170.206445, -72.565332], + [170.030078, -72.115527], + [170.859082, -71.868555], + [170.435742, -71.41875], + [170.162305, -71.630469], + [166.626953, -70.664258], + [163.566504, -70.642285], + [162.674805, -70.30459], + [162.021973, -70.439844], + [162.189453, -71.039551], + [159.783984, -69.521875], + [155.520313, -69.024414], + [153.908008, -68.323145], + [153.081836, -68.856836], + [151.28877, -68.81709], + [150.935938, -68.358496], + [147.093652, -68.368652], + [145.975195, -67.624219], + [143.977344, -67.864551], + [144.621191, -67.141406], + [143.730371, -66.876758], + [142.6875, -67.012793], + [135.351953, -66.127148], + [134.289453, -66.476758], + [133.148242, -66.094824], + [130.120508, -66.291504], + [129.236914, -67.041602], + [128.430566, -67.119141], + [125.865625, -66.364453], + [119.133008, -67.370703], + [120.374805, -66.983789], + [116.713477, -67.047168], + [114.026563, -67.441211], + [113.991211, -67.211914], + [115.635352, -66.771191], + [113.099414, -65.799902], + [110.906738, -66.07666], + [110.622266, -66.524023], + [109.462793, -66.908691], + [102.674219, -65.865137], + [101.381348, -65.973047], + [100.889063, -66.358008], + [99.370117, -66.648242], + [98.257617, -66.46748], + [94.839844, -66.501367], + [93.964258, -66.689648], + [92.073438, -66.50791], + [84.485156, -67.114453], + [83.304297, -67.603027], + [79.035156, -68.175391], + [77.81748, -69.068945], + [75.423828, -69.893066], + [73.324805, -69.848926], + [71.771387, -70.80127], + [71.276758, -71.623926], + [68.419824, -72.515039], + [67.32207, -73.300293], + [66.497656, -73.125488], + [69.250195, -70.431055], + [67.267969, -70.273145], + [68.178125, -69.837305], + [69.082617, -69.866602], + [68.90625, -69.372754], + [69.629492, -69.231641], + [69.982227, -68.464258], + [69.55918, -67.763184], + [68.32793, -67.889551], + [63.699023, -67.508301], + [59.250781, -67.484961], + [57.627441, -67.014063], + [56.154883, -67.264551], + [56.145898, -66.626074], + [57.185449, -66.613281], + [55.504492, -66.002637], + [53.671777, -65.858691], + [51.88457, -66.02002], + [50.332422, -66.444629], + [50.553027, -67.194336], + [49.24707, -66.941602], + [48.465234, -67.043457], + [49.219336, -67.226855], + [48.374512, -67.988086], + [48.209961, -67.699316], + [47.489844, -67.72793], + [47.351562, -67.361914], + [46.559668, -67.268164], + [46.399023, -67.617578], + [42.960938, -68.095312], + [40.215625, -68.804883], + [38.885547, -70.171875], + [37.787109, -69.725684], + [35.357031, -69.681348], + [34.595898, -69.094531], + [33.813672, -69.099316], + [34.192871, -68.702441], + [33.465625, -68.670703], + [32.641602, -68.868945], + [32.621289, -70.000586], + [30.00332, -70.3], + [26.498828, -71.019531], + [24.756738, -70.89209], + [24.024121, -70.413379], + [23.149902, -70.796289], + [22.44541, -70.739746], + [21.70498, -70.258496], + [21.070801, -70.843457], + [19.651855, -70.920605], + [19.009375, -70.212109], + [18.124609, -70.540332], + [16.381055, -70.145117], + [13.822656, -70.343164], + [13.065625, -70.053613], + [11.70127, -70.766602], + [9.141602, -70.183691], + [8.523047, -70.473828], + [7.676758, -70.356348], + [2.609473, -70.900098], + [-0.543164, -71.712695], + [-1.067773, -71.265625], + [-6.11748, -71.325977], + [-5.936328, -70.712695], + [-7.752734, -70.842773], + [-7.713721, -71.546484], + [-8.497705, -71.674805], + [-10.270605, -70.935742], + [-10.825439, -71.55332], + [-12.351318, -71.389746], + [-11.009229, -71.75791], + [-11.496973, -72.412891], + [-13.208594, -72.785059], + [-14.297754, -72.733008], + [-14.164697, -73.102441], + [-15.595996, -73.096777], + [-16.435205, -73.425684], + [-16.220117, -73.915723], + [-14.573828, -73.9375], + [-15.67251, -74.407324], + [-17.43584, -74.379102], + [-18.749219, -75.24209], + [-18.30459, -75.431348], + [-26.059326, -75.957227], + [-34.075781, -77.425391], + [-36.23916, -78.774219], + [-32.994238, -79.228809], + [-30.645264, -79.124121], + [-29.949316, -79.599023], + [-23.406836, -79.858984], + [-31.01543, -80.308105], + [-35.327002, -80.650684], + [-37.209277, -81.063867], + [-38.771729, -80.882324], + [-41.125879, -81.214844], + [-45.04375, -82.437988], + [-46.516748, -82.45459], + [-46.258057, -81.946973], + [-48.360791, -81.892285], + [-53.986084, -82.200586], + [-59.516016, -83.458398], + [-61.425293, -83.395605], + [-62.735645, -82.527344], + [-60.527734, -82.199902], + [-64.91958, -82.370508], + [-66.133838, -81.953418], + [-62.490234, -81.556738], + [-65.573682, -81.460547], + [-70.687891, -80.62627], + [-73.029492, -80.917285], + [-75.075586, -80.860059], + [-76.407324, -80.094922], + [-79.6604, -79.996875], + [-76.557861, -79.903516], + [-76.217676, -79.387207], + [-80.151172, -79.268066], + [-80.891992, -79.501855], + [-83.696631, -78.537305], + [-83.779004, -77.983594], + [-80.292285, -78.822754], + [-77.54502, -78.65957], + [-77.858105, -78.350977], + [-81.580957, -77.846094], + [-80.601562, -77.751953], + [-74.812061, -78.177832], + [-73.251562, -77.894238], + [-72.851953, -77.590234], + [-75.748145, -77.398438], + [-77.190039, -76.629785], + [-70.550781, -76.718066], + [-63.363379, -75.451465], + [-64.279541, -75.292871], + [-63.231055, -75.153809], + [-63.924707, -75.004492], + [-63.178125, -74.68418], + [-62.137793, -74.926367], + [-62.235303, -74.441309], + [-60.704297, -74.307129], + [-61.842773, -74.289648], + [-60.790283, -73.711816], + [-62.008301, -73.147656], + [-60.122217, -73.275293], + [-60.009766, -72.937891], + [-61.286133, -72.600781], + [-60.719434, -72.072656], + [-62.256641, -72.017578], + [-60.949023, -71.747266], + [-61.213574, -71.564062], + [-61.958789, -71.657812], + [-60.962256, -71.244629], + [-62.04043, -70.801367], + [-61.504687, -70.490527], + [-62.377783, -70.364844], + [-61.961084, -70.120117], + [-63.747021, -68.70459], + [-62.933301, -68.442578], + [-63.924463, -68.497656], + [-64.078467, -68.771191], + [-65.15835, -68.617969], + [-65.452002, -68.336719], + [-64.829492, -68.127441], + [-65.639502, -68.130566], + [-65.503125, -67.377246], + [-64.819287, -67.307324], + [-64.686279, -66.80625], + [-63.754736, -66.872949], + [-63.752539, -66.277734], + [-62.628906, -66.706152], + [-62.682031, -66.237305], + [-61.756006, -66.429199], + [-61.431934, -66.144727], + [-61.028418, -66.336523], + [-60.618311, -65.933105], + [-61.839062, -66.119531], + [-62.293262, -65.916406], + [-61.703125, -64.987207], + [-61.059863, -64.98125], + [-59.963086, -64.431348], + [-59.645996, -64.583691], + [-59.546777, -64.358789], + [-58.786084, -64.524219], + [-59.005322, -64.194922], + [-57.460645, -63.513574], + [-56.834766, -63.63125], + [-57.020654, -63.372852], + ], + ], + [ + [ + [-45.222656, -78.810742], + [-43.947217, -78.597559], + [-44.093994, -78.167285], + [-45.993164, -77.826855], + [-47.69209, -77.840137], + [-50.14165, -78.556738], + [-50.339258, -79.479492], + [-52.297168, -80.141211], + [-53.393896, -80.108789], + [-54.1625, -80.870117], + [-43.52793, -80.191406], + [-43.544336, -78.901953], + [-45.222656, -78.810742], + ], + ], + [ + [ + [-59.733936, -80.344141], + [-60.578809, -79.741016], + [-61.633301, -80.344141], + [-66.771143, -80.293848], + [-60.582812, -80.948145], + [-59.733936, -80.344141], + ], + ], + [ + [ + [-70.051123, -69.189062], + [-70.416992, -68.788965], + [-72.137891, -69.114551], + [-71.728516, -70.053711], + [-69.618359, -70.398047], + [-71.190039, -70.65957], + [-69.875781, -70.875977], + [-69.869775, -71.125684], + [-70.380664, -70.946387], + [-72.710449, -71.072949], + [-72.21167, -71.335059], + [-75.335449, -71.645215], + [-73.995605, -72.169824], + [-73.775977, -71.848926], + [-72.927637, -71.92168], + [-72.336621, -71.632227], + [-70.820996, -71.906543], + [-71.892188, -72.152832], + [-70.206006, -72.227734], + [-72.7375, -72.280566], + [-72.36748, -72.669727], + [-69.209326, -72.53418], + [-68.241016, -71.822168], + [-68.459473, -70.68291], + [-70.051123, -69.189062], + ], + ], + [ + [ + [-98.091113, -71.9125], + [-98.61543, -71.76377], + [-99.833203, -72.046094], + [-100.218652, -71.83291], + [-102.313623, -72.081055], + [-98.407812, -72.547656], + [-95.575391, -72.409961], + [-95.6854, -72.056641], + [-96.978906, -72.221875], + [-96.38335, -71.836328], + [-97.345215, -72.189062], + [-97.584766, -71.882617], + [-98.167969, -72.123047], + [-98.091113, -71.9125], + ], + ], + [ + [ + [-120.55625, -73.756055], + [-123.112158, -73.682227], + [-122.859082, -74.342676], + [-121.002441, -74.326367], + [-120.55625, -73.756055], + ], + ], + [ + [ + [-126.329883, -73.28623], + [-127.267627, -73.304004], + [-127.211621, -73.724414], + [-123.937402, -74.256152], + [-124.128516, -73.833984], + [-125.798584, -73.801953], + [-125.263965, -73.666406], + [-126.329883, -73.28623], + ], + ], + [ + [ + [-159.05293, -79.807422], + [-159.963525, -79.324316], + [-163.256104, -78.72207], + [-164.225781, -79.320801], + [-159.05293, -79.807422], + ], + ], + [ + [ + [167.084082, -77.32168], + [166.506348, -77.189355], + [166.729004, -77.850977], + [169.352734, -77.524707], + [167.084082, -77.32168], + ], + ], + [ + [ + [-31.118848, -79.798438], + [-31.68042, -79.634277], + [-31.594238, -79.887695], + [-29.614453, -79.90957], + [-30.779932, -79.647363], + [-31.118848, -79.798438], + ], + ], + [ + [ + [-33.93418, -79.32041], + [-35.534668, -79.090039], + [-36.565967, -79.208789], + [-33.93418, -79.32041], + ], + ], + [ + [ + [-70.334082, -79.679883], + [-67.038135, -78.315723], + [-71.454004, -79.128906], + [-71.68667, -79.568066], + [-70.334082, -79.679883], + ], + ], + [ + [ + [-57.845996, -64.053906], + [-57.925684, -63.806055], + [-58.424951, -64.067773], + [-58.214062, -64.369727], + [-57.294678, -64.366992], + [-57.479736, -63.961621], + [-57.845996, -64.053906], + ], + ], + [ + [ + [-55.528027, -63.173535], + [-56.462842, -63.418066], + [-55.075195, -63.324316], + [-55.528027, -63.173535], + ], + ], + [ + [ + [-57.978418, -61.911914], + [-59.003711, -62.209766], + [-57.639551, -62.02041], + [-57.978418, -61.911914], + ], + ], + [ + [ + [-63.180566, -64.469531], + [-63.485596, -64.260547], + [-64.27207, -64.697559], + [-63.739502, -64.834277], + [-62.836523, -64.571875], + [-63.180566, -64.469531], + ], + ], + [ + [ + [-62.325781, -64.424414], + [-62.058496, -64.138086], + [-62.451416, -64.012402], + [-62.781787, -64.479004], + [-62.325781, -64.424414], + ], + ], + [ + [ + [-67.988477, -67.474414], + [-67.830518, -66.624316], + [-69.120361, -67.57793], + [-68.58042, -67.732813], + [-67.988477, -67.474414], + ], + ], + [ + [ + [-73.706641, -70.635156], + [-74.400977, -70.575879], + [-74.589697, -70.791992], + [-74.953613, -70.590234], + [-76.421484, -71.09043], + [-74.205029, -70.924121], + [-73.706641, -70.635156], + ], + ], + [ + [ + [-74.987109, -69.727832], + [-75.80415, -70.038184], + [-74.848828, -70.179297], + [-74.437988, -69.949609], + [-74.987109, -69.727832], + ], + ], + [ + [ + [-74.354443, -73.098438], + [-75.376855, -72.82041], + [-76.096387, -73.150488], + [-74.574658, -73.611328], + [-74.354443, -73.098438], + ], + ], + [ + [ + [-91.160693, -73.182227], + [-90.76333, -72.681055], + [-91.303516, -72.547363], + [-91.160693, -73.182227], + ], + ], + [ + [ + [167.642773, -78.141406], + [166.111133, -78.089648], + [166.121875, -78.274609], + [167.642773, -78.141406], + ], + ], + [ + [ + [100.981445, -65.677539], + [101.220605, -65.472266], + [100.545117, -65.408984], + [100.292578, -65.65127], + [100.981445, -65.677539], + ], + ], + [ + [ + [26.857227, -70.381152], + [26.301074, -70.072461], + [26.005371, -70.372949], + [26.857227, -70.381152], + ], + ], + [ + [ + [-66.173633, -80.077832], + [-65.579248, -79.770801], + [-67.687939, -79.528418], + [-66.173633, -80.077832], + ], + ], + [ + [ + [-67.261914, -79.452637], + [-68.032568, -79.227148], + [-68.548926, -79.437402], + [-67.261914, -79.452637], + ], + ], + [ + [ + [-55.16543, -61.22041], + [-54.670996, -61.116992], + [-55.387012, -61.072656], + [-55.16543, -61.22041], + ], + ], + [ + [ + [-61.997607, -69.721875], + [-61.815967, -69.376172], + [-62.442139, -69.145996], + [-61.997607, -69.721875], + ], + ], + [ + [ + [-60.625, -62.560059], + [-61.149805, -62.63418], + [-59.849561, -62.614941], + [-60.625, -62.560059], + ], + ], + [ + [ + [96.612695, -66.03584], + [96.307031, -66.18584], + [96.933984, -66.200781], + [96.612695, -66.03584], + ], + ], + [ + [ + [16.222656, -70.007617], + [16.573438, -69.723242], + [15.596875, -69.828027], + [16.222656, -70.007617], + ], + ], + [ + [ + [3.036914, -70.597363], + [3.072168, -70.381641], + [2.584668, -70.53457], + [3.036914, -70.597363], + ], + ], + [ + [ + [-2.532812, -70.767773], + [-3.574658, -70.703125], + [-2.783496, -71.16748], + [-2.092285, -70.820898], + [-2.532812, -70.767773], + ], + ], + [ + [ + [-20.607422, -73.886621], + [-20.580225, -73.619238], + [-22.035352, -74.106543], + [-20.489014, -74.492676], + [-20.607422, -73.886621], + ], + ], + [ + [ + [-67.348926, -67.766211], + [-67.246729, -67.59873], + [-67.743262, -67.66123], + [-67.348926, -67.766211], + ], + ], + [ + [ + [-116.738623, -74.165039], + [-116.381299, -73.865527], + [-117.376465, -74.082813], + [-116.738623, -74.165039], + ], + ], + [ + [ + [-131.066699, -74.583789], + [-130.981104, -74.414062], + [-132.162646, -74.425781], + [-131.066699, -74.583789], + ], + ], + [ + [ + [-149.230664, -77.120508], + [-149.870605, -76.875], + [-150.788525, -76.981641], + [-149.230664, -77.120508], + ], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '荷属圣马丁', full_name: '荷属圣马丁岛', iso_a2: 'SX', iso_a3: 'SXM', iso_n3: '534' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-63.123047, 18.068945], + [-63.023047, 18.019189], + [-63.011182, 18.068945], + [-63.123047, 18.068945], + ], + ], + }, + }, + { + type: 'Feature', + properties: { name: '图瓦卢', full_name: '图瓦卢', iso_a2: 'TV', iso_a3: 'TUV', iso_n3: '798' }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [179.213672, -8.524219], + [179.203027, -8.466309], + [179.195703, -8.534766], + [179.213672, -8.524219], + ], + ], + }, + }, + ], +}; diff --git a/front/src/constants/idc-region-data.ts b/front/src/constants/idc-region-data.ts index 3a9bcc6a97..87838d1cee 100644 --- a/front/src/constants/idc-region-data.ts +++ b/front/src/constants/idc-region-data.ts @@ -1,198 +1,162 @@ // idc机房的经纬度信息 export default { - "type": "FeatureCollection", - "features": [ + type: 'FeatureCollection', + features: [ { - "type": "Feature", - "properties": { - "name": "idc1", - "vendor": "tcloud", - "country": "新加坡", - "region": "新加坡", - }, - "geometry": { - "coordinates":[ - 103.8198, - 1.3521 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc1', + vendor: 'tcloud', + country: '新加坡', + region: '新加坡', + }, + geometry: { + coordinates: [103.8198, 1.3521], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc2", - "vendor": "tcloud", - "country": "印度尼西亚", - "region": "雅加达", - }, - "geometry": { - "coordinates":[ - 106.8451, - -6.2146 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc2', + vendor: 'tcloud', + country: '印度尼西亚', + region: '雅加达', + }, + geometry: { + coordinates: [106.8451, -6.2146], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc3", - "vendor": "tcloud", - "country": "日本", - "region": "东京", - }, - "geometry": { - "coordinates":[ - 139.6917, - 35.6895 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc3', + vendor: 'tcloud', + country: '日本', + region: '东京', + }, + geometry: { + coordinates: [139.6917, 35.6895], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc4", - "vendor": "tcloud", - "country": "德国", - "region": "法兰克福", - }, - "geometry": { - "coordinates":[ - 8.6821, - 50.1109 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc4', + vendor: 'tcloud', + country: '德国', + region: '法兰克福', + }, + geometry: { + coordinates: [8.6821, 50.1109], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc5", - "vendor": "tcloud", - "country": "美国", - "region": "硅谷", - }, - "geometry": { - "coordinates":[ - -122.1430, - 37.4419 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc5', + vendor: 'tcloud', + country: '美国', + region: '硅谷', + }, + geometry: { + coordinates: [-122.143, 37.4419], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc6", - "vendor": "tcloud", - "country": "美国", - "region": "弗吉尼亚", - }, - "geometry": { - "coordinates":[ - -78.6569, - 37.4315 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc6', + vendor: 'tcloud', + country: '美国', + region: '弗吉尼亚', + }, + geometry: { + coordinates: [-78.6569, 37.4315], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc7", - "vendor": "tcloud", - "country": "巴西", - "region": "圣保罗", - }, - "geometry": { - "coordinates":[ - -46.6333, - -23.5505 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc7', + vendor: 'tcloud', + country: '巴西', + region: '圣保罗', + }, + geometry: { + coordinates: [-46.6333, -23.5505], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc8", - "vendor": "huawei", - "country": "墨西哥", - "region": "墨西哥", - }, - "geometry": { - "coordinates":[ - -102.5528, - 23.6345 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc8', + vendor: 'huawei', + country: '墨西哥', + region: '墨西哥', + }, + geometry: { + coordinates: [-102.5528, 23.6345], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc9", - "vendor": "gcp", - "country": "澳大利亚", - "region": "悉尼", - }, - "geometry": { - "coordinates":[ - 151.2093, - -33.8688 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc9', + vendor: 'gcp', + country: '澳大利亚', + region: '悉尼', + }, + geometry: { + coordinates: [151.2093, -33.8688], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc10", - "vendor": "gcp", - "country": "芬兰", - "region": "芬兰", - }, - "geometry": { - "coordinates":[ - 24.9376, - 60.1708 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc10', + vendor: 'gcp', + country: '芬兰', + region: '芬兰', + }, + geometry: { + coordinates: [24.9376, 60.1708], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc11", - "vendor": "tcloud", - "country": "中国香港", - "region": "香港", - }, - "geometry": { - "coordinates":[ - 114.1095, - 22.3964 - ], - "type": "Point" - } + type: 'Feature', + properties: { + name: 'idc11', + vendor: 'tcloud', + country: '中国香港', + region: '香港', + }, + geometry: { + coordinates: [114.1095, 22.3964], + type: 'Point', + }, }, { - "type": "Feature", - "properties": { - "name": "idc12", - "vendor": "baishan", - "country": "阿拉伯联合酋长国", - "region": "迪拜", - }, - "geometry": { - "coordinates":[ - 55.2798, - 25.2049 - ], - "type": "Point" - } - } - ] + type: 'Feature', + properties: { + name: 'idc12', + vendor: 'baishan', + country: '阿拉伯联合酋长国', + region: '迪拜', + }, + geometry: { + coordinates: [55.2798, 25.2049], + type: 'Point', + }, + }, + ], }; diff --git a/front/src/constants/index.ts b/front/src/constants/index.ts index f5fceaadde..636381493e 100644 --- a/front/src/constants/index.ts +++ b/front/src/constants/index.ts @@ -1,3 +1,4 @@ export * from './account'; export * from './resource'; export * from './scheme'; +export * from './clb'; diff --git a/front/src/hooks/useAccessBlock.ts b/front/src/hooks/useAccessBlock.ts index 10ca6d421c..e432cd2081 100644 --- a/front/src/hooks/useAccessBlock.ts +++ b/front/src/hooks/useAccessBlock.ts @@ -13,16 +13,20 @@ export function useBlock(accessType: AccessType) { accessSourceStore.fetchAccessSourceClassify(route.params.projectId as string); } - watch(() => route.params.projectId, async (projectId) => { - isFetching.value = true; - await accessSourceStore.fetchAccessSource(projectId as string); - isFetching.value = false; - }, { - immediate: true, - }); + watch( + () => route.params.projectId, + async (projectId) => { + isFetching.value = true; + await accessSourceStore.fetchAccessSource(projectId as string); + isFetching.value = false; + }, + { + immediate: true, + }, + ); const accessList = computed(() => accessSourceStore.accessDataMap[accessType]); - const classifyList = computed(() => (accessSourceStore.accessClassifyMap?.[accessType] ?? [])); + const classifyList = computed(() => accessSourceStore.accessClassifyMap?.[accessType] ?? []); function insertDataAccess() { const access = { @@ -45,7 +49,7 @@ export function useBlock(accessType: AccessType) { } function getOauthUrlByPlatform(dataType: Platform) { - return classifyList.value.find(item => item.type === dataType)?.oauth_url ?? ''; + return classifyList.value.find((item) => item.type === dataType)?.oauth_url ?? ''; } function removeDataAccess(index: number, access: Access) { diff --git a/front/src/hooks/useActiveTab.ts b/front/src/hooks/useActiveTab.ts new file mode 100644 index 0000000000..b2849cb0e8 --- /dev/null +++ b/front/src/hooks/useActiveTab.ts @@ -0,0 +1,29 @@ +import { ref, watchEffect } from 'vue'; +import { useRouter, useRoute } from 'vue-router'; + +/** + * Tab组件切换Panel, url中query参数(type)为标识 + */ +export default (initialValue: string) => { + const router = useRouter(); + const route = useRoute(); + const activeTab = ref(initialValue); + + // change-handler + const handleActiveTabChange = (v: string) => { + // tab切换 + activeTab.value = v; + // 路由切换 + router.push({ query: { ...route.query, type: v } }); + }; + + // 监听route.query.type的变化, tab状态保持 + watchEffect(() => { + handleActiveTabChange(route.query.type as string); + }); + + return { + activeTab, + handleActiveTabChange, + }; +}; diff --git a/front/src/hooks/useDepartment.ts b/front/src/hooks/useDepartment.ts index 05ccc562d6..18d2b90339 100644 --- a/front/src/hooks/useDepartment.ts +++ b/front/src/hooks/useDepartment.ts @@ -6,19 +6,24 @@ export function useDepartment() { const departmentStore = useDepartmentStore(); const departmentMap = ref>(generateDeptTreeMap()); - const organizationTree = computed(() => Array.from(departmentMap.value.values()) - .filter(department => !department.parent)); + const organizationTree = computed(() => + Array.from(departmentMap.value.values()).filter((department) => !department.parent), + ); const checkedDept = computed(() => getCheckedDept(organizationTree.value)); if (departmentStore.departmentMap.size === 0) { departmentStore.getRootDept(); } - watch(() => departmentStore.departmentMap, (deptMap) => { - departmentMap.value = generateDeptTreeMap(deptMap); - }, { - deep: true, - }); + watch( + () => departmentStore.departmentMap, + (deptMap) => { + departmentMap.value = generateDeptTreeMap(deptMap); + }, + { + deep: true, + }, + ); function generateDeptTreeMap(deptMap?: Map) { const originDepartmentMap = deptMap ?? departmentStore.departmentMap; @@ -30,10 +35,12 @@ export function useDepartment() { acc.set(department.id, { ...department, - ...(department.has_children ? { - children: [], - async: true, - } : {}), + ...(department.has_children + ? { + children: [], + async: true, + } + : {}), checked: (isChecked || curDept?.checked) ?? false, indeterminate: curDept?.indeterminate ?? false, }); @@ -74,10 +81,7 @@ export function useDepartment() { }); } - function recursionCheckChildDept( - list: Department[], - checked: boolean, - ) { + function recursionCheckChildDept(list: Department[], checked: boolean) { return list.forEach((item) => { updateDepartment(item.id, { checked, @@ -89,19 +93,19 @@ export function useDepartment() { }); } - function recursionCheckParentDept( - id: number, - checked: boolean, - ): Record { + function recursionCheckParentDept(id: number, checked: boolean): Record { const curDept = departmentMap.value.get(id); const parent = departmentMap.value.get(curDept.parent); if (parent) { const indeterminate = isHalf(parent.children); - departmentMap.value.set(parent.id, Object.assign(parent, { - checked: checked && !indeterminate, - indeterminate, - })); + departmentMap.value.set( + parent.id, + Object.assign(parent, { + checked: checked && !indeterminate, + indeterminate, + }), + ); if (parent.parent) { return recursionCheckParentDept(parent.id, checked); @@ -117,7 +121,7 @@ export function useDepartment() { if (item.indeterminate) halfLength += 1; }); - return (checkedLength > 0 && checkedLength < children.length) || (halfLength > 0); + return (checkedLength > 0 && checkedLength < children.length) || halfLength > 0; } function getCheckedDept(list = organizationTree.value) { diff --git a/front/src/hooks/useDeveloper.ts b/front/src/hooks/useDeveloper.ts index 2bb6adb4f9..c75284855e 100644 --- a/front/src/hooks/useDeveloper.ts +++ b/front/src/hooks/useDeveloper.ts @@ -23,17 +23,15 @@ export function useMappingDeveloper(isMap = false) { wework: 'demo', }, ]); - const { - pagination, - handleTotalChange, - handlePageValueChange, - handlePageLimitChange, - } = usePagination(); - + const { pagination, handleTotalChange, handlePageValueChange, handlePageLimitChange } = usePagination(); - watch(() => ({ ...pagination }), (newPagination) => { - getDeveloperMap(newPagination); - }, { immediate: true }); + watch( + () => ({ ...pagination }), + (newPagination) => { + getDeveloperMap(newPagination); + }, + { immediate: true }, + ); async function getDeveloperMap(paginationConf: typeof pagination) { const data = await developerStore.fetchDeveloperMap({ diff --git a/front/src/hooks/useFavorite.ts b/front/src/hooks/useFavorite.ts index c1acb29345..3655c27e34 100644 --- a/front/src/hooks/useFavorite.ts +++ b/front/src/hooks/useFavorite.ts @@ -33,9 +33,13 @@ export const getFavoriteList = async (bk_biz_id: number): Promise> }; export const addFavorite = async (bk_biz_id: number) => { - return await http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/bizs/${bk_biz_id}/collections/bizs/create`, { bk_biz_id }); + return await http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/bizs/${bk_biz_id}/collections/bizs/create`, { + bk_biz_id, + }); }; export const removeFavorite = async (bk_biz_id: number) => { - return await http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/bizs/${bk_biz_id}/collections/bizs`, { data: { bk_biz_id } }); + return await http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/bizs/${bk_biz_id}/collections/bizs`, { + data: { bk_biz_id }, + }); }; diff --git a/front/src/hooks/useLocalTable/index.scss b/front/src/hooks/useLocalTable/index.scss index ae0148d4ce..ee904a89e5 100644 --- a/front/src/hooks/useLocalTable/index.scss +++ b/front/src/hooks/useLocalTable/index.scss @@ -1,10 +1,12 @@ -.operation-wrap { - display: flex; - justify-content: space-between; - margin-bottom: 16px; +.local-table-container { + .operation-wrap { + display: flex; + justify-content: space-between; + margin-bottom: 16px; - .bk-radio-button-label { - padding: 0 16px; - font-size: 14px; + .bk-radio-button-label { + padding: 0 16px; + font-size: 14px; + } } -} \ No newline at end of file +} diff --git a/front/src/hooks/useLocalTable/index.tsx b/front/src/hooks/useLocalTable/index.tsx index 5d6e62912b..c083f591f8 100644 --- a/front/src/hooks/useLocalTable/index.tsx +++ b/front/src/hooks/useLocalTable/index.tsx @@ -8,7 +8,7 @@ export interface IProp { data: Array; columns: Array; searchData: Array; -}; +} export const useLocalTable = (props: IProp) => { const CommonLocalTable = defineComponent({ @@ -21,23 +21,15 @@ export const useLocalTable = (props: IProp) => { const searchVal = ref([]); const isLoading = ref(false); return () => ( - <> +
      {slots.tab?.()} - +
      - +
      - + ); }, }); diff --git a/front/src/hooks/useMoreActionDropdown.tsx b/front/src/hooks/useMoreActionDropdown.tsx new file mode 100644 index 0000000000..4d85b48439 --- /dev/null +++ b/front/src/hooks/useMoreActionDropdown.tsx @@ -0,0 +1,63 @@ +import { ref } from 'vue'; +import { $bkPopover } from 'bkui-vue'; +import { TRANSPORT_LAYER_LIST } from '@/constants'; + +/** + * more-action dropdown list render hooks + */ +export default (typeMenuMap: any) => { + const popInstance = ref(); + const currentPopBoundaryNodeKey = ref(''); // 当前弹出层所在节点key + + // 显示popover时, 记录当前显示的节点key + const handlePopShow = (node: any) => { + currentPopBoundaryNodeKey.value = node.id; + }; + + // 隐藏popover时, 清空key + const handlePopHide = () => { + currentPopBoundaryNodeKey.value = ''; + }; + + // 初始化popover, 并显示 + const showDropdownList = (e: any, node: any) => { + popInstance.value?.close(); + popInstance.value = $bkPopover({ + trigger: 'click', + theme: 'light', + renderType: 'shown', + placement: 'bottom-start', + arrow: false, + extCls: 'more-action-dropdown-menu', + allowHtml: true, + target: e, + content: ( + + ), + onShow: () => handlePopShow(node), + onHide: handlePopHide, + }); + popInstance.value?.show(); + popInstance.value?.update(e.target); + popInstance.value?.show(); + }; + + return { + showDropdownList, + currentPopBoundaryNodeKey, + }; +}; diff --git a/front/src/hooks/usePagination.ts b/front/src/hooks/usePagination.ts index 2b7a0d442d..09cd6476ba 100644 --- a/front/src/hooks/usePagination.ts +++ b/front/src/hooks/usePagination.ts @@ -1,31 +1,30 @@ import { reactive } from 'vue'; -const DEFAULT_LIMIT = 20; +export default function usePagination(cb: any) { + const pagination = reactive({ start: 0, limit: 10, count: 0 }); -export function usePagination(limit = DEFAULT_LIMIT) { - const pagination = reactive({ - location: 'left', - align: 'right', - current: 1, - limit, - count: 100, - }); + /** + * 分页条数改变时调用 + * @param v 分页条数 + */ + const handlePageLimitChange = (v: number) => { + pagination.limit = v; + pagination.start = 0; + cb(); + }; - function changePagination(key: keyof typeof pagination) { - return (val: number) => { - Object.assign(pagination, { - [key]: val, - ...(key === 'limit' ? { - current: 1, - } : {}), - }); - }; - } + /** + * 分页 start offset 改变时调用 + * @param v 当前 start offset + */ + const handlePageValueChange = (v: number) => { + pagination.start = (v - 1) * pagination.limit; + cb(); + }; return { pagination, - handlePageValueChange: changePagination('current'), - handlePageLimitChange: changePagination('limit'), - handleTotalChange: changePagination('count'), + handlePageLimitChange, + handlePageValueChange, }; } diff --git a/front/src/hooks/usePermission.ts b/front/src/hooks/usePermission.ts index a5abca2e2e..ec2be3b1a4 100644 --- a/front/src/hooks/usePermission.ts +++ b/front/src/hooks/usePermission.ts @@ -1,5 +1,5 @@ import { usePermissionStore } from '@/stores'; -import { Permission, PermissionType, Role } from '@/typings'; +import { Permission, PermissionType, Role } from '@/typings'; import { Message } from 'bkui-vue'; import { ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; @@ -13,36 +13,41 @@ export function usePermission() { const permissionModel = ref(initPermission()); const isSaving = ref(false); - watch(() => [ - ...permissionStore.permissions, - ], (permissions) => { - permissionModel.value = initPermission(permissions); - }); + watch( + () => [...permissionStore.permissions], + (permissions) => { + permissionModel.value = initPermission(permissions); + }, + ); - watch(() => route.params.projectId, (pid) => { - permissionStore.fetchPermission(pid as string); - }, { immediate: true }); + watch( + () => route.params.projectId, + (pid) => { + permissionStore.fetchPermission(pid as string); + }, + { immediate: true }, + ); function initPermission(permissions = permissionStore.permissions) { - return permissions.reduce(( - acc: any, - item: Permission, - ) => { - acc[item.role][item.type] = { - ...item, - members: [...item.members], - }; - return acc; - }, { - [Role.ADMIN]: { - [PermissionType.USER]: {}, - [PermissionType.ORG]: {}, + return permissions.reduce( + (acc: any, item: Permission) => { + acc[item.role][item.type] = { + ...item, + members: [...item.members], + }; + return acc; }, - [Role.MEMBER]: { - [PermissionType.USER]: {}, - [PermissionType.ORG]: {}, + { + [Role.ADMIN]: { + [PermissionType.USER]: {}, + [PermissionType.ORG]: {}, + }, + [Role.MEMBER]: { + [PermissionType.USER]: {}, + [PermissionType.ORG]: {}, + }, }, - }); + ); } async function savePermissions() { @@ -75,5 +80,3 @@ export function usePermission() { isSaving, }; } - - diff --git a/front/src/hooks/useProjects.ts b/front/src/hooks/useProjects.ts index 55fad1fa76..e6ee923d92 100644 --- a/front/src/hooks/useProjects.ts +++ b/front/src/hooks/useProjects.ts @@ -10,8 +10,9 @@ export function useProjectList(all = false) { const projectStore = useProjectStore(); const projects = computed(() => (all ? projectStore.devopsProjects : projectStore.metricProjects)); const currentProjectId = computed(() => route.params.projectId as string); - const currentProject = computed(() => projects.value.list - .find(project => project.project_id === currentProjectId.value)); + const currentProject = computed(() => + projects.value.list.find((project) => project.project_id === currentProjectId.value), + ); onBeforeMount(async () => { if (!projects.value.fetched) { @@ -34,7 +35,6 @@ export function useProjectList(all = false) { return projectStore.orgAdminMap[dept_id]?.username ?? 'unknow'; } - return { projects, currentProjectId, diff --git a/front/src/hooks/useRecommendStatus.ts b/front/src/hooks/useRecommendStatus.ts index ce8c36b1b2..2e7ba73eb6 100644 --- a/front/src/hooks/useRecommendStatus.ts +++ b/front/src/hooks/useRecommendStatus.ts @@ -5,7 +5,6 @@ import { classes } from '@/utils/utils'; import { computed, onBeforeMount, ref } from 'vue'; import { RouteParams, useRoute } from 'vue-router'; - export function useRecommendStatus() { const accessSourceStore = useAccessSourceStore(); const route = useRoute(); @@ -22,22 +21,29 @@ export function useRecommendStatus() { accessSourceStore.fetchRecommendStatus(); } - const mappingStatus = computed(() => accessSourceStore.recommendStatus.map((status: Status) => { - const isMap = !!mappedStatus.value?.has(status.status_map); - const personalStatus = mappedStatus.value?.get(status.status_map); - const cls = classes({ - active: isMap, - }, 'bk-metrics-status-block'); - return { - ...status, - is_mapped: isMap, - ...(isMap ? { - status_name: personalStatus?.status_name, - status: personalStatus?.status, - } : {}), - cls, - }; - })); + const mappingStatus = computed(() => + accessSourceStore.recommendStatus.map((status: Status) => { + const isMap = !!mappedStatus.value?.has(status.status_map); + const personalStatus = mappedStatus.value?.get(status.status_map); + const cls = classes( + { + active: isMap, + }, + 'bk-metrics-status-block', + ); + return { + ...status, + is_mapped: isMap, + ...(isMap + ? { + status_name: personalStatus?.status_name, + status: personalStatus?.status, + } + : {}), + cls, + }; + }), + ); async function fetchPersonalStatus({ projectId, platform, identification }: RouteParams) { try { @@ -49,7 +55,7 @@ export function useRecommendStatus() { project_id: projectId, }, }); - const unMapped = list.filter(status => !status.is_mapped); + const unMapped = list.filter((status) => !status.is_mapped); const mapped = list.reduce((acc: Map, status: Status) => { if (status.is_mapped) { acc.set(status.status_map, status); @@ -75,7 +81,7 @@ export function useRecommendStatus() { }); if (!dragStatus.is_mapped) { - unMappedStatus.value = unMappedStatus.value.filter(status => status.status_name !== dragStatus.status_name); + unMappedStatus.value = unMappedStatus.value.filter((status) => status.status_name !== dragStatus.status_name); } else if (mappedStatus.value.has(dragStatus.status_map)) { mappedStatus.value.delete(dragStatus.status_map); } @@ -99,4 +105,3 @@ export function useRecommendStatus() { removeMappedStatus, }; } - diff --git a/front/src/hooks/useRouteLinkBtn.tsx b/front/src/hooks/useRouteLinkBtn.tsx index 17e44c96c2..c7b14f5e63 100644 --- a/front/src/hooks/useRouteLinkBtn.tsx +++ b/front/src/hooks/useRouteLinkBtn.tsx @@ -8,9 +8,9 @@ export interface IDetail { } export interface IMeta { - id: string; // IDetail[id] 作为跳转链接参数 id 的值 - type: TypeEnum; // 如果是业务,将跳转到 ${type}BusinessDetail; 如果是资源,跳转到resource/detail/:type; 这里强依赖于 router 配置文件的路由命名规范 - name: string; // IDetail[name] 作为按钮要显示的文字内容 + id: string; // IDetail[id] 作为跳转链接参数 id 的值 + type: TypeEnum; // 如果是业务,将跳转到 ${type}BusinessDetail; 如果是资源,跳转到resource/detail/:type; 这里强依赖于 router 配置文件的路由命名规范 + name: string; // IDetail[name] 作为按钮要显示的文字内容 isExpand?: boolean; // 是否拓展网卡,当存在拓展网卡时,网络、子网、公私IPV4、公私IPV6都是2份,此时 IDetail[id] 和 IDetail[name] 都是一个长度为2的数组, 需要特殊处理 } @@ -27,18 +27,10 @@ export const useRouteLinkBtn = (data: IDetail, meta: IMeta) => { const { id, name, type, isExpand } = meta; const { vendor } = data; // eslint-disable-next-line no-nested-ternary - const computedId = computed(() => (Array.isArray(data[id]) - ? isExpand - ? data[name][1] - : data[id][0] - : data[id])); + const computedId = computed(() => (Array.isArray(data[id]) ? (isExpand ? data[name][1] : data[id][0]) : data[id])); const computedName = computed(() => { // eslint-disable-next-line no-nested-ternary - let txt = Array.isArray(data[name]) - ? isExpand - ? data[name][1] - : data[name][0] - : data[name]; + let txt = Array.isArray(data[name]) ? (isExpand ? data[name][1] : data[name][0]) : data[name]; // eslint-disable-next-line prefer-destructuring if (vendor === VendorEnum.AZURE && type === TypeEnum.VPC) txt = txt.split('/').reverse()[0]; return txt; @@ -54,8 +46,7 @@ export const useRouteLinkBtn = (data: IDetail, meta: IMeta) => { }; if (route.path.includes('business')) { Object.assign(routeInfo, { - name: - type === TypeEnum.ACCOUNT ? 'accountDetail' : `${type}BusinessDetail`, + name: type === TypeEnum.ACCOUNT ? 'accountDetail' : `${type}BusinessDetail`, }); } else { Object.assign(routeInfo, { @@ -74,7 +65,7 @@ export const useRouteLinkBtn = (data: IDetail, meta: IMeta) => { const render = () => { if (!computedName.value) return '--'; return ( - + {computedName.value} ); diff --git a/front/src/hooks/useSingleList.ts b/front/src/hooks/useSingleList.ts new file mode 100644 index 0000000000..aa39e48d66 --- /dev/null +++ b/front/src/hooks/useSingleList.ts @@ -0,0 +1,146 @@ +import { reactive, ref } from 'vue'; +import { useBusinessStore, useResourceStore } from '@/store'; +import { defaults } from 'lodash'; +import { Senarios, useWhereAmI } from './useWhereAmI'; +import { QueryRuleOPEnum, RulesItem } from '@/typings'; + +/** + * hooks 函数 - 适用于单列表 + * @param type 请求的资源类型, 如 cvm, drive, vpc 等... + * @param options 其他配置项, 如 immediate, rules, apiMethod 等... + * @returns dataList, pagination, isDataLoad, isDataRefresh, loadDataList, handleScrollEnd, handleReset, handleRefresh + */ +export function useSingleList( + type: string, + options?: { + // 是否立即加载数据, 值为 true 时可以省略在 onMounted 中加载初始数据 + immediate?: boolean; + // 初始搜索条件, 数组参数形式推荐 id 等不变值搜索条件, 函数参数形式可以支持响应式的搜索条件 + rules?: RulesItem[] | ((...args: any) => RulesItem[]); + // 自定义的 api 方法, 可以替换默认的 list api + apiMethod?: (...args: any) => Promise; + }, +) { + // 设置 options 默认值 + defaults(options, { immediate: false, rules: [], apiMethod: null }); + const getDefaultPagination = () => ({ start: 0, limit: 50, count: 0 }); + + // 引入可能会用到的 hooks 函数以及 store + const { whereAmI } = useWhereAmI(); + const resourceStore = useResourceStore(); + const businessStore = useBusinessStore(); + + const dataList = ref([]); + const pagination = reactive(getDefaultPagination()); + const isDataLoad = ref(false); + const isDataRefresh = ref(false); + + const loadDataList = (customRules: RulesItem[] = []) => { + /** + * @returns 返回一个默认的 list api + */ + const getDefaultApiMethod = () => { + const apiMethod = whereAmI.value === Senarios.business ? businessStore.list : resourceStore.list; + return Promise.all( + [false, true].map((isCount) => + apiMethod( + { + filter: { + op: QueryRuleOPEnum.AND, + rules: [...(typeof options?.rules === 'function' ? options?.rules() : options?.rules), ...customRules], + }, + page: { + count: isCount, + start: isCount ? 0 : pagination.start, + limit: isCount ? 0 : pagination.limit, + sort: isCount ? undefined : 'created_at', + order: isCount ? undefined : 'DESC', + }, + }, + type, + ), + ), + ); + }; + + // 选择 api + const apiMethod = options?.apiMethod || getDefaultApiMethod; + + // 开启 loading 效果 + isDataLoad.value = true; + return apiMethod() + .then(([detailRes, countRes]) => { + // 加载数据 + dataList.value = [...dataList.value, ...detailRes.data.details]; + // 更新分页参数 + pagination.count = countRes.data.count; + // 将加载数据后的 dataList 作为 then 函数的返回值, 用以支持对新的 dataList 做额外的处理 + return dataList.value; + }) + .finally(() => { + // 关闭 loading 效果 + isDataLoad.value = false; + }); + }; + + const handleScrollEnd = () => { + // 判断是否有下一页数据 + if (dataList.value.length >= pagination.count) return; + // 累加 start + pagination.start += pagination.limit; + // 获取数据 + loadDataList(); + }; + + const handleReset = () => { + dataList.value = []; + Object.assign(pagination, getDefaultPagination()); + }; + + const handleRefresh = async () => { + handleReset(); + isDataRefresh.value = true; + await loadDataList(); + isDataRefresh.value = false; + }; + + // 立即执行 + options?.immediate && loadDataList(); + + return { + /** + * 数据列表 + */ + dataList, + /** + * 分页参数 + */ + pagination, + /** + * loading - 数据加载 + */ + isDataLoad, + /** + * loading - 数据刷新 + */ + isDataRefresh, + /** + * 加载数据 + * @param customRules 自定义查询规则 + * @returns 返回一个 Promise 实例, 其成功状态下的结果值为加载数据后的 dataList. + */ + loadDataList, + /** + * 滚动触底 - 加载数据 + */ + handleScrollEnd, + /** + * 数据重置 + */ + handleReset, + /** + * 数据刷新 + */ + handleRefresh, + }; +} diff --git a/front/src/hooks/useTable/index.scss b/front/src/hooks/useTable/index.scss index 8801e516c5..0a36fa4c85 100644 --- a/front/src/hooks/useTable/index.scss +++ b/front/src/hooks/useTable/index.scss @@ -1,37 +1,49 @@ -.operation-wrap { - display: flex; - justify-content: space-between; - margin-bottom: 16px; - - .operate-btn-groups { +.remote-table-container { + height: 100%; + + .operation-wrap { display: flex; + justify-content: space-between; - .bk-button:not(:last-child) { - margin-right: 12px; - } + .operate-btn-groups { + display: flex; - .mw64 { - min-width: 64px; - } + .bk-button:not(:last-child) { + margin-right: 12px; + } - .mw88 { - min-width: 88px; - } + .mw64 { + min-width: 64px; + } + + .mw88 { + min-width: 88px; + } - .f26 { - font-size: 26px; + .f26 { + font-size: 26px; + } } } -} -.loading-table-container { - height: calc(100% - 48px); + .table-search-selector { + margin-bottom: 16px; + min-width: 500px; + } + + .loading-table-container { + height: calc(100% - 48px); - .table-container { - max-height: 100%; + .table-container { + max-height: 100%; - .binding-row td{ - background-color: #f2fff4 !important; + .table-new-row td { + background-color: #f2fff4 !important; + } } } + + &.no-search .loading-table-container { + height: 100%; + } } diff --git a/front/src/hooks/useTable/useTable.tsx b/front/src/hooks/useTable/useTable.tsx index b8f2f8b2ea..aac3e9b435 100644 --- a/front/src/hooks/useTable/useTable.tsx +++ b/front/src/hooks/useTable/useTable.tsx @@ -1,111 +1,192 @@ +/* eslint-disable no-nested-ternary */ import { QueryRuleOPEnum, RulesItem } from '@/typings/common'; import { FilterType } from '@/typings'; import { Loading, SearchSelect, Table } from 'bkui-vue'; import type { Column } from 'bkui-vue/lib/table/props'; import { ISearchItem } from 'bkui-vue/lib/search-select/utils'; -import { defineComponent, reactive, ref, watch } from 'vue'; +import { computed, defineComponent, reactive, ref, watch } from 'vue'; import './index.scss'; import Empty from '@/components/empty'; -import { useAccountStore, useResourceStore } from '@/store'; +import { useAccountStore, useResourceStore, useBusinessStore } from '@/store'; import { useBusinessMapStore } from '@/store/useBusinessMap'; -import { useWhereAmI } from '../useWhereAmI'; +import { useRegionsStore } from '@/store/useRegionsStore'; +import { useWhereAmI, Senarios } from '../useWhereAmI'; import { getDifferenceSet } from '@/common/util'; +import { get as lodash_get } from 'lodash-es'; +import { VendorReverseMap } from '@/common/constant'; +import { LB_NETWORK_TYPE_REVERSE_MAP, LISTENER_BINDING_STATUS_REVERSE_MAP, SCHEDULER_REVERSE_MAP } from '@/constants'; +import usePagination from '../usePagination'; + export interface IProp { - // search-select 相关字段 + // search-select 配置项 searchOptions: { - searchData: Array; // search-select 可选项 - disabled?: boolean, // 是否禁用 search-select - extra?: Object, // 其他 search-select 属性/自定义事件, 比如 placeholder, onSearch... - }, - // table 相关字段 + // search-select 可选项 + searchData?: Array | (() => Array); + // 是否禁用 search-select + disabled?: boolean; + // 其他 search-select 属性/自定义事件, 比如 placeholder, onSearch, searchSelectExtStyle... + extra?: { + searchSelectExtStyle?: Record; // 搜索框样式 + }; + }; + // table 配置项 tableOptions: { - columns: Array; // 表格字段 - reviewData?: Array>; // 用于预览效果的数据 - extra?: Object, // 其他 table 属性/自定义事件, 比如 settings, onSelectionChange... - }, + // 表格字段 + columns: Array | (() => Array); + // 用于预览效果的数据 + reviewData?: Array>; + // 其他 table 属性/自定义事件, 比如 settings, onSelectionChange... + extra?: Object; + }; // 请求相关字段 requestOption: { - type: string, // 资源类型 + // 资源类型 + type: string; + // 排序参数 sortOption?: { - sort: string, // 需要排序的字段 - order: 'ASC' | 'DESC', // 排序方式 - }, // 排序参数 + sort: string; // 需要排序的字段 + order: 'ASC' | 'DESC'; // 排序方式 + }; + // 筛选参数 filterOption?: { - rules: Array, // 规则 + // 规则 + rules: Array; + // Tab 切换时选用项(如选中全部时, 删除对应的 rule) deleteOption?: { - field: string, - flagValue: string, // 当 rule.value = flagValue 时, 删除该 rule - }, // Tab 切换时选用项(如选中全部时, 删除对应的 rule) - }, // 筛选参数 - }, + field: string; + flagValue: string; // 当 rule.value = flagValue 时, 删除该 rule + }; + // 模糊查询开关true开启,false关闭 + fuzzySwitch?: boolean; + }; + // 请求需要的额外荷载数据 + extension?: Record; + // 钩子 - 可以根据当前请求结果异步更新 dataList + resolveDataListCb?: (...args: any) => Promise; + // 钩子 - 可以根据当前请求结果异步更新 pagination.count + resolvePaginationCountCb?: (...args: any) => Promise; + // 列表数据的路径,如 data.details + dataPath?: string; + }; // 资源下筛选业务功能相关的 prop - bizFilter?: FilterType, + bizFilter?: FilterType; } export const useTable = (props: IProp) => { - const { isBusinessPage } = useWhereAmI(); + const { whereAmI } = useWhereAmI(); + + const regionsStore = useRegionsStore(); const resourceStore = useResourceStore(); + const businessStore = useBusinessStore(); const accountStore = useAccountStore(); const businessMapStore = useBusinessMapStore(); + const searchVal = ref(''); const dataList = ref([]); const isLoading = ref(false); - const pagination = reactive({ - start: 0, - limit: 10, - count: 100, - }); - const filter = reactive({ - op: QueryRuleOPEnum.AND, - rules: [], - }); - const handlePageLimitChange = (v: number) => { - pagination.limit = v; - pagination.start = 0; - getListData(); + const sort = ref(props.requestOption.sortOption ? props.requestOption.sortOption.sort : 'created_at'); + const order = ref(props.requestOption.sortOption ? props.requestOption.sortOption.order : 'DESC'); + const getInitialRules = () => { + const { filterOption } = props.requestOption; + return filterOption && !filterOption.deleteOption ? filterOption.rules : []; }; - const handlePageValueChange = (v: number) => { - pagination.start = (v - 1) * pagination.limit; + const filter = reactive({ op: QueryRuleOPEnum.AND, rules: getInitialRules() }); + + const { pagination, handlePageLimitChange, handlePageValueChange } = usePagination(() => getListData()); + + // 钩子 - 表头排序时 + const handleSort = ({ column, type }: any) => { + pagination.start = 0; + sort.value = column.field; + order.value = type === 'asc' ? 'ASC' : 'DESC'; + // 如果type为null,则默认排序 + if (type === 'null') { + sort.value = props.requestOption.sortOption ? props.requestOption.sortOption.sort : 'created_at'; + order.value = props.requestOption.sortOption ? props.requestOption.sortOption.order : 'DESC'; + } getListData(); }; - const getListData = async (customRules: Array<{ - op: QueryRuleOPEnum, - field: string, - value: string | number, - }> = []) => { + + /** + * 请求表格数据 + * @param customRules 自定义规则 + * @param type 资源类型 + */ + const getListData = async (customRules: Array = [], type?: string) => { + buildFilter({ rules: customRules }); // 预览 if (props.tableOptions.reviewData) { dataList.value = props.tableOptions.reviewData; return; } isLoading.value = true; - const [detailsRes, countRes] = await Promise.all([false, true].map(isCount => resourceStore.list({ - page: { - limit: isCount ? 0 : pagination.limit, - start: isCount ? 0 : pagination.start, - ...(isCount ? {} : (props.requestOption.sortOption || {})), - count: isCount, - }, - filter: { - op: filter.op, - rules: [...filter.rules, ...customRules], - }, - }, props.requestOption.type))); - dataList.value = detailsRes?.data?.details; - pagination.count = countRes?.data?.count; + + // 判断是业务下, 还是资源下 + const api = whereAmI.value === Senarios.business ? businessStore.list : resourceStore.list; + // 请求数据 + const [detailsRes, countRes] = await Promise.all( + [false, true].map((isCount) => + api( + { + page: { + limit: isCount ? 0 : pagination.limit, + start: isCount ? 0 : pagination.start, + sort: isCount ? undefined : sort.value, + order: isCount ? undefined : order.value, + count: isCount, + }, + filter: { op: filter.op, rules: filter.rules }, + ...props.requestOption.extension, + }, + type ? type : props.requestOption.type, + ), + ), + ); + // 更新数据 + dataList.value = props.requestOption.dataPath + ? lodash_get(detailsRes, props.requestOption.dataPath) + : detailsRes?.data?.details; + + // 异步处理 dataList + if (typeof props.requestOption.resolveDataListCb === 'function') { + props.requestOption.resolveDataListCb(dataList.value, getListData).then((newDataList: any[]) => { + dataList.value = newDataList; + }); + } + + // 处理 pagination.count + if (typeof props.requestOption.resolvePaginationCountCb === 'function') { + props.requestOption.resolvePaginationCountCb(countRes?.data).then((newCount: number) => { + pagination.count = newCount; + }); + } else { + pagination.count = countRes?.data?.count || 0; + } + isLoading.value = false; }; + const CommonTable = defineComponent({ setup(_props, { slots }) { + const searchData = computed(() => { + return ( + (typeof props.searchOptions.searchData === 'function' + ? props.searchOptions.searchData() + : props.searchOptions.searchData) || [] + ); + }); + return () => ( - <> +
      -
      {slots.operation?.()}
      + {slots.operation &&
      {slots.operation?.()}
      } {!props.searchOptions.disabled && ( )} @@ -114,6 +195,7 @@ export const useTable = (props: IProp) => {
      { {...(props.tableOptions.extra || {})} onPageLimitChange={handlePageLimitChange} onPageValueChange={handlePageValueChange} - onColumnSort={() => {}} + onColumnSort={handleSort} onColumnFilter={() => {}}> {{ empty: () => { @@ -131,11 +213,33 @@ export const useTable = (props: IProp) => { }}
      - +
      ); }, }); + /** + * 处理搜索条件, 有需要映射的字段需要转换 + * @param rule 待添加的搜索条件 + */ + const resolveRule = (rule: RulesItem) => { + const { field, op, value } = rule; + switch (field) { + case 'vendor': + return { field, op, value: VendorReverseMap[value as string] || value }; + case 'region': + return { field, op, value: regionsStore.getRegionNameEN(value as string) || value }; + case 'lb_type': + return { field, op, value: LB_NETWORK_TYPE_REVERSE_MAP[value as string] || value }; + case 'scheduler': + return { field, op, value: SCHEDULER_REVERSE_MAP[value as string] || value }; + case 'binding_status': + return { field, op, value: LISTENER_BINDING_STATUS_REVERSE_MAP[value as string] || value }; + default: + return { field, op, value }; + } + }; + /** * 构建请求筛选条件 * @param options 配置对象 @@ -149,15 +253,16 @@ export const useTable = (props: IProp) => { const filterMap = new Map(); // 先添加新的规则 rules.forEach((rule) => { - const tmpRule = filterMap.get(rule.field); + const newRule = resolveRule(rule); + const tmpRule = filterMap.get(newRule.field); if (tmpRule) { if (Array.isArray(tmpRule.rules)) { - filterMap.set(rule.field, { op: QueryRuleOPEnum.OR, rules: [...tmpRule.rules, rule] }); + filterMap.set(newRule.field, { op: QueryRuleOPEnum.OR, rules: [...tmpRule.rules, newRule] }); } else { - filterMap.set(rule.field, { op: QueryRuleOPEnum.OR, rules: [tmpRule, rule] }); + filterMap.set(newRule.field, { op: QueryRuleOPEnum.OR, rules: [tmpRule, newRule] }); } } else { - filterMap.set(rule.field, JSON.parse(JSON.stringify(rule))); + filterMap.set(newRule.field, JSON.parse(JSON.stringify(newRule))); } }); // 后添加 filter 的规则 @@ -184,28 +289,60 @@ export const useTable = (props: IProp) => { filter.rules = [...filterMap.values()]; }; + /** + * 处理字段的搜索模式 + */ + const resolveSearchFieldOp = (val: any) => { + let op; + const { id, name } = val; + if (!id || !name) return; + // 如果是domain或者zones(数组类型), 则使用JSON_CONTAINS + if ((val?.id === 'domain' && val?.name !== '负载均衡域名') || val?.id === 'zones') { + op = QueryRuleOPEnum.JSON_CONTAINS; + } + // 如果是名称或指定了模糊搜索, 则模糊搜索 + else if ( + props?.requestOption?.filterOption?.fuzzySwitch || + val?.id === 'name' || + (val?.id === 'domain' && val?.name === '负载均衡域名') + ) { + op = QueryRuleOPEnum.CIS; + } + // 如果是任务类型, 则使用 json_neq + else if (val?.id === 'detail.data.res_flow.flow_id') { + op = QueryRuleOPEnum.JSON_NEQ; + } else if (val?.id === 'health_check.health_switch') { + op = QueryRuleOPEnum.JSON_EQ; + } + // 否则, 精确搜索 + else { + op = QueryRuleOPEnum.EQ; + } + return op; + }; + watch( - [ - () => searchVal.value, - () => accountStore.bizs, - ], + [() => searchVal.value, () => accountStore.bizs], ([searchVal, bizs], [oldSearchVal]) => { - if (isBusinessPage && !bizs) return; + if (whereAmI.value === Senarios.business && !bizs) return; // 记录上一次 search-select 的规则名 - const oldSearchFieldList: string[] = (Array.isArray(oldSearchVal) - && oldSearchVal.reduce((prev: any, item: any) => [...prev, item.id], [])) || []; + const oldSearchFieldList: string[] = + (Array.isArray(oldSearchVal) && oldSearchVal.reduce((prev: any, item: any) => [...prev, item.id], [])) || []; // 记录此次 search-select 规则名 const searchFieldList: string[] = []; // 构建当前 search-select 规则 - const searchRules = Array.isArray(searchVal) ? searchVal.map((val: any) => { - const field = val?.id; - const op = val?.id === 'domain' ? QueryRuleOPEnum.JSON_CONTAINS : QueryRuleOPEnum.EQ; - const value = field === 'bk_biz_id' - ? (businessMapStore.businessNameToIDMap.get(val?.values?.[0]?.id) || Number(val?.values?.[0]?.id)) - : val?.values?.[0]?.id; - searchFieldList.push(field); - return { field, op, value }; - }) : []; + const searchRules = Array.isArray(searchVal) + ? searchVal.map((val: any) => { + const field = val?.id; + const op = resolveSearchFieldOp(val); + const value = + field === 'bk_biz_id' + ? businessMapStore.businessNameToIDMap.get(val?.values?.[0]?.id) || Number(val?.values?.[0]?.id) + : val?.values?.[0]?.id; + searchFieldList.push(field); + return { field, op, value }; + }) + : []; // 如果 search-select 的条件减少, 则移除差集中的规则 if (oldSearchFieldList.length > searchFieldList.length) { buildFilter({ rules: searchRules, differenceFields: getDifferenceSet(oldSearchFieldList, searchFieldList) }); @@ -222,20 +359,24 @@ export const useTable = (props: IProp) => { ); // 分配业务筛选 - watch(() => props.bizFilter, (val) => { - const idx = filter.rules.findIndex(rule => rule.field === 'bk_biz_id'); - const bizFilter = val.rules[0]; - if (bizFilter) { - if (idx !== -1) { - filter.rules[idx] = bizFilter; + watch( + () => props.bizFilter, + (val) => { + const idx = filter.rules.findIndex((rule) => rule.field === 'bk_biz_id'); + const bizFilter = val.rules[0]; + if (bizFilter) { + if (idx !== -1) { + filter.rules[idx] = bizFilter; + } else { + filter.rules.push(val.rules[0]); + } } else { - filter.rules.push(val.rules[0]); + filter.rules.splice(idx, 1); } - } else { - filter.rules.splice(idx, 1); - } - getListData(); - }, { deep: true }); + getListData(); + }, + { deep: true }, + ); watch( () => props.requestOption.filterOption, diff --git a/front/src/hooks/useVerify.ts b/front/src/hooks/useVerify.ts index 6b1ac8bcbb..cd7161aaf6 100644 --- a/front/src/hooks/useVerify.ts +++ b/front/src/hooks/useVerify.ts @@ -1,23 +1,21 @@ import { useCommonStore } from '@/store'; import usePagePermissionStore from '@/store/usePagePermissionStore'; // import { Verify } from '@/typings'; -import { - ref, -} from 'vue'; +import { ref } from 'vue'; type paramsType = { - action: string - resource_type: string - bk_biz_id?: number + action: string; + resource_type: string; + bk_biz_id?: number; }; const showPermissionDialog = ref(false); const authVerifyData = ref({ permissionAction: {}, urlParams: {} }); const permissionParams = ref({ system_id: '', actions: [] }); -export enum IAM_CODE { +export enum IAM_CODE { Success = 0, NoPermission = 2000009, -}; +} // 权限hook export function useVerify() { @@ -28,17 +26,21 @@ export function useVerify() { const getAuthVerifyData = async (authData: any[]) => { if (!authData) return; // 格式化参数 - const params = authData?.reduce((p, v) => { - const resourceData: paramsType = { - action: v.action, - resource_type: v.type, - }; - if (v.bk_biz_id) { // 业务需要传业务id - resourceData.bk_biz_id = v.bk_biz_id; - } - p.resources.push(resourceData); - return p; - }, { resources: [] }); + const params = authData?.reduce( + (p, v) => { + const resourceData: paramsType = { + action: v.action, + resource_type: v.type, + }; + if (v.bk_biz_id) { + // 业务需要传业务id + resourceData.bk_biz_id = v.bk_biz_id; + } + p.resources.push(resourceData); + return p; + }, + { resources: [] }, + ); let res; try { res = await commonStore.authVerify(params); @@ -53,7 +55,8 @@ export function useVerify() { } } - if (res?.data?.permission) { // 没有权限才需要获取跳转链接参数 + if (res?.data?.permission) { + // 没有权限才需要获取跳转链接参数 // 每个操作对应的参数 const systemId = res.data.permission.system_id; const urlParams = res.data.permission.actions.reduce((p: any, e: any) => { @@ -66,12 +69,13 @@ export function useVerify() { authVerifyData.value.urlParams = urlParams; } // permissionAction 用于判断按钮状态 仅针对操作按钮有用 - const permissionAction = res?.data?.results.reduce((p: any, e: any, i: number) => { // 将数组转成对象 + const permissionAction = res?.data?.results.reduce((p: any, e: any, i: number) => { + // 将数组转成对象 p[`${authData[i].id}`] = e.authorized; return p; }, {}); - authVerifyData.value.permissionAction = permissionAction; - commonStore.addAuthVerifyData(authVerifyData); // 全局变量管理 + authVerifyData.value.permissionAction = permissionAction; + commonStore.addAuthVerifyData(authVerifyData); // 全局变量管理 return res?.data; }; diff --git a/front/src/hooks/useWhereAmI.ts b/front/src/hooks/useWhereAmI.ts index 005b3f5617..429993f63f 100644 --- a/front/src/hooks/useWhereAmI.ts +++ b/front/src/hooks/useWhereAmI.ts @@ -1,4 +1,4 @@ -import { Ref, computed, watch } from 'vue'; +import { Ref, computed } from 'vue'; import { useRoute } from 'vue-router'; export const useWhereAmI = (): { @@ -11,7 +11,8 @@ export const useWhereAmI = (): { } => { const route = useRoute(); const senario = computed(() => { - if (/^\/resource\/.+$/.test(route.path)) return Senarios.resource; + if (!route) return; + if (/^\/resource\/.+$/.test(route?.path)) return Senarios.resource; if (/^\/business\/.+$/.test(route.path)) return Senarios.business; if (/^\/service\/.+$/.test(route.path)) return Senarios.service; if (/^\/workbench\/.+$/.test(route.path)) return Senarios.workbench; diff --git a/front/src/http/cached-promise.ts b/front/src/http/cached-promise.ts index 404028ab94..eaa7aae557 100644 --- a/front/src/http/cached-promise.ts +++ b/front/src/http/cached-promise.ts @@ -18,7 +18,7 @@ export default class CachedPromise { */ get(id: string) { if (typeof id === 'undefined') { - return Object.keys(this.cache).map(requestId => this.cache[requestId]); + return Object.keys(this.cache).map((requestId) => this.cache[requestId]); } return this.cache[id]; } diff --git a/front/src/http/index.ts b/front/src/http/index.ts index c1f4c99fd4..b0edaef470 100644 --- a/front/src/http/index.ts +++ b/front/src/http/index.ts @@ -30,38 +30,41 @@ const axiosInstance: AxiosInstance = axios.create({ }); /** - * request interceptor - */ -axiosInstance.interceptors.request.use((config: any) => { - // const urlObj = new UrlParse(config.url); - // const query = queryString.parse(urlObj.query as any); - // if (query[AJAX_MOCK_PARAM]) { - // // 直接根路径没有 pathname,例如 http://localhost:LOCAL_DEV_PORT/?mock-file=index&invoke=btn1&btn=btn1 - // // axios get 请求不会请求到 devserver,因此在 pathname 不存在或者为 / 时,加上一个 /mock 的 pathname - // if (!urlObj.pathname) { - // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}/mock/${urlObj.query}`; - // } else if (urlObj.pathname === '/') { - // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}/mock/${urlObj.query}`; - // } else if (LOCAL_DEV_URL && LOCAL_DEV_PORT) { - // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}${urlObj.pathname}${urlObj.query}`; - // } else if (LOCAL_DEV_URL) { - // config.url = `${LOCAL_DEV_URL}${urlObj.pathname}${urlObj.query}`; - // } - // } - // 设置uuid - const uuid = uuidv4(); - axiosInstance.defaults.headers['X-Bkapi-Request-Id'] = uuid; - // 在发起请求前,注入CSRFToken,解决跨域 - injectCSRFTokenToHeaders(); - return config; -}, error => Promise.reject(error)); + * request interceptor + */ +axiosInstance.interceptors.request.use( + (config: any) => { + // const urlObj = new UrlParse(config.url); + // const query = queryString.parse(urlObj.query as any); + // if (query[AJAX_MOCK_PARAM]) { + // // 直接根路径没有 pathname,例如 http://localhost:LOCAL_DEV_PORT/?mock-file=index&invoke=btn1&btn=btn1 + // // axios get 请求不会请求到 devserver,因此在 pathname 不存在或者为 / 时,加上一个 /mock 的 pathname + // if (!urlObj.pathname) { + // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}/mock/${urlObj.query}`; + // } else if (urlObj.pathname === '/') { + // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}/mock/${urlObj.query}`; + // } else if (LOCAL_DEV_URL && LOCAL_DEV_PORT) { + // config.url = `${LOCAL_DEV_URL}:${LOCAL_DEV_PORT}${urlObj.pathname}${urlObj.query}`; + // } else if (LOCAL_DEV_URL) { + // config.url = `${LOCAL_DEV_URL}${urlObj.pathname}${urlObj.query}`; + // } + // } + // 设置uuid + const uuid = uuidv4(); + axiosInstance.defaults.headers['X-Bkapi-Request-Id'] = uuid; + // 在发起请求前,注入CSRFToken,解决跨域 + injectCSRFTokenToHeaders(); + return config; + }, + (error) => Promise.reject(error), +); /** - * response interceptor - */ + * response interceptor + */ axiosInstance.interceptors.response.use( - response => response.data, - error => Promise.reject(error), + (response) => response.data, + (error) => Promise.reject(error), ); const http: HttpApi = { @@ -88,12 +91,12 @@ allMethods.forEach((method) => { }); /** - * 获取 http 不同请求方式对应的函数 - * - * @param {string} http method 与 axios 实例中的 method 保持一致 - * - * @return {Function} 实际调用的请求函数 - */ + * 获取 http 不同请求方式对应的函数 + * + * @param {string} http method 与 axios 实例中的 method 保持一致 + * + * @return {Function} 实际调用的请求函数 + */ function getRequest(method: HttpMethodType) { if (methodsWithData.includes(method)) { return (url: string, data: object, config: object) => getPromise(method, url, data, config); @@ -102,15 +105,15 @@ function getRequest(method: HttpMethodType) { } /** - * 实际发起 http 请求的函数,根据配置调用缓存的 promise 或者发起新的请求 - * - * @param {method} http method 与 axios 实例中的 method 保持一致 - * @param {string} 请求地址 - * @param {Object} 需要传递的数据, 仅 post/put/patch 三种请求方式可用 - * @param {Object} 用户配置,包含 axios 的配置与本系统自定义配置 - * - * @return {Promise} 本次http请求的Promise - */ + * 实际发起 http 请求的函数,根据配置调用缓存的 promise 或者发起新的请求 + * + * @param {method} http method 与 axios 实例中的 method 保持一致 + * @param {string} 请求地址 + * @param {Object} 需要传递的数据, 仅 post/put/patch 三种请求方式可用 + * @param {Object} 用户配置,包含 axios 的配置与本系统自定义配置 + * + * @return {Promise} 本次http请求的Promise + */ async function getPromise(method: HttpMethodType, url: string, data: object | null, userConfig = {}) { const config = initConfig(method, url, userConfig); let promise; @@ -130,8 +133,9 @@ async function getPromise(method: HttpMethodType, url: string, data: object | nu // eslint-disable-next-line no-async-promise-executor, @typescript-eslint/no-misused-promises promise = new Promise(async (resolve, reject) => { const axiosRequest = methodsWithData.includes(method) - // @ts-ignore - ? axiosInstance[method](url, data, config) : axiosInstance[method](url, config); + ? // @ts-ignore + axiosInstance[method](url, data, config) + : axiosInstance[method](url, config); try { const response = await axiosRequest; @@ -146,11 +150,12 @@ async function getPromise(method: HttpMethodType, url: string, data: object | nu Object.assign(config, error.config); reject(error); } - }).catch((error) => { - return handleReject(error, config); }) + .catch((error) => { + return handleReject(error, config); + }) .finally(() => { - // console.log('finally', config) + // console.log('finally', config) }); // 添加请求队列 @@ -162,13 +167,13 @@ async function getPromise(method: HttpMethodType, url: string, data: object | nu } /** - * 处理 http 请求成功结果 - * - * @param {Object} 请求配置 - * @param {Object} cgi 原始返回数据 - * @param {Function} promise 完成函数 - * @param {Function} promise 拒绝函数 - */ + * 处理 http 请求成功结果 + * + * @param {Object} 请求配置 + * @param {Object} cgi 原始返回数据 + * @param {Function} promise 完成函数 + * @param {Function} promise 拒绝函数 + */ function handleResponse(params: { config: any; response: any; resolve: any; reject: any }) { // 关闭节流阀 isLoginValid = false; @@ -184,13 +189,13 @@ function handleResponse(params: { config: any; response: any; resolve: any; reje // token失效时多个接口的错误信息通过isActive节流阀只显示一个 let isLoginValid = false; /** - * 处理 http 请求失败结果 - * - * @param {Object} Error 对象 - * @param {config} 请求配置 - * - * @return {Promise} promise 对象 - */ + * 处理 http 请求失败结果 + * + * @param {Object} Error 对象 + * @param {config} 请求配置 + * + * @return {Promise} promise 对象 + */ function handleReject(error: any, config: any) { // 判断节流阀是否开启,开启则不执行后面的接口错误提示框 if (isLoginValid) { @@ -199,7 +204,15 @@ function handleReject(error: any, config: any) { if (axios.isCancel(error)) { return Promise.reject(error); } - + if (error.code === 2000000 && error.message.includes('do not support create IPv6 full chain loadbalancer')) { + Message({ + theme: 'error', + message: '当前账号不支持购买IPv6,请联系云厂商开通, 参考文档https://cloud.tencent.com/document/product/214/39612', + }); + return Promise.reject( + '当前账号不支持购买IPv6,请联系云厂商开通, 参考文档https://cloud.tencent.com/document/product/214/39612', + ); + } http.queue.delete(config.requestId); if (config.globalError && error.response) { const { status, data } = error.response; @@ -213,7 +226,7 @@ function handleReject(error: any, config: any) { } else if (status === 404) { nextError.message = '不存在'; Message({ theme: 'error', message: nextError.message }); - } else if (status === 500) { + } else if (status === 500) { nextError.message = '系统出现异常'; Message({ theme: 'error', message: nextError.message }); } else if (data?.message && error.code !== 0 && error.code !== 2000009) { @@ -222,29 +235,34 @@ function handleReject(error: any, config: any) { } // messageError(nextError.message) - console.error(nextError.message); return Promise.reject(nextError); } - if (error.code !== 0 && error.code !== 2000009) Message({ theme: 'error', message: error.message }); - console.error(error.message); - // bk_ticket失效后的登录弹框 - if (error.code === 2000000 && (error.message === 'bk_ticket cookie don\'t exists' || error.message === 'bk_token cookie don\'t exists')) { // 打开节流阀 - isLoginValid = true; - InvalidLogin(); - } + handleCustomErrorCode(error); return Promise.reject(error); } +/** + * 处理自定义错误码 + * @param error 异常 + */ +function handleCustomErrorCode(error: any) { + switch (error.code) { + case 2000014: + Message({ message: '当前负载均衡正在变更中,云平台限制新的任务同时变更。', theme: 'error' }); + return; + } + if (error.code !== 0 && error.code !== 2000009) Message({ theme: 'error', message: error.message }); +} /** - * 初始化本系统 http 请求的各项配置 - * - * @param {string} http method 与 axios 实例中的 method 保持一致 - * @param {string} 请求地址, 结合 method 生成 requestId - * @param {Object} 用户配置,包含 axios 的配置与本系统自定义配置 - * - * @return {Promise} 本次 http 请求的 Promise - */ + * 初始化本系统 http 请求的各项配置 + * + * @param {string} http method 与 axios 实例中的 method 保持一致 + * @param {string} 请求地址, 结合 method 生成 requestId + * @param {Object} 用户配置,包含 axios 的配置与本系统自定义配置 + * + * @return {Promise} 本次 http 请求的 Promise + */ function initConfig(method: string, url: string, userConfig: object) { const defaultConfig = { ...getCancelToken(), @@ -267,10 +285,10 @@ function initConfig(method: string, url: string, userConfig: object) { } /** - * 生成 http 请求的 cancelToken,用于取消尚未完成的请求 - * - * @return {Object} {cancelToken: axios 实例使用的 cancelToken, cancelExcutor: 取消http请求的可执行函数} - */ + * 生成 http 请求的 cancelToken,用于取消尚未完成的请求 + * + * @return {Object} {cancelToken: axios 实例使用的 cancelToken, cancelExcutor: 取消http请求的可执行函数} + */ function getCancelToken() { let cancelExcutor; const cancelToken = new axios.CancelToken((excutor) => { @@ -285,8 +303,8 @@ function getCancelToken() { export default http; /** - * 向 http header 注入 CSRFToken,CSRFToken key 值与后端一起协商制定 - */ + * 向 http header 注入 CSRFToken,CSRFToken key 值与后端一起协商制定 + */ export function injectCSRFTokenToHeaders() { const CSRFToken = cookie.parse(document.cookie)[`${window.PROJECT_CONFIG.BKPAAS_APP_ID}_csrftoken`]; if (CSRFToken !== undefined) { diff --git a/front/src/http/request-queue.ts b/front/src/http/request-queue.ts index 7c9e93135b..3105e3d718 100644 --- a/front/src/http/request-queue.ts +++ b/front/src/http/request-queue.ts @@ -21,7 +21,7 @@ export default class RequestQueue { if (typeof id === 'undefined') { return this.queue; } - return this.queue.filter(request => request.requestId === id); + return this.queue.filter((request) => request.requestId === id); } /** @@ -47,7 +47,7 @@ export default class RequestQueue { // const index = this.queue.indexOf(target) // this.queue.splice(index, 1) // } - this.queue = [...this.queue.filter(request => request.requestId !== id)]; + this.queue = [...this.queue.filter((request) => request.requestId !== id)]; } /** diff --git a/front/src/language/lang.ts b/front/src/language/lang.ts index b326b27aaf..34673b971d 100644 --- a/front/src/language/lang.ts +++ b/front/src/language/lang.ts @@ -9,12 +9,8 @@ const lang: ILANG = { 你好世界: ['hello world'], 海垒: ['HCM'], 退出: ['Quit'], - '非平台用户,请联系管理员授权': [ - 'No platform users, please contact the administrator for authorization', - ], - 请联系项目管理员关联业务: [ - 'Please contact the project administrator to associate the business', - ], + '非平台用户,请联系管理员授权': ['No platform users, please contact the administrator for authorization'], + 请联系项目管理员关联业务: ['Please contact the project administrator to associate the business'], 请联系项目管理员关联业务或者授权业务权限: [ 'Please contact the project administrator to associate business or authorize business permissions', ], @@ -99,9 +95,7 @@ const lang: ILANG = { 容量只能为数字: ['Capacity can only numeric'], 容量不能为负数: ['Capacity cannot be negative'], 容量不能超过1000: ['Capacity cannot exceed 1000'], - 修改的容量不能小于已使用配额: [ - 'The modified capacity cannot be less than the used quota', - ], + 修改的容量不能小于已使用配额: ['The modified capacity cannot be less than the used quota'], 容量和超分配额的总量不能小于已分配额: [ 'The total amount of capacity and over quota cannot be less than the allocated quota', ], @@ -110,15 +104,11 @@ const lang: ILANG = { 输入参数: ['Input parameter'], 调用该API传递的参数信息: ['Parameter information passed by calling the API'], 返回变量: ['Return variable'], - 调用成功后API将会返回的变量信息: [ - 'The variable information that the API will return after the call is successful', - ], + 调用成功后API将会返回的变量信息: ['The variable information that the API will return after the call is successful'], 变量名: ['Variable name'], 变量值: ['Variable value'], 接口返回: ['Interface return'], - 调用成功后API将会返回的数据信息: [ - 'The data information that the API will return after the call is successful', - ], + 调用成功后API将会返回的数据信息: ['The data information that the API will return after the call is successful'], 附件来源: ['Attachment source'], 文件类型: ['File type'], 搜索: ['Search'], @@ -216,9 +206,7 @@ const lang: ILANG = { 关联负载均衡次数: ['Associated load balancing times'], 端口: ['Port'], 权重: ['Weight'], - '温馨提示:所选服务器的vpc段必须相同': [ - 'Warm tip: the VPC segments of the selected server must be the same', - ], + '温馨提示:所选服务器的vpc段必须相同': ['Warm tip: the VPC segments of the selected server must be the same'], 添加端口: ['Add port'], 移除: ['Remove'], 上一步: ['Previous step'], @@ -358,14 +346,10 @@ const lang: ILANG = { 请输入磁盘名称: ['Please enter a disk name'], 请选择挂载虚拟机: ['Please choose to mount the virtual machine'], 请选择存储类型: ['Please select storage type'], - '输入磁盘容量,限定为40~4000的整数': [ - 'Enter the disk capacity, which is limited to an integer of 40 ~ 4000', - ], + '输入磁盘容量,限定为40~4000的整数': ['Enter the disk capacity, which is limited to an integer of 40 ~ 4000'], 请选择站点: ['Please select a site'], 请选择存储: ['Please select storage'], - '输入磁盘容量,限定为大于100的整数': [ - 'Enter the disk capacity, limited to an integer greater than 100', - ], + '输入磁盘容量,限定为大于100的整数': ['Enter the disk capacity, limited to an integer greater than 100'], 请选择磁盘种类: ['Please select a disk type'], 请选择镜像: ['Please select a image'], 存储大小: ['Storage size'], @@ -385,12 +369,8 @@ const lang: ILANG = { 站点不能为空: ['Site cannot be empty'], 存储不能为空: ['Storage cannot be empty'], 挂载主机不能为空: ['Mount host cannot be empty'], - '磁盘正在创建中,请稍后刷新页面查看': [ - 'The disk is being created. Please refresh the page later', - ], - 挂载后才会显示到虚拟机详情中: [ - 'After mounting, it will be displayed in the virtual machine details', - ], + '磁盘正在创建中,请稍后刷新页面查看': ['The disk is being created. Please refresh the page later'], + 挂载后才会显示到虚拟机详情中: ['After mounting, it will be displayed in the virtual machine details'], 请选择项目: ['Please select an item'], 云资源分布: ['Cloud Resource Distribution'], 成本组成: ['Cost composition'], @@ -427,9 +407,7 @@ const lang: ILANG = { 资源名称: ['Resource Name'], 邮箱: ['Mail'], 电话: ['Phone'], - '资源快速入口最多存在4个, 还可以选择': [ - 'There are at most 4 resource quick entries, and you can also choose', - ], + '资源快速入口最多存在4个, 还可以选择': ['There are at most 4 resource quick entries, and you can also choose'], 新增成功: ['Added successfully'], '私有网络(Virtual Private Cloud,VPC)是您自己独有专属云上网络空间,您可以完全掌控自己的专有网络,不同私有网络间完全逻辑隔离。您可以自定义网络环境、路由表、安全策略等;同时,私有网络支持多种方式连接 Internet、连接其他 VPC、连接您的本地数据中心,助力您轻松部署云上网络。': [ @@ -485,9 +463,7 @@ const lang: ILANG = { 开始时间不能大于结束时间: ['Start time cannot be greater than end time'], 占总消费: ['Share in total consumption'], 半年成本趋势图: ['Half year cost trend chart'], - 当月成本最高的虚拟机TOP5: [ - 'Top 5 virtual machines with the highest cost in the month', - ], + 当月成本最高的虚拟机TOP5: ['Top 5 virtual machines with the highest cost in the month'], 虚拟机当月总消费: ['Monthly total consumption of virtual machines'], 计算机资源当月消费: ['Monthly consumption of computer resources'], 磁盘当月消费: ['Disk consumption in the current month'], @@ -650,9 +626,7 @@ const lang: ILANG = { 脚本名称不能为空: ['Script name cannot be empty'], 输出参数: ['Output parameters'], 脚本不能为空: ['script cannot be empty'], - 请确认必选参数是否全部填写: [ - 'Please confirm whether all required parameters are filled in', - ], + 请确认必选参数是否全部填写: ['Please confirm whether all required parameters are filled in'], 脚本详情: ['Script details'], 新增脚本: ['Add script'], 值: ['Value'], @@ -691,9 +665,7 @@ const lang: ILANG = { 区域资源分布: ['Regional Resource Distribution'], 规格分布: ['Specification Distribution'], 集群虚拟机数量TOP5: ['The number of cluster virtual machines TOP5'], - esxi主机的虚拟机数量TOP5: [ - 'The number of virtual machines on the esxi host TOP5', - ], + esxi主机的虚拟机数量TOP5: ['The number of virtual machines on the esxi host TOP5'], 集群容量TOP5: ['Cluster Capacity TOP5'], esxi主机容量TOP5: ['esxi host capacity TOP5'], 使用率: ['Usage'], @@ -820,10 +792,9 @@ const lang: ILANG = { ], 解绑: ['Unbind'], 选择资源: ['Choose a resource'], - '标签键和标签值最大支持127个半角字符,支持 UTF-8 格式表示的字符、空格和数字以及特殊字符:+-=._:/@()[]()【】': - [ - 'The label key and label value support up to 127 half width characters, and support UTF-8 format characters, spaces, numbers and special characters: + - =. _:/@ ()[]()【】', - ], + '标签键和标签值最大支持127个半角字符,支持 UTF-8 格式表示的字符、空格和数字以及特殊字符:+-=._:/@()[]()【】': [ + 'The label key and label value support up to 127 half width characters, and support UTF-8 format characters, spaces, numbers and special characters: + - =. _:/@ ()[]()【】', + ], 资源标签: ['Resource tab'], 页面进行管理: ['Page to manage'], 查看资源: ['View resources'], @@ -1005,12 +976,8 @@ const lang: ILANG = { CPU必填且不可为负数: ['CPU is required and cannot be negative'], 内存必填且不可为负数: ['Memory is required and cannot be negative'], 块存储必填且不可为负数: ['Block storage is required and cannot be negative'], - 快照数必填且不可为负数: [ - 'The number of snapshots is required and cannot be negative', - ], - 弹性公网IP数必填且不可为负数: [ - 'The number of EIPs is required and cannot be negative', - ], + 快照数必填且不可为负数: ['The number of snapshots is required and cannot be negative'], + 弹性公网IP数必填且不可为负数: ['The number of EIPs is required and cannot be negative'], 配额修改成功: ['Quota modified successfully'], 新增快照: ['Add snapshot'], 快照ID: ['Snapshot ID'], @@ -1039,9 +1006,7 @@ const lang: ILANG = { '资源池配置:': ['Resource pool configuration: '], 请选择关联的项目: ['Please select an associated item'], 请选择租户资源池: ['Please select a tenant resource pool'], - 请完善资源池配置信息: [ - 'Please complete the resource pool configuration information', - ], + 请完善资源池配置信息: ['Please complete the resource pool configuration information'], 新增租户资源池成功: ['Added tenant resource pool successfully'], 按名称搜索: ['Search by name'], 资源区域: ['Resource area'], @@ -1117,9 +1082,7 @@ const lang: ILANG = { 请选择关联组织: ['Please select an affiliate'], 用户已存在: ['User already exists'], '下列用户:': ['The following users:'], - '已经存在于其他项目中,无法继续创建项目': [ - 'Already exists in another project, cannot continue to create project', - ], + '已经存在于其他项目中,无法继续创建项目': ['Already exists in another project, cannot continue to create project'], 项目详情: ['Project details'], 项目标识: ['Project ID'], 折扣管理: ['Discount Management'], @@ -1196,9 +1159,7 @@ const lang: ILANG = { 测试订单: ['Test order'], 名称不能为空: ['Name cannot be empty'], 服务申请提交成功: ['Service application submitted successfully'], - '3秒之后将返回服务目录': [ - 'After 3 seconds, the service directory will be returned', - ], + '3秒之后将返回服务目录': ['After 3 seconds, the service directory will be returned'], 查看订单: ['Check order'], 请勾选审批流程: ['Please tick the approval process'], 创建订单失败: ['Failed to create order'], @@ -1217,9 +1178,7 @@ const lang: ILANG = { 'Doing this will create a new ticket and jump to the ticket page, please confirm whether to submit', ], 服务添加到购物车成功: ['Service added to cart successfully'], - '3秒之后将返回服务申请': [ - 'The service request will be returned after 3 seconds', - ], + '3秒之后将返回服务申请': ['The service request will be returned after 3 seconds'], 购物车: ['Shopping cart'], 返回服务申请: ['Back to Service Request'], 保存清单成功: ['List saved successfully'], @@ -1242,12 +1201,8 @@ const lang: ILANG = { '请确认所选虚拟机已关机,否则将执行失败': [ 'Please confirm that the selected virtual machine is powered off, otherwise the execution will fail', ], - 虚拟机密码不符合规范: [ - 'The virtual machine password is out of specification', - ], - '项目名称已被占用,请重新命名': [ - 'Project name is already taken, please rename', - ], + 虚拟机密码不符合规范: ['The virtual machine password is out of specification'], + '项目名称已被占用,请重新命名': ['Project name is already taken, please rename'], 提单人: ['Drawer'], 工单节点已完成: ['Work Order Node Completed'], 必选: ['Required'], @@ -1268,35 +1223,26 @@ const lang: ILANG = { 赋值1: ['assign 1'], 赋值2: ['assign 2'], 上传完成: ['upload completed'], - '支持的附件格式:txt,log,pdf,doc,ppt,xls,docx,pptx,xlsx,zip,jpg,png,每个文件不超过200Mb': - [ - 'Supported attachment formats: txt, log, pdf, doc, ppt, xls, docx, pptx, xlsx, zip, jpg, png, each file does not exceed 200Mb', - ], + '支持的附件格式:txt,log,pdf,doc,ppt,xls,docx,pptx,xlsx,zip,jpg,png,每个文件不超过200Mb': [ + 'Supported attachment formats: txt, log, pdf, doc, ppt, xls, docx, pptx, xlsx, zip, jpg, png, each file does not exceed 200Mb', + ], 点击上传: ['Click to upload'], 文件不能超过: ['File cannot exceed'], 文件名不合法: ['Invalid filename'], '不能上传同名文件!': ['Cannot upload a file with the same name!'], '附件格式不支持!': ['Attachment format not supported!'], - '只允许上传JPG|PNG|JPEG格式的图片': [ - 'Only images in JPG|PNG|JPEG format are allowed to be uploaded', - ], + '只允许上传JPG|PNG|JPEG格式的图片': ['Only images in JPG|PNG|JPEG format are allowed to be uploaded'], 图片大小不能超过: ['Image size cannot exceed'], - 请选择在哪朵云上创建资源: [ - 'Please select which cloud to create the resource on', - ], + 请选择在哪朵云上创建资源: ['Please select which cloud to create the resource on'], 实例名: ['Instance name'], 请输入实例名: ['Please enter an instance name'], 网络类型: ['Network Type'], 可用区类型: ['Availability Zone Type'], 所属运营商: ['carrier'], 带宽上限: ['Bandwidth cap'], - '按使用流量计费,请输入带宽上限': [ - 'Billed by usage, please enter a bandwidth limit', - ], + '按使用流量计费,请输入带宽上限': ['Billed by usage, please enter a bandwidth limit'], 虚拟网络: ['Virtual network'], - 请根据所需选择合适的网络: [ - 'Please select the appropriate network according to your needs', - ], + 请根据所需选择合适的网络: ['Please select the appropriate network according to your needs'], 申请数量: ['Number of applications'], 集团总部: ['Group headquarters'], 公网: ['Public net'], @@ -1316,9 +1262,7 @@ const lang: ILANG = { 导入数据: ['Import Data'], 下载表格: ['Download form'], 默认行不能删除: ['Default row cannot be deleted'], - '确认要删除此条数据?': [ - 'Are you sure you want to delete this piece of data?', - ], + '确认要删除此条数据?': ['Are you sure you want to delete this piece of data?'], 请导入有效的文件: ['Please import a valid file'], '该列为多选类型,请从下列选项中输入以英文逗号分隔的key值。': [ 'This column is a multi-select type, please enter the key values separated by English commas from the following options.', @@ -1326,12 +1270,8 @@ const lang: ILANG = { '该列为单选类型,请从下列选项中输入一个key值。': [ 'The column is a radio type, please enter a key value from the following options.', ], - '该列为日期类型,请按以下格式输入': [ - 'The column is a date type, please enter it in the following format', - ], - '该列为时间类型,请按以下格式输入': [ - 'The column is a time type, please enter it in the following format', - ], + '该列为日期类型,请按以下格式输入': ['The column is a date type, please enter it in the following format'], + '该列为时间类型,请按以下格式输入': ['The column is a time type, please enter it in the following format'], 资源账号区域: ['Resource account area'], 申请理由: ['Reason for Application'], 请输入理由: ['Please enter a reason'], @@ -1352,9 +1292,7 @@ const lang: ILANG = { 公共镜像: ['Public image'], 选择日期: ['Select date'], 选择日期时间范围: ['Select date time range'], - 请选择在哪朵云上修改资源: [ - 'Please select which cloud to modify the resource on', - ], + 请选择在哪朵云上修改资源: ['Please select which cloud to modify the resource on'], 云可用区: ['Cloud Availability Zone'], 描述详情: ['Description details'], 请详细描述删除原因: ['Please describe in detail the reason for deletion'], @@ -1366,10 +1304,9 @@ const lang: ILANG = { 文件体积过大: ['File size is too large'], 图片格式错误: ['Wrong image format'], '模板下载:': ['Template download: '], - '支持的附件格式:txt,log,pdf,doc,ppt,xls,docx,pptx,xlsx,zip,jpg,png,msg,每个文件不超过200Mb': - [ - 'Supported attachment formats: txt, log, pdf, doc, ppt, xls, docx, pptx, xlsx, zip, jpg, png, msg, each file does not exceed 200Mb', - ], + '支持的附件格式:txt,log,pdf,doc,ppt,xls,docx,pptx,xlsx,zip,jpg,png,msg,每个文件不超过200Mb': [ + 'Supported attachment formats: txt, log, pdf, doc, ppt, xls, docx, pptx, xlsx, zip, jpg, png, msg, each file does not exceed 200Mb', + ], '支持的附件格式:': ['Supported attachment formats:'], ',每个文件不超过': ['], each file does not exceed'], '中文名:': ['Chinese name: '], @@ -1408,9 +1345,7 @@ const lang: ILANG = { 副本只读: ['Replica read-only'], 'Redis4.0主从版': ['Redis4.0 master slave'], '请输入1~5范围内的数字': ['Please enter a number in the range 1~5'], - '请输入1024~65535范围内的数字': [ - 'Please enter a number in the range of 1024 ~ 65535', - ], + '请输入1024~65535范围内的数字': ['Please enter a number in the range of 1024 ~ 65535'], 选择: ['Choose'], 主可用区: ['Primary Availability Zone'], 从可用区: ['From Availability Zone'], @@ -1432,9 +1367,7 @@ const lang: ILANG = { 一主三从: ['One master and three slaves'], 一主四从: ['One master and four slaves'], 一主五从: ['One master and five slaves'], - '请输入10-1500范围内的数字且为10的倍数': [ - 'A number in the range of 1500 and a multiple of 10', - ], + '请输入10-1500范围内的数字且为10的倍数': ['A number in the range of 1500 and a multiple of 10'], 集群名称: ['Cluster name'], Kubernetes版本: ['Kubernetes version'], Master节点规格: ['Master Node Specifications'], @@ -1579,9 +1512,7 @@ const lang: ILANG = { '预计完成时间:': ['Estimated finish time: '], 当前节点其他用户正在处理: ['Other users of the current node are processing'], 该单据已结束: ['The document has ended'], - '任务进行中,完成后将自动刷新': [ - 'The task is in progress and will be refreshed automatically after completion', - ], + '任务进行中,完成后将自动刷新': ['The task is in progress and will be refreshed automatically after completion'], '超时:': ['Time out:'], 处理中: ['Processing'], 被挂起: ['Suspended'], @@ -1601,13 +1532,9 @@ const lang: ILANG = { '剩余:': ['Remaining: '], 请选择评论节点: ['Please select a comment node'], 请输入评论: ['Please enter a comment'], - 请搜索并选择需要通知的用户: [ - 'Please search and select the user who needs to be notified', - ], + 请搜索并选择需要通知的用户: ['Please search and select the user who needs to be notified'], '评论节点:': ['Comment node: '], - '输入“@”可搜索并选择需要通知的用户': [ - 'Type "@" to search and select users to notify', - ], + '输入“@”可搜索并选择需要通知的用户': ['Type "@" to search and select users to notify'], 附件上传: ['Updating files'], 单据所有处理人: ['Document owner'], 提交评论: ['Submit comments'], @@ -1662,9 +1589,7 @@ const lang: ILANG = { '45天内到期': ['Expires in 45 days'], '60天内到期': ['Expires in 60 days'], kubernetes版本: ['kubernetes version'], - 请前往服务目录内提单删除资源: [ - 'Please go to the bill of lading in the service catalog to delete the resource', - ], + 请前往服务目录内提单删除资源: ['Please go to the bill of lading in the service catalog to delete the resource'], 独立集群: ['Standalone cluster'], 托管集群: ['Managed cluster'], 完成: ['Finish'], @@ -1677,14 +1602,12 @@ const lang: ILANG = { 已选节点: ['Selected node'], 确认密码: ['Confirm Password'], 安全加固: ['Security hardening'], - '提示:以上节点需要重装系统,同时会将以上节点迁移至当前集群的新增资源所属项目下。': - [ - 'Tip: The above nodes need to be reinstalled, and the above nodes will be migrated to the project to which the new resources of the current cluster belong.', - ], - '注意:重装后,节点系统盘内的所有数据将被清除,恢复到初始状态;迁移项目后原安全组解绑,需要重新绑定安全组。': - [ - 'Note: After reinstallation, all data in the node system disk will be cleared and restored to the initial state; after the project is migrated, the original security group will be unbound, and the security group needs to be re-bound.', - ], + '提示:以上节点需要重装系统,同时会将以上节点迁移至当前集群的新增资源所属项目下。': [ + 'Tip: The above nodes need to be reinstalled, and the above nodes will be migrated to the project to which the new resources of the current cluster belong.', + ], + '注意:重装后,节点系统盘内的所有数据将被清除,恢复到初始状态;迁移项目后原安全组解绑,需要重新绑定安全组。': [ + 'Note: After reinstallation, all data in the node system disk will be cleared and restored to the initial state; after the project is migrated, the original security group will be unbound, and the security group needs to be re-bound.', + ], 免费开通: ['Open for free'], '安装组件免费开通DDoS防护、WAF和云镜主机防护': [ 'The installation components enable free DDoS protection, WAF and cloud image host protection', @@ -1746,9 +1669,7 @@ const lang: ILANG = { 已隔离: ['Quarantined'], 已删除: ['deleted'], 新标签不能为空: ['New label cannot be empty'], - '新增Mariadb成功,稍后将自动更新数据': [ - 'Added Mariadb successfully, data will be updated automatically later', - ], + '新增Mariadb成功,稍后将自动更新数据': ['Added Mariadb successfully, data will be updated automatically later'], 新增MariaDB: ['Added MariaDB'], 存储: ['Storage'], 内网端口: ['Intranet port'], @@ -1782,9 +1703,7 @@ const lang: ILANG = { 实例规格不能为空: ['The instance specification cannot be empty'], 分片数量不能为空: ['The number of shards cannot be empty'], 每片节点数量不能为空: ['The number of nodes per slice cannot be empty'], - '磁盘容量不能为空,不能小于250': [ - 'Disk capacity cannot be empty and cannot be less than 250', - ], + '磁盘容量不能为空,不能小于250': ['Disk capacity cannot be empty and cannot be less than 250'], 密码不能为空: ['Password can not be blank'], '密码必须包含字母、数字和特殊字符(长度8-16位)': [ 'Password must contain letters, numbers and special characters (8-16 characters in length)', @@ -1816,9 +1735,7 @@ const lang: ILANG = { 权限组不能为空: ['Permission group cannot be empty'], 标准版: ['Standard Edition'], 集群版: ['Cluster Edition'], - '新增Redis成功,稍后将自动更新数据': [ - 'Successfully added Redis, the data will be automatically updated later', - ], + '新增Redis成功,稍后将自动更新数据': ['Successfully added Redis, the data will be automatically updated later'], 规格信息: ['Specification information'], 网络信息: ['Internet Information'], 内网PV4地址: ['Intranet PV4 address'], @@ -1839,12 +1756,8 @@ const lang: ILANG = { '日志磁盘:': ['log disk: '], 不能超过机型CPU总量: ['Cannot exceed the total amount of CPU of the model'], 不能超过机型内存总量: ['Cannot exceed the total memory of the model'], - 不能超过机型数据磁盘总量: [ - 'Cannot exceed the total amount of data disks of the model', - ], - 不能超过机型日志磁盘总量: [ - 'Cannot exceed the total number of log disks of the model', - ], + 不能超过机型数据磁盘总量: ['Cannot exceed the total amount of data disks of the model'], + 不能超过机型日志磁盘总量: ['Cannot exceed the total number of log disks of the model'], '容灾模式:': ['Disaster recovery mode: '], 一主零备: ['One main and spare'], 一主一备: ['One master and one backup'], @@ -1938,9 +1851,7 @@ const lang: ILANG = { '过期时间:': ['Expiration: '], 永不过期: ['Never expires'], '读写方式:': ['Read and write: '], - '【正常账号】只选择主机进行读写': [ - '[Normal account] Only select the host for reading and writing', - ], + '【正常账号】只选择主机进行读写': ['[Normal account] Only select the host for reading and writing'], '【只读账号】优先在备机进行读,如果备机的延迟都大于设置的延迟,则从主机读取': [ '[Read-only account] Read on the standby machine first, if the delay of the standby machine is greater than the set delay, read from the host machine', ], @@ -1948,9 +1859,7 @@ const lang: ILANG = { '[Read-only account] Only select the standby machine for reading, if all the standby machines are greater than the set delay, an error will be reported directly', ], '当只读时,将:': ['When read-only, will: '], - 选择正常的备机进行读操作: [ - 'Select the normal standby machine for read operation', - ], + 选择正常的备机进行读操作: ['Select the normal standby machine for read operation'], 选择watch节点进行读: ['Select the watch node to read'], '只读备机延迟值:': ['Standby-only latency value:'], 修改权限: ['Edit permission'], @@ -1999,9 +1908,7 @@ const lang: ILANG = { 设置binlog和备份保存天数: ['Set binlog and backup save days'], 删除免切成功: ['Delete without cutting succeeded'], '是否确认初始化:': ['Confirm initialization: '], - '初始化成功,稍后请查看数据': [ - 'Initialization succeeded. Please check the data later', - ], + '初始化成功,稍后请查看数据': ['Initialization succeeded. Please check the data later'], 设置分布式事务: ['Set up distributed transactions'], 工作模式: ['Operating mode'], 开启分布式事务: ['Start distributed transactions'], @@ -2021,17 +1928,13 @@ const lang: ILANG = { 约束: ['constraint'], 默认值: ['Defaults'], 批量修改: ['Batch Edit'], - '修改成功,稍后数据会同步更新': [ - 'Update successful, will be updated synchronously later', - ], + '修改成功,稍后数据会同步更新': ['Update successful, will be updated synchronously later'], 新增SET: ['Add SET'], 同步异步模式: ['Synchronous asynchronous mode'], 所属实例: ['owning instance'], 请补全必需参数: ['Please complete the required parameters'], 隔离中: ['in isolation'], - '新增成功,数据正在同步更新中,请稍后查看...': [ - 'Create successful, updated synchronously, please check later', - ], + '新增成功,数据正在同步更新中,请稍后查看...': ['Create successful, updated synchronously, please check later'], '扩(缩)容': ['Expand (shrink) capacity'], '1主0备': ['1 main 0 backup'], '1主1备': ['1 main 1 backup'], @@ -2093,15 +1996,13 @@ const lang: ILANG = { 关联硬盘: ['Associative hard drive'], 新增快照成功: ['Snapshot added successfully'], 您确定要把: ['Confirm to '], - 的数据回滚到: [' \'s data is rolled back to '], + 的数据回滚到: [" 's data is rolled back to "], '时刻吗?': [' time?'], 回滚后立即启动实例: ['Start instance immediately after rollback'], 虚拟机批量分配: ['Virtual machine batch allocation'], 重置密码: ['Reset Password'], '确认密码:': ['Confirm Password: '], - '修改密码后需要重启实例生效。': [ - 'After changing password, need to restart the instance to take effect.', - ], + '修改密码后需要重启实例生效。': ['After changing password, need to restart the instance to take effect.'], '您已经选择1台实例:': ['You have selected 1 instance: '], 重启: ['reboot'], '绑定/解绑安全组': ['Bind/Unbind security group'], @@ -2178,7 +2079,7 @@ const lang: ILANG = { 批量释放弹性公网IP: ['Release elastic public IP addresses in batches'], 批量释放: ['batch release'], 计费模式不能为空: ['The billing mode cannot be empty'], - 购买时长不能为空: ['Can\'t be empty for purchasing time'], + 购买时长不能为空: ["Can't be empty for purchasing time"], 带宽大小不能为空: ['Bandwidth cannot be empty'], 带宽名称不能为空: ['The bandwidth name cannot be empty'], 带宽类型不能为空: ['Bandwidth type cannot be empty'], @@ -2235,12 +2136,8 @@ const lang: ILANG = { 请先选择后端服务器类型: ['Please select the back -end server type first'], 请先添加服务器: ['Please add the server first'], 请输入端口信息: ['Please enter port information'], - 请填写负载均衡SLB对外服务的端口: [ - 'Please fill in the port of the load balancing SLB external service', - ], - '如不填写,系统默认为‘协议_端口’': [ - 'If you do not fill in, the system defaults to ‘protocol_ port’', - ], + 请填写负载均衡SLB对外服务的端口: ['Please fill in the port of the load balancing SLB external service'], + '如不填写,系统默认为‘协议_端口’': ['If you do not fill in, the system defaults to ‘protocol_ port’'], 选择负载均衡协议: ['Select load balancing protocol'], 请先选择负载均衡协议: ['Please select the load balancing protocol first'], 请先输入监听端口: ['Please enter the monitoring port first'], @@ -2283,10 +2180,9 @@ const lang: ILANG = { 虚拟服务器组名称: ['Virtual server group name'], 请先输入服务器组名称: ['Please enter the server group name first'], 已添加服务器: ['Added server'], - '存在关联监听或关联转发策略,需要先解除监听或转发策略的关联关系,才可以删除该虚拟服务器组': - [ - 'There is a correlation strategy of associated monitoring or associated forwarding, and the relationship between listening or forwarding strategies must be lifted first before you can delete the virtual server group', - ], + '存在关联监听或关联转发策略,需要先解除监听或转发策略的关联关系,才可以删除该虚拟服务器组': [ + 'There is a correlation strategy of associated monitoring or associated forwarding, and the relationship between listening or forwarding strategies must be lifted first before you can delete the virtual server group', + ], 删除后端服务器: ['delete backend server'], 请将端口信息填写完全: ['Please fill in the port information completely'], 创建: ['Create'], @@ -2338,9 +2234,7 @@ const lang: ILANG = { 出站规则: ['Outbound Rules'], 允许: ['allow'], 拒绝: ['reject'], - '除备注外,其他字段均不能为空!': [ - 'Except for remarks, other fields cannot be empty!', - ], + '除备注外,其他字段均不能为空!': ['Except for remarks, other fields cannot be empty!'], '协议端口支持以下格式:': ['Protocol ports support the following formats: '], '其他协议: ICMP,ICMPv6或GRE': ['Other protocols: ICMP, ICMPv6 or GRE'], '规则:': ['Rule: '], @@ -2386,10 +2280,9 @@ const lang: ILANG = { 子网可用区不能为空: ['Subnet Availability Zone cannot be empty'], 新增VPC成功: ['Added VPC successfully'], 新增子网: ['Add subnet'], - '子网的CIDR必须是所在私有网络CIDR的一部分,且不能和该私有网络下已有的CIDR重叠': - [ - 'The CIDR of the subnet must be part of the CIDR of the private network, and it cannot overlap with the existing CIDR under the private network', - ], + '子网的CIDR必须是所在私有网络CIDR的一部分,且不能和该私有网络下已有的CIDR重叠': [ + 'The CIDR of the subnet must be part of the CIDR of the private network, and it cannot overlap with the existing CIDR under the private network', + ], '阿里云安全组名称:长度为2~128个英文或中文字符。必须以大小字母或中文开头。可以包含数字、半角冒号(:)、下划线(_)或者连字符(-)': [ 'Security group name: 2 to 128 English or Chinese characters in length. Must start with upper and lower letters or Chinese. Can contain numbers, colons (:), underscores (_), or hyphens (-)', @@ -2559,17 +2452,11 @@ const lang: ILANG = { 同步结果: ['Sync result'], 余额: ['Balance'], 所属云账号: ['own cloud account'], - 至少有一组集群名和操作接口: [ - 'At least one set of cluster name and operation interface', - ], + 至少有一组集群名和操作接口: ['At least one set of cluster name and operation interface'], 新增账号成功: ['Successfully added account'], 修改账号成功: ['Modify account successfully'], - '请输入服务器名、IP、账号、密码、项目ID': [ - 'Please enter server name, IP, account, password, project ID', - ], - '请输入账号名、SECRETID、SECRET密钥': [ - 'Please enter account name, SECRETID, SECRET key', - ], + '请输入服务器名、IP、账号、密码、项目ID': ['Please enter server name, IP, account, password, project ID'], + '请输入账号名、SECRETID、SECRET密钥': ['Please enter account name, SECRETID, SECRET key'], 测试连接成功: ['Test connection is successful'], 删除账号: ['delete account'], 至少存在一条集群和接口: ['At least one cluster and interface exists'], @@ -2620,14 +2507,12 @@ const lang: ILANG = { 新增存储资源类型: ['Added storage resource type'], '价格 元/(天*GB):': ['Price RMB/(day*GB):'], 关联: ['association'], - '计算资源类型关联计算资源(如:集群),用于对某个计算资源的成本。若没有关联,默认对整个云平台生效。': - [ - 'Computing resource type is associated with a computing resource (such as a cluster), and is used for the cost of a computing resource. If there is no association, it will take effect for the entire cloud platform by default.', - ], - '存储资源类型关联存储资源(如:本地存储),用于对某个存储资源的成本。若没有关联,默认对整个云平台生效。': - [ - 'Storage resource type is associated with storage resources (such as: local storage), which is used for the cost of a storage resource. If there is no association, it will take effect for the entire cloud platform by default.', - ], + '计算资源类型关联计算资源(如:集群),用于对某个计算资源的成本。若没有关联,默认对整个云平台生效。': [ + 'Computing resource type is associated with a computing resource (such as a cluster), and is used for the cost of a computing resource. If there is no association, it will take effect for the entire cloud platform by default.', + ], + '存储资源类型关联存储资源(如:本地存储),用于对某个存储资源的成本。若没有关联,默认对整个云平台生效。': [ + 'Storage resource type is associated with storage resources (such as: local storage), which is used for the cost of a storage resource. If there is no association, it will take effect for the entire cloud platform by default.', + ], 账号选择: ['Account selection'], 计算资源定价: ['Computing resource pricing'], 存储资源定价: ['Storage resource pricing'], @@ -2699,9 +2584,7 @@ const lang: ILANG = { 时间范围不能为空: ['Time range cannot be empty'], 生效范围不能为空: ['Effective range cannot be empty'], 周期不能为空: ['Period cannot be empty'], - '5min使用率百分比和次数不可为空': [ - '5min usage percentage and times cannot be empty', - ], + '5min使用率百分比和次数不可为空': ['5min usage percentage and times cannot be empty'], CPU_5min使用率: ['CPU_5min usage'], load_5min使用率: ['load_5min usage'], 大于: ['more than'], @@ -2712,9 +2595,7 @@ const lang: ILANG = { 规格名称不能为空: ['Specs name cannot be empty'], 规格不能为空: ['Specs cannot be empty'], 请先选择资源类型: ['Please select the resource type first'], - '该映射关系已存在,请重新选择': [ - 'The mapping relationship already exists, please select again', - ], + '该映射关系已存在,请重新选择': ['The mapping relationship already exists, please select again'], CPU和内存均不可为空: ['Neither CPU nor memory can be empty'], 编辑规格: ['Edit specs'], 请输入规格名: ['Please enter specification name'], @@ -2742,7 +2623,7 @@ const lang: ILANG = { 参数配置: ['Params Config'], 开: ['open'], 关: ['close'], - 不操作: ['don\'t operate'], + 不操作: ["don't operate"], 新增标签: ['Add tag'], 请输入标签键: ['Please enter tag key'], 请输入标签值: ['Please enter tag value'], @@ -2760,15 +2641,9 @@ const lang: ILANG = { 请输入loadBalance名称: ['Please enter a loadBalance name'], 修改端口: ['Update Port'], 修改权重: ['Update Weight'], - '正在开机中,已开机的虚拟机不会进行任何操作!': [ - 'Powering on, the powered on virtual machine will not do anything', - ], - '正在关机中,请不要立刻进行其他操作': [ - 'Shutting down, please do not perform other operations immediately', - ], - '正在重启中,请不要立刻进行其他操作': [ - 'Restarting, please do not perform other operations immediately', - ], + '正在开机中,已开机的虚拟机不会进行任何操作!': ['Powering on, the powered on virtual machine will not do anything'], + '正在关机中,请不要立刻进行其他操作': ['Shutting down, please do not perform other operations immediately'], + '正在重启中,请不要立刻进行其他操作': ['Restarting, please do not perform other operations immediately'], 监听器管理: ['Listener management'], '4 层': ['Four'], '7 层': ['Seven'], @@ -2818,7 +2693,7 @@ const lang: ILANG = { 请输入备注: ['Please enter Remark'], 请选择使用业务: ['Please choose to business'], 请输入SecretKey: ['Please enter SecretKey'], - 的: ['\'s '], + 的: ["'s "], 端口已被占用: ['Port is already occupied'], 多个相同主机: ['multiple identical hosts'], 不能设置同样的端口: ['Cannot set the same port'], @@ -2838,9 +2713,7 @@ const lang: ILANG = { '密码长度12-32位,必须包含英文小写字母+大写字母+数字+特殊字符': [ 'Password is 12-32 characters long and must contain lowercase letters + uppercase letters + numbers + special characters', ], - '请输入10 - 16380 之间的10的倍数': [ - 'Please enter a multiple of 10 between 10 - 16380', - ], + '请输入10 - 16380 之间的10的倍数': ['Please enter a multiple of 10 between 10 - 16380'], 请选择所属云区域: ['Please select the cloud region'], 请选择网络类型: ['Please select network type'], 请选择权限组: ['Please select permission group'], @@ -2904,9 +2777,7 @@ const lang: ILANG = { 所属网络不能为空: ['Network cannot be empty'], 最大出带宽不能为空: ['Max bandwidth cannot be empty'], 最大出带宽只能为数字: ['Max output bandwidth can only be numbers'], - '最大出带宽最大值为 2048,最小值为 1': [ - 'Max bandwidth max value is 2048, and the min value is 1', - ], + '最大出带宽最大值为 2048,最小值为 1': ['Max bandwidth max value is 2048, and the min value is 1'], '如果创建公网负载均衡,且使用的账号是传统账户类型,计费模式和最大出带宽参数不生效。如需创建传统类型账户的公网CLB,需要在腾讯云官网控制台创建,详细说明见:': [ 'If the public network load is balanced and the account used is the type of traditional account, the billing mode and the maximum bandwidth parameter do not take effect.If you need to create a traditional type of account CLB, you need to create on the official website of the Tencent Cloud Website. For details, please refer to:', @@ -2921,9 +2792,7 @@ const lang: ILANG = { 监听协议端口只能为数字: ['Listening protocol port can only be numbers'], 均衡方式不能为空: ['Balance method cannot be empty'], 未选择任何安全组: ['No safety group'], - 是否确认解除以下安全组的绑定: [ - 'Whether to confirm the binding of the following security group', - ], + 是否确认解除以下安全组的绑定: ['Whether to confirm the binding of the following security group'], 请选择安全组: ['Please select the security group'], 精确: ['Exact'], 请输入资源名称搜索: ['Please enter resource name'], @@ -2935,24 +2804,17 @@ const lang: ILANG = { 移出成功: ['removed successfully'], 撤回申请成功: ['Withdrawal of application succeeded'], 请输入数字: ['Please key in numbers'], - '(处于关机状态虚拟机才能被删除)': [ - 'The virtual machine can only be deleted when it is powered off', - ], - '正在关机中,请不要立刻进行其他操作!': [ - 'It is shutting down, please do not perform other operations immediately!', - ], + '(处于关机状态虚拟机才能被删除)': ['The virtual machine can only be deleted when it is powered off'], + '正在关机中,请不要立刻进行其他操作!': ['It is shutting down, please do not perform other operations immediately!'], 新增镜像成功: ['Added image successfully'], - '可以是任间网段,如: 0.0.0.0/0, ::/0, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16': - [ - 'Can be any network segment, such as: 0.0.0.0/0, ::/0, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16', - ], + '可以是任间网段,如: 0.0.0.0/0, ::/0, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16': [ + 'Can be any network segment, such as: 0.0.0.0/0, ::/0, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16', + ], '支持格式:': ['Supported formats:'], '单个IP: 192.168.0.1 或 FF05::B5': ['Single IP: 192.168.0.1 or FF05::B5'], '所有IPv4地址:0.0.0.0/0': ['All IPv4 addresses: 0.0.0.0/0'], '所有IPv6地址:0::0/0或者::/0': ['All IPv6 addresses: 0::0/0 or ::/0'], - 'CIDR: 192.168.1.0/24 或 FF05:B5::/60': [ - 'CIDR: 192.168.1.0/24 or FF05:B5::/60', - ], + 'CIDR: 192.168.1.0/24 或 FF05:B5::/60': ['CIDR: 192.168.1.0/24 or FF05:B5::/60'], '单个端口: TCP:80': ['Single port: TCP:80'], '多个端口: UDP:80,443': ['Multiple ports: UDP: 80,443'], '连续端口: TCP:3306-20000': ['Serial Port: TCP:3306-20000'], @@ -2995,10 +2857,9 @@ const lang: ILANG = { '您已选择 {count} 台实例,进行关机操作前,请确认': [ 'You have selected {count} instances, before shutting down, please make sure that.', ], - '您已选择 {count} 台实例,进行重启操作,重启期间,实例将无法正常提供服务,请您做好准备,以免造成影响请确认': - [ - 'You have selected {count} instances for reboot operation, during the reboot, the instances will not be able to provide normal services, please be prepared to avoid the impact please confirm', - ], + '您已选择 {count} 台实例,进行重启操作,重启期间,实例将无法正常提供服务,请您做好准备,以免造成影响请确认': [ + 'You have selected {count} instances for reboot operation, during the reboot, the instances will not be able to provide normal services, please be prepared to avoid the impact please confirm', + ], '您已选择 {count} 台实例,进行重置密码操作': [ 'You have selected {count} station instance for password reset operation', ], @@ -3011,9 +2872,7 @@ const lang: ILANG = { '密码长度8-20位,必须包含英文字母、数字和特殊字符': [ 'Password length 8-20 digits, must contain English letters, numbers and special characters', ], - 确认密码必须和新密码一致: [ - 'Confirm that the password must be the same as the new password', - ], + 确认密码必须和新密码一致: ['Confirm that the password must be the same as the new password'], 强制关机: ['Forced shutdown'], 同时退还挂载在实例上的包年包月弹性数据盘: [ 'Also return the annual and monthly flexible data disk mounted on the instance', @@ -3058,10 +2917,9 @@ const lang: ILANG = { 'win实例:强烈建议您在卸载之前,对该硬盘执行脱机操作': [ 'win example: it is highly recommended that you perform offline operations on this drive before uninstalling', ], - 'linux实例:建议您在卸载之前,确保该硬盘的所有分区处于非加载状态 (umounted)。部分linux操作系统可能不支持硬盘热拔插': - [ - 'linux example: It is recommended that you make sure that all partitions of the drive are unmounted (umounted) before unmounting it. Some linux operating systems may not support hot-plugging of hard drives', - ], + 'linux实例:建议您在卸载之前,确保该硬盘的所有分区处于非加载状态 (umounted)。部分linux操作系统可能不支持硬盘热拔插': [ + 'linux example: It is recommended that you make sure that all partitions of the drive are unmounted (umounted) before unmounting it. Some linux operating systems may not support hot-plugging of hard drives', + ], 卸载云硬盘: ['Uninstall Cloud Drive'], 'VPC:{name}': ['VPC:{name}'], 密钥信息: ['key information'], @@ -3077,10 +2935,9 @@ const lang: ILANG = { 修改人: ['Edited by'], 删除安全组: ['Delete security group'], 删除防火墙规则: ['delete firewall rules'], - '安全组被实例关联或者被其他安全组规则关联时不能直接删除,请删除关联关系后再进行删除': - [ - 'When a security group is associated with an instance or other security group rules, it cannot be deleted directly. Please delete the association relationship before deleting', - ], + '安全组被实例关联或者被其他安全组规则关联时不能直接删除,请删除关联关系后再进行删除': [ + 'When a security group is associated with an instance or other security group rules, it cannot be deleted directly. Please delete the association relationship before deleting', + ], 查看关联实例: ['View associated instances'], 防火墙规则被实例关联: ['Firewall rules are associated with instances'], '请注意删除防火墙规则后无法恢复,请谨慎操作': [ diff --git a/front/src/main.ts b/front/src/main.ts index 6648a25eb7..3cb8558c97 100644 --- a/front/src/main.ts +++ b/front/src/main.ts @@ -24,12 +24,7 @@ const pinia = createPinia(); app.config.globalProperties.$bus = bus; app.config.globalProperties.$http = http; -app.use(i18n) - .use(directive) - .use(router) - .use(components) - .use(pinia) - .use(bkui); +app.use(i18n).use(directive).use(router).use(components).use(pinia).use(bkui); router.isReady().then(() => { app.mount('#app'); diff --git a/front/src/router/header-config.ts b/front/src/router/header-config.ts index 7f9925828d..e63d399ba3 100644 --- a/front/src/router/header-config.ts +++ b/front/src/router/header-config.ts @@ -6,26 +6,22 @@ export const headRouteConfig = [ { id: 'business', name: '资源管理', - route: 'business', - href: '#/business/host', + path: '/business/host', }, { id: 'service', name: '我的单据', - route: 'service', - href: '#/service/my-apply', + path: '/service/my-apply', }, { id: 'resource', name: '资源接入', - route: 'resource', - href: '#/resource/resource', + path: '/resource/resource', }, { id: 'scheme', name: '资源选型', - route: 'scheme', - href: '#/scheme/recommendation', + path: '/scheme/recommendation', }, // 接下来是 资源选型、平台管理 diff --git a/front/src/router/index.ts b/front/src/router/index.ts index 938280badd..fd127ec583 100644 --- a/front/src/router/index.ts +++ b/front/src/router/index.ts @@ -19,7 +19,6 @@ import { useVerify } from '@/hooks'; const { t } = i18n.global; - const routes: RouteRecordRaw[] = [ ...common, ...workbench, @@ -56,9 +55,16 @@ const router = createRouter({ // 进入目标页面 // eslint-disable-next-line max-len -const toCurrentPage = (authVerifyData: any, currentFindAuthData: any, next: NavigationGuardNext, to?: RouteLocationNormalized) => { - if (currentFindAuthData) { // 当前页面需要鉴权 - if (authVerifyData && !authVerifyData?.permissionAction[currentFindAuthData.id]) { // 当前页面没有权限 +const toCurrentPage = ( + authVerifyData: any, + currentFindAuthData: any, + next: NavigationGuardNext, + to?: RouteLocationNormalized, +) => { + if (currentFindAuthData) { + // 当前页面需要鉴权 + if (authVerifyData && !authVerifyData?.permissionAction[currentFindAuthData.id]) { + // 当前页面没有权限 next({ name: '403', params: { @@ -73,7 +79,8 @@ const toCurrentPage = (authVerifyData: any, currentFindAuthData: any, next: Navi next(); } } else { - if (to && to.name === '403' && authVerifyData && authVerifyData?.permissionAction?.account_find) { // 无权限用户切换到有权限用户时需要判断 + if (to && to.name === '403' && authVerifyData && authVerifyData?.permissionAction?.account_find) { + // 无权限用户切换到有权限用户时需要判断 next({ path: '/resource/account', }); @@ -83,19 +90,18 @@ const toCurrentPage = (authVerifyData: any, currentFindAuthData: any, next: Navi } }; - router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { const commonStore = useCommonStore(); - const { pageAuthData, authVerifyData } = commonStore; // 所有需要检验的查看权限数据 + const { pageAuthData, authVerifyData } = commonStore; // 所有需要检验的查看权限数据 const currentFindAuthData = pageAuthData.find((e: any) => e.path === to.path || e?.path?.includes(to.path)); // if (to.path === '/service/my-approval') { // window.open(`${BK_ITSM_URL}/#/workbench/ticket/approval`); // window.location.reload(); // } - console.log(666, from.path, to.path); - if (from.path === '/') { // 刷新或者首次进入请求权限接口 - const { getAuthVerifyData } = useVerify(); // 权限中心权限 + if (from.path === '/') { + // 刷新或者首次进入请求权限接口 + const { getAuthVerifyData } = useVerify(); // 权限中心权限 getAuthVerifyData(pageAuthData).then(() => { const { authVerifyData } = commonStore; toCurrentPage(authVerifyData, currentFindAuthData, next, to); @@ -107,5 +113,4 @@ router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, n } }); - export default router; diff --git a/front/src/router/module/business.ts b/front/src/router/module/business.ts index 62f5c4aadb..a5fefb3c8c 100644 --- a/front/src/router/module/business.ts +++ b/front/src/router/module/business.ts @@ -1,4 +1,5 @@ // import { CogShape } from 'bkui-vue/lib/icon'; +import { LBRouteName } from '@/constants'; import type { RouteRecordRaw } from 'vue-router'; const businesseMenus: RouteRecordRaw[] = [ @@ -310,6 +311,106 @@ const businesseMenus: RouteRecordRaw[] = [ notMenu: true, }, }, + { + path: '/business/loadbalancer', + name: '负载均衡', + component: () => import('@/views/business/load-balancer/index'), + redirect: '/business/loadbalancer/clb-view', + children: [ + { + path: 'clb-view', + name: 'loadbalancer-view', + component: () => import('@/views/business/load-balancer/clb-view/index'), + children: [ + { + path: '', + name: LBRouteName.allLbs, + component: () => import('@/views/business/load-balancer/clb-view/all-clbs-manager/index'), + props(route) { + return route.query; + }, + meta: { + type: 'all', + }, + }, + { + path: 'lb/:id', + name: LBRouteName.lb, + component: () => import('@/views/business/load-balancer/clb-view/specific-clb-manager/index'), + props(route) { + return { ...route.params, ...route.query }; + }, + meta: { + type: 'lb', + }, + }, + { + path: 'listener/:id', + name: LBRouteName.listener, + component: () => import('@/views/business/load-balancer/clb-view/specific-listener-manager/index'), + props(route) { + return { ...route.params, ...route.query }; + }, + meta: { + type: 'listener', + }, + }, + { + path: 'domain/:id', + name: LBRouteName.domain, + component: () => import('@/views/business/load-balancer/clb-view/specific-domain-manager/index'), + props(route) { + return { ...route.params, ...route.query }; + }, + meta: { + type: 'domain', + }, + }, + ], + }, + { + path: 'group-view', + name: 'target-group-view', + component: () => import('@/views/business/load-balancer/group-view/index'), + children: [ + { + path: '', + name: LBRouteName.allTgs, + component: () => import('@/views/business/load-balancer/group-view/all-groups-manager/index'), + props(route) { + return route.query; + }, + }, + { + path: ':id', + name: LBRouteName.tg, + component: () => + import('@/views/business/load-balancer/group-view/specific-target-group-manager/index'), + props(route) { + return { ...route.params, ...route.query }; + }, + }, + ], + meta: { + applyRes: 'targetGroup', + }, + }, + ], + meta: { + activeKey: 'businessClb', + icon: 'hcm-icon bkhcm-icon-loadbalancer', + }, + }, + { + path: '/business/cert', + name: '证书托管', + component: () => import('@/views/business/cert-manager/index'), + meta: { + activeKey: 'businessCert', + isShowBreadcrumb: true, + icon: 'hcm-icon bkhcm-icon-cert', + }, + }, ], }, { @@ -319,7 +420,28 @@ const businesseMenus: RouteRecordRaw[] = [ { path: '/business/record', name: '操作记录', - component: () => import('@/views/resource/resource-manage/operationRecord/index'), + children: [ + { + path: '', + name: 'operationRecords', + component: () => import('@/views/resource/resource-manage/operationRecord/index'), + meta: { + activeKey: 'record', + isShowBreadcrumb: true, + icon: 'hcm-icon bkhcm-icon-operation-record', + }, + }, + { + path: 'detail', + name: 'operationRecordsDetail', + component: () => import('@/views/resource/resource-manage/operationRecord/RecordDetail/index'), + meta: { + activeKey: 'record', + isShowBreadcrumb: true, + icon: 'hcm-icon bkhcm-icon-cert', + }, + }, + ], meta: { activeKey: 'record', isShowBreadcrumb: true, @@ -387,6 +509,18 @@ const businesseMenus: RouteRecordRaw[] = [ notMenu: true, }, }, + { + path: '/business/service/service-apply/clb', + name: 'applyClb', + component: () => import('@/views/service/service-apply/clb'), + meta: { + backRouter: -1, + activeKey: 'businessClb', + breadcrumb: ['资源管理', '负载均衡'], + notMenu: true, + applyRes: 'lb', + }, + }, ], }, ]; diff --git a/front/src/router/module/common.ts b/front/src/router/module/common.ts index f32791f455..9c150a09e4 100644 --- a/front/src/router/module/common.ts +++ b/front/src/router/module/common.ts @@ -25,29 +25,29 @@ const common: RouteRecordRaw[] = [ breadcrumb: ['申请权限'], }, }, -// { -// path: '/root', -// name: 'root', -// alias: '/', -// component: import('@/views/home/RootPath'), -// }, -// { -// path: '/test', -// name: 'test', -// component: () => import('@/views/test/index'), -// }, -// { -// path: '/exception', -// name: 'exception', -// component: () => import('@/views/exception'), -// meta: { -// isHideNav: true, -// }, -// }, -// { -// path: '/403', -// name: '403', -// component: () => import('@/views/403'), -// }, + // { + // path: '/root', + // name: 'root', + // alias: '/', + // component: import('@/views/home/RootPath'), + // }, + // { + // path: '/test', + // name: 'test', + // component: () => import('@/views/test/index'), + // }, + // { + // path: '/exception', + // name: 'exception', + // component: () => import('@/views/exception'), + // meta: { + // isHideNav: true, + // }, + // }, + // { + // path: '/403', + // name: '403', + // component: () => import('@/views/403'), + // }, ]; export default common; diff --git a/front/src/router/module/resource.ts b/front/src/router/module/resource.ts index 82f3b1402c..477b9e3f4d 100644 --- a/front/src/router/module/resource.ts +++ b/front/src/router/module/resource.ts @@ -72,6 +72,16 @@ const resourceMenus: RouteRecordRaw[] = [ notMenu: true, }, }, + { + path: '/resource/record/detail', + name: 'resourceRecordDetail', + component: () => import('@/views/resource/resource-manage/operationRecord/RecordDetail/index'), + meta: { + activeKey: 'resourceResource', + breadcrumb: [t('云管'), t('资源'), '详情'], + notMenu: true, + }, + }, { path: '/resource/service-apply/cvm', name: 'resourceApplyCvm', @@ -109,8 +119,20 @@ const resourceMenus: RouteRecordRaw[] = [ meta: { backRouter: -1, activeKey: 'resourceResource', - breadcrumb: [t('云管'), t('资源'), '新建子网'], + breadcrumb: [t('云管'), t('资源'), '新建子网'], + notMenu: true, + }, + }, + { + path: '/resource/service-apply/clb', + name: 'resourceApplyClb', + component: () => import('@/views/service/service-apply/clb'), + meta: { + backRouter: -1, + activeKey: 'resourceResource', + breadcrumb: [t('云管'), t('资源'), '新建负载均衡'], notMenu: true, + applyRes: 'lb', }, }, { diff --git a/front/src/router/module/scheme.ts b/front/src/router/module/scheme.ts index a412d5a9f3..aea8f62946 100644 --- a/front/src/router/module/scheme.ts +++ b/front/src/router/module/scheme.ts @@ -30,7 +30,7 @@ const scheme: RouteRecordRaw[] = [ meta: { notMenu: true, }, - } + }, ], }, ]; diff --git a/front/src/store/account.ts b/front/src/store/account.ts index 60cf79588d..a6f9261b89 100644 --- a/front/src/store/account.ts +++ b/front/src/store/account.ts @@ -143,7 +143,7 @@ export const useAccountStore = defineStore({ * @return {*} */ async accountDeleteValidate(id: number) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/accounts/${id}/delete/validate`) ; + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/accounts/${id}/delete/validate`); }, /** * @description: 申请账号 @@ -188,8 +188,9 @@ export const useAccountStore = defineStore({ async updateAccountList(data: any) { console.log('data', data); - this.accountList = data?.map(({ id, name }: {id: string, name: string}) => ({ - id, name, + this.accountList = data?.map(({ id, name }: { id: string; name: string }) => ({ + id, + name, })); console.log('this.accountList', this.accountList); }, diff --git a/front/src/store/audit.ts b/front/src/store/audit.ts index 129f129a14..04a4084337 100644 --- a/front/src/store/audit.ts +++ b/front/src/store/audit.ts @@ -5,8 +5,7 @@ const { BK_HCM_AJAX_URL_PREFIX } = window.PROJECT_CONFIG; export const useAuditStore = defineStore({ id: 'audit', - state: () => ({ - }), + state: () => ({}), actions: { list(data: any, bizId: number) { if (bizId > 0) { diff --git a/front/src/store/business.ts b/front/src/store/business.ts index 4fdb36008e..99c1cecfb9 100644 --- a/front/src/store/business.ts +++ b/front/src/store/business.ts @@ -2,22 +2,53 @@ import http from '@/http'; import { defineStore } from 'pinia'; import { useAccountStore } from '@/store'; +import { getQueryStringParams } from '@/common/util'; +import { AsyncTaskDetailResp, ClbQuotasResp, LbPriceInquiryResp } from '@/typings'; const { BK_HCM_AJAX_URL_PREFIX } = window.PROJECT_CONFIG; // 获取 const getBusinessApiPath = () => { const store = useAccountStore(); + const bizs = getQueryStringParams('bizs'); if (location.href.includes('business')) { - return `bizs/${store.bizs}/`; + return `bizs/${store.bizs || bizs}/`; } return ''; }; export const useBusinessStore = defineStore({ id: 'businessStore', - state: () => ({ - }), + state: () => ({}), actions: { + /** + * @description: 获取资源列表 - 业务下 + * @param {any} data + * @param {string} type + * @return {*} + */ + list(data: any, type: string) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}${type}/list`, data); + }, + getCommonList(data: any, url: string) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}${url}`, data); + }, + /** + * 根据id获取对应资源详情信息 + * @param type 资源类型 + * @param id 资源id + * @returns 资源详情信息 + */ + detail(type: string, id: number | string) { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}${type}/${id}`); + }, + /** + * common-批量删除资源 + * @param type 资源类型 + * @param data 资源ids + */ + deleteBatch(type: string, data: { ids: string[] }) { + return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}${type}/batch`, { data }); + }, /** * @description: 新增安全组 * @param {any} data @@ -64,5 +95,416 @@ export const useBusinessStore = defineStore({ if (isRes) return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/subnets/create`, data); return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/bizs/${bizs}/subnets/create`, data); }, + /** + * 获取当前CLB绑定的安全组列表 + */ + async listCLBSecurityGroups(clb_id: string) { + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/res/load_balancer/${clb_id}`, + ); + }, + /** + * 给当前负载均衡绑定安全组 + */ + async bindSecurityToCLB(data: { bk_biz_id: number; lb_id: string; security_group_ids: Array }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/associate/load_balancers`, + data, + ); + }, + /** + * 给当前负载均衡解绑指定的安全组 + */ + async unbindSecurityToCLB(data: { bk_biz_id: number; lb_id: string; security_group_id: string }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/disassociate/load_balancers`, + data, + ); + }, + /* + * 新建目标组 + */ + createTargetGroups(data: any) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/create`, data); + }, + /** + * 获取目标组详情(基本信息和健康检查) + */ + getTargetGroupDetail(id: string) { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${id}`); + }, + /** + * 批量删除目标组 + */ + deleteTargetGroups(data: { bk_biz_id: number; ids: string[] }) { + return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/batch`, { data }); + }, + /** + * 编辑目标组基本信息 + */ + editTargetGroups(data: any) { + return http.patch(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${data.id}`, data); + }, + /** + * 目标组绑定RS列表 + */ + getRsList(tg_id: string, data: { filter: Object; page: Object }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${tg_id}/targets/list`, + data, + ); + }, + /** + * 查询全量的RS列表 + */ + getAllRsList(data: any) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}cvms/list`, data); + }, + /* + * 业务下腾讯云监听器域名列表 + * @param id 监听器ID + * @returns 域名列表 + */ + getDomainListByListenerId(id: string) { + return http.post(` + ${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/listeners/${id}/domains/list + `); + }, + /** + * 获取负载均衡基本信息 + */ + getLbDetail(id: string) { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${id}`); + }, + /** + * 更新负载均衡 + */ + updateLbDetail(data: { + bk_biz_id?: string; + id: string; // 负载均衡ID + name?: string; // 名字 + internet_charge_type?: string; // 计费模式 + internet_max_bandwidth_out?: number; // 最大出带宽 + delete_protect?: boolean; // 删除 + load_balancer_pass_to_target?: boolean; // Target是否放通来自CLB的流量 + memo?: string; // 备注 + }) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/load_balancers/${data.id}`, + data, + ); + }, + /** + * 新增监听器 + * @param data 监听器信息 + */ + createListener(data: any) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${data.lb_id}/listeners/create`, + data, + ); + }, + /** + * 更新监听器 + * @param data 监听器信息 + */ + updateListener(data: any) { + return http.patch(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}listeners/${data.id}`, data); + }, + /* + * 新建域名、新建url + */ + createRules(data: { + bk_biz_id?: number; // 业务ID + lbl_id: string; // 监听器id + // rules: Record; // 待创建规则 + target_group_id?: string; + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/listeners/${ + data.lbl_id + }/rules/create`, + data, + ); + }, + /** + * 更新域名 + */ + updateDomains( + listenerId: string, + data: { + bk_biz_id?: number; + lbl_id: string; // 监听器ID + domain: string; // 新域名 + new_domain?: string; // 新域名 + certificate?: Object; // 证书信息 + default_server?: boolean; // 是否设为默认域名 + }, + ) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}listeners/${listenerId}/domains`, + data, + ); + }, + /** + * 删除域名、URL + */ + deleteRules( + listenerId: string, + data: { + bk_biz_id?: number; // 业务ID + lbl_id: string; // 监听器id + rule_ids?: string[]; // URL规则ID数组 + domain?: string; // 按域名删除, 没有指定规则id的时候必填 + new_default_domain?: string; // 新默认域名,删除的域名是默认域名的时候需要指定 + }, + ) { + return http.delete( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/listeners/${listenerId}/rules/batch`, + { data }, + ); + }, + /** + * 批量删除域名 + */ + batchDeleteDomains(data: { + bk_biz_id?: number; // 业务ID + lbl_id: string; // 监听器id + domains: string[]; // 要删除的域名 + new_default_domain?: string; // 新默认域名,删除的域名是默认域名的时候需要指定 + }) { + return http.delete( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/listeners/${ + data.lbl_id + }/rules/by/domains/batch`, + { data }, + ); + }, + /** + * 更新URL规则 + */ + updateUrl(data: { + bk_biz_id?: number; // 业务ID + lbl_id: string; // 监听器id + rule_id: string; // URL规则ID数组 + url: string; // 监听的url + scheduler: string; // 均衡方式 + certificate?: Record; // 证书信息,当协议为HTTPS时必传 + target_group_id?: string; + }) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/tcloud/listeners/${data.lbl_id}/rules/${ + data.rule_id + }`, + data, + ); + }, + /** + * 业务下给指定目标组批量添加RS + * @param data rs列表 + */ + batchAddTargets(data: { + account_id: string; + target_groups: { + target_group_id: string; + targets: { inst_type: string; cloud_inst_id: string; port: number; weight: number }[]; + }[]; + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/targets/create`, + data, + ); + }, + /** + * 业务下批量修改RS端口 + * @param target_group_id 目标组id + * @param data { target_ids, new_port } + */ + batchUpdateRsPort(target_group_id: string, data: any) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${target_group_id}/targets/port`, + data, + ); + }, + /** + * 业务下批量修改RS权重 + * @param target_group_id 目标组id + * @param data { target_ids, new_weight } + */ + batchUpdateRsWeight(target_group_id: string, data: any) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${target_group_id}/targets/weight`, + data, + ); + }, + /** + * 查询操作记录异步记录指定批次的子任务列表 + * @param data + * @returns + */ + getAsyncTaskList(data: { + audit_id: string; // 操作记录ID + flow_id: string; // 任务ID + action_id: string; // 子任务ID + }) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}audits/async_task/list`, data); + }, + /** + * 查询操作记录异步任务进度流 + */ + getAsyncFlowList(data: { + audit_id: number; // 操作记录ID + flow_id: string; // 任务ID + }) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}audits/async_flow/list`, data); + }, + /** + * 更新目标组健康检查 + */ + updateHealthCheck(data: { + id: string; // 目标组ID + health_check: { + health_switch: 0 | 1; // 是否开启健康检查:1(开启)、0(关闭) + time_out: number; // 健康检查的响应超时时间,可选值:2~60,单位:秒 + interval_time: number; // 健康检查探测间隔时间 + health_num: number; // 健康阈值 + un_health_num: number; // 不健康阈值 + check_port: number; // 自定义探测相关参数。健康检查端口,默认为后端服务的端口 + check_type: 'TCP' | 'HTTP' | 'HTTPS' | 'GRPC' | 'PING' | 'CUSTOM'; // 健康检查使用的协议 + http_code: string; // http状态码,用于健康检查 + http_version: string; // HTTP版本 + http_check_path?: string; // 健康检查路径(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式) + http_check_domain?: string; // 健康检查域名 + http_check_method?: 'HEAD' | 'GET'; // 健康检查方法(仅适用于HTTP/HTTPS转发规则、TCP监听器的HTTP健康检查方式),默认值:HEAD,可选值HEAD或GET + source_ip_type: 0 | 1; // 健康检查源IP类型:0(使用LB的VIP作为源IP),1(使用100.64网段IP作为源IP) + context_type: 'HEX' | 'TEXT'; // 健康检查的输入格式 + }; + }) { + return http.patch( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${data.id}/health_check`, + data, + ); + }, + /** + * 获取目标组列表 + */ + getTargetGroupList(data: any) { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/list`, data); + }, + /* + * 业务下给指定目标组移除RS + * @param data + */ + batchDeleteTargets(data: { + account_id: string; + target_groups: { target_group_id: string; target_ids: string[] }[]; + }) { + return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/targets/batch`, { + data, + }); + }, + /** + * 查询指定的目标组绑定的负载均衡下的端口健康信息 + * @param target_group_id 目标组id + * @param data { cloud_lb_ids: 云负载均衡ID数组 } + */ + asyncGetTargetsHealth(target_group_id: string, data: { cloud_lb_ids: string[] }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}target_groups/${target_group_id}/targets/health`, + data, + ); + }, + /** + * 查询指定的负载均衡下的监听器数量 + * @param data { lb_ids: 负载均衡ID数组 } + */ + asyncGetListenerCount(data: { lb_ids: string[] }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/listeners/count`, + data, + ); + }, + /** + * 重试异步任务 + */ + retryAsyncTask(data: { + bk_biz_id?: number; // 业务ID + lb_id: string; // 负载均衡ID + flow_id: string; // Flow ID + task_id: string; // 待重新执行的Task ID + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${data.lb_id}/async_tasks/retry`, + data, + ); + }, + /** + * 终止指定异步任务操作 + */ + endTask(data: { + bk_biz_id?: number; // 业务ID + lb_id: string; // 负载均衡ID + flow_id: string; // Flow ID + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${ + data.lb_id + }/async_flows/terminate`, + data, + ); + }, + /** + * 获取任务终止后rs的状态,仅支持任务终止后五分钟内 + */ + getFlowResults(data: { + bk_biz_id?: number; // 业务ID + lb_id?: string; // 负载均衡ID + flow_id: string; // Flow ID + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${data.lb_id}/async_tasks/result`, + data, + ); + }, + /** + * 复制flow参数重新执行 + */ + excuteTask(data: { + bk_biz_id?: number; // 业务ID + lb_id: string; // 负载均衡ID + flow_id: string; // Flow ID + }) { + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${data.lb_id}/async_flows/clone`, + data, + ); + }, + /** + * 获取腾讯云账号负载均衡的配额 + * @param data { account_id: 云账号ID region: 地域 } + */ + getClbQuotas(data: { account_id: string; region: string }): Promise { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/quotas`, data); + }, + /** + * 查询负载均衡价格 + * @param data 负载均衡参数 + */ + lbPricesInquiry(data: any): Promise { + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/load_balancers/prices/inquiry`, data); + }, + /** + * 查询负载均衡状态锁定详情 + */ + getLBLockStatus(id: string) { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}load_balancers/${id}/lock/status`); + }, + /** + * 查询异步任务详情 + * @param flowId 异步任务id + */ + getAsyncTaskDetail(flowId: string): Promise { + return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/async_task/flows/${flowId}`); + }, }, }); diff --git a/front/src/store/common.ts b/front/src/store/common.ts index 09efe2ce58..486fd3e264 100644 --- a/front/src/store/common.ts +++ b/front/src/store/common.ts @@ -13,11 +13,20 @@ export const useCommonStore = defineStore({ authVerifyParams: null as any, pageAuthData: [ // { type: 'cloud_selection_scheme', action: 'create', id: 'cloud_selection_recommend' }, - { type: 'cloud_selection_scheme', action: 'create', id: 'cloud_selection_recommend', path: '/scheme/recommendation' }, + { + type: 'cloud_selection_scheme', + action: 'create', + id: 'cloud_selection_recommend', + path: '/scheme/recommendation', + }, { type: 'cloud_selection_scheme', action: 'find', id: 'cloud_selection_find', path: '/scheme/deployment/list' }, { type: 'cloud_selection_scheme', action: 'update', id: 'cloud_selection_edit', path: '/scheme/recommendation' }, - { type: 'cloud_selection_scheme', action: 'delete', id: 'cloud_selection_delete', path: '/scheme/recommendation' }, - + { + type: 'cloud_selection_scheme', + action: 'delete', + id: 'cloud_selection_delete', + path: '/scheme/recommendation', + }, { type: 'account', action: 'find', id: 'account_find', path: '/resource/account' }, // 如果是列表查看权限 需要加上path { type: 'account', action: 'import', id: 'account_import', path: '/resource/resource' }, @@ -27,27 +36,32 @@ export const useCommonStore = defineStore({ { type: 'biz', action: 'access', id: 'biz_access' }, // 目前资源下主机、vpc、子网、安全组、云硬盘、网络接口、弹性IP、路由表、镜像等都当作iaas统一鉴权,为了方便,使用cvm当作整个iaas鉴权 - { type: 'cvm', action: 'find', id: 'resource_find', path: ['/resource/resource'] }, // 业务 资源对应的路径 - { type: 'cvm', action: 'create', id: 'iaas_resource_create' }, // iaas创建 - { type: 'cvm', action: 'update', id: 'iaas_resource_operate' }, // iaas编辑更新 - { type: 'cvm', action: 'delete', id: 'iaas_resource_delete' }, // iaas删除 + { type: 'cvm', action: 'find', id: 'resource_find', path: ['/resource/resource'] }, // 业务 资源对应的路径 + { type: 'cvm', action: 'create', id: 'iaas_resource_create' }, // iaas创建 + { type: 'cvm', action: 'update', id: 'iaas_resource_operate' }, // iaas编辑更新 + { type: 'cvm', action: 'delete', id: 'iaas_resource_delete' }, // iaas删除 // // 安全组 // eslint-disable-next-line max-len // { type: 'security_group', action: 'find', id: 'resource_find_security', path: ['/business/security', '/resource/resource'] }, // 业务 资源对应的路径 // { type: 'security_group', action: 'delete', id: 'iaas_resource_delete_security' }, // iaas删除 - // 目前业务下主机、vpc、子网、安全组、云硬盘、网络接口、弹性IP、路由表、镜像等都当作iaas统一鉴权,为了方便,使用cvm当作整个业务iaas鉴权 - { type: 'cvm', action: 'find', id: 'resource_find', bk_biz_id: 0 }, // 业务 资源对应的路径 - { type: 'cvm', action: 'create', id: 'biz_iaas_resource_create', bk_biz_id: 0 }, // 业务iaas创建 - { type: 'cvm', action: 'update', id: 'biz_iaas_resource_operate', bk_biz_id: 0 }, // 业务iaas编辑更新 - { type: 'cvm', action: 'delete', id: 'biz_iaas_resource_delete', bk_biz_id: 0 }, // 业务iaas删除 + { type: 'cvm', action: 'find', id: 'resource_find', bk_biz_id: 0 }, // 业务 资源对应的路径 + { type: 'cvm', action: 'create', id: 'biz_iaas_resource_create', bk_biz_id: 0 }, // 业务iaas创建 + { type: 'cvm', action: 'update', id: 'biz_iaas_resource_operate', bk_biz_id: 0 }, // 业务iaas编辑更新 + { type: 'cvm', action: 'delete', id: 'biz_iaas_resource_delete', bk_biz_id: 0 }, // 业务iaas删除 + + { type: 'biz_audit', action: 'find', id: 'resource_audit_find' }, // 审计查看权限 - { type: 'biz_audit', action: 'find', id: 'resource_audit_find' }, // 审计查看权限 + { type: 'recycle_bin', action: 'find', id: 'recycle_bin_find', path: '/resource/recyclebin' }, // 回收站查看权限 + { type: 'recycle_bin', action: 'recycle', id: 'recycle_bin_manage' }, // 回收站管理 - { type: 'recycle_bin', action: 'find', id: 'recycle_bin_find', path: '/resource/recyclebin' }, // 回收站查看权限 - { type: 'recycle_bin', action: 'recycle', id: 'recycle_bin_manage' }, // 回收站管理 + // 证书权限 + { type: 'cert', action: 'create', id: 'cert_resource_create' }, // 资源 证书上传 + { type: 'cert', action: 'create', id: 'biz_cert_resource_create', bk_biz_id: 0 }, // 业务 证书上传 + { type: 'cert', action: 'delete', id: 'cert_resource_delete' }, // 资源 证书删除 + { type: 'cert', action: 'delete', id: 'biz_cert_resource_delete', bk_biz_id: 0 }, // 业务 证书删除 ], }), actions: { diff --git a/front/src/store/departments.ts b/front/src/store/departments.ts index a4bb5162ac..6635b3b573 100644 --- a/front/src/store/departments.ts +++ b/front/src/store/departments.ts @@ -25,7 +25,7 @@ export const useDepartmentStore = defineStore({ const headTag = document.getElementsByTagName('head')[0]; // @ts-ignore - window[params.callback] = ({ data, result }: { data: Staff[], result: boolean }) => { + window[params.callback] = ({ data, result }: { data: Staff[]; result: boolean }) => { if (result) { if (field === 'level') { data.forEach((item: any) => { @@ -76,4 +76,3 @@ export const useDepartmentStore = defineStore({ }, }, }); - diff --git a/front/src/store/index.ts b/front/src/store/index.ts index c16394eec8..b4ea463c15 100644 --- a/front/src/store/index.ts +++ b/front/src/store/index.ts @@ -10,6 +10,7 @@ export * from './resource'; export * from './common'; export * from './host'; export * from './scheme'; +export * from './loadbalancer'; // @ts-ignore if (import.meta.hot) { diff --git a/front/src/store/loadbalancer.ts b/front/src/store/loadbalancer.ts new file mode 100644 index 0000000000..0f7d7f8b0f --- /dev/null +++ b/front/src/store/loadbalancer.ts @@ -0,0 +1,71 @@ +import { defineStore } from 'pinia'; +import { Ref, ref } from 'vue'; + +export interface ITreeNode { + [key: string]: any; + lb: Record; // 当前域名节点所属的负载均衡信息,非域名节点时不生效 + listener: Record; // 当前域名节点所属的监听器信息,非域名节点时不生效 +} +// 目标组视角 - 操作场景 +export type TGOperationScene = + | 'add' // 新建目标组 + | 'edit' // 编辑目标组基本信息 + | 'BatchDelete' // 批量删除目标组 + | 'AddRs' // 添加rs + | 'BatchAddRs' // 批量添加rs + | 'BatchDeleteRs' // 批量删除rs + | 'port' // 批量修改端口 + | 'weight'; // 批量修改权重 + +export const useLoadBalancerStore = defineStore('load-balancer', () => { + // state - 目标组id + const targetGroupId = ref(''); + const setTargetGroupId = (v: string) => { + targetGroupId.value = v; + }; + + // state - lb-tree - 当前选中的资源 + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const currentSelectedTreeNode: Ref = ref({} as ITreeNode); + const setCurrentSelectedTreeNode = (node: ITreeNode) => { + // 其中, node 可能为 lb, listener, domain 节点 + currentSelectedTreeNode.value = node; + }; + + // state - 目标组操作场景 + const updateCount = ref(0); // 记录修改次数: 当值为2时, 重新对场景进行判断(判断第一次回显数据的影响) + const setUpdateCount = (v: number) => { + updateCount.value = v; + }; + const currentScene = ref(); // 记录当前操作类型 + const setCurrentScene = (v: TGOperationScene) => { + currentScene.value = v; + }; + + // state - lb-tree的搜索条件, 用于链接跳转 + const lbTreeSearchTarget = ref(); + const setLbTreeSearchTarget = (v: any) => { + lbTreeSearchTarget.value = v; + }; + + // state - 目标组视角下的搜索条件, 用于链接跳转 + const tgSearchTarget = ref(); + const setTgSearchTarget = (v: any) => { + tgSearchTarget.value = v; + }; + + return { + targetGroupId, + setTargetGroupId, + currentSelectedTreeNode, + setCurrentSelectedTreeNode, + updateCount, + setUpdateCount, + currentScene, + setCurrentScene, + lbTreeSearchTarget, + setLbTreeSearchTarget, + tgSearchTarget, + setTgSearchTarget, + }; +}); diff --git a/front/src/store/resource.ts b/front/src/store/resource.ts index 6c6d77fd02..49384fef1e 100644 --- a/front/src/store/resource.ts +++ b/front/src/store/resource.ts @@ -39,7 +39,9 @@ export const useResourceStore = defineStore({ }, detail(type: string, id: number | string, vendor?: string) { if (vendor) { - return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}vendors/${vendor}/${type}/${id}`); + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}vendors/${vendor}/${type}/${id}`, + ); } return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}${type}/${id}`); }, @@ -62,7 +64,12 @@ export const useResourceStore = defineStore({ return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/web/cloud_areas/list`, data); }, getRouteList(type: string, id: string, data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}vendors/${type}/route_tables/${id}/routes/list`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath( + type, + )}vendors/${type}/route_tables/${id}/routes/list`, + data, + ); }, // 分配到业务下 assignBusiness(type: string, data: any) { @@ -81,32 +88,44 @@ export const useResourceStore = defineStore({ return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}subnets/${id}/ips/count`); }, getEipListByCvmId(vendor: string, id: string) { - return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/${vendor}/eips/cvms/${id}`); + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/${vendor}/eips/cvms/${id}`, + ); }, getDiskListByCvmId(vendor: string, id: string) { - return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/${vendor}/disks/cvms/${id}`); + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}vendors/${vendor}/disks/cvms/${id}`, + ); }, // 获取根据主机安全组列表 getSecurityGroupsListByCvmId(id: string) { return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/cvms/${id}`); }, // 操作主机相关 - cvmOperate(type: string, data: {ids: string[]}) { + cvmOperate(type: string, data: { ids: string[] }) { return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}cvms/batch/${type}`, data); }, // 主机分配 - cvmAssignBizs(data: {cvm_ids: string[], bk_biz_id: string}) { + cvmAssignBizs(data: { cvm_ids: string[]; bk_biz_id: string }) { return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}cvms/assign/bizs`, data); }, // 网络接口 cvmNetwork(type: string, id: string) { - return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}vendors/${type}/network_interfaces/cvms/${id}`); + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath( + type, + )}vendors/${type}/network_interfaces/cvms/${id}`, + ); }, getCommonList(data: any, url: string) { return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}${url}`, data); }, getNetworkList(type: string, id: string) { - return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}vendors/${type}/network_interfaces/cvms/${id}`); + return http.get( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath( + type, + )}vendors/${type}/network_interfaces/cvms/${id}`, + ); }, attachDisk(data: any) { return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath('disks')}disks/attach`, data); @@ -125,7 +144,9 @@ export const useResourceStore = defineStore({ }, // 销毁 deleteRecycledData(type: string, data: any) { - return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}recycled/${type}/batch`, { data }); + return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath(type)}recycled/${type}/batch`, { + data, + }); }, // 回收 recoverRecycledData(type: string, data: any) { @@ -159,32 +180,50 @@ export const useResourceStore = defineStore({ // 绑定主机安全组信息 bindSecurityInfo(type: string, data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/associate/${type}`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/associate/${type}`, + data, + ); }, // 解绑主机安全组信息 unBindSecurityInfo(type: string, data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/disassociate/${type}`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}security_groups/disassociate/${type}`, + data, + ); }, // 获取未绑定eip的网络接口列表 getUnbindEipNetworkList(data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}network_interfaces/associate/list`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}network_interfaces/associate/list`, + data, + ); }, // 获取未绑定disk的主机列表 getUnbindDiskCvms(data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}disk_cvm_rels/with/cvms/list`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}disk_cvm_rels/with/cvms/list`, + data, + ); }, // 获取未绑定主机的disk列表 getUnbindCvmDisks(data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}disk_cvm_rels/with/disks/without/cvm/list`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}disk_cvm_rels/with/disks/without/cvm/list`, + data, + ); }, // 获取未绑定主机的eips列表 getUnbindCvmEips(data: any) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}eip_cvm_rels/with/eips/without/cvm/list`, data); + return http.post( + `${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/${getBusinessApiPath()}eip_cvm_rels/with/eips/without/cvm/list`, + data, + ); }, // 创建 create(type: string, data: any) { diff --git a/front/src/store/scheme.ts b/front/src/store/scheme.ts index a5499c027e..a25d05db6f 100644 --- a/front/src/store/scheme.ts +++ b/front/src/store/scheme.ts @@ -2,11 +2,21 @@ import http from '@/http'; import { defineStore } from 'pinia'; import { QueryFilterType, IPageQuery, IQueryResData } from '@/typings/common'; -import { IAreaInfo, IBizTypeResData, ICountriesListResData, IGenerateSchemesResData, IUserDistributionResData, IGenerateSchemesReqParams, IRecommendSchemeList, IIdcServiceAreaRel, IIdcInfo, ISchemeSelectorItem } from '@/typings/scheme'; +import { + IAreaInfo, + IBizTypeResData, + ICountriesListResData, + IGenerateSchemesResData, + IUserDistributionResData, + IGenerateSchemesReqParams, + IRecommendSchemeList, + IIdcServiceAreaRel, + IIdcInfo, + ISchemeSelectorItem, +} from '@/typings/scheme'; const { BK_HCM_AJAX_URL_PREFIX } = window.PROJECT_CONFIG; - // 资源选型模块相关状态管理和接口定义 export const useSchemeStore = defineStore({ id: 'schemeStore', @@ -54,7 +64,9 @@ export const useSchemeStore = defineStore({ this.schemeData = data; }, sortSchemes(choice: string, isDes = true) { - this.recommendationSchemes = this.recommendationSchemes.sort((a, b) => (b[choice] - a[choice]) * (isDes ? 1 : -1)); + this.recommendationSchemes = this.recommendationSchemes.sort( + (a, b) => (b[choice] - a[choice]) * (isDes ? 1 : -1), + ); }, /** * 获取资源选型方案列表 @@ -86,9 +98,9 @@ export const useSchemeStore = defineStore({ * @param id 方案id * @param data 方案数据 */ - updateCloudSelectionScheme(id: string, data: { name: string; bk_biz_id?: number; }) { + updateCloudSelectionScheme(id: string, data: { name: string; bk_biz_id?: number }) { const { name, bk_biz_id } = data; - const params: { name: string; bk_biz_id?: number; } = { name }; + const params: { name: string; bk_biz_id?: number } = { name }; if (bk_biz_id) { params.bk_biz_id = bk_biz_id; } @@ -102,17 +114,20 @@ export const useSchemeStore = defineStore({ return http.get(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/collections/cloud_selection_scheme/list`); }, /** 添加收藏 - * @param id 方案id - * @returns - */ + * @param id 方案id + * @returns + */ createCollection(id: string) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/collections/create`, { res_type: 'cloud_selection_scheme', res_id: id }); + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/collections/create`, { + res_type: 'cloud_selection_scheme', + res_id: id, + }); }, /** - * 取消收藏 - * @param id 收藏id - * @returns - */ + * 取消收藏 + * @param id 收藏id + * @returns + */ deleteCollection(id: number) { return http.delete(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/collections/${id}`); }, @@ -131,7 +146,10 @@ export const useSchemeStore = defineStore({ * @param ids idc列表 */ queryBizLatency(topo: IAreaInfo[], ids: string[]) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/selections/latency/biz/query`, { area_topo: topo, idc_ids: ids }); + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/selections/latency/biz/query`, { + area_topo: topo, + idc_ids: ids, + }); }, /** * 查询ping延迟数据 @@ -139,7 +157,10 @@ export const useSchemeStore = defineStore({ * @param ids idc列表 */ queryPingLatency(topo: IAreaInfo[], ids: string[]) { - return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/selections/latency/ping/query`, { area_topo: topo, idc_ids: ids }); + return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/selections/latency/ping/query`, { + area_topo: topo, + idc_ids: ids, + }); }, /** * 获取云选型数据支持的国家列表 @@ -179,7 +200,11 @@ export const useSchemeStore = defineStore({ * @param area_topo 国家城市拓扑 * @returns 服务区 */ - queryIdcServiceArea(datasource: string, idc_ids: Array, area_topo: Array): IQueryResData> { + queryIdcServiceArea( + datasource: string, + idc_ids: Array, + area_topo: Array, + ): IQueryResData> { return http.post(`${BK_HCM_AJAX_URL_PREFIX}/api/v1/cloud/selections/idcs/service_areas/${datasource}/query`, { idc_ids, area_topo, diff --git a/front/src/store/staff.ts b/front/src/store/staff.ts index 7384a2d138..5b80aa41a4 100644 --- a/front/src/store/staff.ts +++ b/front/src/store/staff.ts @@ -4,7 +4,6 @@ import QueryString from 'qs'; import { shallowRef } from 'vue'; const { BK_COMPONENT_API_URL } = window.PROJECT_CONFIG; - export const useStaffStore = defineStore({ id: 'staffStore', state: () => ({ @@ -31,7 +30,7 @@ export const useStaffStore = defineStore({ const headTag = document.getElementsByTagName('head')[0]; // @ts-ignore - window[params.callback] = ({ data, result }: { data: any, result: boolean }) => { + window[params.callback] = ({ data, result }: { data: any; result: boolean }) => { if (result) { this.fetching = false; // this.list = [...data.results, ...this.list]; diff --git a/front/src/store/usePagePermissionStore.ts b/front/src/store/usePagePermissionStore.ts index 2b00010f64..0f3caa3716 100644 --- a/front/src/store/usePagePermissionStore.ts +++ b/front/src/store/usePagePermissionStore.ts @@ -5,8 +5,8 @@ import { ref } from 'vue'; export default defineStore('usePagePermissionStore', () => { const hasPagePermission = ref(true); const permissionMsg = ref(''); - const setHasPagePermission = (val: boolean) => hasPagePermission.value = val; - const setPermissionMsg = (val: string) => permissionMsg.value = val; + const setHasPagePermission = (val: boolean) => (hasPagePermission.value = val); + const setPermissionMsg = (val: string) => (permissionMsg.value = val); const logout = () => { deleteCookie('bk_token'); diff --git a/front/src/store/useRegionsStore.ts b/front/src/store/useRegionsStore.ts index 91752a132a..b88c3c58dd 100644 --- a/front/src/store/useRegionsStore.ts +++ b/front/src/store/useRegionsStore.ts @@ -7,7 +7,8 @@ import { CLOUD_AREA_REGION_AWS, CLOUD_AREA_REGION_GCP_EN, CLOUD_AREA_REGION_AWS_EN, - VendorEnum } from '@/common/constant'; + VendorEnum, +} from '@/common/constant'; import { swapMapKeysAndValuesToObj } from '@/common/util'; export const useRegionsStore = defineStore('useRegions', () => { @@ -49,17 +50,24 @@ export const useRegionsStore = defineStore('useRegions', () => { const getRegionName = (vendor: VendorEnum, id: string) => { if (!isChinese) return id; + let regionName; switch (vendor) { case VendorEnum.AWS: - return CLOUD_AREA_REGION_AWS[id] || id; + regionName = CLOUD_AREA_REGION_AWS[id] || id; + break; case VendorEnum.GCP: - return CLOUD_AREA_REGION_GCP[id] || id; + regionName = CLOUD_AREA_REGION_GCP[id] || id; + break; case VendorEnum.HUAWEI: - return huawei.value.get(id) || id; + regionName = huawei.value.get(id) || id; + break; case VendorEnum.TCLOUD: - return tcloud.value.get(id) || id; + regionName = tcloud.value.get(id) || id; + break; + default: + regionName = id; } - return id; + return regionName || '--'; }; const getRegionNameEN = (id: string) => { @@ -69,11 +77,11 @@ export const useRegionsStore = defineStore('useRegions', () => { if (CLOUD_AREA_REGION_TCLOUD_EN[id]) { vendor.value = VendorEnum.TCLOUD; return CLOUD_AREA_REGION_TCLOUD_EN[id]; - }; + } if (CLOUD_AREA_REGION_HUAWEI_EN[id]) { vendor.value = VendorEnum.HUAWEI; return CLOUD_AREA_REGION_HUAWEI_EN[id]; - }; + } if (CLOUD_AREA_REGION_AWS_EN[id]) { vendor.value = VendorEnum.AWS; return CLOUD_AREA_REGION_AWS_EN[id]; diff --git a/front/src/store/useResourceAccountStore.ts b/front/src/store/useResourceAccountStore.ts index 9e26b2f4ce..98ee51a501 100644 --- a/front/src/store/useResourceAccountStore.ts +++ b/front/src/store/useResourceAccountStore.ts @@ -30,7 +30,7 @@ export type IAccount = { cloud_subscription_name?: string; cloud_sub_account_id?: string; cloud_sub_account_name?: string; - } + }; }; export const useResourceAccountStore = defineStore('useResourceAccountStore', () => { diff --git a/front/src/style/override/bkinput.scss b/front/src/style/override/bkinput.scss new file mode 100644 index 0000000000..078098f91e --- /dev/null +++ b/front/src/style/override/bkinput.scss @@ -0,0 +1,5 @@ +.bk-input.no-number-control { + .bk-input--number-control { + display: none; + } +} diff --git a/front/src/style/override/index.scss b/front/src/style/override/index.scss index 7aa8f89996..be9f896f04 100644 --- a/front/src/style/override/index.scss +++ b/front/src/style/override/index.scss @@ -1 +1,2 @@ -@import './bktable.scss'; \ No newline at end of file +@import './bktable.scss'; +@import './bkinput.scss'; \ No newline at end of file diff --git a/front/src/typings/business.ts b/front/src/typings/business.ts index e538adca17..c29834cba5 100644 --- a/front/src/typings/business.ts +++ b/front/src/typings/business.ts @@ -1,7 +1,7 @@ export interface BusinessFormFilter { - vendor: string - account_id: string | number - region: string | number + vendor: string; + account_id: string | number; + region: string | number; } export enum EipStatus { @@ -18,19 +18,19 @@ export enum EipStatus { } export interface IEip { - account_id: string, - bk_biz_id: number, - cloud_id: string, - created_at: string, - creator: string, - id: string, - instance_id: string, - name: string, - public_ip: string, - region: string, - reviser: string, - status: EipStatus, - updated_at: string, - vendor: string, - cvm_id?: string + account_id: string; + bk_biz_id: number; + cloud_id: string; + created_at: string; + creator: string; + id: string; + instance_id: string; + name: string; + public_ip: string; + region: string; + reviser: string; + status: EipStatus; + updated_at: string; + vendor: string; + cvm_id?: string; } diff --git a/front/src/typings/common.ts b/front/src/typings/common.ts index e8c76c1155..355b93065f 100644 --- a/front/src/typings/common.ts +++ b/front/src/typings/common.ts @@ -1,8 +1,8 @@ export interface Verify { - action: string - resource_type: string - bk_biz_id?: number - resource_id?: number + action: string; + resource_type: string; + bk_biz_id?: number; + resource_id?: number; } export enum QueryRuleOPEnum { EQ = 'eq', @@ -16,14 +16,16 @@ export enum QueryRuleOPEnum { CS = 'cs', CIS = 'cis', JSON_EQ = 'json_eq', + JSON_NEQ = 'json_neq', + JSON_OVERLAPS = 'json_overlaps', OR = 'or', AND = 'and', - JSON_CONTAINS = 'json_contains' + JSON_CONTAINS = 'json_contains', } export type QueryFilterType = { op: 'and' | 'or'; - rules: Array + rules: Array; }; export type RulesItem = { @@ -34,8 +36,8 @@ export type RulesItem = { export interface IOption { id: string; - name: string -}; + name: string; +} // 列表接口分页参数 export interface IPageQuery { @@ -52,7 +54,7 @@ interface IBaseResData { } // list 接口响应 -export interface IListResData extends IBaseResData{ +export interface IListResData extends IBaseResData { data: { details: T; count: number }; } diff --git a/front/src/typings/index.ts b/front/src/typings/index.ts index a55711c9d9..ed3e345513 100644 --- a/front/src/typings/index.ts +++ b/front/src/typings/index.ts @@ -4,4 +4,4 @@ export * from './account'; export * from './resource'; export * from './service'; export * from './business'; - +export * from './loadbalancer'; diff --git a/front/src/typings/loadbalancer.ts b/front/src/typings/loadbalancer.ts new file mode 100644 index 0000000000..faab79694b --- /dev/null +++ b/front/src/typings/loadbalancer.ts @@ -0,0 +1,93 @@ +import { IQueryResData } from './common'; + +export type IOriginPage = 'lb' | 'listener' | 'domain'; + +// 腾讯云账号的负载均衡配额名称 +export enum CLB_QUOTA_NAME { + // 用户当前地域下的公网CLB配额 + TOTAL_OPEN_CLB_QUOTA = 'TOTAL_OPEN_CLB_QUOTA', + // 用户当前地域下的内网CLB配额 + TOTAL_INTERNAL_CLB_QUOTA = 'TOTAL_INTERNAL_CLB_QUOTA', + // 一个CLB下的监听器配额 + TOTAL_LISTENER_QUOTA = 'TOTAL_LISTENER_QUOTA', + // 一个监听器下的转发规则配额 + TOTAL_LISTENER_RULE_QUOTA = 'TOTAL_LISTENER_RULE_QUOTA', + // 一条转发规则下可绑定设备的配额 + TOTAL_TARGET_BIND_QUOTA = 'TOTAL_TARGET_BIND_QUOTA', + // 一个CLB实例下跨地域2.0的SNAT IP配额 + TOTAL_SNAP_IP_QUOTA = 'TOTAL_SNAP_IP_QUOTA', + // 用户当前地域下的三网CLB配额 + TOTAL_ISP_CLB_QUOTA = 'TOTAL_ISP_CLB_QUOTA', +} + +// 腾讯云账号的负载均衡配额信息 +export interface ClbQuota { + // 配额名称 + quota_id: CLB_QUOTA_NAME; + // 当前使用数量,为 null 时表示无意义 + quota_current: number; + // 配额数量 + quota_limit: number; +} + +// response - 腾讯云账号的负载均衡配额信息 +export type ClbQuotasResp = IQueryResData; + +// 负载均衡价格 +export interface LbPrice { + // 网络价格信息,对于标准账户,网络在cvm上计费,该选项为空 + bandwidth_price: LbPriceItem; + // 实例价格信息 + instance_price: LbPriceItem; + // lcu 价格信息 + lcu_price: LbPriceItem; +} + +// 负载均衡价格项 +interface LbPriceItem { + // 后续计价单元,HOUR、GB + charge_unit: string; + // 折扣 ,如20.0代表2折 + discount: number; + // 预支费用的折扣价,单位:元 + discount_price: number; + // 预支费用的原价,单位:元 + original_price: number; + // 后付费单价,单位:元 + unit_price: number; + // 后付费的折扣单价,单位:元 + unit_price_discount: number; +} + +// response - 负载均衡价格信息 +export type LbPriceInquiryResp = IQueryResData; + +// 异步任务Flow详情 +export interface AsyncTaskDetail { + id: string; + name: string; + state: string; + reason: string; + creator: string; + reviser: string; + created_at: string; + updated_at: string; +} + +// response - 异步任务Flow详情 +export type AsyncTaskDetailResp = IQueryResData; + +// CLB 规格类型 +export type CLBSpecType = + | 'shared' + | 'clb.c1.small' + | 'clb.c2.medium' + | 'clb.c3.small' + | 'clb.c3.medium' + | 'clb.c4.small' + | 'clb.c4.medium' + | 'clb.c4.large' + | 'clb.c4.xlarge'; + +// 协议类型 +export type Protocol = 'TCP' | 'UDP' | 'HTTP' | 'HTTPS'; diff --git a/front/src/typings/resource.ts b/front/src/typings/resource.ts index 1a9cd328cf..dc107ec8a7 100644 --- a/front/src/typings/resource.ts +++ b/front/src/typings/resource.ts @@ -2,11 +2,11 @@ import { QueryRuleOPEnum } from './common'; // define export type PlainObject = { - [k: string]: string | boolean | number + [k: string]: string | boolean | number; }; export type DoublePlainObject = { - [k: string]: PlainObject + [k: string]: PlainObject; }; export type FilterType = { @@ -15,7 +15,7 @@ export type FilterType = { field: string; op: QueryRuleOPEnum; value: string | number | string[] | any; - }[] + }[]; }; export enum GcpTypeEnum { @@ -54,5 +54,5 @@ export enum HostCloudEnum { REBOOTING = '重启中', SHUTDOWN = '停止待销毁', TERMINATING = '销毁中', - running = '运行中' + running = '运行中', } diff --git a/front/src/typings/scheme.ts b/front/src/typings/scheme.ts index c485bd2dd0..c90bed6c77 100644 --- a/front/src/typings/scheme.ts +++ b/front/src/typings/scheme.ts @@ -54,12 +54,12 @@ export interface ISchemeSelectorItem { // idc机房延迟数据列表单条数据 export interface IIdcLatencyListItem { name: string; - children: { name: string; value: { [key: string]: number; } }[]; + children: { name: string; value: { [key: string]: number } }[]; } export interface IUserDistributionItem { name: string; - children: { name: string; value: number; }[]; + children: { name: string; value: number }[]; } /** @@ -97,7 +97,7 @@ export interface IAreaInfo { children?: Array; } export interface IGenerateSchemesReqParams { - selected_countries: Array, + selected_countries: Array; cover_ping: number; deployment_architecture: Array<'distributed' | 'centralized'>; biz_type: string; @@ -124,7 +124,7 @@ export interface IServiceArea { country_name: string; province_name: string; network_latency: number; -}; +} export interface IIdcServiceAreaRel { idc_id: string; diff --git a/front/src/utils/common.ts b/front/src/utils/common.ts new file mode 100644 index 0000000000..734b3759a5 --- /dev/null +++ b/front/src/utils/common.ts @@ -0,0 +1,29 @@ +/** + * 获取实例的ip地址 + * @param inst 实例 + * @returns 实例的ip地址 + */ +const getInstVip = (inst: any) => { + const { + private_ipv4_addresses, + private_ipv6_addresses, + public_ipv4_addresses, + public_ipv6_addresses, + private_ip_address, + public_ip_address, + } = inst; + if (private_ipv4_addresses || private_ipv6_addresses || public_ipv4_addresses || public_ipv6_addresses) { + if (public_ipv4_addresses.length > 0) return public_ipv4_addresses.join(','); + if (public_ipv6_addresses.length > 0) return public_ipv6_addresses.join(','); + if (private_ipv4_addresses.length > 0) return private_ipv4_addresses.join(','); + if (private_ipv6_addresses.length > 0) return private_ipv6_addresses.join(','); + } + if (private_ip_address || public_ip_address) { + if (private_ip_address.length > 0) return private_ip_address.join(','); + if (public_ip_address.length > 0) return public_ip_address.join(','); + } + + return '--'; +}; + +export { getInstVip }; diff --git a/front/src/utils/index.ts b/front/src/utils/index.ts new file mode 100644 index 0000000000..6ba459cc84 --- /dev/null +++ b/front/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './lb'; diff --git a/front/src/utils/lb.ts b/front/src/utils/lb.ts new file mode 100644 index 0000000000..16412ed964 --- /dev/null +++ b/front/src/utils/lb.ts @@ -0,0 +1,81 @@ +/** + * 统一管理负载均衡需求下的 utils 函数 + */ + +// import stores +import { useBusinessStore } from '@/store'; +const businessStore = useBusinessStore(); + +/** + * 异步加载负载均衡的监听器数量 + * @param lbList 负载均衡列表 + * @returns 负载均衡列表(带有listenerNum字段) + */ +const asyncGetListenerCount = async (lbList: any) => { + // 如果lbList长度为0, 则无需请求监听器数量 + if (lbList.length === 0) return; + // 负载均衡ids + const lb_ids = lbList.map(({ id }: { id: string }) => id); + // 查询负载均衡对应的监听器数量 + const res = await businessStore.asyncGetListenerCount({ lb_ids }); + // 构建映射关系 + const listenerCountMap = {}; + res.data.details.forEach(({ lb_id, num }: { lb_id: string; num: number }) => { + listenerCountMap[lb_id] = num; + }); + // 根据映射关系进行匹配, 将 num 添加到 lbList 中并返回 + return lbList.map((data: any) => { + const { id } = data; + return { ...data, listenerNum: listenerCountMap[id] }; + }); +}; + +/** + * 获取search-select组合后的过滤条件, 可用于本地表格数据的过滤 + * @param searchVal search-select 的值 + * @param resolveRuleValue 用于处理规则值的函数(比如中英文映射...) + * @returns 过滤条件 + */ +const getLocalFilterConditions = (searchVal: any[], resolveRuleValue: (rule: any) => void) => { + if (!searchVal || searchVal.length === 0) return {}; + const filterConditions = {}; + searchVal.forEach((rule: any) => { + // 获取规则值 + const ruleValue = resolveRuleValue(rule); + + // 组装过滤条件 + if (filterConditions[rule.id]) { + // 如果 filterConditions[rule.id] 已经存在,则合并为一个数组 + filterConditions[rule.id] = [...filterConditions[rule.id], ruleValue]; + } else { + filterConditions[rule.id] = [ruleValue]; + } + }); + return filterConditions; +}; + +/** + * 当负载均衡被锁定时, 可以链接至异步任务详情页面 + * @param flowId 异步任务id + */ +const goAsyncTaskDetail = async (flowId: string) => { + // 1. 点击后, 先查询到 audit_id + const { data } = await businessStore.list( + { + page: { limit: 1, start: 0, count: false }, + filter: { + op: 'and', + rules: [{ field: 'detail.data.res_flow.flow_id', op: 'json_eq', value: flowId }], + }, + }, + 'audits', + ); + const { id, res_name: name, res_id, bk_biz_id } = data.details[0]; + // 2. 新开页面查看异步任务详情 + window.open( + `/#/business/record/detail?id=${id}&name=${name}&res_id=${res_id}&bizs=${bk_biz_id}&flow=${flowId}`, + '_blank', + ); +}; + +export { asyncGetListenerCount, getLocalFilterConditions, goAsyncTaskDetail }; diff --git a/front/src/views/business/business-detail.vue b/front/src/views/business/business-detail.vue index 251992d0fc..ac131552f0 100644 --- a/front/src/views/business/business-detail.vue +++ b/front/src/views/business/business-detail.vue @@ -1,11 +1,6 @@ diff --git a/front/src/views/business/business-manage.vue b/front/src/views/business/business-manage.vue index 9e5addcbd6..ed2661e5ce 100644 --- a/front/src/views/business/business-manage.vue +++ b/front/src/views/business/business-manage.vue @@ -142,7 +142,6 @@ const handleSuccess = () => { const handleSecrityType = (val: string) => { securityType.value = val; - console.log(' securityType.value', securityType.value); }; // 新增修改防火墙规则 @@ -199,7 +198,8 @@ const { :class="[ route.path === '/business/host' ? 'is-host-page' : '', route.path === '/business/recyclebin' ? 'is-recycle-page' : '', - ]"> + ]" + > { + if (authVerifyData?.permissionAction?.biz_iaas_resource_create) { + handleAdd(); + } else { + handleAuth('biz_iaas_resource_create'); + } } - }" + " > {{ renderComponent === DriveManage || - renderComponent === HostManage || - renderComponent === SubnetManage || - renderComponent === VpcManage + renderComponent === HostManage || + renderComponent === SubnetManage || + renderComponent === VpcManage ? '申请' : '新增' }} @@ -285,11 +287,13 @@ const { :is-show="isTemplateDialogShow" :is-edit="isTemplateDialogEdit" :payload="templateDialogPayload" - :handle-close="() => isTemplateDialogShow = false" - :handle-success="() => { - isTemplateDialogShow = false; - handleSuccess(); - }" + :handle-close="() => (isTemplateDialogShow = false)" + :handle-success=" + () => { + isTemplateDialogShow = false; + handleSuccess(); + } + " />
      @@ -334,7 +338,7 @@ const { .bk-table-head .bk-checkbox { vertical-align: middle; } - .bk-table-head tr th:nth-of-type(2) .cell{ + .bk-table-head tr th:nth-of-type(2) .cell { padding-left: 8px; } .bk-table-body .cell.selection { diff --git a/front/src/views/business/cert-manager/index.scss b/front/src/views/business/cert-manager/index.scss new file mode 100644 index 0000000000..27b4c35955 --- /dev/null +++ b/front/src/views/business/cert-manager/index.scss @@ -0,0 +1,72 @@ +.cert-manager-page { + height: 100%; + padding: 24px; + overflow: hidden; + box-sizing: border-box; + + .common-card-wrap { + width: 100%; + height: 100%; + padding: 16px 24px; + background-color: #fff; + box-sizing: border-box; + + .operation-wrap .operate-btn-groups .bk-button { + margin-right: 0; + } + } + + &.has-selection { + padding: 0; + .common-card-wrap { + padding: 0; + } + .bk-table .bk-table-head .bk-checkbox { + vertical-align: middle; + } + .bk-table .bk-table-head tr th:nth-of-type(2) .cell { + padding-left: 8px; + } + .bk-table .bk-table-body .cell.selection { + text-align: right; + .bk-checkbox { + vertical-align: middle; + } + } + .bk-table .bk-table-body tr td:nth-of-type(2) .cell { + padding-left: 8px; + } + } +} + +.cert-upload-sideslider { + .bk-upload--button { + .bk-button { + width: 100px; + } + .bk-upload__tip { + margin-left: 12px; + color: #979ba5 !important; + } + } +} + +.operate-text-btn, .link-text-btn { + color: #3a84ff; + cursor: pointer; +} + +.upload-textarea-wrap { + min-width: 100%; + max-width: 100%; + textarea { + height: 100%; + } +} + +.upload-error-text { + margin-bottom: 8px; + font-size: 12px; + line-height: 1; + color: #ea3636; +} \ No newline at end of file diff --git a/front/src/views/business/cert-manager/index.tsx b/front/src/views/business/cert-manager/index.tsx new file mode 100644 index 0000000000..c6ebe72cfc --- /dev/null +++ b/front/src/views/business/cert-manager/index.tsx @@ -0,0 +1,405 @@ +import { computed, defineComponent, reactive, ref, PropType } from 'vue'; +import { Button, Form, Input, Upload, Message } from 'bkui-vue'; +import BkRadio, { BkRadioGroup } from 'bkui-vue/lib/radio'; +import './index.scss'; +import { VendorEnum } from '@/common/constant'; +import { DoublePlainObject, FilterType } from '@/typings'; +import { useTable } from '@/hooks/useTable/useTable'; +import { useWhereAmI, Senarios } from '@/hooks/useWhereAmI'; +import { useAccountStore, useResourceStore } from '@/store'; +import { useResourceAccountStore } from '@/store/useResourceAccountStore'; +import useColumns from '@/views/resource/resource-manage/hooks/use-columns'; +import useSelection from '@/views/resource/resource-manage/hooks/use-selection'; +import CommonSideslider from '@/components/common-sideslider'; +import AccountSelector from '@/components/account-selector/index.vue'; +import { BatchDistribution, DResourceType } from '@/views/resource/resource-manage/children/dialog/batch-distribution'; +import Confirm from '@/components/confirm'; +import { getTableNewRowClass } from '@/common/util'; +import PermissionDialog from '@/components/permission-dialog'; +import { useVerify } from '@/hooks'; +const { FormItem } = Form; +export default defineComponent({ + name: 'CertManager', + props: { + filter: Object as PropType, + }, + setup(props) { + const { isResourcePage, isBusinessPage, whereAmI } = useWhereAmI(); + const accountStore = useAccountStore(); + const resourceStore = useResourceStore(); + const resourceAccountStore = useResourceAccountStore(); + + const { selections, handleSelectionChange, resetSelections } = useSelection(); + + const isRowSelectEnable = ({ row, isCheckAll }: DoublePlainObject) => { + if (isCheckAll) return true; + return isCurRowSelectEnable(row); + }; + const isCurRowSelectEnable = (row: any) => { + if (isBusinessPage) return true; + if (row.id) { + return row.bk_biz_id === -1; + } + }; + + const { columns } = useColumns('cert'); + const tableColumns = computed(() => { + const result = [ + ...columns, + { + label: '操作', + width: 120, + render: ({ data }: { data: any }) => ( + + ), + }, + ]; + if (isResourcePage) { + result.unshift({ + type: 'selection', + width: 32, + minWidth: 32, + onlyShowOnList: true, + align: 'right', + }); + } + return result; + }); + const { CommonTable, getListData } = useTable({ + searchOptions: { + searchData: [ + { + name: '证书名称', + id: 'name', + }, + { + name: '资源ID', + id: 'cloud_id', + }, + { + name: '域名', + id: 'domain', + }, + ], + }, + tableOptions: { + columns: tableColumns.value, + extra: { + isRowSelectEnable, + onSelectionChange: (selections: any) => handleSelectionChange(selections, isCurRowSelectEnable), + onSelectAll: (selections: any) => handleSelectionChange(selections, isCurRowSelectEnable, true), + rowClass: getTableNewRowClass(), + }, + }, + requestOption: { + type: 'certs', + sortOption: { + sort: 'cloud_created_time', + order: 'DESC', + }, + async resolveDataListCb(dataList: any[]) { + if (dataList.length === 0) return; + return dataList.map((item: any) => { + // 与表头筛选配合 + item.cert_type = item.cert_type === 'SVR' ? '服务器证书' : '客户端CA证书'; + item.cert_status = item.cert_status === '1' ? '正常' : '已过期'; + return item; + }); + }, + }, + bizFilter: props.filter, + }); + const isCertUploadSidesliderShow = ref(false); + const isLoading = ref(false); + const formRef = ref(); + const formModel = reactive({ + account_id: '' as string, // 账户ID + name: '' as string, // 证书名称 + vendor: VendorEnum.TCLOUD, // 云厂商 + cert_type: 'SVR' as 'CA' | 'SVR', // 证书类型 + public_key: '' as string, // 证书信息 + private_key: '' as string, // 私钥信息 + }); + const formRules = { + name: [{ message: '不能超过200个字且不能为空', validator: (value: string) => value.trim().length <= 200 }], + }; + + // 上传证书错误提示 + const uploadPublicKeyErrorText = ref(''); + const uploadPrivateKeyErrorText = ref(''); + // 错误提示映射 + const errorTextMap = { + public_key: uploadPublicKeyErrorText, + private_key: uploadPrivateKeyErrorText, + }; + + // 表单项配置 + const formItemOptions = computed(() => [ + { + label: '云账号', + property: 'account_id', + required: true, + content: () => ( + + ), + }, + { + label: '证书名称', + property: 'name', + required: true, + content: () => , + }, + { + label: '证书类型', + property: 'cert_type', + required: true, + content: () => ( + + 服务器证书 + 客户端CA证书 + + ), + }, + { + label: '证书上传', + property: 'public_key', + required: true, + content: () => ( + <> + handleUploadCertKey(file)} + onDelete={() => handleUploadFileDelete('public_key')} + onError={(_: any, fileList: any, error: Error) => handleUploadError(fileList, error, 'public_key')} + onExceed={() => handleUploadExceed('public_key')} + /> + {uploadPublicKeyErrorText.value &&
      {uploadPublicKeyErrorText.value}
      } + + + ), + }, + { + label: '私钥上传', + property: 'private_key', + required: true, + hidden: formModel.cert_type === 'CA', + content: () => ( + <> + handleUploadPrimaryKey(file)} + onDelete={() => handleUploadFileDelete('private_key')} + onError={(_: any, fileList: any, error: Error) => handleUploadError(fileList, error, 'private_key')} + onExceed={() => handleUploadExceed('private_key')} + /> + {uploadPrivateKeyErrorText.value &&
      {uploadPrivateKeyErrorText.value}
      } + + + ), + }, + ]); + + const resetForm = () => { + Object.assign(formModel, { + account_id: resourceAccountStore?.resourceAccount?.id || '', + name: '', + vendor: VendorEnum.TCLOUD, + cert_type: 'SVR', + public_key: '', + private_key: '', + }); + uploadPublicKeyErrorText.value = ''; + uploadPrivateKeyErrorText.value = ''; + }; + + const showCreateCertSideslider = () => { + isCertUploadSidesliderShow.value = true; + resetForm(); + }; + + // 回显证书内容 + const echoCertContent = (file: any, key: string) => { + const fileReader = new FileReader(); + fileReader.onload = (e: any) => { + formModel[key] = e.target.result; + }; + fileReader.readAsText(file); + }; + // 处理证书上传文件成功执行的事件 + const handleUploadCertKey = (file: any) => { + echoCertContent(file, 'public_key'); + uploadPublicKeyErrorText.value = ''; + }; + // 处理密钥上传文件成功执行的事件 + const handleUploadPrimaryKey = (file: any) => { + echoCertContent(file, 'private_key'); + uploadPrivateKeyErrorText.value = ''; + }; + // 处理文件上传失败的事件 + const handleUploadError = (fileList: any, error: Error, type: string) => { + if (error.message === 'invalid filename') { + errorTextMap[type].value = '请上传正确的证书文件,该证书将于 2s 后移除!'; + setTimeout(() => { + fileList.pop(); + errorTextMap[type].value = ''; + }, 2000); + } + }; + // 处理文件上传个数超出限制后的事件 + const handleUploadExceed = (type: string) => { + errorTextMap[type].value = '证书文件只支持上传 1 个,如需更换,请移除当前证书文件后再进行上传操作!'; + }; + // 处理 + const handleUploadFileDelete = (type: 'public_key' | 'private_key') => { + formModel[type] = ''; + errorTextMap[type].value = ''; + }; + + // 证书上传 + const handleCreateCert = async () => { + await formRef.value.validate(); + isLoading.value = true; + try { + await resourceStore.create('certs', { + ...formModel, + public_key: btoa(formModel.public_key), + private_key: btoa(formModel.private_key), + }); + Message({ theme: 'success', message: '证书上传成功' }); + isCertUploadSidesliderShow.value = false; + await getListData(); + } finally { + isLoading.value = false; + } + }; + // 删除指定证书 + const handleDeleteCert = async (cert: any) => { + Confirm('请确定删除证书', `将删除证书【${cert.name}】`, () => { + resourceStore.delete('certs', cert.id).then(() => { + Message({ theme: 'success', message: '证书删除成功' }); + getListData(); + }); + }); + }; + + // 权限 hooks + const { + showPermissionDialog, + handlePermissionConfirm, + handlePermissionDialog, + handleAuth, + permissionParams, + authVerifyData, + } = useVerify(); + + const hasCreateCertPermission = computed(() => { + if (whereAmI.value === Senarios.business) { + return authVerifyData.value?.permissionAction?.biz_cert_resource_create; + } + return authVerifyData.value?.permissionAction?.cert_resource_create; + }); + const hasDeleteCertPermission = computed(() => { + if (whereAmI.value === Senarios.business) { + return authVerifyData.value?.permissionAction?.biz_cert_resource_delete; + } + return authVerifyData.value?.permissionAction?.cert_resource_delete; + }); + + return () => ( +
      +
      + + {{ + operation: () => ( + <> + + { + getListData(); + resetSelections(); + }} + /> + + ), + }} + +
      + +
      + {formItemOptions.value.map(({ label, property, required, content, hidden }) => { + if (hidden) return null; + return ( + + {content()} + + ); + })} +
      +
      + {/* 申请权限 */} + +
      + ); + }, +}); diff --git a/front/src/views/business/components/form-select.vue b/front/src/views/business/components/form-select.vue index e871e41981..f3ceef0ed2 100644 --- a/front/src/views/business/components/form-select.vue +++ b/front/src/views/business/components/form-select.vue @@ -2,11 +2,7 @@ import { reactive, watch, ref, inject } from 'vue'; import { useI18n } from 'vue-i18n'; import { useAccountStore, useResourceStore } from '@/store'; -import { - BusinessFormFilter, - QueryFilterType, - QueryRuleOPEnum, -} from '@/typings'; +import { BusinessFormFilter, QueryFilterType, QueryRuleOPEnum } from '@/typings'; import { CLOUD_TYPE } from '@/constants'; import { VendorEnum } from '@/common/constant'; import { useWhereAmI } from '@/hooks/useWhereAmI'; @@ -158,22 +154,23 @@ const getAccountList = async () => { accountLoading.value = true; const payload = isResourcePage ? { - page: { - count: false, - limit: 100, - start: 0, - }, - filter: { op: 'and', rules: [] }, - } + page: { + count: false, + limit: 100, + start: 0, + }, + filter: { op: 'and', rules: [] }, + } : { - params: { - account_type: 'resource', - }, - }; + params: { + account_type: 'resource', + }, + }; const res = await accountStore.getAccountList(payload, accountStore.bizs); if (resourceAccountStore.resourceAccount?.id) { - accountList.value = res.data?.details - .filter(({ id }: {id: string}) => id === resourceAccountStore.resourceAccount.id); + accountList.value = res.data?.details.filter( + ({ id }: { id: string }) => id === resourceAccountStore.resourceAccount.id, + ); // 自动填充当前账号 state.filter.account_id = accountList.value?.[0].id; return; @@ -182,9 +179,9 @@ const getAccountList = async () => { if (props.type === 'security') { // 安全组需要区分 if (securityType.value && securityType.value === 'gcp') { - accountList.value = accountList.value.filter(e => e.vendor === 'gcp'); + accountList.value = accountList.value.filter((e) => e.vendor === 'gcp'); } else { - accountList.value = accountList.value.filter(e => e.vendor !== 'gcp'); + accountList.value = accountList.value.filter((e) => e.vendor !== 'gcp'); } } } catch (error) { @@ -225,42 +222,14 @@ getAccountList(); diff --git a/front/src/views/resource/resource-manage/children/components/host/host-info/components/tcloud-info.vue b/front/src/views/resource/resource-manage/children/components/host/host-info/components/tcloud-info.vue index 825636a6df..650c055215 100644 --- a/front/src/views/resource/resource-manage/children/components/host/host-info/components/tcloud-info.vue +++ b/front/src/views/resource/resource-manage/children/components/host/host-info/components/tcloud-info.vue @@ -4,12 +4,7 @@ import DetailInfo from '@/views/resource/resource-manage/common/info/detail-info import { PropType } from 'vue'; import { TypeEnum, useRouteLinkBtn } from '@/hooks/useRouteLinkBtn'; -import { - CLOUD_HOST_STATUS, - INSTANCE_CHARGE_MAP, - NET_CHARGE_MAP, - VendorEnum, -} from '@/common/constant'; +import { CLOUD_HOST_STATUS, INSTANCE_CHARGE_MAP, NET_CHARGE_MAP, VendorEnum } from '@/common/constant'; import { useRegionsStore } from '@/store/useRegionsStore'; import { timeFormatter } from '@/common/util'; @@ -33,11 +28,12 @@ const cvmInfo = [ { name: '账号', prop: 'account_id', - render: () => useRouteLinkBtn(props.data, { - id: 'account_id', - name: 'account_id', - type: TypeEnum.ACCOUNT, - }), + render: () => + useRouteLinkBtn(props.data, { + id: 'account_id', + name: 'account_id', + type: TypeEnum.ACCOUNT, + }), }, { name: '云厂商', @@ -56,9 +52,7 @@ const cvmInfo = [ name: '业务', prop: 'bk_biz_id', render() { - return props.data.bk_biz_id === -1 - ? '未分配' - : `${props.data.bk_biz_id_name} (${props.data.bk_biz_id})`; + return props.data.bk_biz_id === -1 ? '未分配' : `${props.data.bk_biz_id_name} (${props.data.bk_biz_id})`; }, }, { @@ -95,20 +89,22 @@ const netInfo = [ { name: '所属网络', prop: 'cloud_vpc_ids', - render: () => useRouteLinkBtn(props.data, { - id: 'vpc_ids', - name: 'cloud_vpc_ids', - type: TypeEnum.VPC, - }), + render: () => + useRouteLinkBtn(props.data, { + id: 'vpc_ids', + name: 'cloud_vpc_ids', + type: TypeEnum.VPC, + }), }, { name: '所属子网', prop: 'cloud_subnet_ids', - render: () => useRouteLinkBtn(props.data, { - id: 'subnet_ids', - name: 'cloud_subnet_ids', - type: TypeEnum.SUBNET, - }), + render: () => + useRouteLinkBtn(props.data, { + id: 'subnet_ids', + name: 'cloud_subnet_ids', + type: TypeEnum.SUBNET, + }), }, // { // name: '用作公网网关', @@ -164,11 +160,12 @@ const settingInfo = [ { name: '镜像ID', prop: 'cloud_image_id', - render: () => useRouteLinkBtn(props.data, { - id: 'image_id', - type: TypeEnum.IMAGE, - name: 'cloud_image_id', - }), + render: () => + useRouteLinkBtn(props.data, { + id: 'image_id', + type: TypeEnum.IMAGE, + name: 'cloud_image_id', + }), }, ]; @@ -188,9 +185,7 @@ const priceInfo = [ { name: '网络计费模式', prop: 'internet_charge_type', - render: () => NET_CHARGE_MAP[ - props?.data?.extension?.internet_accessible?.internet_charge_type - ], + render: () => NET_CHARGE_MAP[props?.data?.extension?.internet_accessible?.internet_charge_type], }, { name: '到期时间', @@ -209,24 +204,14 @@ const priceInfo = [

      网络信息

      - +

      配置信息

      - +

      计费信息

      - +
      - diff --git a/front/src/views/resource/resource-manage/children/components/host/host-info/index.vue b/front/src/views/resource/resource-manage/children/components/host/host-info/index.vue index 15cfffb2b3..2a6b7084b0 100644 --- a/front/src/views/resource/resource-manage/children/components/host/host-info/index.vue +++ b/front/src/views/resource/resource-manage/children/components/host/host-info/index.vue @@ -8,9 +8,7 @@ import HuaweiInfo from './components/huawei-info.vue'; // import AzureNetwork from './components/azure-network.vue'; // import GcpNetwork from './components/gcp-network.vue'; -import { - PropType, -} from 'vue'; +import { PropType } from 'vue'; // import { // useRoute, @@ -37,14 +35,13 @@ const componentMap = { // const renderComponent = componentMap[route.params.type as string]; const renderComponent = componentMap[props.type]; - diff --git a/front/src/views/resource/resource-manage/children/components/host/host-ip.vue b/front/src/views/resource/resource-manage/children/components/host/host-ip.vue index c5f46deb47..1d34e9b132 100644 --- a/front/src/views/resource/resource-manage/children/components/host/host-ip.vue +++ b/front/src/views/resource/resource-manage/children/components/host/host-ip.vue @@ -1,19 +1,7 @@