diff --git a/bcs-ops/install_master.sh b/bcs-ops/install_master.sh index a2d0ea5b7a..c7b4c12eb6 100755 --- a/bcs-ops/install_master.sh +++ b/bcs-ops/install_master.sh @@ -47,18 +47,12 @@ safe_source "${ROOT_DIR}/functions/k8s.sh" "${ROOT_DIR}"/system/config_envfile.sh -c init "${ROOT_DIR}"/system/config_system.sh -c dns sysctl "${ROOT_DIR}"/tools/install_tools.sh jq yq -"${ROOT_DIR}"/system/install_yq "${ROOT_DIR}"/k8s/install_cri.sh "${ROOT_DIR}"/k8s/install_k8s_tools "${ROOT_DIR}"/k8s/render_kubeadm safe_source "${ROOT_DIR}/env/bcs.env" -# pull image -if [[ -z ${BCS_OFFLINE:-} ]]; then - kubeadm --config="${ROOT_DIR}/kubeadm-config" config images pull \ - || utils::log "FATAL" "fail to pull k8s image" -fi # wait to check kubelet start sleep 30 diff --git a/bcs-ops/install_node.sh b/bcs-ops/install_node.sh index 5453604886..77cd5ecbe0 100755 --- a/bcs-ops/install_node.sh +++ b/bcs-ops/install_node.sh @@ -122,10 +122,6 @@ case "${K8S_CSI,,}" in ;; esac -if [[ -z ${BCS_OFFLINE:-} ]]; then - kubeadm --config="${ROOT_DIR}/kubeadm-config" config images pull \ - || utils::log "FATAL" "fail to pull k8s image" -fi # wait kubelet to start sleep 30 diff --git a/bcs-ops/k8s/install_helm b/bcs-ops/k8s/install_helm index 18d6a01b78..8cf8509cf5 100755 --- a/bcs-ops/k8s/install_helm +++ b/bcs-ops/k8s/install_helm @@ -35,7 +35,7 @@ for file in "${source_files[@]}"; do safe_source "$file" done -if ! helm version --short /dev/null | grep -qoE "^v${HELM_VER}"; then +if ! helm version --short 2>/dev/null | grep -qoE "^v${HELM_VER}"; then helm_image=${BK_PUBLIC_REPO:-"docker.io"}/alpine/helm:3.7.2 utils::log "DEBUG" "helm image url: ${helm_image}" diff --git a/bcs-ops/k8s/install_k8s b/bcs-ops/k8s/install_k8s index eb5f43be0a..e1230effb1 100644 --- a/bcs-ops/k8s/install_k8s +++ b/bcs-ops/k8s/install_k8s @@ -138,7 +138,9 @@ for pod in ${pods[@]};do fi ;; "containerd") - if ! crictl ps |grep ${pod}|grep -i running;then + if ! crictl --runtime-endpoint=unix:///run/containerd/containerd.sock ps \ + | grep "${pod}" \ + | grep -i running; then utils::log "ERROR" "${pod} fail to run " fi ;; @@ -155,7 +157,7 @@ if [[ -z ${MASTER_JOIN_CMD:-} ]]; then kubectl get cm -n kube-system kube-proxy -o yaml|yq '.data.["kubeconfig.conf"]' > ${ROOT_DIR}/kubeconfig.conf kubectl get cm -n kube-system kube-proxy -o yaml|yq '.data.["config.conf"]'|yq '.ipvs.udpTimeout="10s"' > ${ROOT_DIR}/config.conf - kubectl delete cm kube-proxy -n kube-system + kubectl delete cm kube-proxy -n kube-system kubectl create cm kube-proxy -n kube-system --from-file config.conf --from-file kubeconfig.conf if ! kubectl get ds -n kube-system kube-proxy -o yaml|grep madvdontneed;then @@ -176,4 +178,4 @@ fi #coredns configuration -utils::log "OK" "K8S configuration done!" \ No newline at end of file +utils::log "OK" "K8S configuration done!" diff --git a/bcs-ops/readme.md b/bcs-ops/readme.md index 4f516c7b94..ec52fb748d 100644 --- a/bcs-ops/readme.md +++ b/bcs-ops/readme.md @@ -63,7 +63,7 @@ ip -6 route add fd00::/8 via dev src > 注意:`fe80::/10` link-local 地址不能用于 k8s 的 node-ip。 ## 安装示例 - +目前仅支持 k8s `1.20.15` (默认), `1.23.17` 和 `1.24.15` 版本。 ### 集群创建与节点添加 1. 在第一台主机(后称中控机)上启动集群控制平面:`./bcs-ops --instal master`,集群启动成功后会显示加入集群的指令 @@ -105,6 +105,15 @@ ip -6 route add fd00::/8 via dev src ## 环境变量 通过配置环境变量来设置集群相关的参数。在中控机创建集群前,通过 `set -a` 设置环境变量。 你可以执行 `system/config_envfile.sh -init` 查看默认的环境变量。 +注意,当你要使用多个特性时,相关的环境变量都得申明 + +### 示例:使用 containerd 作为容器运行时 +```bash +set -a +K8S_VER="1.24.15" +CRI_TYPE="containerd" +set +a +``` ### 示例:创建 ipv6 双栈集群 @@ -118,9 +127,20 @@ set +a ./bcs-ops -i master ``` +### 示例: 修改镜像 registry,并信任 +相关环境变量。镜像仓库默认为蓝鲸官方镜像仓库`hub.bktencent.com`,如果采用自己的镜像仓库,并且没有证书信任,需要添加下面两项环境变量 +```bash +# 默认镜像地址 +set -a +BK_PUBLIC_REPO=hub.bktencent.com +# 信任不安全的registry +INSECURE_REGISTRY="" +set +a +``` + ### 示例:离线安装 -离线安装资源清单见 `env/offline-manifest.yaml`。目前仅支持 k8s `1.20.15`, `1.23.17` 和 `1.24.15` 版本。 +离线安装资源清单见 `env/offline-manifest.yaml`。 你需要把对应的离线包解压到 bcs-ops 的工作根目录下 `tar xfvz bcs-ops-offline-${version}.tgz`,并且安装对应的版本 `${VERSION}`。 @@ -128,7 +148,16 @@ set +a set -a BCS_OFFLINE="1" K8S_VER="${VERSION}" -./bcs-ops -i master +set +a +``` + +### 示例:开启 apiserver 高可用 +APISERVER_HA_MODE 支持 [bcs-apiserver-proxy](https://github.com/TencentBlueKing/bk-bcs/blob/master/docs/features/bcs-apiserver-proxy/bcs-apiserver-proxy.md)(默认) 和 kube-vip。 +```bash +set -a +VIP=192.168.1.1 # 按照实际的需求填写,避免冲突 +ENABLE_APISERVER_HA=true +APISERVER_HA_MODE=bcs-apiserver-proxy set +a ``` @@ -138,11 +167,14 @@ bcs-ops 脚本工具集也支持安装 k8s 相关插件。多数的插件需要 ### csi -安装的 k8s 组件由 `K8S_CSI` 环境变量决定,目前默认且只支持 `localpv` +安装的 k8s 组件由 `K8S_CSI` 环境变量决定,默认为空,只支持 `localpv` #### localpv +相关配置项,中控机启动前需要运行 ```bash +# 申明 CSI 组件 为 `localpv` +K8S_CSI=localpv # localpv 挂载点,默认为${BK_HOME}/localpv LOCALPV_DIR=${LOCALPV_DIR:-${BK_HOME}/localpv} # 创建的 localpv 数量,默认为20个 diff --git a/bcs-ops/system/config_envfile.sh b/bcs-ops/system/config_envfile.sh index af19df1ac6..51cfaabd68 100755 --- a/bcs-ops/system/config_envfile.sh +++ b/bcs-ops/system/config_envfile.sh @@ -118,7 +118,7 @@ init_env() { BK_PUBLIC_REPO=${BK_PUBLIC_REPO:-"hub.bktencent.com"} # helm - BKREPO_URL=${BKREPO_URL:-"https://hub.bktencent.com/chartrepo"} + BKREPO_URL=${BKREPO_URL:-"https://hub.bktencent.com/chartrepo/blueking"} # apiserver HA ENABLE_APISERVER_HA=${ENABLE_APISERVER_HA:-"false"} diff --git a/bcs-services/bcs-bscp/cmd/data-service/service/app_template_variable.go b/bcs-services/bcs-bscp/cmd/data-service/service/app_template_variable.go index 7b8b6a4881..8ae14f251a 100644 --- a/bcs-services/bcs-bscp/cmd/data-service/service/app_template_variable.go +++ b/bcs-services/bcs-bscp/cmd/data-service/service/app_template_variable.go @@ -13,7 +13,6 @@ package service import ( - "bytes" "context" "sort" "time" @@ -28,7 +27,6 @@ import ( pbcontent "bscp.io/pkg/protocol/core/content" pbtv "bscp.io/pkg/protocol/core/template-variable" pbds "bscp.io/pkg/protocol/data-service" - "bscp.io/pkg/tools" "bscp.io/pkg/types" ) @@ -44,25 +42,15 @@ func (s *Service) ExtractAppTmplVariables(ctx context.Context, req *pbds.Extract return nil, err } - contents, err := s.downloadTmplContent(kt, tmplRevisions) + var allVars []string + _, _, allVars, err = s.getVariables(kt, tmplRevisions, cis) if err != nil { - logs.Errorf("download template content failed, err: %v, rid: %s", err, kt.Rid) + logs.Errorf("get variables failed, err: %v, rid: %s", err, kt.Rid) return nil, err } - ciContents, err := s.downloadCIContent(kt, cis) - if err != nil { - logs.Errorf("download config item content failed, err: %v, rid: %s", err, kt.Rid) - return nil, err - } - contents = append(contents, ciContents...) - - // merge all template content - allContent := bytes.Join(contents, []byte(" ")) - // extract all template variables - variables := s.tmplProc.ExtractVariables(allContent) return &pbds.ExtractAppTmplVariablesResp{ - Details: variables, + Details: allVars, }, nil } @@ -205,28 +193,18 @@ func getPbConfigItemsFromReleased(releasedCIs []*table.ReleasedConfigItem) []*pb // GetAppTmplVariableRefs get app template variable references. func (s *Service) getVariableReferences(kt *kit.Kit, tmplRevisions []*table.TemplateRevision, cis []*pbci.ConfigItem) ( []*pbatv.AppTemplateVariableReference, error) { - contents, err := s.downloadTmplContent(kt, tmplRevisions) + vars, ciVars, allVars, err := s.getVariables(kt, tmplRevisions, cis) if err != nil { - logs.Errorf("download template content failed, err: %v, rid: %s", err, kt.Rid) - return nil, err - } - ciContents, err := s.downloadCIContent(kt, cis) - if err != nil { - logs.Errorf("download config item content failed, err: %v, rid: %s", err, kt.Rid) + logs.Errorf("get variables failed, err: %v, rid: %s", err, kt.Rid) return nil, err } - allVariables := make([]string, 0) revisionVariableMap := make(map[uint32]map[string]struct{}, len(tmplRevisions)) revisionMap := make(map[uint32]*table.TemplateRevision, len(tmplRevisions)) for idx, r := range tmplRevisions { - // extract template variables for one template config item - variables := s.tmplProc.ExtractVariables(contents[idx]) - allVariables = append(allVariables, variables...) revisionMap[r.ID] = r - revisionVariableMap[r.ID] = map[string]struct{}{} - for _, v := range variables { + for _, v := range vars[idx] { revisionVariableMap[r.ID][v] = struct{}{} } } @@ -234,23 +212,16 @@ func (s *Service) getVariableReferences(kt *kit.Kit, tmplRevisions []*table.Temp ciVariableMap := make(map[uint32]map[string]struct{}, len(cis)) ciMap := make(map[uint32]*pbci.ConfigItem, len(cis)) for idx, ci := range cis { - // extract config item variables for one config item - variables := s.tmplProc.ExtractVariables(ciContents[idx]) - allVariables = append(allVariables, variables...) ciMap[ci.Id] = ci ciVariableMap[ci.Id] = map[string]struct{}{} - for _, v := range variables { + for _, v := range ciVars[idx] { ciVariableMap[ci.Id][v] = struct{}{} } } - allVariables = tools.RemoveDuplicateStrings(allVariables) - // Sort in ascending order - sort.Strings(allVariables) - - refs := make([]*pbatv.AppTemplateVariableReference, len(allVariables)) - for idx, v := range allVariables { + refs := make([]*pbatv.AppTemplateVariableReference, len(allVars)) + for idx, v := range allVars { ref := &pbatv.AppTemplateVariableReference{ VariableName: v, } diff --git a/bcs-services/bcs-bscp/cmd/data-service/service/release.go b/bcs-services/bcs-bscp/cmd/data-service/service/release.go index 0c3587e1a2..4fe92e5855 100644 --- a/bcs-services/bcs-bscp/cmd/data-service/service/release.go +++ b/bcs-services/bcs-bscp/cmd/data-service/service/release.go @@ -13,7 +13,6 @@ package service import ( - "bytes" "context" "errors" "fmt" @@ -161,11 +160,18 @@ func (s *Service) doConfigItemOperations(kt *kit.Kit, variables []*pbtv.Template inputVarMap[v.Name] = v.TemplateVariableSpec() } - // NOTE: optimize to get config items whose content really contains variable - tmplRevisionsNeedRender := filterSizeForTmplRevisions(tmplRevisions) + tmplsNeedRender := filterSizeForTmplRevisions(tmplRevisions) cisNeedRender := filterSizeForConfigItems(cis) - contents, err := s.downloadTmplContent(kt, tmplRevisionsNeedRender) + vars, ciVars, allVars, err := s.getVariables(kt, tmplsNeedRender, cisNeedRender) + if err != nil { + logs.Errorf("get variables failed, err: %v, rid: %s", err, kt.Rid) + return err + } + tmplsNeedRender = filterVarsForTmplRevisions(tmplsNeedRender, vars) + cisNeedRender = filterVarsForConfigItems(cisNeedRender, ciVars) + + contents, err := s.downloadTmplContent(kt, tmplsNeedRender) if err != nil { logs.Errorf("download template content failed, err: %v, rid: %s", err, kt.Rid) return err @@ -175,12 +181,6 @@ func (s *Service) doConfigItemOperations(kt *kit.Kit, variables []*pbtv.Template logs.Errorf("download config item content failed, err: %v, rid: %s", err, kt.Rid) return err } - contents = append(contents, ciContents...) - - // merge all template content - allContent := bytes.Join(contents, []byte(" ")) - // extract all template variables - allVars := s.tmplProc.ExtractVariables(allContent) usedVars, renderKV, err := s.getRenderedVars(kt, allVars, inputVarMap) if err != nil { @@ -194,7 +194,7 @@ func (s *Service) doConfigItemOperations(kt *kit.Kit, variables []*pbtv.Template byteSizeMap := make(map[uint32]uint64, len(tmplRevisions)) revisionMap := make(map[uint32]*table.TemplateRevision, len(tmplRevisions)) // data which need render - for idx, r := range tmplRevisionsNeedRender { + for idx, r := range tmplsNeedRender { revisionMap[r.ID] = r renderedContentMap[r.ID] = s.tmplProc.Render(contents[idx], renderKV) signatureMap[r.ID] = tools.ByteSHA256(renderedContentMap[r.ID]) diff --git a/bcs-services/bcs-bscp/cmd/data-service/service/upload_download.go b/bcs-services/bcs-bscp/cmd/data-service/service/upload_download.go index 8717de9279..54a21b2f12 100644 --- a/bcs-services/bcs-bscp/cmd/data-service/service/upload_download.go +++ b/bcs-services/bcs-bscp/cmd/data-service/service/upload_download.go @@ -24,6 +24,7 @@ import ( "bscp.io/pkg/logs" pbci "bscp.io/pkg/protocol/core/config-item" pbds "bscp.io/pkg/protocol/data-service" + "bscp.io/pkg/tools" "bscp.io/pkg/types" ) @@ -91,10 +92,10 @@ func (s *Service) getAppConfigItems(kt *kit.Kit) ([]*pbci.ConfigItem, error) { return resp.Details, nil } -// filterSizeForTmplRevisions get template config items which can be rendered -func filterSizeForTmplRevisions(tmplRevisions []*table.TemplateRevision) []*table.TemplateRevision { +// filterSizeForTmplRevisions get template config items whose content size are satisfied to render +func filterSizeForTmplRevisions(tmpls []*table.TemplateRevision) []*table.TemplateRevision { rs := make([]*table.TemplateRevision, 0) - for _, r := range tmplRevisions { + for _, r := range tmpls { if r.Spec.ContentSpec.ByteSize <= constant.MaxRenderBytes { rs = append(rs, r) } @@ -102,7 +103,7 @@ func filterSizeForTmplRevisions(tmplRevisions []*table.TemplateRevision) []*tabl return rs } -// filterSizeForConfigItems get non-template config items which can be rendered +// filterSizeForConfigItems get non-template config items whose content size are satisfied to render func filterSizeForConfigItems(cis []*pbci.ConfigItem) []*pbci.ConfigItem { rs := make([]*pbci.ConfigItem, 0) for _, ci := range cis { @@ -113,6 +114,141 @@ func filterSizeForConfigItems(cis []*pbci.ConfigItem) []*pbci.ConfigItem { return rs } +// filterVarsForTmplRevisions get template config items which have variables so that need to render +func filterVarsForTmplRevisions(tmpls []*table.TemplateRevision, vars [][]string) []*table.TemplateRevision { + rs := make([]*table.TemplateRevision, 0) + for idx, v := range vars { + if len(v) > 0 { + rs = append(rs, tmpls[idx]) + } + } + return rs +} + +// filterVarsForConfigItems get non-template config items which have variables so that need to render +func filterVarsForConfigItems(cis []*pbci.ConfigItem, ciVars [][]string) []*pbci.ConfigItem { + rs := make([]*pbci.ConfigItem, 0) + for idx, v := range ciVars { + if len(v) > 0 { + rs = append(rs, cis[idx]) + } + } + return rs +} + +// getVariables get variables of template config items, normal config items and all +func (s *Service) getVariables(kt *kit.Kit, tmplRevisions []*table.TemplateRevision, cis []*pbci.ConfigItem) ( + vars [][]string, ciVars [][]string, allVars []string, err error) { + vars, err = s.getTmplVariables(kt, tmplRevisions) + if err != nil { + logs.Errorf("get template variables failed, err: %v, rid: %s", err, kt.Rid) + return nil, nil, nil, err + } + + ciVars, err = s.getCIVariables(kt, cis) + if err != nil { + logs.Errorf("get normal config item variables failed, err: %v, rid: %s", err, kt.Rid) + return nil, nil, nil, err + } + + // merge all template variables + allVariables := make([][]string, 0, len(vars)+len(ciVars)) + allVariables = append(allVariables, vars...) + allVariables = append(allVariables, ciVars...) + + allVars = tools.MergeDoubleStringSlice(allVariables) + + return vars, ciVars, allVars, nil +} + +// getTmplVariables get variables of template config items from repo +// the order of elements in slice variables and slice tmplRevisions is consistent +func (s *Service) getTmplVariables(kt *kit.Kit, tmplRevisions []*table.TemplateRevision) ([][]string, error) { + if len(tmplRevisions) == 0 { + return [][]string{}, nil + } + + variables := make([][]string, len(tmplRevisions)) + var hitError error + pipe := make(chan struct{}, 10) + wg := sync.WaitGroup{} + + for idx, r := range tmplRevisions { + wg.Add(1) + + pipe <- struct{}{} + go func(idx int, r *table.TemplateRevision) { + defer func() { + wg.Done() + <-pipe + }() + + k := kt.GetKitForRepoTmpl(r.Attachment.TemplateSpaceID) + // all the files have filtered by content size, so no need to check size again in repo + vars, err := s.repo.GetVariables(k, r.Spec.ContentSpec.Signature, false) + if err != nil { + hitError = fmt.Errorf("get template config variables from repo failed, "+ + "template id: %d, name: %s, path: %s, error: %v", + r.Attachment.TemplateID, r.Spec.Name, r.Spec.Path, err) + return + } + variables[idx] = vars + }(idx, r) + } + wg.Wait() + + if hitError != nil { + logs.Errorf("get template config variables failed, err: %v, rid: %s", hitError, kt.Rid) + return nil, hitError + } + + return variables, nil +} + +// getCIVariables get variables of normal config items from repo +// the order of elements in slice variables and slice tmplRevisions is consistent +func (s *Service) getCIVariables(kt *kit.Kit, cis []*pbci.ConfigItem) ([][]string, error) { + if len(cis) == 0 { + return [][]string{}, nil + } + + variables := make([][]string, len(cis)) + var hitError error + pipe := make(chan struct{}, 10) + wg := sync.WaitGroup{} + + for idx, c := range cis { + wg.Add(1) + + pipe <- struct{}{} + go func(idx int, c *pbci.ConfigItem) { + defer func() { + wg.Done() + <-pipe + }() + + k := kt.GetKitForRepoCfg() + // all the files have filtered by content size, so no need to check size again in repo + vars, err := s.repo.GetVariables(k, c.CommitSpec.Content.Signature, false) + if err != nil { + hitError = fmt.Errorf("get config item variables from repo failed, "+ + "config item id: %d, name: %s, path: %s, error: %v", + c.Id, c.Spec.Name, c.Spec.Path, err) + return + } + variables[idx] = vars + }(idx, c) + } + wg.Wait() + + if hitError != nil { + logs.Errorf("get config item variables failed, err: %v, rid: %s", hitError, kt.Rid) + return nil, hitError + } + + return variables, nil +} + // downloadTmplContent download template config item content from repo. // the order of elements in slice contents and slice tmplRevisions is consistent func (s *Service) downloadTmplContent(kt *kit.Kit, tmplRevisions []*table.TemplateRevision) ([][]byte, error) { diff --git a/bcs-services/bcs-bscp/cmd/ui/service/web.go b/bcs-services/bcs-bscp/cmd/ui/service/web.go index 436d55d9e8..586af1f8b8 100644 --- a/bcs-services/bcs-bscp/cmd/ui/service/web.go +++ b/bcs-services/bcs-bscp/cmd/ui/service/web.go @@ -164,6 +164,7 @@ func (s *WebServer) subRouter() http.Handler { SiteURL: config.G.Web.RoutePrefix, APIURL: config.G.Frontend.Host.BSCPAPIURL, IAMHost: config.G.Frontend.Host.BKIAMHost, + CMDBHost: config.G.Frontend.Host.BKCMDBHost, } if shouldProxyAPI { diff --git a/bcs-services/bcs-bscp/embed.go b/bcs-services/bcs-bscp/embed.go index b92778fc6f..4f76361fdc 100644 --- a/bcs-services/bcs-bscp/embed.go +++ b/bcs-services/bcs-bscp/embed.go @@ -50,6 +50,7 @@ type IndexConfig struct { RunEnv string StaticURL string IAMHost string + CMDBHost string APIURL string SiteURL string // vue 路由前缀 ProxyAPI bool @@ -158,6 +159,7 @@ func (e *EmbedWeb) RenderIndexHandler(conf *IndexConfig) http.Handler { "RUN_ENV": conf.RunEnv, "BK_BCS_BSCP_API": conf.APIURL, "BK_IAM_HOST": conf.IAMHost, + "BK_CC_HOST": conf.CMDBHost, "BK_BSCP_CONFIG": bscpConfig, "SITE_URL": conf.SiteURL, } diff --git a/bcs-services/bcs-bscp/pkg/cc/types.go b/bcs-services/bcs-bscp/pkg/cc/types.go index 548b4433bd..b5c735d3ca 100644 --- a/bcs-services/bcs-bscp/pkg/cc/types.go +++ b/bcs-services/bcs-bscp/pkg/cc/types.go @@ -257,9 +257,10 @@ const ( // Repository defines all the repo related runtime. type Repository struct { - StorageType StorageMode `yaml:"storageType"` - S3 S3Storage `yaml:"s3"` - BkRepo BkRepoStorage `yaml:"bkRepo"` + StorageType StorageMode `yaml:"storageType"` + S3 S3Storage `yaml:"s3"` + BkRepo BkRepoStorage `yaml:"bkRepo"` + RedisCluster RedisCluster `yaml:"redisCluster"` } // BkRepoStorage BKRepo 存储类型 diff --git a/bcs-services/bcs-bscp/pkg/config/frontend.go b/bcs-services/bcs-bscp/pkg/config/frontend.go index c3307a6371..6d69e34751 100644 --- a/bcs-services/bcs-bscp/pkg/config/frontend.go +++ b/bcs-services/bcs-bscp/pkg/config/frontend.go @@ -16,6 +16,7 @@ package config // HostConf host conf type HostConf struct { BKIAMHost string `yaml:"bk_iam_host"` // 权限中心 + BKCMDBHost string `yaml:"bk_cmdb_host"` // 配置平台 BSCPAPIURL string `yaml:"bscp_api_url"` // bscp api地址 } diff --git a/bcs-services/bcs-bscp/pkg/dal/repository/bkrepo.go b/bcs-services/bcs-bscp/pkg/dal/repository/bkrepo.go index ef1f1364fb..5e82901155 100644 --- a/bcs-services/bcs-bscp/pkg/dal/repository/bkrepo.go +++ b/bcs-services/bcs-bscp/pkg/dal/repository/bkrepo.go @@ -372,8 +372,8 @@ func (c *bkrepoClient) AsyncDownloadStatus(kt *kit.Kit, sign string, taskID stri return false, nil } -// newBKRepoProvider new bkrepo provider -func newBKRepoProvider(settings cc.Repository) (Provider, error) { +// newBKRepoClient new bkrepo client +func newBKRepoClient(settings cc.Repository) (BaseProvider, error) { cli, err := repo.NewClient(settings, metrics.Register()) if err != nil { return nil, err @@ -400,3 +400,22 @@ func newBKRepoProvider(settings cc.Repository) (Provider, error) { return p, nil } + +// newBKRepoProvider new bkrepo provider +func newBKRepoProvider(settings cc.Repository) (Provider, error) { + p, err := newBKRepoClient(settings) + if err != nil { + return nil, err + } + + var c VariableCacher + c, err = newVariableCacher(settings.RedisCluster, p) + if err != nil { + return nil, err + } + + return &repoProvider{ + BaseProvider: p, + VariableCacher: c, + }, nil +} diff --git a/bcs-services/bcs-bscp/pkg/dal/repository/cache.go b/bcs-services/bcs-bscp/pkg/dal/repository/cache.go new file mode 100644 index 0000000000..9a497cb42c --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/dal/repository/cache.go @@ -0,0 +1,147 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 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. + */ + +package repository + +import ( + "encoding/json" + "fmt" + "io" + + "bscp.io/pkg/cc" + "bscp.io/pkg/criteria/constant" + "bscp.io/pkg/dal/bedis" + "bscp.io/pkg/kit" + "bscp.io/pkg/tmplprocess" +) + +// variableCacheTTLSeconds is ttl for variable cache, 7 day. +const ( + variableCacheTTLSeconds = 3600 * 24 * 7 +) + +var errContentSizeExceedsLimit = fmt.Errorf("content size exceeds maximum limit %d", constant.MaxRenderBytes) + +// VariableCacher is used to set/get template variables with cache +type VariableCacher interface { + // SetVariables sets template variables into cache, the variables extracted from repository file content + SetVariables(kt *kit.Kit, sign string, checkSize bool) ([]string, error) + // GetVariables gets template variables from the cache firstly; if not found, then get from the repository + GetVariables(kt *kit.Kit, sign string, checkSize bool) ([]string, error) +} + +type varCacher struct { + p BaseProvider + bds bedis.Client + tmplProc tmplprocess.TmplProcessor +} + +// newVariableCacher new a variable cacher +func newVariableCacher(redisConf cc.RedisCluster, p BaseProvider) (VariableCacher, error) { + // init redis client + bds, err := bedis.NewRedisCache(redisConf) + if err != nil { + return nil, fmt.Errorf("new redis cluster failed, err: %v", err) + } + + return &varCacher{ + p: p, + bds: bds, + tmplProc: tmplprocess.NewTmplProcessor(), + }, nil +} + +// SetVariables sets template variables into cache, the variables extracted from repository file content +func (c *varCacher) SetVariables(kt *kit.Kit, sign string, checkSize bool) ([]string, error) { + if checkSize { + // check content byte size + m, err := c.p.Metadata(kt, sign) + if err != nil { + return nil, err + } + if m.ByteSize > constant.MaxRenderBytes { + return nil, errContentSizeExceedsLimit + } + } + + // get template variables from repository + vars, err := c.getVarsFromRepo(kt, sign) + if err != nil { + return nil, err + } + + var varsBytes []byte + varsBytes, err = json.Marshal(vars) + if err != nil { + return nil, err + } + + // set variables into cache + err = c.bds.Set(kt.Ctx, variableCacheKey(sign), string(varsBytes), variableCacheTTLSeconds) + if err != nil { + return nil, err + } + + return vars, nil +} + +// GetVariables gets template variables from the cache firstly; if not found, then get from the repository +func (c *varCacher) GetVariables(kt *kit.Kit, sign string, checkSize bool) ([]string, error) { + if checkSize { + // check content byte size + m, err := c.p.Metadata(kt, sign) + if err != nil { + return nil, err + } + if m.ByteSize > constant.MaxRenderBytes { + return nil, errContentSizeExceedsLimit + } + } + + // get variables from cache + val, err := c.bds.Get(kt.Ctx, variableCacheKey(sign)) + if err != nil { + return nil, err + } + + var vars []string + if len(val) > 0 { + if err = json.Unmarshal([]byte(val), &vars); err != nil { + return nil, err + } + return vars, nil + } + + // if no variables in cache, get them from the repository and set them into cache + return c.SetVariables(kt, sign, false) +} + +// getVarsFromRepo get template variables from repository +func (c *varCacher) getVarsFromRepo(kt *kit.Kit, sign string) ([]string, error) { + // download content and extract variables from it + body, _, err := c.p.Download(kt, sign) + if err != nil { + return nil, err + } + + var content []byte + content, err = io.ReadAll(body) + if err != nil { + return nil, err + } + + return c.tmplProc.ExtractVariables(content), nil +} + +func variableCacheKey(sign string) string { + return "vars_" + sign +} diff --git a/bcs-services/bcs-bscp/pkg/dal/repository/cos.go b/bcs-services/bcs-bscp/pkg/dal/repository/cos.go index 20deb332a6..a103400e7e 100644 --- a/bcs-services/bcs-bscp/pkg/dal/repository/cos.go +++ b/bcs-services/bcs-bscp/pkg/dal/repository/cos.go @@ -184,8 +184,8 @@ func (c *cosClient) AsyncDownloadStatus(kt *kit.Kit, sign string, taskID string) return false, errNotImplemented } -// newCosProvider new cos provider -func newCosProvider(conf cc.S3Storage) (Provider, error) { +// newCosClient new cos client +func newCosClient(conf cc.S3Storage) (BaseProvider, error) { host := fmt.Sprintf("%s://%s.%s", cosSchema, conf.BucketName, conf.Endpoint) // cos 鉴权签名 @@ -209,3 +209,22 @@ func newCosProvider(conf cc.S3Storage) (Provider, error) { return p, nil } + +// newCosProvider new cos provider +func newCosProvider(settings cc.Repository) (Provider, error) { + p, err := newCosClient(settings.S3) + if err != nil { + return nil, err + } + + var c VariableCacher + c, err = newVariableCacher(settings.RedisCluster, p) + if err != nil { + return nil, err + } + + return &repoProvider{ + BaseProvider: p, + VariableCacher: c, + }, nil +} diff --git a/bcs-services/bcs-bscp/pkg/dal/repository/repository.go b/bcs-services/bcs-bscp/pkg/dal/repository/repository.go index de93151a2e..14952c6931 100644 --- a/bcs-services/bcs-bscp/pkg/dal/repository/repository.go +++ b/bcs-services/bcs-bscp/pkg/dal/repository/repository.go @@ -77,14 +77,20 @@ type ObjectDownloader interface { URIDecorator(bizID uint32) DecoratorInter } -// Provider repo provider interface -type Provider interface { +// BaseProvider repo base provider interface +type BaseProvider interface { ObjectDownloader Upload(kt *kit.Kit, sign string, body io.Reader) (*ObjectMetadata, error) Download(kt *kit.Kit, sign string) (io.ReadCloser, int64, error) Metadata(kt *kit.Kit, sign string) (*ObjectMetadata, error) } +// Provider repo provider interface +type Provider interface { + BaseProvider + VariableCacher +} + // GetFileSign get file sha256 func GetFileSign(r *http.Request) (string, error) { sign := strings.ToLower(r.Header.Get(constant.ContentIDHeaderKey)) @@ -163,11 +169,17 @@ func newUriDecoratorInter(bizID uint32) DecoratorInter { return &uriDecoratorInter{bizID: bizID} } +// repoProvider implements interface Provider +type repoProvider struct { + BaseProvider + VariableCacher +} + // NewProvider init provider factory by storage type func NewProvider(conf cc.Repository) (Provider, error) { switch strings.ToUpper(string(conf.StorageType)) { case string(cc.S3): - return newCosProvider(conf.S3) + return newCosProvider(conf) case string(cc.BkRepo): return newBKRepoProvider(conf) } diff --git a/bcs-services/bcs-bscp/pkg/tools/tools.go b/bcs-services/bcs-bscp/pkg/tools/tools.go index 2674569af6..0d1101e1af 100644 --- a/bcs-services/bcs-bscp/pkg/tools/tools.go +++ b/bcs-services/bcs-bscp/pkg/tools/tools.go @@ -13,6 +13,7 @@ package tools import ( + "sort" "strconv" "strings" ) @@ -158,3 +159,26 @@ func IsSameSlice(s1, s2 []uint32) bool { return true } + +// MergeDoubleStringSlice merge [][]string elements into []string +// keep the elements in result is unique and sorted in ASCII character order +// eg: input [][]string{{"a", "b"}, {"a", "c"}} return []string{"a", "b", "c"} +func MergeDoubleStringSlice(input [][]string) []string { + uniqueMap := make(map[string]bool) + + for _, subSlice := range input { + for _, element := range subSlice { + uniqueMap[element] = true + } + } + + uniqueElements := make([]string, 0, len(uniqueMap)) + for element := range uniqueMap { + uniqueElements = append(uniqueElements, element) + } + + // Sort the unique elements in ASCII character order + sort.Strings(uniqueElements) + + return uniqueElements +} diff --git a/bcs-services/bcs-bscp/ui/index.html b/bcs-services/bcs-bscp/ui/index.html index 5d5451bd6e..9fc1581392 100644 --- a/bcs-services/bcs-bscp/ui/index.html +++ b/bcs-services/bcs-bscp/ui/index.html @@ -9,6 +9,7 @@ var BK_BCS_BSCP_API = '{{ .BK_BCS_BSCP_API }}' var SITE_URL = '{{ .SITE_URL }}' var BK_IAM_HOST = '{{ .BK_IAM_HOST }}' + var BK_CC_HOST = '{{ .BK_CC_HOST }}' window.BSCP_CONFIG = JSON.parse('{{ .BK_BSCP_CONFIG }}') diff --git a/bcs-services/bcs-bscp/ui/src/components/code-editor/index.vue b/bcs-services/bcs-bscp/ui/src/components/code-editor/index.vue index 259c63d2d8..915ee96637 100644 --- a/bcs-services/bcs-bscp/ui/src/components/code-editor/index.vue +++ b/bcs-services/bcs-bscp/ui/src/components/code-editor/index.vue @@ -51,7 +51,7 @@ const props = withDefaults( editable: true, lfEol: true, language: '', - } + }, ); const emit = defineEmits(['update:modelValue', 'change', 'enter']); @@ -72,21 +72,21 @@ watch( if (val !== localVal.value) { editor.setValue(val); } - } + }, ); watch( () => props.language, (val) => { monaco.editor.setModelLanguage(editor.getModel() as monaco.editor.ITextModel, val); - } + }, ); watch( () => props.editable, (val) => { editor.updateOptions({ readOnly: !val }); - } + }, ); watch( @@ -95,14 +95,14 @@ watch( if (Array.isArray(val) && val.length > 0) { editorHoverProvider = useEditorVariableReplace(editor, val); } - } + }, ); watch( () => props.errorLine, () => { setErrorLine(); - } + }, ); onMounted(() => { @@ -167,9 +167,9 @@ const handleVariableList = async () => { getVariableList(bkBizId.value, { start: 0, limit: 1000 }), getUnReleasedAppVariables(bkBizId.value, appId.value), ]); - variableNameList.value = variableList.details.map((item: any) => item.spec.name); - privateVariableNameList.value = privateVariableList.details.map((item: any) => item.name); - variableNameList.value?.filter((item) => !privateVariableNameList.value!.includes(item)); + variableNameList.value = variableList.details.map((item: any) => `.${item.spec.name}`); + privateVariableNameList.value = privateVariableList.details.map((item: any) => `.${item.name}`); + variableNameList.value?.filter(item => !privateVariableNameList.value!.includes(item)); }; // 注册自定义语言 @@ -208,22 +208,18 @@ const aotoCompletion = () => { const lineContent = model.getLineContent(position.lineNumber); const charBeforeCursor = lineContent.charAt(position.column - 2); // 根据当前的文本内容和光标位置,返回自动补全的候选项列表 - const variableSuggestions = variableNameList.value!.map((item: string) => { - return { - label: item, // 候选项的显示文本 - kind: monaco.languages.CompletionItemKind.Variable, // 候选项的类型 - insertText: item, // 插入光标后的文本 - range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), - }; - }); - const privateVariableSuggestions = privateVariableNameList.value!.map((item: string) => { - return { - label: item, - kind: monaco.languages.CompletionItemKind.Variable, - insertText: item, - range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), - }; - }); + const variableSuggestions = variableNameList.value!.map((item: string) => ({ + label: item, // 候选项的显示文本 + kind: monaco.languages.CompletionItemKind.Variable, // 候选项的类型 + insertText: item, // 插入光标后的文本 + range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), + })); + const privateVariableSuggestions = privateVariableNameList.value!.map((item: string) => ({ + label: item, + kind: monaco.languages.CompletionItemKind.Variable, + insertText: item, + range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), + })); const suggestions = [...variableSuggestions, ...privateVariableSuggestions]; if (charBeforeCursor === '{') { return { @@ -234,9 +230,7 @@ const aotoCompletion = () => { suggestions: [], }; }, - resolveCompletionItem: (item: any) => { - return item; - }, + resolveCompletionItem: (item: any) => item, }); }; diff --git a/bcs-services/bcs-bscp/ui/src/components/diff/text.vue b/bcs-services/bcs-bscp/ui/src/components/diff/text.vue index 0f1f7107a3..3f85cf09d1 100644 --- a/bcs-services/bcs-bscp/ui/src/components/diff/text.vue +++ b/bcs-services/bcs-bscp/ui/src/components/diff/text.vue @@ -59,7 +59,7 @@ const props = withDefaults( currentVariables: () => [], currentLanguage: '', baseVariables: () => [], - } + }, ); const textDiffRef = ref(); @@ -76,7 +76,7 @@ watch( () => { updateModel(); replaceDiffVariables(); - } + }, ); watch( @@ -84,7 +84,7 @@ watch( () => { updateModel(); replaceDiffVariables(); - } + }, ); watch( @@ -92,7 +92,7 @@ watch( () => { updateModel(); replaceDiffVariables(); - } + }, ); onMounted(() => { @@ -182,7 +182,7 @@ const replaceDiffVariables = () => { permissionDiffEditorHoverProvider = useDiffEditorVariableReplace( permissionEditor, props.currentVariables, - props.baseVariables + props.baseVariables, ); } }; diff --git a/bcs-services/bcs-bscp/ui/src/components/footer.vue b/bcs-services/bcs-bscp/ui/src/components/footer.vue index 3b1da15c22..884db5d887 100644 --- a/bcs-services/bcs-bscp/ui/src/components/footer.vue +++ b/bcs-services/bcs-bscp/ui/src/components/footer.vue @@ -12,7 +12,7 @@ import { defineComponent } from 'vue'; export default defineComponent({ - name: 'app-footer', + name: 'AppFooter', setup() { return { // @ts-ignore diff --git a/bcs-services/bcs-bscp/ui/src/components/head.vue b/bcs-services/bcs-bscp/ui/src/components/head.vue index f5346120c0..2f5bb9f02a 100644 --- a/bcs-services/bcs-bscp/ui/src/components/head.vue +++ b/bcs-services/bcs-bscp/ui/src/components/head.vue @@ -11,7 +11,7 @@ :class="['nav-item', { actived: route.meta.navModule === nav.module }]" :key="nav.id" :to="{ name: nav.id, params: { spaceId: spaceId || 0 } }" - > + @click="handleNavClick(nav.id)"> {{ nav.name }} @@ -35,6 +35,12 @@ +
([]); const crtSpaceText = computed(() => { - const space = spaceList.value.find((item) => item.space_id === spaceId.value); + const space = spaceList.value.find(item => item.space_id === spaceId.value); if (space) { return `${space.space_name}(${spaceId.value})`; } @@ -135,9 +141,22 @@ watch( }, { immediate: true, - } + }, ); +const handleNavClick = (navId: String) => { + if (navId === 'service-all') { + const lastAccessedServiceDetail = localStorage.getItem('lastAccessedServiceDetail'); + if (lastAccessedServiceDetail) { + const detail = JSON.parse(lastAccessedServiceDetail); + if (detail.spaceId === spaceId.value) { + router.push({ name: 'service-config', params: { spaceId: detail.spaceId, appId: detail.appId } }); + return; + }; + }; + }; +}; + const handleSpaceSearch = (searchStr: string) => { if (searchStr) { optionList.value = spaceList.value.filter((item) => { @@ -174,7 +193,7 @@ const handleSelectSpace = (id: string) => { state.currentTemplateSpace = 0; state.currentPkg = ''; }); - const nav = navList.find((item) => item.module === route.meta.navModule); + const nav = navList.find(item => item.module === route.meta.navModule); if (nav) { router.push({ name: nav.id, params: { spaceId: id } }); } else { @@ -242,6 +261,10 @@ Object.keys(module).forEach((path) => { const handleLoginOut = () => { loginOut(); }; +const handleToCMDB = () => { + // @ts-ignore + window.open(BK_CC_HOST); // eslint-disable-line no-undef +}; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue index b73ff9289a..6fca728cf4 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue @@ -246,7 +246,7 @@ watch( createPending.value = false; getPermData(); refreshListWithLoading(); - } + }, ); onMounted(() => { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue b/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue index 2ded1f0674..5f7bfd755c 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue @@ -136,7 +136,7 @@ watch( () => props.group, (val) => { formData.value = cloneDeep(val); - } + }, ); onMounted(() => { @@ -148,9 +148,7 @@ const getServiceList = async () => { try { const bizId = route.params.spaceId as string; const query = { - start: 0, - limit: 1000, // @todo 确认拉全量列表参数 - operator: userInfo.value.username, + all: true, }; const resp = await getAppList(bizId, query); serviceList.value = resp.details; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/scripts/list/create-script.vue b/bcs-services/bcs-bscp/ui/src/views/space/scripts/list/create-script.vue index 20f26eabb0..2f734656ef 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/scripts/list/create-script.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/scripts/list/create-script.vue @@ -2,7 +2,7 @@ + + + @@ -71,6 +74,9 @@ import VersionLayout from '../config/components/version-layout.vue'; import ConfirmDialog from './publish-version/confirm-dialog.vue'; import SelectGroup from './publish-version/select-group/index.vue'; import VersionDiff from '../config/components/version-diff/index.vue'; +import { useRoute } from 'vue-router'; +import { getConfigVersionList } from '../../../../../api/config'; +import { IConfigVersion } from '../../../../../../types/config'; const { permissionQuery, showApplyPermDialog } = storeToRefs(useGlobalStore()); const serviceStore = useServiceStore(); @@ -87,6 +93,10 @@ const props = defineProps<{ const emit = defineEmits(['confirm']); +const route = useRoute(); +const bkBizId = String(route.params.spaceId); +const appId = Number(route.params.appId); +const versionList = ref([]); const isSelectGroupPanelOpen = ref(false); const isDiffSliderShow = ref(false); const isConfirmDialogShow = ref(false); @@ -107,7 +117,27 @@ const permissionQueryResource = computed(() => [ }, ]); +// 判断是否需要对比上线版本 +const handleDiffOrPublish = () => { + if (versionList.value.length) { + isDiffSliderShow.value = true; + return; + } + handleOpenPublishDialog(); +}; + +// 获取所有对比基准版本 +const getVersionList = async () => { + try { + const res = await getConfigVersionList(bkBizId, appId, { start: 0, all: true }); + versionList.value = res.data.details.filter((item: IConfigVersion) => item.id !== versionData.value.id && item.status.publish_status === 'partial_released'); + } catch (e) { + console.error(e); + } +}; + const handleBtnClick = () => { + getVersionList(); if (props.hasPerm) { openSelectGroupPanel(); } else { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/confirm-dialog.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/confirm-dialog.vue index 262bd273a3..01bbb4eaa3 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/confirm-dialog.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/confirm-dialog.vue @@ -77,9 +77,9 @@ const rules = { watch( () => props.groups, () => { - localVal.value.groups = props.groups.map((item) => item.id); + localVal.value.groups = props.groups.map(item => item.id); }, - { immediate: true } + { immediate: true }, ); const handleClose = () => { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue index cd986f2791..5323bd9339 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue @@ -31,7 +31,9 @@ /> @@ -52,6 +54,7 @@ :base-version-id="baseVersionId" :show-publish-btn="true" @publish="handleOpenPublishDialog" + :version-diff-list="versionList" /> @@ -70,6 +73,9 @@ import VersionLayout from '../../config/components/version-layout.vue'; import ConfirmDialog from './confirm-dialog.vue'; import SelectGroup from './select-group/index.vue'; import VersionDiff from '../../config/components/version-diff/index.vue'; +import { useRoute } from 'vue-router'; +import { getConfigVersionList } from '../../../../../../api/config'; +import { IConfigVersion } from '../../../../../../../types/config'; const { permissionQuery, showApplyPermDialog } = storeToRefs(useGlobalStore()); const serviceStore = useServiceStore(); @@ -86,6 +92,10 @@ const props = defineProps<{ const emit = defineEmits(['confirm']); +const route = useRoute(); +const bkBizId = String(route.params.spaceId); +const appId = Number(route.params.appId); +const versionList = ref([]); const isSelectGroupPanelOpen = ref(false); const isDiffSliderShow = ref(false); const isConfirmDialogShow = ref(false); @@ -104,7 +114,27 @@ const permissionQueryResource = computed(() => [ }, ]); +// 判断是否需要对比上线版本 +const handleDiffOrPublish = () => { + if (versionList.value.length) { + isDiffSliderShow.value = true; + return; + } + handleOpenPublishDialog(); +}; + +// 获取所有对比基准版本 +const getVersionList = async () => { + try { + const res = await getConfigVersionList(bkBizId, appId, { start: 0, all: true }); + versionList.value = res.data.details.filter((item: IConfigVersion) => item.id !== versionData.value.id && item.status.publish_status === 'partial_released'); + } catch (e) { + console.error(e); + } +}; + const handleBtnClick = () => { + getVersionList(); if (props.hasPerm) { isSelectGroupPanelOpen.value = true; } else { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/index.vue index 54f8cc9313..58b2691abe 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/index.vue @@ -20,9 +20,9 @@ - {{ previewGroup.name }} {{ previewGroup.children.length }} 个分组 + >共 {{ previewGroup.children.length }} 个分组

上线预览 - 上线后,所选分组将从以下各版本更新至当前版本 + 上线后,以下分组将从以下各版本更新至当前版本

@@ -16,10 +16,11 @@ v-for="previewGroup in previewData" :key="previewGroup.id" :preview-group="previewGroup" - :allow-preview-delete="allowPreviewDelete" + :allow-preview-delete="props.groupType === 'select'" :disabled="props.disabled" @diff="emits('diff', $event)" - @delete="handleDelete"> + @delete="handleDelete" + >
@@ -28,9 +29,14 @@ diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/tree.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/tree.vue index 272289d7f0..8e7abee85d 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/tree.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/tree.vue @@ -110,11 +110,11 @@ const categorizingData = (groupList: IGroupToPublish[]) => { // id为0表示默认分组,在分组节点树中不可选 return; } - const checked = props.value.findIndex((item) => item.id === group.id) > -1; + const checked = props.value.findIndex(item => item.id === group.id) > -1; const disabled = props.disabled.includes(group.id); group.rules.forEach((rule) => { const nodeId = `${rule.key}_${group.id}`; // 用在节点树上做唯一标识 - const parentNode = treeNodeData.find((item) => item.node_id === rule.key); + const parentNode = treeNodeData.find(item => item.node_id === rule.key); const nodeData = { ...group, node_id: nodeId, checked, disabled }; if (parentNode) { parentNode.count += 1; @@ -143,15 +143,14 @@ const categorizingData = (groupList: IGroupToPublish[]) => { // 父级分类节点是否选中 const isParentNodeChecked = (node: ITreeParentNodeData) => { - const res = node.children.length > 0 && node.children.every((group) => group.checked); + const res = node.children.length > 0 && node.children.every(group => group.checked); return res; }; // 父级分类节点是否半选 -const isParentNodeIndeterminate = (node: ITreeParentNodeData) => - node.children.length > 0 && - !node.children.every((group) => group.checked) && - node.children.some((group) => group.checked); +const isParentNodeIndeterminate = (node: ITreeParentNodeData) => node.children.length > 0 && + !node.children.every(group => group.checked) && + node.children.some(group => group.checked); const props = defineProps<{ groupListLoading: boolean; @@ -175,7 +174,7 @@ const isSearchEmpty = ref(false); const searchTreeData = computed(() => { if (searchStr.value === '') return treeData.value; isSearchEmpty.value = true; - return treeData.value.filter((treeNode) => treeNode.name.toLowerCase().includes(searchStr.value.toLowerCase())); + return treeData.value.filter(treeNode => treeNode.name.toLowerCase().includes(searchStr.value.toLowerCase())); }); // 分组列表变更 @@ -184,14 +183,14 @@ watch( (val) => { categorizingData(val); }, - { immediate: true } + { immediate: true }, ); // 选中小组变更 watch( () => props.value, (val) => { - const ids = val.map((item) => item.id); + const ids = val.map(item => item.id); treeData.value.forEach((parentNode) => { parentNode.children.forEach((node) => { node.checked = ids.includes(node.id); @@ -199,15 +198,15 @@ watch( parentNode.checked = isParentNodeChecked(parentNode); parentNode.indeterminate = isParentNodeIndeterminate(parentNode); }); - } + }, ); // 全选 const handleSelectAll = () => { const groupList: IGroupToPublish[] = []; props.groupList.forEach((group) => { - const hasGroupChecked = props.value.findIndex((item) => item.id === group.id) > -1; // 分组在编辑前是否选中 - const hasAdded = groupList.findIndex((item) => item.id === group.id) > -1; // 分组已添加 + const hasGroupChecked = props.value.findIndex(item => item.id === group.id) > -1; // 分组在编辑前是否选中 + const hasAdded = groupList.findIndex(item => item.id === group.id) > -1; // 分组已添加 const isDisabled = props.disabled.includes(group.id); if (group.id !== 0 && !hasAdded && (!isDisabled || hasGroupChecked)) { groupList.push(group); @@ -219,7 +218,7 @@ const handleSelectAll = () => { // 全不选 const handleClearAll = () => { const hasCheckedGroups = props.groupList.filter((group) => { - const res = props.disabled.includes(group.id) && props.value.findIndex((item) => item.id === group.id) > -1; + const res = props.disabled.includes(group.id) && props.value.findIndex(item => item.id === group.id) > -1; return res; }); emits('change', hasCheckedGroups); @@ -235,7 +234,7 @@ const handleSelectVersion = (versions: number[]) => { } else { // 选择部分 versions.forEach((id) => { - const version = props.versionList.find((item) => item.id === id); + const version = props.versionList.find(item => item.id === id); if (version) { selectedVersion.push(version); } @@ -243,8 +242,8 @@ const handleSelectVersion = (versions: number[]) => { } selectedVersion.forEach((version) => { version.status.released_groups.forEach((releaseItem) => { - if (!list.find((item) => releaseItem.id === item.id)) { - const group = allGroupNode.value.find((groupItem) => groupItem.id === releaseItem.id); + if (!list.find(item => releaseItem.id === item.id)) { + const group = allGroupNode.value.find(groupItem => groupItem.id === releaseItem.id); if (group) { list.push(group); } @@ -259,21 +258,21 @@ const handleNodeCheckChange = (node: IGroupNodeData | ITreeParentNodeData, check const list = props.value.slice(); if (Object.prototype.hasOwnProperty.call(node, 'parent')) { // 分类节点 - const treeParentNode = treeData.value.find((parentNode) => parentNode.node_id === node.node_id); + const treeParentNode = treeData.value.find(parentNode => parentNode.node_id === node.node_id); if (treeParentNode) { if (checked) { treeParentNode.children - .filter((group) => !group.disabled) + .filter(group => !group.disabled) .forEach((group) => { - if (!list.find((item) => item.id === group.id)) { + if (!list.find(item => item.id === group.id)) { list.push(group); } }); } else { treeParentNode.children - .filter((group) => !group.disabled) + .filter(group => !group.disabled) .forEach((group) => { - const index = list.findIndex((item) => item.id === group.id); + const index = list.findIndex(item => item.id === group.id); if (index > -1) { list.splice(index, 1); } @@ -282,12 +281,12 @@ const handleNodeCheckChange = (node: IGroupNodeData | ITreeParentNodeData, check } } else { // 叶子节点 - const group = props.groupList.find((group) => group.id === (node as IGroupNodeData).id); + const group = props.groupList.find(group => group.id === (node as IGroupNodeData).id); if (group) { if (checked) { list.push(group); } else { - const index = list.findIndex((item) => item.id === group.id); + const index = list.findIndex(item => item.id === group.id); list.splice(index, 1); } } diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/service-selector.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/service-selector.vue index 318a72f6ed..85b1f1d6c6 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/service-selector.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/service-selector.vue @@ -2,28 +2,44 @@
+ @change="handleAppChange"> - -
-
@@ -32,7 +48,7 @@ import { ref, watch, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { storeToRefs } from 'pinia'; import { AngleDown } from 'bkui-vue/lib/icon'; -import useUserStore from '../../../../../store/user'; +import useGlobalStore from '../../../../../store/global'; import useServiceStore from '../../../../../store/service'; import { IAppItem } from '../../../../../../types/app'; import { getAppList } from '../../../../../api'; @@ -41,7 +57,7 @@ const route = useRoute(); const router = useRouter(); const { appData } = storeToRefs(useServiceStore()); -const { userInfo } = storeToRefs(useUserStore()); +const { showApplyPermDialog, permissionQuery } = storeToRefs(useGlobalStore()); const bizId = route.params.spaceId as string; @@ -54,6 +70,7 @@ defineEmits(['change']); const serviceList = ref([]); const loading = ref(false); const localVal = ref(props.value); +const selectorRef = ref(); watch( () => props.value, @@ -71,8 +88,7 @@ const loadServiceList = async () => { try { const query = { start: 0, - limit: 100, - operator: userInfo.value.username, + all: true, }; const resp = await getAppList(bizId, query); serviceList.value = resp.details; @@ -83,6 +99,28 @@ const loadServiceList = async () => { } }; +// 点击无查看权限的选项,弹出申请权限弹窗 +const handleOptionClick = (service: IAppItem, event: Event) => { + if (!service.permissions.view) { + selectorRef.value.hidePopover(); + event.stopPropagation(); + permissionQuery.value = { + resources: [ + { + biz_id: service.biz_id, + basic: { + type: 'app', + action: 'view', + resource_id: service.id, + }, + }, + ], + }; + + showApplyPermDialog.value = true; + } +}; + const handleAppChange = (id: number) => { const service = serviceList.value.find(service => service.id === id); if (service) { @@ -91,7 +129,7 @@ const handleAppChange = (id: number) => { }; + diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/aside-menu/configs.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/aside-menu/configs.vue index b876d00b8b..4a1b89cde7 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/aside-menu/configs.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/aside-menu/configs.vue @@ -133,7 +133,7 @@ const props = withDefaults( { unNamedVersionVariables: () => [], selectedConfig: () => ({ pkgId: 0, id: 0, version: 0 }), - } + }, ); const emits = defineEmits(['selected']); @@ -168,7 +168,7 @@ watch( groupedConfigListOnShow.value = aggregatedList.value.slice(); setDefaultSelected(); isOnlyShowDiff.value && handleSearch(); - } + }, ); // 当前版本默认选中的配置文件 @@ -181,7 +181,7 @@ watch( }, { immediate: true, - } + }, ); watch( @@ -209,7 +209,7 @@ watch( current: { content: '', variables: '' }, }); } - } + }, ); onMounted(async () => { @@ -312,7 +312,7 @@ const getBoundTemplateList = async (id: number) => { const group: IConfigsGroupData = { template_space_id, id: template_set_id, - name: `${template_space_name === 'default_space' ? '默认空间' : template_space_name} - ${template_set_name}`, + name: `${template_space_name} - ${template_set_name}`, expand: false, configs: [], }; @@ -416,7 +416,7 @@ const calcDiff = () => { // 计算当前版本删除项 baseGroupList.value.forEach((baseGroupItem) => { const { template_space_id, id, name, expand, configs } = baseGroupItem; - const groupIndex = list.findIndex((item) => item.id === baseGroupItem.id); + const groupIndex = list.findIndex(item => item.id === baseGroupItem.id); const diffGroup: IDiffGroupData = groupIndex > -1 ? list[groupIndex] : { template_space_id, id, name, expand, configs: [] }; @@ -458,13 +458,13 @@ const calcDiff = () => { // 否则取第一个非空分组的第一个配置文件 const setDefaultSelected = () => { if (props.selectedConfig.id) { - const pkg = aggregatedList.value.find((group) => group.id === props.selectedConfig.pkgId); + const pkg = aggregatedList.value.find(group => group.id === props.selectedConfig.pkgId); if (pkg) { pkg.expand = true; } handleSelectItem(props.selectedConfig); } else { - const group = aggregatedList.value.find((group) => group.configs.length > 0); + const group = aggregatedList.value.find(group => group.configs.length > 0); if (group) { handleSelectItem({ pkgId: group.id, id: group.configs[0].id, version: group.configs[0].template_revision_id }); } @@ -508,7 +508,7 @@ const getItemSelectedStatus = (pkgId: number, config: IConfigDiffItem) => { // 选择对比配置文件后,加载配置文件详情,组装对比数据 const handleSelectItem = async (selectedConfig: IConfigDiffSelected) => { - const pkg = aggregatedList.value.find((item) => item.id === selectedConfig.pkgId); + const pkg = aggregatedList.value.find(item => item.id === selectedConfig.pkgId); if (pkg) { const config = pkg.configs.find((item) => { const res = item.id === selectedConfig.id && item.template_revision_id === selectedConfig.version; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/index.vue index 5ca39be96f..aee3fe8805 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/version-diff/index.vue @@ -81,6 +81,7 @@ const props = defineProps<{ unNamedVersionVariables?: IVariableEditParams[]; baseVersionId?: number; // 默认选中的基准版本id selectedConfig?: IConfigDiffSelected; // 默认选中的配置文件id + versionDiffList?: IConfigVersion[]; }>(); const emits = defineEmits(['update:show', 'publish']); @@ -112,7 +113,7 @@ watch( if (val) { getVersionList(); } - } + }, ); watch( @@ -122,13 +123,17 @@ watch( selectedBaseVersion.value = val; } }, - { immediate: true } + { immediate: true }, ); // 获取所有对比基准版本 const getVersionList = async () => { try { versionListLoading.value = true; + if (props.versionDiffList) { + versionList.value = props.versionDiffList; + return; + } const res = await getConfigVersionList(bkBizId, appId, { start: 0, all: true }); versionList.value = res.data.details.filter((item: IConfigVersion) => item.id !== props.currentVersion.id); } catch (e) { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-simple-list/config-list-with-templates.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-simple-list/config-list-with-templates.vue index e93b2c6d3b..4f61030e5c 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-simple-list/config-list-with-templates.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-simple-list/config-list-with-templates.vue @@ -112,7 +112,7 @@ watch( () => versionData.value.id, () => { getListData(); - } + }, ); // 是否为未命名版本 @@ -197,13 +197,12 @@ const transListToTableData = () => { }; // 将非模板配置文件数据转为表格数据 -const transConfigsToTableItemData = (list: IConfigItem[]) => - list.map((item: IConfigItem) => { - const { id, spec, revision, file_state } = item; - const { name, file_type, path } = spec; - const { creator, reviser, update_at } = revision; - return { id, name, versionId: 0, versionName: '--', path, creator, reviser, update_at, file_type, file_state }; - }); +const transConfigsToTableItemData = (list: IConfigItem[]) => list.map((item: IConfigItem) => { + const { id, spec, revision, file_state } = item; + const { name, file_type, path } = spec; + const { creator, reviser, update_at } = revision; + return { id, name, versionId: 0, versionName: '--', path, creator, reviser, update_at, file_type, file_state }; +}); // 将模板按套餐分组,并将模板数据格式转为表格数据 const groupTplsByPkg = (list: IBoundTemplateGroup[]) => { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-table-list/config-form.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-table-list/config-form.vue index ca2f4b8f5f..8c47c1ab73 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-table-list/config-form.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/config-list/config-table-list/config-form.vue @@ -98,14 +98,19 @@ :disabled="!editable" :multiple="false" :files="fileList" - :custom-request="handleFileUpload" - > + :custom-request="handleFileUpload"> @@ -124,13 +129,13 @@ import { ref, computed, watch } from 'vue'; import SHA256 from 'crypto-js/sha256'; import WordArray from 'crypto-js/lib-typedarrays'; -import { TextFill, Done } from 'bkui-vue/lib/icon'; +import { TextFill, Done, Error } from 'bkui-vue/lib/icon'; import BkMessage from 'bkui-vue/lib/message'; import { IConfigEditParams, IFileConfigContentSummary } from '../../../../../../../../types/config'; import { IVariableEditParams } from '../../../../../../../../types/variable'; import { updateConfigContent, downloadConfigContent } from '../../../../../../../api/config'; import { downloadTemplateContent, updateTemplateContent } from '../../../../../../../api/template'; -import { stringLengthInBytes } from '../../../../../../../utils/index'; +import { stringLengthInBytes, byteUnitConverse } from '../../../../../../../utils/index'; import { transFileToObject, fileDownload } from '../../../../../../../utils/file'; import { CONFIG_FILE_TYPE } from '../../../../../../../constants/config'; import ConfigContentEditor from '../../components/config-content-editor.vue'; @@ -160,7 +165,7 @@ const props = withDefaults( }>(), { editable: true, - } + }, ); const emits = defineEmits(['change', 'update:fileUploading']); @@ -196,7 +201,7 @@ const rules = { }, { validator: () => { - const privilege = parseInt(privilegeInputVal.value[0]); + const privilege = parseInt(privilegeInputVal.value[0], 10); return privilege >= 4; }, message: '文件own必须有读取权限', @@ -209,11 +214,21 @@ const rules = { message: '最大长度256个字符', }, { - validator: (value: string) => /^\/([a-zA-Z0-9\/\-\.]+\/)*[a-zA-Z0-9\/\-\.]+$/.test(value), + validator: (value: string) => /^\/([a-zA-Z0-9/\-.]+\/)*[a-zA-Z0-9/\-.]+$/.test(value), message: '无效的路径,路径不符合Unix文件路径格式规范', trigger: 'blur', }, ], + memo: [ + { + validator: (value: string) => { + if (!value) return true; + return /^[\u4E00-\u9FA5a-zA-Z0-9_\- ]*[\u4E00-\u9FA5a-zA-Z0-9](?!.*[,])[\u4E00-\u9FA5a-zA-Z0-9_\- ]*$/.test(value); + }, + message: '只允许包含中文、英文、数字、下划线、连字符、空格,并且必须以中文、英文、数字开头和结尾。', + trigger: 'change', + }, + ], }; // 传入到bk-upload组件的文件对象 @@ -223,7 +238,7 @@ const fileList = computed(() => (fileContent.value ? [transFileToObject(fileCont const privilegeGroupsValue = computed(() => { const data: { [index: string]: number[] } = { 0: [], 1: [], 2: [] }; if (typeof localVal.value.privilege === 'string' && localVal.value.privilege.length > 0) { - const valArr = localVal.value.privilege.split('').map((i) => parseInt(i, 10)); + const valArr = localVal.value.privilege.split('').map(i => parseInt(i, 10)); valArr.forEach((item, index) => { data[index as keyof typeof data] = PRIVILEGE_VALUE_MAP[item as keyof typeof PRIVILEGE_VALUE_MAP]; }); @@ -236,7 +251,7 @@ watch( (val) => { privilegeInputVal.value = val as string; }, - { immediate: true } + { immediate: true }, ); watch( @@ -248,7 +263,7 @@ watch( stringContent.value = props.content as string; } }, - { immediate: true } + { immediate: true }, ); // 权限输入框失焦后,校验输入是否合法,如不合法回退到上次输入 @@ -456,9 +471,20 @@ defineExpose({ align-items: center; color: #979ba5; font-size: 12px; - .done-icon { - font-size: 20px; - color: #2dcb56; + .status-icon-area { + display: flex; + width: 20px; + height: 100%; + align-items: center; + justify-content: center; + .success-icon { + font-size: 20px; + color: #2dcb56; + } + .error-icon { + font-size: 14px; + color: #ea3636; + } } .file-icon { margin: 0 6px 0 0; @@ -477,6 +503,12 @@ defineExpose({ } } } + .error-msg { + padding: 0 0 10px 38px; + line-height: 1; + font-size: 12px; + color: #ff5656; + } } + + diff --git a/bcs-services/bcs-bscp/ui/src/views/space/templates/list/package-menu/package-form.vue b/bcs-services/bcs-bscp/ui/src/views/space/templates/list/package-menu/package-form.vue index 381be0c842..79ba31a53d 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/templates/list/package-menu/package-form.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/templates/list/package-menu/package-form.vue @@ -55,12 +55,7 @@ const props = defineProps<{ const emits = defineEmits(['change']); -const localVal = ref({ - name: '', - memo: '', - public: true, - bound_apps: [], -}); +const localVal = ref(cloneDeep(props.data)); const formRef = ref(); const serviceLoading = ref(false); const serviceList = ref([]); @@ -83,15 +78,11 @@ watch( () => props.data, (val) => { localVal.value = cloneDeep(val); - }, - { - immediate: true, - }, + } ); onMounted(() => { getServiceList(); - localVal.value.name = ''; }); const getServiceList = async () => { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/templates/list/space/selector.vue b/bcs-services/bcs-bscp/ui/src/views/space/templates/list/space/selector.vue index 8f91ad8e89..6689a355ff 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/templates/list/space/selector.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/templates/list/space/selector.vue @@ -12,14 +12,14 @@ >
-
{{ item.spec.name === 'default_space' ? '默认空间' : item.spec.name }}
+
{{ item.spec.name }}
{ - if (templateSpaceDetail.value.name === 'default_space') { - return '默认空间'; - } - return templateSpaceDetail.value.name; -}); - const templateSpaceDetail = computed(() => { - const item = templateSpaceList.value.find((item) => item.id === currentTemplateSpace.value); + const item = templateSpaceList.value.find(item => item.id === currentTemplateSpace.value); if (item) { const { name, memo } = item.spec; return { name, memo }; @@ -92,7 +85,7 @@ watch( () => spaceId.value, () => { initData(); - } + }, ); onMounted(() => { @@ -120,7 +113,13 @@ const loadList = async () => { all: true, }; const res = await getTemplateSpaceList(spaceId.value, params); - spaceList.value = res.details; + const index = (res.details as ITemplateSpaceItem[]).findIndex(item => ['默认空间', 'default_space'].includes(item.spec.name)) + if (index > -1) { + // 默认空间放到首位 + spaceList.value = res.details.splice(index, 1).concat(res.details) + } else { + spaceList.value = res.details; + } templateStore.$patch((state) => { state.templateSpaceList = spaceList.value; }); diff --git a/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-bound-by-apps-detail.vue b/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-bound-by-apps-detail.vue index 79aa78c687..8aeeacf8fc 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-bound-by-apps-detail.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-bound-by-apps-detail.vue @@ -70,7 +70,7 @@ watch( searchStr.value = ''; getList(); } - } + }, ); const getList = async () => { @@ -88,7 +88,7 @@ const getList = async () => { props.currentTemplateSpace, props.config.id, props.config.versionId, - params + params, ); appList.value = res.details; pagination.value.count = res.count; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-detail/create-version-confirm-dialog.vue b/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-detail/create-version-confirm-dialog.vue index 7bd174087d..68e3e2af95 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-detail/create-version-confirm-dialog.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/templates/version-manage/version-detail/create-version-confirm-dialog.vue @@ -16,7 +16,7 @@