diff --git a/docker-images/core/ci/dockerfile/backend.bkci.sh b/docker-images/core/ci/dockerfile/backend.bkci.sh index 42587af073e..8cf50cb3adc 100644 --- a/docker-images/core/ci/dockerfile/backend.bkci.sh +++ b/docker-images/core/ci/dockerfile/backend.bkci.sh @@ -1,7 +1,10 @@ #!/bin/bash +JAVA_TOOL_OPTIONS_TMP=$JAVA_TOOL_OPTIONS echo "source env files..." source service.env -MEM_OPTS="-XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:MetaspaceSize=500m -XX:MaxMetaspaceSize=500m -XX:-UseAdaptiveSizePolicy" +export JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS $JAVA_TOOL_OPTIONS_TMP" + +MEM_OPTS="-XX:+UseContainerSupport -Xss512k -XX:MaxMetaspaceSize=500m -XX:CompressedClassSpaceSize=100m -XX:ReservedCodeCacheSize=400m -XX:-UseAdaptiveSizePolicy -XX:MaxGCPauseMillis=100" GC_LOG="-Xloggc:/data/workspace/$MS_NAME/jvm/gc-%t.log -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps" API_PORT=80 diff --git a/helm-charts/core/ci/base/values.yaml b/helm-charts/core/ci/base/values.yaml index 3ab9f191484..122c78621c3 100644 --- a/helm-charts/core/ci/base/values.yaml +++ b/helm-charts/core/ci/base/values.yaml @@ -484,6 +484,9 @@ log: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -522,82 +525,9 @@ auth: limits: cpu: 500m memory: 1500Mi - hostAliases: [] - containerSecurityContext: - enabled: false - runAsUser: 1001 - runAsNonRoot: true - podSecurityContext: - enabled: false - fsGroup: 1001 - podAffinityPreset: "" - podAntiAffinityPreset: soft - nodeAffinityPreset: - type: "" - key: "" - values: [] - affinity: {} - nodeSelector: {} - tolerations: [] - podAnnotations: {} - priorityClassName: "" - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPU: 80 - targetMemory: 80 - -# agentless Deployment -agentless: - enabled: true - replicas: 1 - podLabels: {} - resources: - requests: - cpu: 100m - memory: 1000Mi - limits: - cpu: 500m - memory: 1500Mi - hostAliases: [] - containerSecurityContext: - enabled: false - runAsUser: 1001 - runAsNonRoot: true - podSecurityContext: - enabled: false - fsGroup: 1001 - podAffinityPreset: "" - podAntiAffinityPreset: soft - nodeAffinityPreset: - type: "" - key: "" - values: [] - affinity: {} - nodeSelector: {} - tolerations: [] - podAnnotations: {} - priorityClassName: "" - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPU: 80 - targetMemory: 80 - -# dockerhost Deployment -dockerhost: - enabled: true - replicas: 1 - podLabels: {} - resources: - requests: - cpu: 100m - memory: 1000Mi - limits: - cpu: 500m - memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -636,6 +566,9 @@ artifactory: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -674,6 +607,9 @@ dispatch: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -712,6 +648,9 @@ dispatchDocker: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -750,6 +689,9 @@ dispatchKubernetes: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -788,6 +730,9 @@ environment: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -826,6 +771,9 @@ image: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -864,6 +812,9 @@ misc: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -902,6 +853,9 @@ metrics: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -940,6 +894,9 @@ monitoring: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -978,6 +935,9 @@ notify: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1019,6 +979,9 @@ openapi: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1057,6 +1020,9 @@ plugin: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1095,6 +1061,9 @@ process: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1133,6 +1102,9 @@ project: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1171,6 +1143,9 @@ quality: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1209,44 +1184,9 @@ repository: limits: cpu: 500m memory: 1500Mi - hostAliases: [] - containerSecurityContext: - enabled: false - runAsUser: 1001 - runAsNonRoot: true - podSecurityContext: - enabled: false - fsGroup: 1001 - podAffinityPreset: "" - podAntiAffinityPreset: soft - nodeAffinityPreset: - type: "" - key: "" - values: [] - affinity: {} - nodeSelector: {} - tolerations: [] - podAnnotations: {} - priorityClassName: "" - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPU: 80 - targetMemory: 80 - -# sign Deployment -sign: - enabled: true - replicas: 1 - podLabels: {} - resources: - requests: - cpu: 100m - memory: 1000Mi - limits: - cpu: 500m - memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1285,6 +1225,9 @@ store: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1323,6 +1266,9 @@ ticket: limits: cpu: 500m memory: 1500Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1361,44 +1307,9 @@ websocket: limits: cpu: 500m memory: 1500Mi - hostAliases: [] - containerSecurityContext: - enabled: false - runAsUser: 1001 - runAsNonRoot: true - podSecurityContext: - enabled: false - fsGroup: 1001 - podAffinityPreset: "" - podAntiAffinityPreset: soft - nodeAffinityPreset: - type: "" - key: "" - values: [] - affinity: {} - nodeSelector: {} - tolerations: [] - podAnnotations: {} - priorityClassName: "" - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPU: 80 - targetMemory: 80 - -# angetless Deployment -angetless: - enabled: true - replicas: 1 - podLabels: {} - resources: - requests: - cpu: 100m - memory: 1000Mi - limits: - cpu: 500m - memory: 3000Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false @@ -1516,7 +1427,7 @@ gateway: # stream Deployment stream: - enabled: true + enabled: false replicas: 1 podLabels: {} resources: @@ -1526,6 +1437,9 @@ stream: limits: cpu: 500m memory: 3000Mi + jvm: + heapPct: "70.0" + initSecs: 50 hostAliases: [] containerSecurityContext: enabled: false diff --git a/helm-charts/core/ci/templates/artifactory/statefulset.yaml b/helm-charts/core/ci/templates/artifactory/statefulset.yaml index 05590626893..d3382f41bab 100644 --- a/helm-charts/core/ci/templates/artifactory/statefulset.yaml +++ b/helm-charts/core/ci/templates/artifactory/statefulset.yaml @@ -86,7 +86,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.artifactory.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.artifactory.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -111,7 +111,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.artifactory.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -120,7 +120,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.artifactory.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/auth/deployment.yaml b/helm-charts/core/ci/templates/auth/deployment.yaml index 80083a9bf30..699b52ad945 100644 --- a/helm-charts/core/ci/templates/auth/deployment.yaml +++ b/helm-charts/core/ci/templates/auth/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.auth.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.auth.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.auth.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.auth.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/dispatch/deployment.yaml b/helm-charts/core/ci/templates/dispatch/deployment.yaml index 8f9952afd7b..7197895b3c2 100644 --- a/helm-charts/core/ci/templates/dispatch/deployment.yaml +++ b/helm-charts/core/ci/templates/dispatch/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.dispatch.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.dispatch.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -99,7 +99,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatch.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -108,7 +108,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatch.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/dispatchDocker/deployment.yaml b/helm-charts/core/ci/templates/dispatchDocker/deployment.yaml index 99c6cd61ecb..53186adff5f 100644 --- a/helm-charts/core/ci/templates/dispatchDocker/deployment.yaml +++ b/helm-charts/core/ci/templates/dispatchDocker/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.dispatchDocker.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.dispatchDocker.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -99,7 +99,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatchDocker.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -108,7 +108,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatchDocker.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/dispatchKubernetes/deployment.yaml b/helm-charts/core/ci/templates/dispatchKubernetes/deployment.yaml index 3653a47c231..522a4c3e7a7 100644 --- a/helm-charts/core/ci/templates/dispatchKubernetes/deployment.yaml +++ b/helm-charts/core/ci/templates/dispatchKubernetes/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.dispatchKubernetes.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.dispatchKubernetes.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatchKubernetes.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.dispatchKubernetes.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/environment/deployment.yaml b/helm-charts/core/ci/templates/environment/deployment.yaml index 336fb907ba4..fe496d356a3 100644 --- a/helm-charts/core/ci/templates/environment/deployment.yaml +++ b/helm-charts/core/ci/templates/environment/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.environment.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.environment.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -99,7 +99,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.environment.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -108,7 +108,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.environment.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/gateway/deployment.yaml b/helm-charts/core/ci/templates/gateway/deployment.yaml index da5245b708b..5d4ef2839a1 100644 --- a/helm-charts/core/ci/templates/gateway/deployment.yaml +++ b/helm-charts/core/ci/templates/gateway/deployment.yaml @@ -87,8 +87,6 @@ spec: value: {{ .Values.multiCluster.enabled | quote }} - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} - name: POD_NAME valueFrom: fieldRef: diff --git a/helm-charts/core/ci/templates/image/deployment.yaml b/helm-charts/core/ci/templates/image/deployment.yaml index 36d839c9000..555fdcd18ed 100644 --- a/helm-charts/core/ci/templates/image/deployment.yaml +++ b/helm-charts/core/ci/templates/image/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.image.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.image.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.image.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.image.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/log/deployment.yaml b/helm-charts/core/ci/templates/log/deployment.yaml index ff906d94367..e5a46b8311c 100644 --- a/helm-charts/core/ci/templates/log/deployment.yaml +++ b/helm-charts/core/ci/templates/log/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.log.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.log.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.log.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.log.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/metrics/deployment.yaml b/helm-charts/core/ci/templates/metrics/deployment.yaml index 59657395c39..a0d0e9c1363 100644 --- a/helm-charts/core/ci/templates/metrics/deployment.yaml +++ b/helm-charts/core/ci/templates/metrics/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.metrics.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.metrics.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.metrics.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.metrics.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/misc/deployment.yaml b/helm-charts/core/ci/templates/misc/deployment.yaml index 3845f016231..8627569073b 100644 --- a/helm-charts/core/ci/templates/misc/deployment.yaml +++ b/helm-charts/core/ci/templates/misc/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.misc.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.misc.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.misc.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.misc.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/monitoring/deployment.yaml b/helm-charts/core/ci/templates/monitoring/deployment.yaml index a2d0d9c8485..c85e22f4deb 100644 --- a/helm-charts/core/ci/templates/monitoring/deployment.yaml +++ b/helm-charts/core/ci/templates/monitoring/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.monitoring.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.monitoring.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.monitoring.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.monitoring.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/notify/deployment.yaml b/helm-charts/core/ci/templates/notify/deployment.yaml index b5646878220..a56c90af61f 100644 --- a/helm-charts/core/ci/templates/notify/deployment.yaml +++ b/helm-charts/core/ci/templates/notify/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.notify.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.notify.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.notify.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.notify.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/openapi/deployment.yaml b/helm-charts/core/ci/templates/openapi/deployment.yaml index c8bdacea44a..aabdf067e1c 100644 --- a/helm-charts/core/ci/templates/openapi/deployment.yaml +++ b/helm-charts/core/ci/templates/openapi/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.openapi.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.openapi.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.openapi.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.openapi.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/plugin/deployment.yaml b/helm-charts/core/ci/templates/plugin/deployment.yaml index 39c2718a6ea..b922ce43748 100644 --- a/helm-charts/core/ci/templates/plugin/deployment.yaml +++ b/helm-charts/core/ci/templates/plugin/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.plugin.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.plugin.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.plugin.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.plugin.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/process/deployment.yaml b/helm-charts/core/ci/templates/process/deployment.yaml index 4b5d925b30a..b7a313ee807 100644 --- a/helm-charts/core/ci/templates/process/deployment.yaml +++ b/helm-charts/core/ci/templates/process/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.process.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.process.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.process.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.process.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/project/deployment.yaml b/helm-charts/core/ci/templates/project/deployment.yaml index fe2ee47a3a4..d4f2227c924 100644 --- a/helm-charts/core/ci/templates/project/deployment.yaml +++ b/helm-charts/core/ci/templates/project/deployment.yaml @@ -51,26 +51,6 @@ spec: {{- if .Values.project.podSecurityContext.enabled }} securityContext: {{- omit .Values.project.podSecurityContext "enabled" | toYaml | nindent 8 }} {{- end }} - {{- if .Values.mysql.enabled }} - initContainers: - - name: project-wait-for-mysql - image: {{ include "common.images.image" ( dict "imageRoot" .Values.backendImage "global" .Values.global) }} - imagePullPolicy: {{ .Values.backendImage.pullPolicy }} - {{ $mysqlData := split ":" (include "bkci.mysqlAddr" .) }} - command: - - "/bin/sh" - - "-c" - - | - while true; do - count=$(echo 'select count(1) from devops_ci_project.T_MESSAGE_CODE_DETAIL' | mysql -u{{- include "bkci.mysqlUsername" . }} -p{{- include "bkci.mysqlPassword" . }} -h{{ $mysqlData._0 }} -P{{ $mysqlData._1 }} | tail -1) - if [[ $count -gt 0 ]]; then - echo 'T_MESSAGE_CODE_DETAIL init success' - break - fi - echo 'T_MESSAGE_CODE_DETAIL initing...' - sleep 10s - done - {{- end }} containers: - name: project image: {{ include "common.images.image" ( dict "imageRoot" .Values.backendImage "global" $) }} @@ -98,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.project.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.project.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -117,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.project.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -126,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.project.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/quality/deployment.yaml b/helm-charts/core/ci/templates/quality/deployment.yaml index 3c11673544a..46a650413c0 100644 --- a/helm-charts/core/ci/templates/quality/deployment.yaml +++ b/helm-charts/core/ci/templates/quality/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.quality.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.quality.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.quality.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.quality.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/repository/deployment.yaml b/helm-charts/core/ci/templates/repository/deployment.yaml index ee2daea2fe8..444145e636f 100644 --- a/helm-charts/core/ci/templates/repository/deployment.yaml +++ b/helm-charts/core/ci/templates/repository/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.repository.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.repository.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.repository.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.repository.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/store/deployment.yaml b/helm-charts/core/ci/templates/store/deployment.yaml index c944d1fb069..76269d43546 100644 --- a/helm-charts/core/ci/templates/store/deployment.yaml +++ b/helm-charts/core/ci/templates/store/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.store.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.store.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.store.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 30 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.store.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 30 diff --git a/helm-charts/core/ci/templates/stream/deployment.yaml b/helm-charts/core/ci/templates/stream/deployment.yaml index a390f2d455d..b0aadb8b93f 100644 --- a/helm-charts/core/ci/templates/stream/deployment.yaml +++ b/helm-charts/core/ci/templates/stream/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.stream.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.stream.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.stream.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.stream.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/ticket/deployment.yaml b/helm-charts/core/ci/templates/ticket/deployment.yaml index 61e5b29bd1d..97f109ae4cd 100644 --- a/helm-charts/core/ci/templates/ticket/deployment.yaml +++ b/helm-charts/core/ci/templates/ticket/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.ticket.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.ticket.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -97,7 +97,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.ticket.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -106,7 +106,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.ticket.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/turbo/deployment.yaml b/helm-charts/core/ci/templates/turbo/deployment.yaml index 5a4d9629ac9..4549c00d4bf 100644 --- a/helm-charts/core/ci/templates/turbo/deployment.yaml +++ b/helm-charts/core/ci/templates/turbo/deployment.yaml @@ -80,7 +80,7 @@ spec: httpGet: path: /management/health/liveness port: monitor - initialDelaySeconds: 60 + initialDelaySeconds: 30 periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -89,7 +89,7 @@ spec: httpGet: path: /management/health/readiness port: monitor - initialDelaySeconds: 60 + initialDelaySeconds: 30 periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/helm-charts/core/ci/templates/websocket/statefulset.yaml b/helm-charts/core/ci/templates/websocket/statefulset.yaml index 10d80e426f9..55719b64d3f 100644 --- a/helm-charts/core/ci/templates/websocket/statefulset.yaml +++ b/helm-charts/core/ci/templates/websocket/statefulset.yaml @@ -79,7 +79,7 @@ spec: - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} - name: JAVA_TOOL_OPTIONS - value: {{ .Values.java.options }} + value: {{ .Values.java.options }} -XX:InitialRAMPercentage={{ .Values.websocket.jvm.heapPct }} -XX:MaxRAMPercentage={{ .Values.websocket.jvm.heapPct }} - name: POD_NAME valueFrom: fieldRef: @@ -98,7 +98,7 @@ spec: httpGet: path: /management/health/livenessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.websocket.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 @@ -107,7 +107,7 @@ spec: httpGet: path: /management/health/readinessState port: http - initialDelaySeconds: 60 + initialDelaySeconds: {{ .Values.websocket.jvm.initSecs }} periodSeconds: 15 timeoutSeconds: 10 failureThreshold: 20 diff --git a/src/backend/ci/.gitignore b/src/backend/ci/.gitignore index e7fd0a014da..d858712732c 100644 --- a/src/backend/ci/.gitignore +++ b/src/backend/ci/.gitignore @@ -16,3 +16,4 @@ build.yml .temp .codecc *.log.gz +i18n/ diff --git a/src/backend/ci/boot-assembly/build.gradle.kts b/src/backend/ci/boot-assembly/build.gradle.kts index e8d847ed964..963d9ce13af 100644 --- a/src/backend/ci/boot-assembly/build.gradle.kts +++ b/src/backend/ci/boot-assembly/build.gradle.kts @@ -26,12 +26,6 @@ */ dependencies { - - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("org.jetbrains.kotlin:kotlin-reflect") - testImplementation("junit:junit") - testImplementation("org.springframework.boot:spring-boot-starter-test") - implementation(project(":core:common:common-auth:common-auth-mock")) implementation(project(":core:common:common-auth:common-auth-blueking")) implementation(project(":core:common:common-auth:common-auth-v3")) diff --git a/src/backend/ci/build.gradle.kts b/src/backend/ci/build.gradle.kts index a447990558a..9e7f0d0d62e 100644 --- a/src/backend/ci/build.gradle.kts +++ b/src/backend/ci/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.tencent.devops.boot") version "0.0.7-SNAPSHOT" + id("com.tencent.devops.boot") version "0.0.7" detektCheck } @@ -58,6 +58,7 @@ allprojects { dependency("org.apache.pulsar:pulsar-client:${Versions.Pulsar}") dependency("com.github.oshi:oshi-core:${Versions.Oshi}") dependency("com.tencent.devops.leaf:leaf-boot-starter:${Versions.Leaf}") + dependency("com.github.xingePush:xinge:${Versions.Xinge}") dependency("org.reflections:reflections:${Versions.reflections}") dependency("org.dom4j:dom4j:${Versions.Dom4j}") dependency("org.apache.commons:commons-compress:${Versions.Compress}") diff --git a/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt b/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt index 116cb0c6c11..c07978a97c0 100644 --- a/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt +++ b/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt @@ -38,6 +38,7 @@ object Versions { const val Leaf = "1.0.2-RELEASE" const val p4 = "2021.1.2163843" const val YamlSchema = "1.0.49" + const val Xinge = "1.2.4.9" const val Pulsar = "2.7.2" const val reflections = "0.10.2" const val mockk = "1.12.2" @@ -45,5 +46,5 @@ object Versions { const val jjwt = "0.11.5" const val Okhttp = "4.9.0" const val jgit = "5.13.1.202206130422-r" - const val iam = "1.0.20-SNAPSHOT" + const val iam = "1.0.21-SNAPSHOT" } diff --git a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts index dbbe675ea39..f023d7da697 100644 --- a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts +++ b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts @@ -60,10 +60,10 @@ if (toImage.isNullOrBlank() || (toImageRepo.isNullOrBlank() && toImageTag.isNull "-XX:HeapDumpPath=/data/workspace/$service/jvm/oom.hprof", "-XX:ErrorFile=/data/workspace/$service/jvm/error_sys.log", "-XX:+UseContainerSupport", - "-XX:InitialRAMPercentage=70.0", - "-XX:MaxRAMPercentage=70.0", - "-XX:MetaspaceSize=500m", + "-Xss512k", "-XX:MaxMetaspaceSize=500m", + "-XX:CompressedClassSpaceSize=100m", + "-XX:ReservedCodeCacheSize=400m", "-XX:-UseAdaptiveSizePolicy", "-Dspring.jmx.enabled=true", "-Dservice.log.dir=/data/workspace/$service/logs/", diff --git a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-i18n-load.gradle.kts b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-i18n-load.gradle.kts index f89a0cd5fec..aae50b4e6c6 100644 --- a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-i18n-load.gradle.kts +++ b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-i18n-load.gradle.kts @@ -28,7 +28,11 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.util.Properties -val i18nPath = joinPath(rootDir.absolutePath.replace("/src/backend/ci", ""), "support-files", "i18n") +val i18nPath = joinPath( + rootDir.absolutePath.replace("${File.separator}src${File.separator}backend${File.separator}ci", ""), + "support-files", + "i18n" +) println("rootDir is: $rootDir, i18nPath is: $i18nPath, projectName is: ${project.name}") if (File(i18nPath).isDirectory) { println("i18n load register , Path is $i18nPath") diff --git a/src/backend/ci/core/artifactory/api-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/api/ServiceArchiveAtomFileResource.kt b/src/backend/ci/core/artifactory/api-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/api/ServiceArchiveAtomFileResource.kt index f8dbe9dccfe..b6527485f57 100644 --- a/src/backend/ci/core/artifactory/api-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/api/ServiceArchiveAtomFileResource.kt +++ b/src/backend/ci/core/artifactory/api-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/api/ServiceArchiveAtomFileResource.kt @@ -61,9 +61,6 @@ interface ServiceArchiveAtomFileResource { @ApiParam("项目编码", required = true) @QueryParam("projectCode") projectCode: String, - @ApiParam("插件ID", required = true) - @QueryParam("atomId") - atomId: String, @ApiParam("插件代码", required = true) @QueryParam("atomCode") atomCode: String, diff --git a/src/backend/ci/core/artifactory/biz-artifactory-sample/build.gradle.kts b/src/backend/ci/core/artifactory/biz-artifactory-sample/build.gradle.kts index 422cf2fe3fb..85185578b92 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-sample/build.gradle.kts +++ b/src/backend/ci/core/artifactory/biz-artifactory-sample/build.gradle.kts @@ -27,5 +27,5 @@ dependencies { api(project(":core:artifactory:api-artifactory-sample")) - api(project(":core:artifactory:biz-artifactory")) + api(project(":core:artifactory:biz-artifactory-store")) } diff --git a/src/backend/ci/core/artifactory/biz-artifactory-sample/src/main/kotlin/com/tencent/devops/artifactory/service/SampleArchiveAtomToBkRepoServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory-sample/src/main/kotlin/com/tencent/devops/artifactory/service/SampleArchiveAtomToBkRepoServiceImpl.kt new file mode 100644 index 00000000000..9a870b8c9bd --- /dev/null +++ b/src/backend/ci/core/artifactory/biz-artifactory-sample/src/main/kotlin/com/tencent/devops/artifactory/service/SampleArchiveAtomToBkRepoServiceImpl.kt @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.artifactory.service + +import com.tencent.devops.artifactory.constant.BKREPO_STORE_PROJECT_ID +import com.tencent.devops.artifactory.constant.REALM_BK_REPO +import com.tencent.devops.artifactory.constant.REPO_NAME_PLUGIN +import com.tencent.devops.artifactory.service.impl.ArchiveAtomToBkRepoServiceImpl +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Service + +@Service +@ConditionalOnProperty(prefix = "artifactory", name = ["realm"], havingValue = REALM_BK_REPO) +class SampleArchiveAtomToBkRepoServiceImpl : ArchiveAtomToBkRepoServiceImpl() { + + override fun getBkRepoProjectId(): String { + return BKREPO_STORE_PROJECT_ID + } + + override fun getBkRepoName(): String { + return REPO_NAME_PLUGIN + } + + override fun deleteAtom(userId: String, projectCode: String, atomCode: String) { + bkRepoClient.delete(userId, BKREPO_STORE_PROJECT_ID, REPO_NAME_PLUGIN, "$projectCode/$atomCode") + } +} diff --git a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/ServiceArchiveAtomFileResourceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/ServiceArchiveAtomFileResourceImpl.kt index 69deffd5078..d6875f7c933 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/ServiceArchiveAtomFileResourceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/ServiceArchiveAtomFileResourceImpl.kt @@ -50,7 +50,6 @@ class ServiceArchiveAtomFileResourceImpl @Autowired constructor( override fun archiveAtomFile( userId: String, projectCode: String, - atomId: String, atomCode: String, version: String, releaseType: ReleaseTypeEnum, @@ -62,7 +61,6 @@ class ServiceArchiveAtomFileResourceImpl @Autowired constructor( userId = userId, inputStream = inputStream, disposition = disposition, - atomId = atomId, archiveAtomRequest = ArchiveAtomRequest( projectCode = projectCode, atomCode = atomCode, diff --git a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/UserArchiveAtomResourceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/UserArchiveAtomResourceImpl.kt index dbbb79f4995..11c05b00293 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/UserArchiveAtomResourceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/resources/UserArchiveAtomResourceImpl.kt @@ -58,7 +58,6 @@ class UserArchiveAtomResourceImpl @Autowired constructor(private val archiveAtom userId = userId, inputStream = inputStream, disposition = disposition, - atomId = atomId, archiveAtomRequest = ArchiveAtomRequest( projectCode = projectCode, atomCode = atomCode, diff --git a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/ArchiveAtomService.kt b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/ArchiveAtomService.kt index bb4cea03e12..7de063454ba 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/ArchiveAtomService.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/ArchiveAtomService.kt @@ -43,7 +43,6 @@ interface ArchiveAtomService { userId: String, inputStream: InputStream, disposition: FormDataContentDisposition, - atomId: String, archiveAtomRequest: ArchiveAtomRequest ): Result diff --git a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomServiceImpl.kt index 89ff2f3e196..bedbac15888 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomServiceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomServiceImpl.kt @@ -93,10 +93,9 @@ abstract class ArchiveAtomServiceImpl : ArchiveAtomService { userId: String, inputStream: InputStream, disposition: FormDataContentDisposition, - atomId: String, archiveAtomRequest: ArchiveAtomRequest ): Result { - logger.info("archiveAtom userId:$userId,atomId:$atomId,archiveAtomRequest:$archiveAtomRequest") + logger.info("archiveAtom userId:$userId,archiveAtomRequest:$archiveAtomRequest") // 校验用户上传的插件包是否合法 val projectCode = archiveAtomRequest.projectCode val atomCode = archiveAtomRequest.atomCode @@ -135,6 +134,9 @@ abstract class ArchiveAtomServiceImpl : ArchiveAtomService { atomEnvRequests = atomConfigResult.atomEnvRequests!! packageFileInfos = mutableListOf() atomEnvRequests.forEach { atomEnvRequest -> + if (atomEnvRequest.pkgLocalPath.isNullOrBlank()) { + return@forEach + } val packageFilePathPrefix = buildAtomArchivePath(projectCode, atomCode, version) val packageFile = File("$packageFilePathPrefix/${atomEnvRequest.pkgLocalPath}") val packageFileInfo = PackageFileInfo( @@ -234,7 +236,6 @@ abstract class ArchiveAtomServiceImpl : ArchiveAtomService { userId = userId, inputStream = inputStream, disposition = disposition, - atomId = atomId, archiveAtomRequest = archiveAtomRequest ) if (archiveAtomResult.isNotOk()) { diff --git a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomToBkRepoServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomToBkRepoServiceImpl.kt index 847065554b9..8beb2d6ad7b 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomToBkRepoServiceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory-store/src/main/kotlin/com/tencent/devops/artifactory/service/impl/ArchiveAtomToBkRepoServiceImpl.kt @@ -1,26 +1,19 @@ package com.tencent.devops.artifactory.service.impl import com.tencent.devops.artifactory.constant.BKREPO_DEFAULT_USER -import com.tencent.devops.artifactory.constant.BKREPO_STORE_PROJECT_ID import com.tencent.devops.artifactory.constant.BK_CI_ATOM_DIR import com.tencent.devops.artifactory.constant.BK_CI_PLUGIN_FE_DIR -import com.tencent.devops.artifactory.constant.REALM_BK_REPO -import com.tencent.devops.artifactory.constant.REPO_NAME_PLUGIN import com.tencent.devops.artifactory.constant.REPO_NAME_STATIC import com.tencent.devops.artifactory.util.DefaultPathUtils import com.tencent.devops.common.api.constant.STATIC import com.tencent.devops.common.api.exception.RemoteServiceException import org.glassfish.jersey.media.multipart.FormDataContentDisposition import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Service import java.io.File import java.io.InputStream import javax.ws.rs.NotFoundException -@Service -@ConditionalOnProperty(prefix = "artifactory", name = ["realm"], havingValue = REALM_BK_REPO) -class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { +abstract class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { override fun getAtomArchiveBasePath(): String { return System.getProperty("java.io.tmpdir") @@ -48,7 +41,7 @@ class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { directoryFile = File(atomArchivePath), prefix = "${getAtomArchiveBasePath()}/$BK_CI_ATOM_DIR", directoryPath = atomArchivePath, - repoName = REPO_NAME_PLUGIN + repoName = getBkRepoName() ) directoryIteration( directoryFile = File(frontendDir), @@ -73,7 +66,7 @@ class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { bkRepoClient.uploadLocalFile( userId = BKREPO_DEFAULT_USER, - projectId = BKREPO_STORE_PROJECT_ID, + projectId = getBkRepoProjectId(), repoName = repoName, path = path, file = it @@ -87,27 +80,23 @@ class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { return try { bkRepoClient.downloadFile( userId = BKREPO_DEFAULT_USER, - projectId = BKREPO_STORE_PROJECT_ID, - repoName = REPO_NAME_PLUGIN, + projectId = getBkRepoProjectId(), + repoName = getBkRepoName(), fullPath = filePath, destFile = tmpFile ) tmpFile.readText(Charsets.UTF_8) - } catch (e: NotFoundException) { + } catch (ignored: NotFoundException) { logger.warn("file[$filePath] not exists") "" - } catch (e: RemoteServiceException) { - logger.warn("download file[$filePath] error: $e") + } catch (ignored: RemoteServiceException) { + logger.warn("download file[$filePath] error: $ignored") "" } finally { tmpFile.delete() } } - override fun deleteAtom(userId: String, projectCode: String, atomCode: String) { - bkRepoClient.delete(userId, BKREPO_STORE_PROJECT_ID, REPO_NAME_PLUGIN, "$projectCode/$atomCode") - } - override fun clearServerTmpFile(projectCode: String, atomCode: String, version: String) { val atomArchivePath = buildAtomArchivePath(projectCode, atomCode, version) val frontendDir = buildAtomFrontendPath(atomCode, version) @@ -115,6 +104,10 @@ class ArchiveAtomToBkRepoServiceImpl : ArchiveAtomServiceImpl() { File(frontendDir).deleteRecursively() } + abstract fun getBkRepoProjectId(): String + + abstract fun getBkRepoName(): String + companion object { private val logger = LoggerFactory.getLogger(ArchiveAtomToBkRepoServiceImpl::class.java) } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/OpenAuthResourceCallBackResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/OpenAuthResourceCallBackResource.kt index f11ff59bd9b..69c9c10cb7c 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/OpenAuthResourceCallBackResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/OpenAuthResourceCallBackResource.kt @@ -33,10 +33,10 @@ import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam import javax.ws.rs.Consumes +import javax.ws.rs.HeaderParam import javax.ws.rs.POST import javax.ws.rs.Path import javax.ws.rs.Produces -import javax.ws.rs.HeaderParam import javax.ws.rs.core.MediaType @Api(tags = ["AUTH_RESOURCE_CALLBACK"], description = "权限-资源-回调接口") diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/ServiceAuthResourceCallBackResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/ServiceAuthResourceCallBackResource.kt index 60d23f7d882..78e09961297 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/ServiceAuthResourceCallBackResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/callback/ServiceAuthResourceCallBackResource.kt @@ -33,10 +33,10 @@ import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam import javax.ws.rs.Consumes +import javax.ws.rs.HeaderParam import javax.ws.rs.POST import javax.ws.rs.Path import javax.ws.rs.Produces -import javax.ws.rs.HeaderParam import javax.ws.rs.core.MediaType @Api(tags = ["AUTH_RESOURCE_CALLBACK"], description = "权限-资源-回调接口") diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthApplyResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthApplyResource.kt index 83af0a74514..3ed454a6791 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthApplyResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthApplyResource.kt @@ -7,6 +7,7 @@ import com.tencent.devops.auth.pojo.vo.AuthApplyRedirectInfoVo import com.tencent.devops.auth.pojo.vo.GroupPermissionDetailVo import com.tencent.devops.auth.pojo.vo.ManagerRoleGroupVO import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo +import com.tencent.devops.common.api.annotation.BkInterfaceI18n import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Result import io.swagger.annotations.Api @@ -31,6 +32,7 @@ interface UserAuthApplyResource { @GET @Path("listResourceTypes") @ApiOperation("资源类型列表") + @BkInterfaceI18n(keyPrefixNames = ["{data[*].resourceType}"]) fun listResourceTypes( @ApiParam(name = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) @@ -40,6 +42,7 @@ interface UserAuthApplyResource { @GET @Path("listActions") @ApiOperation("展示动作列表") + @BkInterfaceI18n(keyPrefixNames = ["{data[*].action}"]) fun listActions( @ApiParam(name = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) @@ -77,6 +80,7 @@ interface UserAuthApplyResource { @GET @Path("{groupId}/getGroupPermissionDetail") @ApiOperation("查询用户组权限详情") + @BkInterfaceI18n(keyPrefixNames = ["{data[*].actionId}"]) fun getGroupPermissionDetail( @ApiParam(name = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthPermissionResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthPermissionResource.kt index 2e019692150..b331fba962d 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthPermissionResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthPermissionResource.kt @@ -47,6 +47,7 @@ import javax.ws.rs.core.MediaType @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) interface UserAuthPermissionResource { + @POST @Path("/batch/validate") @ApiOperation("批量校验用户是否拥有某个资源实例的操作") diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt index e2cadf8260a..a19729fcdf6 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt @@ -31,6 +31,7 @@ package com.tencent.devops.auth.api.user import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO import com.tencent.devops.auth.pojo.vo.IamGroupPoliciesVo +import com.tencent.devops.common.api.annotation.BkInterfaceI18n import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Result import io.swagger.annotations.Api @@ -55,6 +56,7 @@ interface UserAuthResourceGroupResource { @GET @Path("{groupId}/groupPolicies") @ApiOperation("获取组策略详情") + @BkInterfaceI18n(keyPrefixNames = ["{data[*].action}"]) fun getGroupPolicies( @ApiParam(name = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt index 5287409e683..6faf03948be 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt @@ -1,6 +1,11 @@ package com.tencent.devops.auth.constant object AuthI18nConstants { + const val RESOURCE_TYPE_NAME_SUFFIX = ".resourceType.name" + const val RESOURCE_TYPE_DESC_SUFFIX = ".resourceType.desc" + const val AUTH_RESOURCE_GROUP_CONFIG_GROUP_NAME_SUFFIX = ".authResourceGroupConfig.groupName" + const val AUTH_RESOURCE_GROUP_CONFIG_DESCRIPTION_SUFFIX = ".authResourceGroupConfig.description" + const val ACTION_NAME_SUFFIX = ".actionName" const val BK_AGREE_RENEW = "bkAgreeRenew" // 同意续期 const val BK_YOU_AGREE_RENEW = "bkYouAgreeRenew" // 你已选择同意用户续期 const val BK_REFUSE_RENEW = "bkRefuseRenew" // 拒绝续期 diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ActionInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ActionInfoVo.kt index 36be7647e0e..d6431201f37 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ActionInfoVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ActionInfoVo.kt @@ -1,5 +1,6 @@ package com.tencent.devops.auth.pojo.vo +import com.tencent.devops.common.api.annotation.BkFieldI18n import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -8,6 +9,7 @@ data class ActionInfoVo( @ApiModelProperty("action") val action: String, @ApiModelProperty("动作名") + @BkFieldI18n val actionName: String, @ApiModelProperty("蓝盾-关联资源类型") val resourceType: String, diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupPermissionDetailVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupPermissionDetailVo.kt index 43806c8bd13..1b4a349422d 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupPermissionDetailVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupPermissionDetailVo.kt @@ -1,6 +1,7 @@ package com.tencent.devops.auth.pojo.vo import com.tencent.devops.auth.pojo.RelatedResourceInfo +import com.tencent.devops.common.api.annotation.BkFieldI18n import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -9,6 +10,7 @@ data class GroupPermissionDetailVo( @ApiModelProperty("操作id") val actionId: String, @ApiModelProperty("操作名") + @BkFieldI18n(convertName = "actionName") val name: String, @ApiModelProperty("关联资源") val relatedResourceInfo: RelatedResourceInfo diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/IamGroupPoliciesVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/IamGroupPoliciesVo.kt index ef138c9242c..193789ddfc4 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/IamGroupPoliciesVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/IamGroupPoliciesVo.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.pojo.vo +import com.tencent.devops.common.api.annotation.BkFieldI18n import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -36,6 +37,7 @@ data class IamGroupPoliciesVo( @ApiModelProperty("操作") val action: String, @ApiModelProperty("操作名") + @BkFieldI18n val actionName: String, @ApiModelProperty("是否该action操作权限") val permission: Boolean diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceTypeInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceTypeInfoVo.kt index d8c496ac3de..96c0d493e23 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceTypeInfoVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceTypeInfoVo.kt @@ -1,5 +1,6 @@ package com.tencent.devops.auth.pojo.vo +import com.tencent.devops.common.api.annotation.BkFieldI18n import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -10,6 +11,7 @@ data class ResourceTypeInfoVo( @ApiModelProperty("资源类型") val resourceType: String, @ApiModelProperty("资源类型名") + @BkFieldI18n(keyPrefixName = "resourceType") val name: String, @ApiModelProperty("父类资源") val parent: String, diff --git a/src/backend/ci/core/auth/biz-auth-blueking/src/main/kotlin/com/tencent/devops/auth/AuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth-blueking/src/main/kotlin/com/tencent/devops/auth/AuthConfiguration.kt index bc52a5e3bae..5c82f82a3bf 100644 --- a/src/backend/ci/core/auth/biz-auth-blueking/src/main/kotlin/com/tencent/devops/auth/AuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth-blueking/src/main/kotlin/com/tencent/devops/auth/AuthConfiguration.kt @@ -83,7 +83,7 @@ class AuthConfiguration { @Value("\${auth.url:}") val iamBaseUrl = "" - @Value("\${auth.appCode:}") + @Value("\${auth.iamSystem:}") val systemId = "" @Value("\${auth.appCode:}") diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/config/RbacAuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/config/RbacAuthConfiguration.kt index ec82b45f410..34c450fa3e8 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/config/RbacAuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/config/RbacAuthConfiguration.kt @@ -338,7 +338,8 @@ class RbacAuthConfiguration { permissionService: PermissionService, rbacCacheService: RbacCacheService, authMigrationDao: AuthMigrationDao, - deptService: DeptService + deptService: DeptService, + permissionGroupPoliciesService: PermissionGroupPoliciesService ) = MigrateV3PolicyService( v2ManagerService = v2ManagerService, iamConfiguration = iamConfiguration, @@ -351,7 +352,8 @@ class RbacAuthConfiguration { permissionService = permissionService, rbacCacheService = rbacCacheService, authMigrationDao = authMigrationDao, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ) @Bean @@ -367,7 +369,8 @@ class RbacAuthConfiguration { permissionService: PermissionService, rbacCacheService: RbacCacheService, authMigrationDao: AuthMigrationDao, - deptService: DeptService + deptService: DeptService, + permissionGroupPoliciesService: PermissionGroupPoliciesService ) = MigrateV0PolicyService( v2ManagerService = v2ManagerService, iamConfiguration = iamConfiguration, @@ -380,7 +383,8 @@ class RbacAuthConfiguration { permissionService = permissionService, rbacCacheService = rbacCacheService, authMigrationDao = authMigrationDao, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ) @Bean diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGradeManagerService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGradeManagerService.kt index e69fbe15297..c9a15b49587 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGradeManagerService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGradeManagerService.kt @@ -78,11 +78,11 @@ import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.project.api.service.ServiceProjectApprovalResource import com.tencent.devops.project.pojo.enums.ProjectApproveStatus import com.tencent.devops.project.pojo.enums.ProjectAuthSecrecyStatus -import java.util.Arrays import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value +import java.util.Arrays @Suppress("LongParameterList", "TooManyFunctions") class PermissionGradeManagerService @Autowired constructor( diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGroupPoliciesService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGroupPoliciesService.kt index d96fa8174ad..bd3f720964a 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGroupPoliciesService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/PermissionGroupPoliciesService.kt @@ -62,7 +62,7 @@ class PermissionGroupPoliciesService( private const val PROJECT_ID_PLACEHOLDER = "#projectId#" private const val PROJECT_NAME_PLACEHOLDER = "#projectName#" private const val RESOURCE_CODE_PLACEHOLDER = "#resourceCode#" - private const val RESOURCE_Name_PLACEHOLDER = "#resourceName#" + private const val RESOURCE_NAME_PLACEHOLDER = "#resourceName#" } /** @@ -80,7 +80,8 @@ class PermissionGroupPoliciesService( .replace(PROJECT_ID_PLACEHOLDER, projectCode) .replace(PROJECT_NAME_PLACEHOLDER, projectName) .replace(RESOURCE_CODE_PLACEHOLDER, iamResourceCode) - .replace(RESOURCE_Name_PLACEHOLDER, resourceName) + // 如果资源名中有\,需要转义,不然json序列化时会报错 + .replace(RESOURCE_NAME_PLACEHOLDER, resourceName.replace("\\", "\\\\")) logger.info("$projectCode authorization scopes after replace $replaceAuthorizationScopesStr ") return JsonUtil.to(replaceAuthorizationScopesStr, object : TypeReference>() {}) } diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionApplyService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionApplyService.kt index 55cf9ccf0bc..3c806c88683 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionApplyService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionApplyService.kt @@ -8,6 +8,10 @@ import com.tencent.bk.sdk.iam.dto.manager.V2ManagerRoleGroupInfo import com.tencent.bk.sdk.iam.dto.manager.dto.SearchGroupDTO import com.tencent.bk.sdk.iam.dto.manager.vo.V2ManagerRoleGroupVO import com.tencent.bk.sdk.iam.service.v2.V2ManagerService +import com.tencent.devops.auth.constant.AuthI18nConstants +import com.tencent.devops.auth.constant.AuthI18nConstants.ACTION_NAME_SUFFIX +import com.tencent.devops.auth.constant.AuthI18nConstants.AUTH_RESOURCE_GROUP_CONFIG_GROUP_NAME_SUFFIX +import com.tencent.devops.auth.constant.AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao @@ -31,6 +35,7 @@ import com.tencent.devops.common.auth.api.pojo.DefaultGroupType import com.tencent.devops.common.auth.utils.RbacAuthUtils import com.tencent.devops.common.client.Client import com.tencent.devops.common.service.config.CommonConfig +import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.api.user.UserPipelineViewResource import com.tencent.devops.project.api.service.ServiceProjectTagResource import java.util.concurrent.Executors @@ -292,7 +297,9 @@ class RbacPermissionApplyService @Autowired constructor( buildRelatedResourceTypesDTO(instancesDTO = relatedResourceTypesDTO.condition[0].instances[0]) val relatedResourceInfo = RelatedResourceInfo( type = relatedResourceTypesDTO.type, - name = rbacCacheService.getResourceTypeInfo(relatedResourceTypesDTO.type).name, + name = I18nUtil.getCodeLanMessage( + relatedResourceTypesDTO.type + RESOURCE_TYPE_NAME_SUFFIX + ), instances = relatedResourceTypesDTO.condition[0].instances[0] ) GroupPermissionDetailVo( @@ -329,7 +336,10 @@ class RbacPermissionApplyService @Autowired constructor( // 判断action是否为空 val actionInfo = if (action != null) rbacCacheService.getActionInfo(action) else null val iamRelatedResourceType = actionInfo?.relatedResourceType ?: resourceType - val resourceTypeName = rbacCacheService.getResourceTypeInfo(resourceType).name + val resourceTypeName = I18nUtil.getCodeLanMessage( + messageCode = resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, + defaultMessage = rbacCacheService.getResourceTypeInfo(resourceType).name + ) val projectInfo = authResourceService.get( projectCode = projectId, @@ -372,7 +382,12 @@ class RbacPermissionApplyService @Autowired constructor( auth = isEnablePermission, resourceTypeName = resourceTypeName, resourceName = resourceName, - actionName = actionInfo?.actionName, + actionName = actionInfo?.let { + I18nUtil.getCodeLanMessage( + messageCode = "${it.action}$ACTION_NAME_SUFFIX", + defaultMessage = it.actionName + ) + }, groupInfoList = groupInfoList ) } @@ -456,7 +471,11 @@ class RbacPermissionApplyService @Autowired constructor( authApplyRedirectUrl, projectId, projectName, resourceType, resourceName, iamResourceCode, action, resourceGroup.groupName, resourceGroup.relationId ), - groupName = resourceGroup.groupName + groupName = I18nUtil.getCodeLanMessage( + messageCode = "${resourceGroup.resourceType}.${resourceGroup.groupCode}" + + AUTH_RESOURCE_GROUP_CONFIG_GROUP_NAME_SUFFIX, + defaultMessage = resourceGroup.groupName + ) ) ) } diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionProjectService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionProjectService.kt index 0cc5900a2b0..656b6d89960 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionProjectService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionProjectService.kt @@ -155,13 +155,13 @@ class RbacPermissionProjectService( try { val useAction = RbacAuthUtils.buildAction(AuthPermission.get(action), AuthResourceType.PROJECT) val instanceMap = authHelper.groupRbacInstanceByType(userId, useAction) - val projectList = instanceMap[AuthResourceType.PROJECT.value] ?: emptyList() - return if (projectList.contains("*")) { + return if (instanceMap.contains("*")) { logger.info("super manager has all project|$userId") authResourceService.getAllResourceCode( resourceType = AuthResourceType.PROJECT.value ) } else { + val projectList = instanceMap[AuthResourceType.PROJECT.value] ?: emptyList() logger.info("get user projects:$projectList") projectList } diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceGroupService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceGroupService.kt index 10a0a27952c..6d626a50cf2 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceGroupService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceGroupService.kt @@ -336,7 +336,9 @@ class RbacPermissionResourceGroupService @Autowired constructor( v2PageInfoDTO.page = PageUtil.DEFAULT_PAGE val gradeManagerRoleGroupList = iamV2ManagerService.getGradeManagerRoleGroupV2(projectInfo.relationId, searchGroupDTO, v2PageInfoDTO) - if (gradeManagerRoleGroupList.results.isNotEmpty()) { + if (gradeManagerRoleGroupList.results.isNotEmpty() && + gradeManagerRoleGroupList.results.map { it.name }.contains(groupName) + ) { throw ErrorCodeException( errorCode = GROUP_EXIST, defaultMessage = "group name already exists" diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceService.kt index 455e1d45355..77b9e699c97 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionResourceService.kt @@ -162,13 +162,13 @@ class RbacPermissionResourceService( enable = resourceType != AuthResourceType.PIPELINE_GROUP.value, relationId = managerId.toString() ) - } catch (exception: Exception) { + } catch (ignore: Exception) { if (resourceType == AuthResourceType.PROJECT.value) { iamV2ManagerService.deleteManagerV2(managerId.toString()) } else { iamV2ManagerService.deleteSubsetManager(managerId.toString()) } - logger.warn("create resource failed|$userId|$projectCode|$resourceType|$resourceName", exception) + logger.warn("create resource failed|$userId|$projectCode|$resourceType|$resourceName", ignore) throw ErrorCodeException( errorCode = ERROR_RESOURCE_CREATE_FAIL, defaultMessage = "create resource failed|$userId|$projectCode|$resourceType|$resourceName" diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionService.kt index fe97437fa7f..7b0955db4fa 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/RbacPermissionService.kt @@ -76,6 +76,9 @@ class RbacPermissionService constructor( } } + /** + * 如果没有具体资源,则校验是否有项目下任意资源权限 + */ override fun validateUserResourcePermission( userId: String, action: String, @@ -86,8 +89,8 @@ class RbacPermissionService constructor( userId = userId, action = action, projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - resourceCode = projectCode, + resourceType = resourceType!!, + resourceCode = "*", relationResourceType = null ) } @@ -102,8 +105,8 @@ class RbacPermissionService constructor( ): Boolean { val resource = if (resourceType == AuthResourceType.PROJECT.value) { AuthResourceInstance( - resourceType = resourceType, - resourceCode = resourceCode + resourceType = AuthResourceType.PROJECT.value, + resourceCode = projectCode ) } else { val projectResourceInstance = AuthResourceInstance( @@ -124,7 +127,7 @@ class RbacPermissionService constructor( ) } - @Suppress("ReturnCount") + @Suppress("ReturnCount", "ComplexMethod") override fun validateUserResourcePermissionByInstance( userId: String, action: String, @@ -152,11 +155,15 @@ class RbacPermissionService constructor( ) { return true } - val iamResourceCode = authResourceCodeConverter.code2IamCode( - projectCode = projectCode, - resourceType = resource.resourceType, - resourceCode = resource.resourceCode - ) ?: return false + val iamResourceCode = if (resource.resourceCode == "*") { + resource.resourceCode + } else { + authResourceCodeConverter.code2IamCode( + projectCode = projectCode, + resourceType = resource.resourceType, + resourceCode = resource.resourceCode + ) + } ?: return false val subject = SubjectDTO.builder() .id(userId) .type(ManagerScopesEnum.getType(ManagerScopesEnum.USER)) @@ -424,7 +431,7 @@ class RbacPermissionService constructor( if (rbacCacheService.checkProjectManager(userId = userId, projectCode = projectCode)) { return actions.associate { val authPermission = it.substringAfterLast("_") - AuthPermission.get(authPermission) to resources.map { it.resourceCode } + AuthPermission.get(authPermission) to resources.map { resource -> resource.resourceCode } } } val instanceList = resources.map { resource -> @@ -452,12 +459,23 @@ class RbacPermissionService constructor( actions.parallelStream().forEach { action -> MDC.put(TraceTag.BIZID, traceId) val authPermission = action.substringAfterLast("_") - val iamResourceCodes = authHelper.isAllowed(userId, action, instanceList) - permissionMap[AuthPermission.get(authPermission)] = authResourceCodeConverter.batchIamCode2Code( - projectCode = projectCode, - resourceType = resourceType, - iamResourceCodes = iamResourceCodes - ) + // 具有action管理员权限,那么有所有资源权限 + if (permissionSuperManagerService.reviewManagerCheck( + userId = userId, + projectCode = projectCode, + resourceType = resourceType, + action = action + ) + ) { + permissionMap[AuthPermission.get(authPermission)] = resources.map { it.resourceCode } + } else { + val iamResourceCodes = authHelper.isAllowed(userId, action, instanceList) + permissionMap[AuthPermission.get(authPermission)] = authResourceCodeConverter.batchIamCode2Code( + projectCode = projectCode, + resourceType = resourceType, + iamResourceCodes = iamResourceCodes + ) + } } return permissionMap } finally { diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyService.kt index c6f8749b4fe..8f777f6c1f6 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyService.kt @@ -46,6 +46,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionGroupPoliciesService import com.tencent.devops.auth.service.RbacCacheService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.auth.service.migrate.MigrateIamApiService.Companion.GROUP_API_POLICY @@ -61,10 +62,10 @@ import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.api.pojo.DefaultGroupType import com.tencent.devops.common.auth.utils.RbacAuthUtils import com.tencent.devops.common.web.utils.I18nUtil -import java.time.LocalDateTime -import java.util.concurrent.TimeUnit import org.jooq.DSLContext import org.slf4j.LoggerFactory +import java.time.LocalDateTime +import java.util.concurrent.TimeUnit @Suppress("LongParameterList", "TooManyFunctions") abstract class AbMigratePolicyService( @@ -77,7 +78,8 @@ abstract class AbMigratePolicyService( private val authMigrationDao: AuthMigrationDao, private val permissionService: PermissionService, private val rbacCacheService: RbacCacheService, - private val deptService: DeptService + private val deptService: DeptService, + private val permissionGroupPoliciesService: PermissionGroupPoliciesService ) { companion object { @@ -169,8 +171,8 @@ abstract class AbMigratePolicyService( results = taskDataResp.results ) page++ - totalCount += taskDataResp.count - } while (taskDataResp.count == pageSize) + totalCount = taskDataResp.count + } while (taskDataResp.results.size == pageSize) return totalCount } @@ -249,7 +251,12 @@ abstract class AbMigratePolicyService( abstract fun getGroupName(projectName: String, result: MigrateTaskDataResult): String - fun migrateUserCustomPolicy(projectCode: String, version: String) { + fun migrateUserCustomPolicy( + projectCode: String, + projectName: String, + version: String, + gradeManagerId: Int + ) { logger.info("start to migrate user custom policy|$projectCode") val startEpoch = System.currentTimeMillis() try { @@ -266,6 +273,8 @@ abstract class AbMigratePolicyService( ) loopMigrateUserCustom( projectCode = projectCode, + projectName = projectName, + gradeManagerId = gradeManagerId, managerGroupId = managerGroupId, version = version ) @@ -278,6 +287,8 @@ abstract class AbMigratePolicyService( private fun loopMigrateUserCustom( projectCode: String, + projectName: String, + gradeManagerId: Int, managerGroupId: Int, version: String ): Int { @@ -294,17 +305,21 @@ abstract class AbMigratePolicyService( ) migrateUserCustom( projectCode = projectCode, + projectName = projectName, + gradeManagerId = gradeManagerId, managerGroupId = managerGroupId, results = taskDataResp.results ) page++ - totalCount += taskDataResp.count - } while (taskDataResp.count == pageSize) + totalCount = taskDataResp.count + } while (taskDataResp.results.size == pageSize) return totalCount } private fun migrateUserCustom( projectCode: String, + projectName: String, + gradeManagerId: Int, managerGroupId: Int, results: List ) { @@ -313,18 +328,20 @@ abstract class AbMigratePolicyService( val userId = result.subject.id // 离职人员,直接忽略 if (deptService.getUserInfo(userId = "admin", name = userId) == null) { - logger.warn("user has left, skip custom policy migration|${result.projectId}|$userId") + logger.warn("user has resigned, skip custom policy migration|${result.projectId}|$userId") return@forEach } result.permissions.forEach permission@{ permission -> - val groupId = matchResourceGroup( + val groupIds = matchResourceGroup( userId = userId, projectCode = projectCode, + projectName = projectName, + gradeManagerId = gradeManagerId, managerGroupId = managerGroupId, permission = permission ) - if (groupId != null) { + groupIds.forEach { groupId -> val managerMember = ManagerMember(ManagerScopesEnum.getType(ManagerScopesEnum.USER), userId) val managerMemberGroupDTO = ManagerMemberGroupDTO.builder() .members(listOf(managerMember)) @@ -337,12 +354,21 @@ abstract class AbMigratePolicyService( } } + /** + * 匹配到的资源组Id列表 + * + * v3 action只能匹配到一个组 + * v0 iam返回的action列表包含创建动作,创建动作依赖的资源是项目,所以一个action组可能匹配到多个组, + * 如actions:[pipeline_create,pipeline_view],pipeline_create需查找项目下的组,pipeline_view需查找pipeline下的组 + */ abstract fun matchResourceGroup( userId: String, projectCode: String, + projectName: String, + gradeManagerId: Int, managerGroupId: Int, permission: AuthorizationScopes - ): Int? + ): List /** * 根据action匹配资源最小action组 @@ -354,6 +380,8 @@ abstract class AbMigratePolicyService( resourceCode: String, actions: List ): Int? { + logger.info("match min resource group|$userId|$resourceType|$actions") + // 判断用户是否已有资源actions权限 val hasPermission = permissionService.batchValidateUserResourcePermission( userId = userId, actions = actions, @@ -361,32 +389,93 @@ abstract class AbMigratePolicyService( resourceCode = resourceCode, resourceType = resourceType ).all { it.value } - // 没有action的权限,匹配资源默认用户组权限 - if (!hasPermission) { - rbacCacheService.getGroupConfigAction(resourceType).forEach groupConfig@{ groupConfig -> - if (groupConfig.actions.containsAll(actions)) { - val groupId = authResourceGroupDao.get( - dslContext = dslContext, - projectCode = projectCode, - resourceType = resourceType, - resourceCode = resourceCode, - groupCode = groupConfig.groupCode - )?.relationId?.toInt() - logger.info( - "user match resource group" + - "|$userId|$actions|$projectCode|$resourceCode|${groupConfig.groupCode}|$groupId" - ) - return groupId - } - } + + if (hasPermission) { + logger.info("user has resource action permission|$userId|$actions$projectCode|$resourceCode") + return null + } + return getMatchResourceGroupId( + resourceType = resourceType, + actions = actions, + projectCode = projectCode, + resourceCode = resourceCode, + userId = userId + ).second ?: run { logger.info("user not match resource group|$userId|$actions$projectCode|$resourceCode") - } else { - logger.info( - "user has resource action permission" + - "|$userId|$resourceCode|$actions$projectCode|$resourceCode" + null + } + } + + /** + * 匹配或创建项目资源用户组 + * + * 有任意资源权限,直接匹配项目下资源权限组,如果没有匹配到,则创建 + */ + protected fun matchOrCreateProjectResourceGroup( + userId: String, + projectCode: String, + projectName: String, + resourceType: String, + actions: List, + gradeManagerId: Int + ): Int? { + logger.info("match or create project resource group|$userId|$resourceType|$actions") + // 判断用户是否已有项目任意资源actions权限 + val hasPermission = actions.all { action -> + permissionService.validateUserResourcePermission( + userId = userId, + action = action, + projectCode = projectCode, + resourceType = resourceType ) } - return null + if (hasPermission) { + logger.info("user has project any resource permission|$userId|$actions|$projectCode") + return null + } + val (groupConfigId, groupId) = getMatchResourceGroupId( + resourceType = AuthResourceType.PROJECT.value, + actions = actions, + projectCode = projectCode, + resourceCode = projectCode, + userId = userId + ) + return groupId ?: run { + groupConfigId?.let { + createProjectResourceGroup( + groupConfigId = groupConfigId, + gradeManagerId = gradeManagerId, + projectCode = projectCode, + projectName = projectName + ) + } + } + } + + private fun getMatchResourceGroupId( + resourceType: String, + actions: List, + projectCode: String, + resourceCode: String, + userId: String + ): Pair { + rbacCacheService.getGroupConfigAction(resourceType).forEach groupConfig@{ groupConfig -> + if (groupConfig.actions.containsAll(actions)) { + val groupId = authResourceGroupDao.get( + dslContext = dslContext, + projectCode = projectCode, + resourceType = resourceType, + resourceCode = resourceCode, + groupCode = groupConfig.groupCode + )?.relationId?.toInt() + logger.info( + "user match resource group" + + "|$userId|$actions|$projectCode|$resourceCode|${groupConfig.groupCode}|$groupId" + ) + return Pair(groupConfig.id, groupId) + } + } + return Pair(null, null) } private fun createRbacGroup( @@ -430,6 +519,55 @@ abstract class AbMigratePolicyService( return groupId } + /** + * 创建项目级资源用户组 + * + * 针对用户自定义权限,当用户有资源任意权限,如有任意流水线执行权限,项目默认组不能匹配该action,需要创建项目级流水线执行者组,用于管理这类权限 + */ + private fun createProjectResourceGroup( + groupConfigId: Long, + gradeManagerId: Int, + projectCode: String, + projectName: String + ): Int? { + logger.info("create project resource group|$groupConfigId|$gradeManagerId|$projectCode|$projectName") + val groupConfig = authResourceGroupConfigDao.getById(dslContext = dslContext, id = groupConfigId) ?: run { + logger.warn("group config not found|id:$groupConfigId") + return null + } + val managerRoleGroup = ManagerRoleGroup().apply { + name = groupConfig.groupName + description = groupConfig.description + } + val managerRoleGroupDTO = ManagerRoleGroupDTO.builder() + .groups(listOf(managerRoleGroup)) + .createAttributes(false) + .build() + val iamGroupId = v2ManagerService.batchCreateRoleGroupV2(gradeManagerId, managerRoleGroupDTO) + + authResourceGroupDao.create( + dslContext = dslContext, + projectCode = projectCode, + resourceType = AuthResourceType.PROJECT.value, + resourceCode = projectCode, + resourceName = projectName, + iamResourceCode = projectCode, + groupCode = groupConfig.groupCode, + groupName = groupConfig.groupName, + defaultGroup = false, + relationId = iamGroupId.toString() + ) + permissionGroupPoliciesService.grantGroupPermission( + authorizationScopesStr = groupConfig.authorizationScopes, + projectCode = projectCode, + projectName = projectName, + iamResourceCode = projectCode, + resourceName = projectName, + iamGroupId = iamGroupId + ) + return iamGroupId + } + private fun calculateGroupCount(projectCode: String, beforeGroupCount: Int) { val afterGroupCount = authResourceGroupDao.countByResourceCode( dslContext = dslContext, diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateIamApiService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateIamApiService.kt index ab1b7d64410..16eb24a43a3 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateIamApiService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateIamApiService.kt @@ -166,7 +166,7 @@ class MigrateIamApiService { private fun getBody(operation: String, request: Request): String { OkhttpUtils.doHttp(request).use { response -> val responseContent = response.body!!.string() - logger.info("request ${request.url} response|$responseContent") + logger.info("request iam migrate api ${request.url} response|$responseContent") if (!response.isSuccessful) { logger.warn("Failed to request(${request.url}), code ${response.code}, content: $responseContent") throw RemoteServiceException(operation) diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateResourceService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateResourceService.kt index 86ef3350a38..2b95c4ed787 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateResourceService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateResourceService.kt @@ -51,12 +51,12 @@ import com.tencent.devops.common.auth.api.AuthTokenApi import com.tencent.devops.common.auth.code.ProjectAuthServiceCode import com.tencent.devops.common.auth.utils.RbacAuthUtils import com.tencent.devops.common.service.trace.TraceTag -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executors import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.slf4j.MDC import org.springframework.beans.factory.annotation.Autowired +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors /** * 将资源迁移到权限中心 diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyService.kt index e10ce32fbe0..47b023c702c 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyService.kt @@ -43,13 +43,15 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.service.AuthResourceCodeConverter import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionGroupPoliciesService import com.tencent.devops.auth.service.RbacCacheService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType -import java.util.concurrent.TimeUnit +import org.apache.commons.lang3.RandomUtils import org.jooq.DSLContext import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit @Suppress("LongParameterList", "NestedBlockDepth", "TooManyFunctions") class MigrateV0PolicyService constructor( @@ -64,7 +66,8 @@ class MigrateV0PolicyService constructor( private val permissionService: PermissionService, private val rbacCacheService: RbacCacheService, private val authMigrationDao: AuthMigrationDao, - private val deptService: DeptService + private val deptService: DeptService, + private val permissionGroupPoliciesService: PermissionGroupPoliciesService ) : AbMigratePolicyService( v2ManagerService = v2ManagerService, iamConfiguration = iamConfiguration, @@ -75,16 +78,15 @@ class MigrateV0PolicyService constructor( authMigrationDao = authMigrationDao, permissionService = permissionService, rbacCacheService = rbacCacheService, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ) { companion object { private val logger = LoggerFactory.getLogger(MigrateV0PolicyService::class.java) - // v0默认用户组过期时间 2038-01-01 - private const val V0_DEFAULT_GROUP_EXPIRED_AT = 2145888000L - // v0用户自定义组默认360天 - private const val V0_CUSTOM_GROUP_EXPIRED_DAY = 360L + // v0默认用户组过期时间,2年或者3年 + private val V0_GROUP_EXPIRED_DAY = listOf(180L, 360L, 720L, 1080L) // v0的资源类型group(版本体验、质量红线组),task(版本体验、codecc任务)存在重复,iam迁移时添加了serviceCode,所以需要转换 private val oldActionMappingNewAction = mapOf( @@ -137,7 +139,7 @@ class MigrateV0PolicyService constructor( val projectActions = mutableListOf() result.permissions.forEach permission@{ permission -> val (resourceCreateActions, resourceActions) = buildRbacActions( - permission = permission + actions = permission.actions.map { it.id } ) if (resourceCreateActions.isNotEmpty()) { projectActions.addAll(resourceCreateActions) @@ -175,10 +177,10 @@ class MigrateV0PolicyService constructor( return rbacAuthorizationScopes } - private fun buildRbacActions(permission: AuthorizationScopes): Pair, List> { + private fun buildRbacActions(actions: List): Pair, List> { val resourceCreateActions = mutableListOf() val resourceActions = mutableListOf() - replaceOrRemoveAction(permission.actions.map { it.id }).forEach { action -> + replaceOrRemoveAction(actions).forEach { action -> // 创建的action,需要关联在项目下 if (action.contains(AuthPermission.CREATE.value)) { resourceCreateActions.add(Action(action)) @@ -283,25 +285,54 @@ class MigrateV0PolicyService constructor( override fun matchResourceGroup( userId: String, projectCode: String, + projectName: String, + gradeManagerId: Int, managerGroupId: Int, permission: AuthorizationScopes - ): Int? { + ): List { val resource = permission.resources[0] val resourceType = oldResourceTypeMappingNewResourceType[resource.type] ?: resource.type val userActions = permission.actions.map { it.id } - logger.info("find match resource group|$projectCode|$resourceType|$userActions") - if (resource.paths.isEmpty() || resource.paths[0].isEmpty()) { - logger.info("resource paths is empty,skip|$projectCode|$resourceType|$userActions") - return null - } - val v0ResourceCode = resource.paths[0][0].id - return v0MatchMinResourceGroup( - userId = userId, - projectCode = projectCode, - resourceType = resourceType, - v0ResourceCode = v0ResourceCode, - userActions = userActions + logger.info("find match resource group|$userId|$projectCode|$resourceType|$userActions") + val (resourceCreateActions, resourceActions) = buildRbacActions( + actions = permission.actions.map { it.id } ) + val matchGroupIds = mutableListOf() + // 创建action匹配到的组 + if (resourceCreateActions.isNotEmpty()) { + v0MatchMinResourceGroup( + userId = userId, + projectCode = projectCode, + resourceType = AuthResourceType.PROJECT.value, + v0ResourceCode = projectCode, + userActions = resourceCreateActions.map { it.id } + )?.let { matchGroupIds.add(it) } + } + // 资源action匹配到的组 + if (resourceActions.isNotEmpty()) { + val matchResourceGroupId = if (resource.paths.isEmpty() || resource.paths[0].isEmpty()) { + matchOrCreateProjectResourceGroup( + userId = userId, + projectCode = projectCode, + projectName = projectName, + resourceType = resourceType, + actions = resourceActions.map { it.id }, + gradeManagerId = gradeManagerId + ) + } else { + val v0ResourceCode = resource.paths[0][0].id + v0MatchMinResourceGroup( + userId = userId, + projectCode = projectCode, + resourceType = resourceType, + v0ResourceCode = v0ResourceCode, + userActions = resourceActions.map { it.id } + ) + } + matchResourceGroupId?.let { matchGroupIds.add(matchResourceGroupId) } + } + // 项目下任意资源 + return matchGroupIds } private fun v0MatchMinResourceGroup( @@ -350,11 +381,14 @@ class MigrateV0PolicyService constructor( } override fun batchAddGroupMember(groupId: Int, defaultGroup: Boolean, members: List?) { - val expiredAt = if (defaultGroup) { - V0_DEFAULT_GROUP_EXPIRED_AT + val expiredDay = if (defaultGroup) { + // 默认用户组,2年或3年随机过期 + V0_GROUP_EXPIRED_DAY[RandomUtils.nextInt(2, 3)] } else { - System.currentTimeMillis() / MILLISECOND + TimeUnit.DAYS.toSeconds(V0_CUSTOM_GROUP_EXPIRED_DAY) + // 自定义用户组,半年或者一年过期 + V0_GROUP_EXPIRED_DAY[RandomUtils.nextInt(0, 1)] } + val expiredAt = System.currentTimeMillis() / MILLISECOND + TimeUnit.DAYS.toSeconds(expiredDay) members?.forEach member@{ member -> val managerMember = ManagerMember(member.type, member.id) val managerMemberGroupDTO = ManagerMemberGroupDTO.builder() diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyService.kt index 7a020febb3c..fc3b3058962 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyService.kt @@ -44,11 +44,13 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.service.AuthResourceCodeConverter import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionGroupPoliciesService import com.tencent.devops.auth.service.RbacCacheService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.auth.api.AuthResourceType import org.jooq.DSLContext import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit /** * v3权限策略迁移到rbac @@ -73,7 +75,8 @@ class MigrateV3PolicyService constructor( private val permissionService: PermissionService, private val rbacCacheService: RbacCacheService, private val authMigrationDao: AuthMigrationDao, - private val deptService: DeptService + private val deptService: DeptService, + private val permissionGroupPoliciesService: PermissionGroupPoliciesService ) : AbMigratePolicyService( v2ManagerService = v2ManagerService, iamConfiguration = iamConfiguration, @@ -84,7 +87,8 @@ class MigrateV3PolicyService constructor( authMigrationDao = authMigrationDao, permissionService = permissionService, rbacCacheService = rbacCacheService, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ) { companion object { @@ -100,6 +104,8 @@ class MigrateV3PolicyService constructor( private const val PROJECT_ENABLE = "project_enable" // v3质量红线启用,rbac没有 private const val QUALITY_GROUP_ENABLE = "quality_group_enable" + // 过期用户增加5分钟 + private const val EXPIRED_MEMBER_ADD_TIME = 5L private val logger = LoggerFactory.getLogger(MigrateV3PolicyService::class.java) } @@ -238,9 +244,11 @@ class MigrateV3PolicyService constructor( override fun matchResourceGroup( userId: String, projectCode: String, + projectName: String, + gradeManagerId: Int, managerGroupId: Int, permission: AuthorizationScopes - ): Int? { + ): List { // v3资源都只有一层 val resource = permission.resources[0] val resourceType = resource.type @@ -248,9 +256,10 @@ class MigrateV3PolicyService constructor( logger.info("match resource group|$projectCode|$resourceType|$userActions") // 如果path为空,则直接跳过 if (resource.paths.isEmpty()) { - return null + return emptyList() } - return when { + val groupIds = mutableListOf() + val matchGroupId = when { // 如果有all_action,直接加入管理员组 userActions.contains(Constants.ALL_ACTION) -> managerGroupId // 项目类型 @@ -262,13 +271,19 @@ class MigrateV3PolicyService constructor( v3ResourceCode = projectCode, userActions = permission.actions.map { it.id } ) - isSkipMatchResourceGroup(resource) -> { - logger.info( - "user cannot match all resources and matching will be skipped|" + - "$userId|$projectCode|$resourceType|$userActions" + // 项目任意资源 + isAnyResource(resource) -> { + val finalUserActions = replaceOrRemoveAction(userActions) + matchOrCreateProjectResourceGroup( + userId = userId, + projectCode = projectCode, + projectName = projectName, + resourceType = resourceType, + actions = finalUserActions, + gradeManagerId = gradeManagerId ) - null } + // 具体资源权限 resource.paths[0].size >= 2 -> { v3MatchMinResourceGroup( userId = userId, @@ -280,12 +295,16 @@ class MigrateV3PolicyService constructor( } else -> null } + matchGroupId?.let { groupIds.add(it) } + return groupIds } - private fun isSkipMatchResourceGroup( + /** + * 有项目下任意资源权限 + */ + private fun isAnyResource( resource: ManagerResources ): Boolean { - // 项目下所有的资源不能找到对应的用户组,直接跳过,如自定义权限是项目下所有流水线pipeline_execute权限,默认的用户组是不能匹配改策略 return resource.paths[0].size >= 2 && resource.paths[0][1].id == "*" || (resource.paths[0].size == 1 && resource.paths[0][0].type == AuthResourceType.PROJECT.value) } @@ -342,14 +361,16 @@ class MigrateV3PolicyService constructor( override fun batchAddGroupMember(groupId: Int, defaultGroup: Boolean, members: List?) { members?.forEach member@{ member -> - // 过期的用户直接移除 - if (member.expiredAt * MILLISECOND < System.currentTimeMillis()) { - return@member + // 已过期用户,迁移时无法添加到用户组成员,增加5分钟添加到iam就过期,方便用户续期 + val expiredAt = if (member.expiredAt * MILLISECOND < System.currentTimeMillis()) { + System.currentTimeMillis() / MILLISECOND + TimeUnit.MINUTES.toSeconds(EXPIRED_MEMBER_ADD_TIME) + } else { + member.expiredAt } val managerMember = ManagerMember(member.type, member.id) val managerMemberGroupDTO = ManagerMemberGroupDTO.builder() .members(listOf(managerMember)) - .expiredAt(member.expiredAt) + .expiredAt(expiredAt) .build() v2ManagerService.createRoleGroupMemberV2(groupId, managerMemberGroupDTO) } diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/RbacPermissionMigrateService.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/RbacPermissionMigrateService.kt index 0d4f512133c..24c4333bb59 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/RbacPermissionMigrateService.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/main/kotlin/com/tencent/devops/auth/service/migrate/RbacPermissionMigrateService.kt @@ -54,6 +54,7 @@ import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.slf4j.MDC import org.springframework.beans.factory.annotation.Value +import java.util.concurrent.CompletionException import java.util.concurrent.Executors /** @@ -330,7 +331,9 @@ class RbacPermissionMigrateService constructor( watcher.start("migrateUserCustomPolicy") migrateV3PolicyService.migrateUserCustomPolicy( projectCode = projectCode, - version = version + projectName = projectName, + version = version, + gradeManagerId = gradeManagerId ) // 对比迁移结果 watcher.start("comparePolicy") @@ -359,7 +362,9 @@ class RbacPermissionMigrateService constructor( watcher.start("migrateUserCustomPolicy") migrateV0PolicyService.migrateUserCustomPolicy( projectCode = projectCode, - version = version + projectName = projectName, + version = version, + gradeManagerId = gradeManagerId ) // 对比迁移结果 watcher.start("comparePolicy") @@ -399,6 +404,9 @@ class RbacPermissionMigrateService constructor( is ErrorCodeException -> { exception.defaultMessage } + is CompletionException -> { + exception.cause?.message ?: exception.message + } else -> { exception.toString() } diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyServiceTest.kt index 88fb6a17592..586f6b17d5b 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/AbMigratePolicyServiceTest.kt @@ -38,6 +38,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.service.AuthResourceCodeConverter import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionGroupPoliciesService import com.tencent.devops.auth.service.RbacCacheService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.util.JsonUtil @@ -61,6 +62,7 @@ open class AbMigratePolicyServiceTest : BkCiAbstractTest() { val migrateResourceCodeConverter: MigrateResourceCodeConverter = mockk() val authResourceCodeConverter: AuthResourceCodeConverter = mockk() val deptService: DeptService = mockk() + val permissionGroupPoliciesService: PermissionGroupPoliciesService = mockk() @BeforeEach fun before() { diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyServiceTest.kt index 467ff492f5a..f538ed99f09 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV0PolicyServiceTest.kt @@ -50,7 +50,8 @@ class MigrateV0PolicyServiceTest : AbMigratePolicyServiceTest() { permissionService = permissionService, rbacCacheService = rbacCacheService, authMigrationDao = authMigrationDao, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ), recordPrivateCalls = true ) diff --git a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyServiceTest.kt index 08ad356ede9..2a9e5e215b3 100644 --- a/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth-rbac/src/test/kotlin/com/tencent/devops/auth/service/migrate/MigrateV3PolicyServiceTest.kt @@ -56,7 +56,8 @@ class MigrateV3PolicyServiceTest : AbMigratePolicyServiceTest() { permissionService = permissionService, rbacCacheService = rbacCacheService, authMigrationDao = authMigrationDao, - deptService = deptService + deptService = deptService, + permissionGroupPoliciesService = permissionGroupPoliciesService ), recordPrivateCalls = true ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronManager.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronManager.kt index 40013586eb0..872cd10fd0e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronManager.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronManager.kt @@ -27,29 +27,50 @@ package com.tencent.devops.auth.cron +import com.tencent.devops.auth.constant.AuthI18nConstants.AUTH_RESOURCE_GROUP_CONFIG_DESCRIPTION_SUFFIX +import com.tencent.devops.auth.constant.AuthI18nConstants.AUTH_RESOURCE_GROUP_CONFIG_GROUP_NAME_SUFFIX +import com.tencent.devops.auth.constant.AuthI18nConstants.RESOURCE_TYPE_DESC_SUFFIX +import com.tencent.devops.auth.constant.AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX +import com.tencent.devops.auth.dao.AuthActionDao +import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao +import com.tencent.devops.auth.dao.AuthResourceTypeDao import com.tencent.devops.auth.entity.ManagerChangeType import com.tencent.devops.auth.refresh.dispatch.AuthRefreshDispatch import com.tencent.devops.auth.refresh.event.ManagerOrganizationChangeEvent import com.tencent.devops.auth.service.AuthManagerApprovalService import com.tencent.devops.auth.service.ManagerOrganizationService import com.tencent.devops.auth.service.ManagerUserService +import com.tencent.devops.common.api.constant.SYSTEM +import com.tencent.devops.common.api.util.MessageUtil +import com.tencent.devops.common.api.util.PageUtil import com.tencent.devops.common.client.ClientTokenService import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.config.CommonConfig +import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupConfigRecord +import com.tencent.devops.model.auth.tables.records.TAuthResourceTypeRecord +import java.time.LocalDateTime +import java.util.concurrent.Executors +import javax.annotation.PostConstruct +import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component -import javax.annotation.PostConstruct @Component class AuthCronManager @Autowired constructor( + val dslContext: DSLContext, val managerUserService: ManagerUserService, val managerOrganizationService: ManagerOrganizationService, val refreshDispatch: AuthRefreshDispatch, val clientTokenService: ClientTokenService, val authManagerApprovalService: AuthManagerApprovalService, - val redisOperation: RedisOperation + val redisOperation: RedisOperation, + val authActionDao: AuthActionDao, + val authResourceTypeDao: AuthResourceTypeDao, + val authResourceGroupConfigDao: AuthResourceGroupConfigDao, + val commonConfig: CommonConfig ) { @PostConstruct @@ -57,6 +78,9 @@ class AuthCronManager @Autowired constructor( logger.info("start init system authToken") clientTokenService.setSystemToken(null) logger.info("init system authToken success ${clientTokenService.getSystemToken(null)}") + updateAuthActionI18n() + updateAuthResourceTypeI18n() + updateAuthResourceGroupConfigI18n() } /** @@ -113,9 +137,149 @@ class AuthCronManager @Autowired constructor( } } + private fun updateAuthActionI18n() { + val redisLock = RedisLock(redisOperation, AUTH_ACTION_UPDATE_LOCK, expiredTimeInSeconds) + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { + try { + logger.info("start init auth Action I18n") + val authActionI18nMap = mutableMapOf() + var page = PageUtil.DEFAULT_PAGE + do { + val actionRecordResult = authActionDao.list( + dslContext = dslContext, + page = page, + pageSize = PageUtil.DEFAULT_PAGE_SIZE + ) + actionRecordResult.forEach { + val actionName = MessageUtil.getMessageByLocale( + messageCode = "${it.action}.actionName", + language = commonConfig.devopsDefaultLocaleLanguage + ) + if (actionName.isNotBlank()) { + authActionI18nMap[it.action] = actionName + } + } + if (authActionI18nMap.isNotEmpty()) { + authActionDao.updateActionName( + dslContext = dslContext, + authActionI18nMap = authActionI18nMap + ) + } + page ++ + } while (actionRecordResult.size == PageUtil.DEFAULT_PAGE_SIZE) + logger.info("init auth Action I18n end") + } finally { + redisLock.unlock() + } + } + } + } + + private fun updateAuthResourceTypeI18n() { + val redisLock = RedisLock(redisOperation, AUTH_RESOURCE_TYPE_UPDATE_LOCK, expiredTimeInSeconds) + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { + try { + logger.info("start init auth resource type I18n") + var page = PageUtil.DEFAULT_PAGE + do { + val authResourceTypes = mutableListOf() + val resourceTypeResult = authResourceTypeDao.list( + dslContext = dslContext, + page = page, + pageSize = PageUtil.DEFAULT_PAGE_SIZE + ) + resourceTypeResult.forEach { + val name = MessageUtil.getMessageByLocale( + messageCode = it.resourceType + RESOURCE_TYPE_NAME_SUFFIX, + language = commonConfig.devopsDefaultLocaleLanguage + ) + val desc = MessageUtil.getMessageByLocale( + messageCode = it.resourceType + RESOURCE_TYPE_DESC_SUFFIX, + language = commonConfig.devopsDefaultLocaleLanguage + ) + if (name.isNotBlank()) { + it.name = name + } + if (desc.isNotBlank()) { + it.desc = desc + } + it.updateTime = LocalDateTime.now() + it.updateUser = SYSTEM + authResourceTypes.add(it) + } + if (authResourceTypes.isNotEmpty()) { + authResourceTypeDao.batchUpdateAuthResourceType( + dslContext = dslContext, + authActionResourceTypes = authResourceTypes + ) + } + page++ + } while (resourceTypeResult.size == PageUtil.DEFAULT_PAGE_SIZE) + logger.info("init auth resource type I18n end") + } finally { + redisLock.unlock() + } + } + } + } + + private fun updateAuthResourceGroupConfigI18n() { + val redisLock = RedisLock(redisOperation, AUTH_RESOURCE_TYPE_GROUP_CONFIG_LOCK, expiredTimeInSeconds) + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { + try { + logger.info("start init auth resource group config type I18n") + val authAuthResourceGroupConfigs = mutableListOf() + var page = PageUtil.DEFAULT_PAGE + do { + val resourceGroupConfigResult = authResourceGroupConfigDao.list( + dslContext = dslContext, + page = page, + pageSize = PageUtil.DEFAULT_PAGE_SIZE + ) + resourceGroupConfigResult.forEach { + val groupName = MessageUtil.getMessageByLocale( + messageCode = "${it.resourceType}.${it.groupCode}" + + AUTH_RESOURCE_GROUP_CONFIG_GROUP_NAME_SUFFIX, + language = commonConfig.devopsDefaultLocaleLanguage + ) + val description = MessageUtil.getMessageByLocale( + messageCode = "${it.resourceType}.${it.groupCode}" + + AUTH_RESOURCE_GROUP_CONFIG_DESCRIPTION_SUFFIX, + language = commonConfig.devopsDefaultLocaleLanguage + ) + if (groupName.isNotBlank()) { + it.groupName = groupName + } + if (description.isNotBlank()) { + it.description = description + } + it.updateTime = LocalDateTime.now() + authAuthResourceGroupConfigs.add(it) + } + if (authAuthResourceGroupConfigs.isNotEmpty()) { + authResourceGroupConfigDao.batchUpdateAuthResourceGroupConfig( + dslContext, + authAuthResourceGroupConfigs + ) + } + page++ + } while (resourceGroupConfigResult.size == PageUtil.DEFAULT_PAGE) + logger.info("init auth resource group config I18n end") + } finally { + redisLock.unlock() + } + } + } + } companion object { val logger = LoggerFactory.getLogger(AuthCronManager::class.java) private const val AUTH_EXPIRING_MANAGAER_APPROVAL = "auth:expiring:manager:approval" private const val expiredTimeInSeconds = 60L + private const val AUTH_RESOURCE_TYPE_UPDATE_LOCK = "auth:resourceType:update" + private const val AUTH_ACTION_UPDATE_LOCK = "auth:action:update" + private const val AUTH_RESOURCE_TYPE_GROUP_CONFIG_LOCK = "auth:resource:type:group:config:update" } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthActionDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthActionDao.kt index 6bbe9513b8a..22b2f40417f 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthActionDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthActionDao.kt @@ -2,6 +2,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.model.auth.tables.TAuthAction import com.tencent.devops.model.auth.tables.records.TAuthActionRecord +import java.time.LocalDateTime import org.jooq.DSLContext import org.jooq.Result import org.springframework.stereotype.Repository @@ -27,4 +28,33 @@ class AuthActionDao { .fetchAny() } } + + fun list( + dslContext: DSLContext, + page: Int, + pageSize: Int + ): Result { + return with(TAuthAction.T_AUTH_ACTION) { + dslContext.selectFrom(this) + .orderBy(CREATE_TIME.desc(), ACTION) + .limit(pageSize).offset((page - 1) * pageSize) + .fetch() + } + } + + fun updateActionName( + dslContext: DSLContext, + authActionI18nMap: Map + ) { + dslContext.batch( + authActionI18nMap.map { + with(TAuthAction.T_AUTH_ACTION) { + dslContext.update(this) + .set(ACTION_NAME, it.value) + .set(UPDATE_TIME, LocalDateTime.now()) + .where(ACTION.eq(it.key)) + } + } + ).execute() + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt index aefe2567a2e..04dae75395b 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt @@ -87,6 +87,17 @@ class AuthResourceGroupConfigDao { } } + fun getById( + dslContext: DSLContext, + id: Long + ): TAuthResourceGroupConfigRecord? { + return with(TAuthResourceGroupConfig.T_AUTH_RESOURCE_GROUP_CONFIG) { + dslContext.selectFrom(this) + .where(ID.eq(id)) + .fetchOne() + } + } + fun countByResourceType( dslContext: DSLContext, resourceType: String @@ -98,4 +109,26 @@ class AuthResourceGroupConfigDao { .fetchOne(0, Int::class.java)!! } } + fun list( + dslContext: DSLContext, + page: Int, + pageSize: Int + ): Result { + return with(TAuthResourceGroupConfig.T_AUTH_RESOURCE_GROUP_CONFIG) { + dslContext.selectFrom(this) + .orderBy(CREATE_TIME.desc(), RESOURCE_TYPE, GROUP_CODE) + .limit(pageSize).offset((page - 1) * pageSize) + .fetch() + } + } + + fun batchUpdateAuthResourceGroupConfig( + dslContext: DSLContext, + authAuthResourceGroupConfigs: List + ) { + if (authAuthResourceGroupConfigs.isEmpty()) { + return + } + dslContext.batchUpdate(authAuthResourceGroupConfigs).execute() + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt index adaac2facf6..ac8c5cc21e6 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt @@ -30,10 +30,10 @@ package com.tencent.devops.auth.dao import com.tencent.devops.model.auth.tables.TAuthResourceGroup import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord -import java.time.LocalDateTime import org.jooq.DSLContext import org.jooq.Result import org.springframework.stereotype.Repository +import java.time.LocalDateTime @Repository @Suppress("LongParameterList", "TooManyFunctions") diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceTypeDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceTypeDao.kt index 04f4caf5091..756f2e3c6bd 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceTypeDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceTypeDao.kt @@ -13,4 +13,27 @@ class AuthResourceTypeDao { dslContext.selectFrom(this).where(DELETE.eq(false)).orderBy(ID.asc()).fetch() } } + + fun list( + dslContext: DSLContext, + page: Int, + pageSize: Int + ): Result { + return with(TAuthResourceType.T_AUTH_RESOURCE_TYPE) { + dslContext.selectFrom(this) + .orderBy(CREATE_TIME.desc(), RESOURCE_TYPE) + .limit(pageSize).offset((page - 1) * pageSize) + .fetch() + } + } + + fun batchUpdateAuthResourceType( + dslContext: DSLContext, + authActionResourceTypes: List + ) { + if (authActionResourceTypes.isEmpty()) { + return + } + dslContext.batchUpdate(authActionResourceTypes).execute() + } } diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/constant/CommonMessageCode.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/constant/CommonMessageCode.kt index 91fa17ff841..66e1a5d92fb 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/constant/CommonMessageCode.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/constant/CommonMessageCode.kt @@ -193,7 +193,7 @@ object CommonMessageCode { const val BK_CREATE_SERVICE = "bkCreateService" // 创建{0}服务 const val BK_SESSION_ID = "bkSessionId" // 会话ID const val BK_GROUP_ID = "bkGroupId" // 群ID - const val BK_THIS_GROUP_ID = "bkThisGroupId" // 本群ID='{0}'。PS:群ID可用于蓝盾平台上任意企业微信群通知。 + const val BK_THIS_GROUP_ID = "bkThisGroupId" // 本群ID={0}。PS:群ID可用于蓝盾平台上任意企业微信群通知。 const val BK_MISSING_RESOURCE_DEPENDENCY = "bkMissingResourceDependency" // 依赖的资源不存在 const val BK_REQUEST_TIMED_OUT = "bkRequestTimedOut" // 请求超时 diff --git a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthPermissionApi.kt b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthPermissionApi.kt index 4a9aac5f1e2..a5fa48f12ac 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthPermissionApi.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthPermissionApi.kt @@ -51,7 +51,10 @@ class RbacAuthPermissionApi( userId = user, action = RbacAuthUtils.buildAction(authResourceType = resourceType, authPermission = permission), projectCode = projectCode, - resourceCode = resourceType.value + resourceCode = RbacAuthUtils.getRelationResourceType( + authPermission = permission, + authResourceType = resourceType + ) ).data!! } diff --git a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthTokenApi.kt b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthTokenApi.kt index 518c652621c..516fe34bdbe 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthTokenApi.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/api/RbacAuthTokenApi.kt @@ -30,10 +30,10 @@ package com.tencent.devops.common.auth.api import com.tencent.bk.sdk.iam.service.impl.TokenServiceImpl import com.tencent.devops.common.auth.code.AuthServiceCode import com.tencent.devops.common.redis.RedisOperation -import java.nio.charset.Charset -import java.util.Base64 import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value +import java.nio.charset.Charset +import java.util.Base64 class RbacAuthTokenApi constructor( private val redisOperation: RedisOperation, diff --git a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/utils/RbacAuthUtils.kt b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/utils/RbacAuthUtils.kt index 89fee8122cf..448047f59a2 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/utils/RbacAuthUtils.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-rbac/src/main/kotlin/com/tencent/devops/common/auth/utils/RbacAuthUtils.kt @@ -9,6 +9,14 @@ object RbacAuthUtils { return "${extResourceType(authResourceType)}_${authPermission.value}" } + fun getRelationResourceType(authPermission: AuthPermission, authResourceType: AuthResourceType): String { + return if (authPermission == AuthPermission.CREATE) { + AuthResourceType.PROJECT.value + } else { + authResourceType.value + } + } + fun extResourceType(authResourceType: AuthResourceType): String { return when (authResourceType) { AuthResourceType.QUALITY_GROUP -> "quality_group" diff --git a/src/backend/ci/core/common/common-auth/common-auth-v3/src/main/kotlin/com/tencent/devops/common/auth/BluekingV3AuthAutoConfiguration.kt b/src/backend/ci/core/common/common-auth/common-auth-v3/src/main/kotlin/com/tencent/devops/common/auth/BluekingV3AuthAutoConfiguration.kt index 73c7ec143cc..55f015b2c4e 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-v3/src/main/kotlin/com/tencent/devops/common/auth/BluekingV3AuthAutoConfiguration.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-v3/src/main/kotlin/com/tencent/devops/common/auth/BluekingV3AuthAutoConfiguration.kt @@ -33,10 +33,10 @@ import com.tencent.bk.sdk.iam.service.impl.DefaultHttpClientServiceImpl import com.tencent.bk.sdk.iam.service.impl.GrantServiceImpl import com.tencent.bk.sdk.iam.service.impl.PolicyServiceImpl import com.tencent.bk.sdk.iam.service.impl.TokenServiceImpl +import com.tencent.devops.common.auth.api.BluekingV3AuthPermissionApi import com.tencent.devops.common.auth.api.BluekingV3AuthProjectApi -import com.tencent.devops.common.auth.api.BluekingV3ResourceApi import com.tencent.devops.common.auth.api.BluekingV3AuthTokenApi -import com.tencent.devops.common.auth.api.BluekingV3AuthPermissionApi +import com.tencent.devops.common.auth.api.BluekingV3ResourceApi import com.tencent.devops.common.auth.code.BluekingV3ArtifactoryAuthServiceCode import com.tencent.devops.common.auth.code.BluekingV3BcsAuthServiceCode import com.tencent.devops.common.auth.code.BluekingV3CodeAuthServiceCode @@ -70,7 +70,7 @@ class BluekingV3AuthAutoConfiguration { @Value("\${auth.url:}") val iamBaseUrl = "" - @Value("\${auth.appCode:}") + @Value("\${auth.iamSystem:}") val systemId = "" @Value("\${auth.appCode:}") diff --git a/src/backend/ci/core/common/common-db-sharding/build.gradle.kts b/src/backend/ci/core/common/common-db-sharding/build.gradle.kts index 1cc4d527e64..93f75ddd69f 100644 --- a/src/backend/ci/core/common/common-db-sharding/build.gradle.kts +++ b/src/backend/ci/core/common/common-db-sharding/build.gradle.kts @@ -26,6 +26,7 @@ */ dependencies { + implementation("io.micrometer:micrometer-core") api(project(":core:common:common-db-base")) api("org.apache.shardingsphere:shardingsphere-jdbc-core:${Versions.ShardingSphere}") } diff --git a/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt b/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt index cdb723aa984..6d94d294dcf 100644 --- a/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt +++ b/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt @@ -34,6 +34,8 @@ import com.tencent.devops.common.db.pojo.DatabaseShardingStrategyEnum import com.tencent.devops.common.db.pojo.TableRuleConfig import com.tencent.devops.common.db.pojo.TableShardingStrategyEnum import com.zaxxer.hikari.HikariDataSource +import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory +import io.micrometer.core.instrument.MeterRegistry import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration @@ -74,22 +76,29 @@ class BkShardingDataSourceConfiguration { @Value("\${sharding.log.switch:false}") private val shardingLogSwitch: Boolean = false + @Value("\${sharding.databaseShardingStrategy.algorithmClassName:#{null}}") private val databaseAlgorithmClassName: String? = null + @Value("\${sharding.databaseShardingStrategy.shardingField:#{null}}") private val databaseShardingField: String? = null + @Value("\${sharding.tableShardingStrategy.algorithmClassName:#{null}}") private val tableAlgorithmClassName: String? = null + @Value("\${sharding.tableShardingStrategy.shardingField:#{null}}") private val tableShardingField: String? = null + @Value("\${spring.datasource.minimumIdle:#{1}}") private val datasourceMinimumIdle: Int = 1 + @Value("\${spring.datasource.maximumPoolSize:#{50}}") private val datasourceMaximumPoolSize: Int = 50 + @Value("\${spring.datasource.idleTimeout:#{60000}}") private val datasourceIdleTimeout: Long = 60000 - private fun dataSourceMap(config: DataSourceProperties): Map { + private fun dataSourceMap(config: DataSourceProperties, registry: MeterRegistry): Map { val dataSourceMap: MutableMap = mutableMapOf() val dataSourceConfigs = config.dataSourceConfigs // 根据配置文件中的数据源配置项列表动态生成数据源集合 @@ -101,7 +110,8 @@ class BkShardingDataSourceConfiguration { datasourceUsername = dataSourceConfig.username, datasourcePassword = dataSourceConfig.password, datasourceInitSql = dataSourceConfig.initSql, - datasourceLeakDetectionThreshold = dataSourceConfig.leakDetectionThreshold + datasourceLeakDetectionThreshold = dataSourceConfig.leakDetectionThreshold, + metricsRegistry = registry ) } return dataSourceMap @@ -113,7 +123,8 @@ class BkShardingDataSourceConfiguration { datasourceUsername: String, datasourcePassword: String, datasourceInitSql: String?, - datasourceLeakDetectionThreshold: Long + datasourceLeakDetectionThreshold: Long, + metricsRegistry: MeterRegistry ): HikariDataSource { return HikariDataSource().apply { poolName = datasourcePoolName @@ -126,11 +137,12 @@ class BkShardingDataSourceConfiguration { idleTimeout = datasourceIdleTimeout connectionInitSql = datasourceInitSql leakDetectionThreshold = datasourceLeakDetectionThreshold + metricsTrackerFactory = MicrometerMetricsTrackerFactory(metricsRegistry) } } @Bean - fun shardingDataSource(config: DataSourceProperties): DataSource { + fun shardingDataSource(config: DataSourceProperties, registry: MeterRegistry): DataSource { val shardingRuleConfig = ShardingRuleConfiguration() // 设置分片表的路由规则 val dataSourceSize = config.dataSourceConfigs.size @@ -177,7 +189,7 @@ class BkShardingDataSourceConfiguration { // 是否打印SQL解析和改写日志 dataSourceProperties.setProperty("sql-show", shardingLogSwitch.toString()) return ShardingSphereDataSourceFactory.createDataSource( - dataSourceMap(config), + dataSourceMap(config, registry), listOf(shardingRuleConfig), dataSourceProperties ) @@ -200,7 +212,8 @@ class BkShardingDataSourceConfiguration { val lastDsIndex = dataSourceSize - 1 val lastTableIndex = tableRuleConfig.shardingNum - 1 val actualDataNodes = if (databaseShardingStrategy != null && - tableShardingStrategy == TableShardingStrategyEnum.SHARDING) { + tableShardingStrategy == TableShardingStrategyEnum.SHARDING + ) { // 生成分库分表场景下的节点规则 if (databaseShardingStrategy == DatabaseShardingStrategyEnum.SPECIFY) { "${DATA_SOURCE_NAME_PREFIX}0.${tableName}_\${0..$lastTableIndex}" @@ -221,8 +234,10 @@ class BkShardingDataSourceConfiguration { "${DATA_SOURCE_NAME_PREFIX}0.$tableName" } val shardingTableRuleConfig = ShardingTableRuleConfiguration(tableName, actualDataNodes) - logger.info("BkShardingDataSourceConfiguration table:$tableName|databaseShardingStrategy:" + - "$databaseShardingStrategy|tableShardingStrategy:$tableShardingStrategy|actualDataNodes:$actualDataNodes ") + logger.info( + "BkShardingDataSourceConfiguration table:$tableName|databaseShardingStrategy: $databaseShardingStrategy|" + + "tableShardingStrategy:$tableShardingStrategy|actualDataNodes:$actualDataNodes " + ) // 设置表的分库策略 shardingTableRuleConfig.databaseShardingStrategy = if (databaseShardingStrategy != null) { StandardShardingStrategyConfiguration(databaseShardingField, DB_SHARDING_ALGORITHM_NAME) diff --git a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/listener/BuildListener.kt b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/listener/BuildListener.kt index 4e40a0e3c40..4ff7a1e7da3 100644 --- a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/listener/BuildListener.kt +++ b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/listener/BuildListener.kt @@ -36,6 +36,7 @@ import com.tencent.devops.common.dispatch.sdk.pojo.DispatchMessage import com.tencent.devops.common.dispatch.sdk.service.DispatchService import com.tencent.devops.common.dispatch.sdk.service.JobQuotaService import com.tencent.devops.common.dispatch.sdk.utils.DispatchLogRedisUtils +import com.tencent.devops.common.event.pojo.pipeline.IPipelineEvent import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent import com.tencent.devops.common.log.utils.BuildLogPrinter @@ -153,8 +154,12 @@ interface BuildListener { ) } - fun retry(sleepTimeInMS: Int = 30000, retryTimes: Int = 3): Boolean { - val event = DispatcherContext.getEvent() + fun retry( + sleepTimeInMS: Int = 30000, + retryTimes: Int = 3, + pipelineEvent: IPipelineEvent? = null + ): Boolean { + val event = pipelineEvent ?: DispatcherContext.getEvent() if (event == null) { logger.warn("The event is empty") return false diff --git a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt index 6c31a364daa..d874cdccc48 100644 --- a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt +++ b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt @@ -48,6 +48,7 @@ import com.tencent.devops.common.dispatch.sdk.pojo.docker.DockerConstants.ENV_KE import com.tencent.devops.common.dispatch.sdk.pojo.docker.DockerConstants.ENV_KEY_PROJECT_ID import com.tencent.devops.common.dispatch.sdk.utils.ChannelUtils import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher +import com.tencent.devops.common.event.pojo.pipeline.IPipelineEvent import com.tencent.devops.common.log.utils.BuildLogPrinter import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.redis.RedisOperation @@ -195,7 +196,7 @@ class DispatchService constructor( } } - fun redispatch(event: PipelineAgentStartupEvent) { + fun redispatch(event: IPipelineEvent) { logger.info("Re-dispatch the agent event - ($event)") pipelineEventDispatcher.dispatch(event) } diff --git a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/AntPathMatcher.java b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/AntPathMatcher.java index 3c22a720a48..005586111a1 100644 --- a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/AntPathMatcher.java +++ b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/AntPathMatcher.java @@ -23,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.jetbrains.annotations.Nullable; /** diff --git a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/CollectionUtils.java b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/CollectionUtils.java index 1fc7a9fc0d3..2375627df15 100644 --- a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/CollectionUtils.java +++ b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/CollectionUtils.java @@ -16,9 +16,8 @@ package com.tencent.devops.common.expression.utils; -import org.jetbrains.annotations.Nullable; - import java.util.Collection; +import org.jetbrains.annotations.Nullable; @SuppressWarnings("all") public abstract class CollectionUtils { diff --git a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/StringUtils.java b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/StringUtils.java index d297dd68d41..c73ea13f467 100644 --- a/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/StringUtils.java +++ b/src/backend/ci/core/common/common-expression/src/main/java/com/tencent/devops/common/expression/utils/StringUtils.java @@ -16,12 +16,11 @@ package com.tencent.devops.common.expression.utils; -import org.jetbrains.annotations.Nullable; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.StringTokenizer; +import org.jetbrains.annotations.Nullable; @SuppressWarnings("all") public abstract class StringUtils { diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/EnvReplacementParser.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/EnvReplacementParser.kt index 23360b887ae..7fea8cabdbf 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/EnvReplacementParser.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/EnvReplacementParser.kt @@ -50,7 +50,8 @@ import java.io.InputStreamReader "ComplexCondition", "ComplexMethod", "NestedBlockDepth", - "ReturnCount" + "ReturnCount", + "LongParameterList" ) object EnvReplacementParser { diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/ElementAdditionalOptions.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/ElementAdditionalOptions.kt index 7561207e858..e94f1061bff 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/ElementAdditionalOptions.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/ElementAdditionalOptions.kt @@ -41,9 +41,9 @@ data class ElementAdditionalOptions( @ApiModelProperty("是否出现跳过按钮(手动继续)", required = false) val manualSkip: Boolean? = null, // (continueWhenFailed = true && manualSkip = true) 出现跳过按钮(手动继续) @ApiModelProperty("是否失败时重试", required = false) - val retryWhenFailed: Boolean = false, + var retryWhenFailed: Boolean = false, @ApiModelProperty("重试计数", required = false) - val retryCount: Int = 0, + var retryCount: Int = 0, @ApiModelProperty("是否允许手动重试", required = false) val manualRetry: Boolean = true, // 自动重试一直失败后,界面出现重试按钮, 默认允许手动重试(为了兼容旧数据使用习惯) @ApiModelProperty("超时分钟", required = false) @@ -53,19 +53,19 @@ data class ElementAdditionalOptions( @JsonIgnore // 表示是否有修改,比如timeout. 注解 @JsonIgnore 表示本字段不会持久到数据库存储,只做临时的校验字段,不做任何保证 var change: Boolean = false, @ApiModelProperty("执行条件", required = false) - val runCondition: RunCondition?, + var runCondition: RunCondition?, @ApiModelProperty("是否配置前置暂停", required = false) var pauseBeforeExec: Boolean? = false, // 是否配置前置暂停 @ApiModelProperty("订阅暂停通知用户", required = false) - val subscriptionPauseUser: String? = "", // 订阅暂停通知用户 + var subscriptionPauseUser: String? = "", // 订阅暂停通知用户 @ApiModelProperty("", required = false) - val otherTask: String? = null, + var otherTask: String? = null, @ApiModelProperty("自定义变量", required = false) val customVariables: List? = null, @ApiModelProperty("自定义条件", required = false) - val customCondition: String? = "", + var customCondition: String? = "", @ApiModelProperty("插件post信息", required = false) - val elementPostInfo: ElementPostInfo? = null, + var elementPostInfo: ElementPostInfo? = null, @ApiModelProperty("是否设置自定义环境变量", required = false) val enableCustomEnv: Boolean? = false, // 是否设置自定义环境变量 @ApiModelProperty("自定义环境变量", required = false) diff --git a/src/backend/ci/core/common/common-redis/build.gradle.kts b/src/backend/ci/core/common/common-redis/build.gradle.kts index 90839b199f7..feee0bf3ed9 100644 --- a/src/backend/ci/core/common/common-redis/build.gradle.kts +++ b/src/backend/ci/core/common/common-redis/build.gradle.kts @@ -26,6 +26,7 @@ */ dependencies { + implementation("io.micrometer:micrometer-core") api("org.springframework.boot:spring-boot-starter-data-redis") api("org.apache.commons:commons-pool2") } diff --git a/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisAutoConfiguration.kt b/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisAutoConfiguration.kt index b010a136530..8f025782cd6 100644 --- a/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisAutoConfiguration.kt +++ b/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisAutoConfiguration.kt @@ -28,10 +28,15 @@ package com.tencent.devops.common.redis import com.tencent.devops.common.redis.concurrent.SimpleRateLimiter +import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder +import io.lettuce.core.metrics.MicrometerOptions +import io.lettuce.core.resource.ClientResources +import io.micrometer.core.instrument.MeterRegistry import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.AutoConfigureBefore import org.springframework.boot.autoconfigure.AutoConfigureOrder +import org.springframework.boot.autoconfigure.data.redis.ClientResourcesBuilderCustomizer import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -76,4 +81,15 @@ class RedisAutoConfiguration { fun simpleRateLimiter(@Autowired redisOperation: RedisOperation): SimpleRateLimiter { return SimpleRateLimiter(redisOperation) } + + @Bean + @Primary + fun lettuceMetrics(meterRegistry: MeterRegistry): ClientResourcesBuilderCustomizer { + val options = MicrometerOptions.builder().build() + return ClientResourcesBuilderCustomizer { client: ClientResources.Builder -> + client.commandLatencyRecorder( + MicrometerCommandLatencyRecorder(meterRegistry, options) + ) + } + } } diff --git a/src/backend/ci/core/common/common-service/build.gradle.kts b/src/backend/ci/core/common/common-service/build.gradle.kts index 30cd0743654..fdbb33dabef 100644 --- a/src/backend/ci/core/common/common-service/build.gradle.kts +++ b/src/backend/ci/core/common/common-service/build.gradle.kts @@ -38,7 +38,6 @@ dependencies { api("org.springframework:spring-web") api("org.apache.commons:commons-lang3") api("org.springframework.cloud:spring-cloud-starter-bootstrap") - api("io.micrometer:micrometer-registry-prometheus") api("org.jooq:jooq") api("io.micrometer:micrometer-registry-prometheus") api("io.micrometer:micrometer-jersey2") diff --git a/src/backend/ci/core/common/common-service/src/main/kotlin/com/tencent/devops/common/service/MicroService.kt b/src/backend/ci/core/common/common-service/src/main/kotlin/com/tencent/devops/common/service/MicroService.kt index e1e664370bc..acb539832b6 100644 --- a/src/backend/ci/core/common/common-service/src/main/kotlin/com/tencent/devops/common/service/MicroService.kt +++ b/src/backend/ci/core/common/common-service/src/main/kotlin/com/tencent/devops/common/service/MicroService.kt @@ -27,10 +27,12 @@ package com.tencent.devops.common.service +import org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication + /** * * Powered By Tencent */ -@SpringBootApplication +@SpringBootApplication(exclude = [LettuceMetricsAutoConfiguration::class]) annotation class MicroService diff --git a/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties b/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties index 8121950dd7f..c9139f1101a 100644 --- a/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties +++ b/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties @@ -32,7 +32,6 @@ management.endpoint.health.show-details=ALWAYS management.security.enabled=false management.endpoints.web.base-path=/management management.metrics.tags.application=${spring.application.name} -management.metrics.web.server.request.autotime.enabled=false spring.cloud.consul.discovery.health-check-path=/management/health spring.cloud.consul.discovery.heartbeat.enabled=true spring.cloud.consul.discovery.query-passing=true diff --git a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/BkJerseyTagProvider.kt b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/BkJerseyTagProvider.kt new file mode 100644 index 00000000000..9705423f5f0 --- /dev/null +++ b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/BkJerseyTagProvider.kt @@ -0,0 +1,42 @@ +package com.tencent.devops.common.web + +import io.micrometer.core.annotation.Timed +import io.micrometer.core.instrument.Tag +import io.micrometer.core.instrument.Tags +import io.micrometer.core.instrument.binder.jersey.server.JerseyTags +import io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider +import org.glassfish.jersey.server.internal.routing.UriRoutingContext +import org.glassfish.jersey.server.model.ResourceMethodInvoker +import org.glassfish.jersey.server.monitoring.RequestEvent + +class BkJerseyTagProvider : JerseyTagsProvider { + override fun httpRequestTags(event: RequestEvent): MutableIterable { + val response = event.containerResponse + return if (isTimed(event)) { + Tags.of( + JerseyTags.method(event.containerRequest), JerseyTags.uri(event), + JerseyTags.exception(event), JerseyTags.status(response), JerseyTags.outcome(response) + ) + } else { + Tags.of(JerseyTags.outcome(response)) + } + } + + override fun httpLongRequestTags(event: RequestEvent): MutableIterable { + return if (isTimed(event)) { + Tags.of(JerseyTags.method(event.containerRequest), JerseyTags.uri(event)) + } else { + Tags.of(JerseyTags.method(event.containerRequest)) + } + } + + private fun isTimed(event: RequestEvent): Boolean { + val uriInfo = event.containerRequest.uriInfo + return if (uriInfo is UriRoutingContext) { + val endpoint = uriInfo.endpoint + endpoint is ResourceMethodInvoker && endpoint.resourceMethod.isAnnotationPresent(Timed::class.java) + } else { + false + } + } +} diff --git a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/WebAutoConfiguration.kt b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/WebAutoConfiguration.kt index 897db86e471..ea834848c59 100644 --- a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/WebAutoConfiguration.kt +++ b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/WebAutoConfiguration.kt @@ -30,6 +30,7 @@ package com.tencent.devops.common.web import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.web.interceptor.BkWriterInterceptor import com.tencent.devops.common.web.jasypt.DefaultEncryptor +import io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider import io.undertow.UndertowOptions import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value @@ -73,6 +74,12 @@ class WebAutoConfiguration { @Profile("!prod") fun jerseySwaggerConfig() = JerseySwaggerConfig() + @Bean + @Primary + fun jerseyTagsProvider(): JerseyTagsProvider { + return BkJerseyTagProvider() + } + @Bean @Primary fun objectMapper() = JsonUtil.getObjectMapper() diff --git a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/BkI18nLanguageCacheUtil.kt b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/BkI18nLanguageCacheUtil.kt new file mode 100644 index 00000000000..1e7565977e2 --- /dev/null +++ b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/BkI18nLanguageCacheUtil.kt @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.common.web.utils + +import com.github.benmanes.caffeine.cache.Caffeine +import java.util.concurrent.TimeUnit + +/** + * 国际化语言信息缓存 + * + * @since: 2023-06-13 + * @version: $Revision$ $Date$ $LastChangedBy$ + * + */ +object BkI18nLanguageCacheUtil { + + private val i18nLanguageCache = Caffeine.newBuilder() + .maximumSize(20000) + .expireAfterWrite(6, TimeUnit.SECONDS) + .build() + + /** + * 保存国际化语言信息缓存 + * @param userId 用户Id + * @param language 国际化语言信息 + */ + fun put(userId: String, language: String) { + i18nLanguageCache.put(userId, language) + } + + /** + * 从缓存中获取国际化语言信息 + * @param userId 用户Id + * @return 国际化语言信息 + */ + fun getIfPresent(userId: String): String? { + return i18nLanguageCache.getIfPresent(userId) + } +} diff --git a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/I18nUtil.kt b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/I18nUtil.kt index a087c2051ec..94a68a1439f 100644 --- a/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/I18nUtil.kt +++ b/src/backend/ci/core/common/common-web/src/main/kotlin/com/tencent/devops/common/web/utils/I18nUtil.kt @@ -55,8 +55,14 @@ object I18nUtil { * @return 语言信息 */ fun getUserLocaleLanguageFromCache(userId: String): String? { - val redisOperation: RedisOperation = SpringContextUtil.getBean(RedisOperation::class.java) - return redisOperation.get(LocaleUtil.getUserLocaleLanguageKey(userId)) + // 先从本地缓存中获取用户语言信息 + var language = BkI18nLanguageCacheUtil.getIfPresent(userId) + if (language.isNullOrBlank()) { + // 本地缓存中获取不到语言信息再从redis中获取 + val redisOperation: RedisOperation = SpringContextUtil.getBean(RedisOperation::class.java) + language = redisOperation.get(LocaleUtil.getUserLocaleLanguageKey(userId)) + } + return language } /** @@ -116,6 +122,7 @@ object I18nUtil { client.get(ServiceLocaleResource::class).getUserLocale(userId).data?.language ?: defaultLanguage val redisOperation: RedisOperation = SpringContextUtil.getBean(RedisOperation::class.java) // 把查出来的用户语言放入缓存 + BkI18nLanguageCacheUtil.put(userId, language) redisOperation.set(LocaleUtil.getUserLocaleLanguageKey(userId), language) } language diff --git a/src/backend/ci/core/common/common-web/src/main/resources/common-web.properties b/src/backend/ci/core/common/common-web/src/main/resources/common-web.properties index dd48fa1b3cd..18219b1ebac 100644 --- a/src/backend/ci/core/common/common-web/src/main/resources/common-web.properties +++ b/src/backend/ci/core/common/common-web/src/main/resources/common-web.properties @@ -31,3 +31,6 @@ server.servlet-path=/ server.undertow.accesslog.enabled=false server.undertow.accesslog.pattern='%h %I "%{i,X-DEVOPS-UID}" [%{time,yyyyMMddHHmmss.S}] "%r" %s %b "%{i,Referer}" "%{i,User-Agent}"' server.shutdown=graceful +spring.jersey.servlet.load-on-startup=1 +spring.mvc.servlet.load-on-startup=1 +spring.webservices.servlet.load-on-startup=1 diff --git a/src/backend/ci/core/common/common-wechatwork/src/main/kotlin/com/tencent/devops/common/wechatwork/model/sendmessage/richtext/RichtextMessage.kt b/src/backend/ci/core/common/common-wechatwork/src/main/kotlin/com/tencent/devops/common/wechatwork/model/sendmessage/richtext/RichtextMessage.kt index 450f0280270..e5a5a0bf121 100644 --- a/src/backend/ci/core/common/common-wechatwork/src/main/kotlin/com/tencent/devops/common/wechatwork/model/sendmessage/richtext/RichtextMessage.kt +++ b/src/backend/ci/core/common/common-wechatwork/src/main/kotlin/com/tencent/devops/common/wechatwork/model/sendmessage/richtext/RichtextMessage.kt @@ -26,6 +26,7 @@ */ package com.tencent.devops.common.wechatwork.model.sendmessage.richtext + import com.fasterxml.jackson.annotation.JsonProperty import com.tencent.devops.common.wechatwork.model.sendmessage.Receiver diff --git a/src/backend/ci/core/dispatch-docker/biz-dispatch-docker/src/main/kotlin/com/tencent/devops/dispatch/docker/client/BuildLessStartHandler.kt b/src/backend/ci/core/dispatch-docker/biz-dispatch-docker/src/main/kotlin/com/tencent/devops/dispatch/docker/client/BuildLessStartHandler.kt index 1df44f9142c..c39b09f7ee5 100644 --- a/src/backend/ci/core/dispatch-docker/biz-dispatch-docker/src/main/kotlin/com/tencent/devops/dispatch/docker/client/BuildLessStartHandler.kt +++ b/src/backend/ci/core/dispatch-docker/biz-dispatch-docker/src/main/kotlin/com/tencent/devops/dispatch/docker/client/BuildLessStartHandler.kt @@ -63,6 +63,7 @@ class BuildLessStartHandler @Autowired constructor( ) : Handler() { private val logger = LoggerFactory.getLogger(BuildLessStartHandler::class.java) + @Suppress("NestedBlockDepth") override fun handlerRequest(handlerContext: BuildLessStartHandlerContext) { with(handlerContext) { val buildLessStartInfo = BuildLessStartInfo( diff --git a/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/api/ServiceNodeResource.kt b/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/api/ServiceNodeResource.kt index 03a1230b633..7913431effd 100644 --- a/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/api/ServiceNodeResource.kt +++ b/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/api/ServiceNodeResource.kt @@ -173,4 +173,22 @@ interface ServiceNodeResource { @QueryParam("agentId") agentId: String ): Result + + @ApiOperation("指定构建环境获取所有节点信息") + @GET + @Path("/projects/{projectId}/third_party_env2nodes") + fun thirdPartyEnv2Nodes( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID(项目英文名)", required = true) + @PathParam("projectId") + projectId: String, + @ApiParam("环境 hashId (envHashId和envName选填一项)", required = false) + @QueryParam("envHashId") + envHashId: String?, + @ApiParam("环境名称 (envHashId和envName选填一项)", required = false) + @QueryParam("envName") + envName: String? + ): Result> } diff --git a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/dao/NodeDao.kt b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/dao/NodeDao.kt index 0d0f81ee094..2a84a56ebee 100644 --- a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/dao/NodeDao.kt +++ b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/dao/NodeDao.kt @@ -61,11 +61,16 @@ class NodeDao { } } - fun listThirdpartyNodes(dslContext: DSLContext, projectId: String): List { + fun listThirdpartyNodes( + dslContext: DSLContext, + projectId: String, + nodeIds: Collection? = null + ): List { with(TNode.T_NODE) { return dslContext.selectFrom(this) .where(PROJECT_ID.eq(projectId)) .and(NODE_TYPE.eq(NodeType.THIRDPARTY.name)) + .let { if (nodeIds != null) it.and(NODE_ID.`in`(nodeIds)) else it } .orderBy(NODE_ID.desc()) .fetch() } diff --git a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/resources/ServiceNodeResourceImpl.kt b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/resources/ServiceNodeResourceImpl.kt index 3f3a7f430e5..2c97843e970 100644 --- a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/resources/ServiceNodeResourceImpl.kt +++ b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/resources/ServiceNodeResourceImpl.kt @@ -119,4 +119,25 @@ class ServiceNodeResourceImpl @Autowired constructor( nodeService.deleteNodeByAgentId(userId, projectId, agentId) return Result(true) } + + override fun thirdPartyEnv2Nodes( + userId: String, + projectId: String, + envHashId: String?, + envName: String? + ): Result> { + if (envHashId.isNullOrBlank() && envName.isNullOrBlank()) { + throw ErrorCodeException( + errorCode = CommonMessageCode.ERROR_NEED_PARAM_, + params = arrayOf("envHashId") + ) + } + val envId = envHashId ?: envName?.let { + envService.getByName(projectId, it)?.envHashId + } ?: throw ErrorCodeException( + errorCode = CommonMessageCode.ERROR_NEED_PARAM_, + params = arrayOf("envName") + ) + return Result(envService.thirdPartyEnv2Nodes(userId, projectId, envId)) + } } diff --git a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/EnvService.kt b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/EnvService.kt index dcc9e3aa12d..4264bea07ec 100644 --- a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/EnvService.kt +++ b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/EnvService.kt @@ -69,6 +69,7 @@ import com.tencent.devops.environment.pojo.EnvWithNodeCount import com.tencent.devops.environment.pojo.EnvWithPermission import com.tencent.devops.environment.pojo.EnvironmentId import com.tencent.devops.environment.pojo.NodeBaseInfo +import com.tencent.devops.environment.pojo.NodeWithPermission import com.tencent.devops.environment.pojo.SharedProjectInfo import com.tencent.devops.environment.pojo.enums.EnvType import com.tencent.devops.environment.pojo.enums.NodeStatus @@ -80,11 +81,11 @@ import com.tencent.devops.environment.utils.AgentStatusUtils.getAgentStatus import com.tencent.devops.environment.utils.NodeStringIdUtils import com.tencent.devops.model.environment.tables.records.TEnvRecord import com.tencent.devops.project.api.service.ServiceProjectResource -import java.text.SimpleDateFormat import org.jooq.DSLContext import org.jooq.impl.DSL import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.text.SimpleDateFormat @Service @Suppress("ALL") @@ -97,6 +98,7 @@ class EnvService @Autowired constructor( private val slaveGatewayService: SlaveGatewayService, private val environmentPermissionService: EnvironmentPermissionService, private val envShareProjectDao: EnvShareProjectDao, + private val nodeService: NodeService, private val client: Client ) : IEnvService { @@ -513,6 +515,22 @@ class EnvService @Autowired constructor( return resultMap } + fun thirdPartyEnv2Nodes( + userId: String, + projectId: String, + envHashId: String + ): List { + val envId = HashUtil.decodeIdToLong(envHashId) + if (!environmentPermissionService.checkEnvPermission(userId, projectId, envId, AuthPermission.VIEW)) { + throw PermissionForbiddenException( + message = I18nUtil.getCodeLanMessage(ERROR_ENV_NO_VIEW_PERMISSSION) + ) + } + val envNodes = envNodeDao.list(dslContext, projectId, listOf(envId)) + val nodes = nodeDao.listThirdpartyNodes(dslContext, projectId, envNodes.map { it.nodeId }) + return nodeService.formatNodeWithPermissions(userId, projectId, nodes) + } + override fun listAllEnvNodes(userId: String, projectId: String, envHashIds: List): List { val envIds = envHashIds.map { HashUtil.decodeIdToLong(it) } val canViewEnvIdList = environmentPermissionService.listEnvByViewPermission(userId, projectId) @@ -702,6 +720,27 @@ class EnvService @Autowired constructor( ) } + fun getByName(projectId: String, envName: String): EnvWithPermission? { + return envDao.getByEnvName(dslContext, projectId, envName)?.let { + EnvWithPermission( + envHashId = HashUtil.encodeLongId(it.envId), + name = it.envName, + desc = it.envDesc, + envType = if (it.envType == EnvType.TEST.name) EnvType.DEV.name else it.envType, // 兼容性代码 + nodeCount = null, + envVars = jacksonObjectMapper().readValue(it.envVars), + createdUser = it.createdUser, + createdTime = it.createdTime.timestamp(), + updatedUser = it.updatedUser, + updatedTime = it.updatedTime.timestamp(), + canEdit = null, + canDelete = null, + canUse = null, + projectName = null + ) + } + } + private fun format(records: List): List { return records.map { EnvWithPermission( diff --git a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/NodeService.kt b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/NodeService.kt index 88b4ea8805a..34012499241 100644 --- a/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/NodeService.kt +++ b/src/backend/ci/core/environment/biz-environment/src/main/kotlin/com/tencent/devops/environment/service/NodeService.kt @@ -53,16 +53,17 @@ import com.tencent.devops.environment.service.node.NodeActionFactory import com.tencent.devops.environment.service.slave.SlaveGatewayService import com.tencent.devops.environment.utils.AgentStatusUtils.getAgentStatus import com.tencent.devops.environment.utils.NodeStringIdUtils -import java.time.format.DateTimeFormatter -import java.util.concurrent.Executors -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit +import com.tencent.devops.model.environment.tables.records.TNodeRecord import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.time.format.DateTimeFormatter +import java.util.concurrent.Executors +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit @Service @Suppress("ALL") @@ -131,6 +132,14 @@ class NodeService @Autowired constructor( return emptyList() } + return formatNodeWithPermissions(userId, projectId, nodeRecordList) + } + + fun formatNodeWithPermissions( + userId: String, + projectId: String, + nodeRecordList: List + ): List { val permissionMap = environmentPermissionService.listNodeByPermissions( userId = userId, projectId = projectId, permissions = setOf(AuthPermission.USE, AuthPermission.EDIT, AuthPermission.DELETE) diff --git a/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/constant/LogMessageCode.kt b/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/constant/LogMessageCode.kt index c4fe3b0ab28..b6261e6cc7b 100644 --- a/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/constant/LogMessageCode.kt +++ b/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/constant/LogMessageCode.kt @@ -26,6 +26,7 @@ object LogMessageCode { const val PRINT_IS_DISABLED = "2108006" // log print config is disabled const val FILE_NOT_FOUND_CHECK_PATH = "2108007" // 未找到 {0} 文件,请检查路径是否正确: const val LOG_INDEX_HAS_BEEN_CLEANED = "2108008" // 日志索引已被清理无法查看 + const val ERROR_PIPELINE_NOT_EXISTS = "2108009" // 流水线{0}不存在 const val BK_FAILED_INSERT_DATA = "bkFailedInsertData" // 蓝盾ES集群插入数据失败 const val BK_ES_CLUSTER_RECOVERY = "bkEsClusterRecovery" // 蓝盾ES集群恢复 diff --git a/src/backend/ci/core/log/biz-log-sample/src/main/kotlin/com/tencent/devops/log/service/impl/RbacLogPermissionService.kt b/src/backend/ci/core/log/biz-log-sample/src/main/kotlin/com/tencent/devops/log/service/impl/RbacLogPermissionService.kt index e1fc6cea25f..4c478394efb 100644 --- a/src/backend/ci/core/log/biz-log-sample/src/main/kotlin/com/tencent/devops/log/service/impl/RbacLogPermissionService.kt +++ b/src/backend/ci/core/log/biz-log-sample/src/main/kotlin/com/tencent/devops/log/service/impl/RbacLogPermissionService.kt @@ -7,10 +7,10 @@ import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.utils.RbacAuthUtils import com.tencent.devops.common.client.Client import com.tencent.devops.common.client.ClientTokenService +import com.tencent.devops.common.log.constant.LogMessageCode import com.tencent.devops.common.pipeline.enums.ChannelCode import com.tencent.devops.log.service.LogPermissionService import com.tencent.devops.process.api.service.ServicePipelineResource -import com.tencent.devops.process.constant.ProcessMessageCode import org.springframework.beans.factory.annotation.Autowired class RbacLogPermissionService @Autowired constructor( @@ -29,7 +29,7 @@ class RbacLogPermissionService @Autowired constructor( pipelineId = pipelineId, channelCode = null ).data ?: throw ErrorCodeException( - errorCode = ProcessMessageCode.ERROR_PIPELINE_NOT_EXISTS + errorCode = LogMessageCode.ERROR_PIPELINE_NOT_EXISTS ) // 兼容CodeCC场景,CodeCC创建的流水线未向权限中心注册,调鉴权接口会报错。 return pipelineInfo.channelCode != ChannelCode.BS || diff --git a/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/api/builds/BuildNotifyResource.kt b/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/api/builds/BuildNotifyResource.kt index 0bb6d84e5c1..ea4420d1d05 100644 --- a/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/api/builds/BuildNotifyResource.kt +++ b/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/api/builds/BuildNotifyResource.kt @@ -63,7 +63,6 @@ interface BuildNotifyResource { fun sendRtxNotify( @ApiParam(value = "RTX信息内容", required = true) message: RtxNotifyMessage - ): Result @ApiOperation("发送电子邮件通知") diff --git a/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/pojo/messageTemplate/MessageTemplate.kt b/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/pojo/messageTemplate/MessageTemplate.kt index eda14d933c9..6e46c610ac5 100644 --- a/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/pojo/messageTemplate/MessageTemplate.kt +++ b/src/backend/ci/core/notify/api-notify/src/main/kotlin/com/tencent/devops/notify/pojo/messageTemplate/MessageTemplate.kt @@ -58,5 +58,5 @@ data class MessageTemplate( @ApiModelProperty("创建人", required = true) val creator: String, @ApiModelProperty("修改人", required = true) - val modifier: String + val modifior: String ) diff --git a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt index 7d973d0fec2..183cfc27721 100644 --- a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt +++ b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt @@ -43,6 +43,7 @@ import com.tencent.devops.common.notify.enums.NotifyType import com.tencent.devops.common.notify.utils.NotifyUtils import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.common.wechatwork.WechatWorkRobotService import com.tencent.devops.common.wechatwork.WechatWorkService @@ -66,6 +67,7 @@ import com.tencent.devops.notify.pojo.SendNotifyMessageTemplateRequest import com.tencent.devops.notify.pojo.SubNotifyMessageTemplate import com.tencent.devops.notify.pojo.WechatNotifyMessage import com.tencent.devops.notify.pojo.messageTemplate.MessageTemplate +import java.io.File import java.time.LocalDateTime import java.util.concurrent.Executors import java.util.regex.Pattern @@ -91,7 +93,8 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( private val wechatWorkService: WechatWorkService, private val wechatWorkRobotService: WechatWorkRobotService, private val redisOperation: RedisOperation, - private val messageTemplateDao: MessageTemplateDao + private val messageTemplateDao: MessageTemplateDao, + private val commonConfig: CommonConfig ) : NotifyMessageTemplateService { companion object { @@ -110,12 +113,14 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( expiredTimeInSeconds = 60 ) - if (redisLock.tryLock()) { - Executors.newFixedThreadPool(1).submit { + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { try { logger.info("start init MessageTemplate") updateMessageTemplate() - logger.info("start init succeed") + logger.info("start init MessageTemplate succeed") + } catch (ignored: Throwable) { + logger.warn("start init MessageTemplate fail! error:${ignored.message}") } finally { redisLock.unlock() } @@ -125,7 +130,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( fun updateMessageTemplate() { val classPathResource = ClassPathResource( - "template_${I18nUtil.getDefaultLocaleLanguage()}.yaml" + "i18n${File.separator}template_${commonConfig.devopsDefaultLocaleLanguage}.yaml" ) val inputStream = classPathResource.inputStream val yamlStr = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } @@ -151,7 +156,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( this.title = wechatTemplate.title this.sender = wechatTemplate.sender this.creator = template.creator - this.modifior = template.modifier + this.modifior = template.modifior this.createTime = LocalDateTime.now() this.updateTime = LocalDateTime.now() } @@ -164,7 +169,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( this.body = weworkGroupTemplate.body this.title = weworkGroupTemplate.title this.creator = template.creator - this.modifior = template.modifier + this.modifior = template.modifior this.createTime = LocalDateTime.now() this.updateTime = LocalDateTime.now() } @@ -178,7 +183,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( this.title = weworkTemplate.title this.sender = weworkTemplate.sender this.creator = template.creator - this.modifior = template.modifier + this.modifior = template.modifior this.createTime = LocalDateTime.now() this.updateTime = LocalDateTime.now() } @@ -193,7 +198,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( this.bodyFormat = emailTemplate.bodyFormat?.getValue()?.toByte() this.emailType = emailTemplate.emailType?.getValue()?.toByte() this.creator = template.creator - this.modifior = template.modifier + this.modifior = template.modifior this.createTime = LocalDateTime.now() this.updateTime = LocalDateTime.now() } diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/environment/ApigwEnvironmentResourceV4.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/environment/ApigwEnvironmentResourceV4.kt index c8e194ef7e4..5a4adb2dd1c 100644 --- a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/environment/ApigwEnvironmentResourceV4.kt +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/environment/ApigwEnvironmentResourceV4.kt @@ -359,4 +359,22 @@ interface ApigwEnvironmentResourceV4 { @ApiParam(value = "共享的项目列表", required = true) sharedProjects: SharedProjectInfoWrap ): Result + + @ApiOperation("指定构建环境获取所有节点信息", tags = ["v4_user_third_party_env2nodes", "v4_app_third_party_env2nodes"]) + @GET + @Path("/third_party_env2nodes") + fun thirdPartyEnv2Nodes( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID(项目英文名)", required = true) + @PathParam("projectId") + projectId: String, + @ApiParam("环境 hashId (envHashId和envName选填一项)", required = false) + @QueryParam("envHashId") + envHashId: String?, + @ApiParam("环境名称 (envHashId和envName选填一项)", required = false) + @QueryParam("envName") + envName: String? + ): Result> } diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/utils/markdown/Table.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/utils/markdown/Table.kt index 71f5891f124..b9804c4fee7 100644 --- a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/utils/markdown/Table.kt +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/utils/markdown/Table.kt @@ -1,6 +1,6 @@ package com.tencent.devops.openapi.utils.markdown -import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.api.util.MessageUtil import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_NO_SUCH_PARAMETER import com.tencent.devops.openapi.utils.markdown.MarkdownCharacter.FILL import com.tencent.devops.openapi.utils.markdown.MarkdownCharacter.MIN_FILL @@ -20,7 +20,7 @@ class Table( val body = StringBuffer() if (rows.isEmpty()) { body.append( - Text(6, I18nUtil.getCodeLanMessage(BK_NO_SUCH_PARAMETER), "") + Text(6, MessageUtil.getMessageByLocale(BK_NO_SUCH_PARAMETER, "zh_CN"), "") ) return body.toString() } diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/environment/ApigwEnvironmentResourceV4Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/environment/ApigwEnvironmentResourceV4Impl.kt index c37c6b30818..ebbaa79aa9b 100644 --- a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/environment/ApigwEnvironmentResourceV4Impl.kt +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/environment/ApigwEnvironmentResourceV4Impl.kt @@ -208,6 +208,16 @@ class ApigwEnvironmentResourceV4Impl @Autowired constructor( return client.get(ServiceEnvironmentResource::class).setShareEnv(userId, projectId, envHashId, sharedProjects) } + override fun thirdPartyEnv2Nodes( + userId: String, + projectId: String, + envHashId: String?, + envName: String? + ): Result> { + logger.info("OPENAPI_ENVIRONMENT_V4|$userId|third party env2nodes|$projectId|$envHashId|$envName") + return client.get(ServiceNodeResource::class).thirdPartyEnv2Nodes(userId, projectId, envHashId, envName) + } + companion object { private val logger = LoggerFactory.getLogger(ApigwEnvironmentResourceV4Impl::class.java) } diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt index aab2e6dc337..6fbab9689cc 100644 --- a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt @@ -30,7 +30,7 @@ import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.util.FileUtil import com.tencent.devops.common.api.util.JsonUtil -import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.api.util.MessageUtil import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_ALL_MODEL_DATA import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_APPLICATION_STATE_REQUIRED import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_BODY_PARAMETER @@ -827,8 +827,9 @@ class DocumentService { } private fun getI18n(code: String, params: Array? = null): String { - return I18nUtil.getCodeLanMessage( + return MessageUtil.getMessageByLocale( messageCode = code, + language = "zh_CN", params = params ) } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResource.kt index 8e85172d6f7..7af0b2b71cb 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResource.kt @@ -82,6 +82,12 @@ interface OpPipelineSettingResource { @POST @Path("/updateMaxConRunningQueueSize") fun updateMaxConRunningQueueSize( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID", required = true) + @QueryParam("projectId") + projectId: String, @ApiParam("流水线id", required = true) @QueryParam("pipelineId") pipelineId: String, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt index 0a51a2c345a..4fdfd8ada15 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt @@ -279,6 +279,25 @@ interface UserBuildResource { buildNo: Int ): Result + @ApiOperation("获取构建详情") + @GET + // @Path("/projects/{projectId}/pipelines/{pipelineId}/buildNo/{buildNo}/detail") + @Path("/projects/{projectId}/pipelines/{pipelineId}/record/{buildNum}") + fun getBuildRecordByBuildNum( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID", required = true) + @PathParam("projectId") + projectId: String, + @ApiParam("流水线ID", required = true) + @PathParam("pipelineId") + pipelineId: String, + @ApiParam("构建序号(buildNum)", required = true) + @PathParam("buildNum") + buildNum: Int + ): Result + @ApiOperation("获取已完成的最新构建详情") @GET // @Path("/projects/{projectId}/pipelines/{pipelineId}/latestFinished") diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt index a36d04e091c..4479c8908cd 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt @@ -84,7 +84,7 @@ import java.time.LocalDateTime /** * 启动流水线上下文类,属于非线程安全类 */ -@Suppress("ComplexMethod") +@Suppress("ComplexMethod", "LongParameterList") data class StartBuildContext( val now: LocalDateTime = LocalDateTime.now(), val projectId: String, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordContainer.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordContainer.kt index cda8887e009..a67fe19307e 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordContainer.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordContainer.kt @@ -28,7 +28,6 @@ package com.tencent.devops.process.pojo.pipeline.record import com.tencent.devops.common.pipeline.container.Container -import com.tencent.devops.common.pipeline.container.Stage import com.tencent.devops.common.pipeline.container.TriggerContainer import com.tencent.devops.common.pipeline.container.VMBuildContainer import com.tencent.devops.common.pipeline.enums.BuildRecordTimeStamp @@ -39,7 +38,7 @@ import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty import java.time.LocalDateTime -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LongMethod") @ApiModel("构建详情记录-插件任务") data class BuildRecordContainer( @ApiModelProperty("构建ID", required = true) @@ -77,12 +76,13 @@ data class BuildRecordContainer( ) { companion object { + @Suppress("ComplexMethod") fun MutableList.addRecords( - stage: Stage, + stageId: String, container: Container, context: StartBuildContext, buildStatus: BuildStatus?, - taskBuildRecords: MutableList + taskBuildRecords: MutableList? ) { val containerVar = mutableMapOf() containerVar[Container::name.name] = container.name @@ -108,7 +108,7 @@ data class BuildRecordContainer( pipelineId = context.pipelineId, resourceVersion = context.resourceVersion, buildId = context.buildId, - stageId = stage.id!!, + stageId = stageId, containerId = container.containerId!!, containerType = container.getClassType(), executeCount = context.executeCount, @@ -118,13 +118,14 @@ data class BuildRecordContainer( timestamps = mapOf() ) ) + if (taskBuildRecords == null) return container.elements.forEachIndexed { index, element -> taskBuildRecords.add( BuildRecordTask( projectId = context.projectId, pipelineId = context.pipelineId, buildId = context.buildId, - stageId = stage.id!!, + stageId = stageId, containerId = container.containerId!!, taskId = element.id!!, classType = element.getClassType(), @@ -134,7 +135,10 @@ data class BuildRecordContainer( taskSeq = index, status = buildStatus?.name, taskVar = mutableMapOf(), - timestamps = mapOf() + timestamps = mapOf(), + elementPostInfo = element.additionalOptions?.elementPostInfo?.takeIf { info -> + info.parentElementId != element.id + } ) ) } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordStage.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordStage.kt index bd1b29cb5c1..e05685099d8 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordStage.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordStage.kt @@ -38,6 +38,7 @@ import io.swagger.annotations.ApiModelProperty import java.time.LocalDateTime @ApiModel("构建详情记录-插件任务") +@Suppress("LongParameterList") data class BuildRecordStage( @ApiModelProperty("构建ID", required = true) val buildId: String, @@ -89,7 +90,7 @@ data class BuildRecordStage( ) stage.containers.forEach { container -> containerBuildRecords.addRecords( - stage = stage, + stageId = stage.id!!, container = container, context = context, buildStatus = buildStatus, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt index b5acb79c962..8c00655f7fa 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt @@ -28,7 +28,10 @@ package com.tencent.devops.process.pojo.pipeline.record import com.tencent.devops.common.pipeline.enums.BuildRecordTimeStamp +import com.tencent.devops.common.pipeline.enums.EnvControlTaskType +import com.tencent.devops.common.pipeline.pojo.element.ElementPostInfo import com.tencent.devops.common.pipeline.pojo.time.BuildTimestampType +import com.tencent.devops.process.engine.pojo.PipelineBuildTask import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty import java.time.LocalDateTime @@ -55,6 +58,8 @@ data class BuildRecordTask( val executeCount: Int, @ApiModelProperty("执行变量", required = true) var taskVar: MutableMap, + @ApiModelProperty("插件post信息", required = false) + val elementPostInfo: ElementPostInfo? = null, @ApiModelProperty("插件类型标识", required = true) val classType: String, @ApiModelProperty("市场插件标识", required = true) @@ -69,4 +74,28 @@ data class BuildRecordTask( var endTime: LocalDateTime? = null, @ApiModelProperty("业务时间戳集合", required = true) var timestamps: Map -) +) { + companion object { + fun MutableList.addRecords( + buildTaskList: MutableList, + resourceVersion: Int + ) { + buildTaskList.forEach { + // 自动填充的构建机控制插件,不需要存入Record + if (EnvControlTaskType.parse(it.taskType) != null) return@forEach + this.add( + BuildRecordTask( + projectId = it.projectId, pipelineId = it.pipelineId, buildId = it.buildId, + stageId = it.stageId, containerId = it.containerId, taskSeq = it.taskSeq, + taskId = it.taskId, classType = it.taskType, atomCode = it.atomCode ?: it.taskAtom, + executeCount = it.executeCount ?: 1, resourceVersion = resourceVersion, + taskVar = mutableMapOf(), timestamps = mapOf(), + elementPostInfo = it.additionalOptions?.elementPostInfo?.takeIf { info -> + info.parentElementId != it.taskId + } + ) + ) + } + } + } +} diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/utils/Constants.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/utils/Constants.kt index ee1577efa43..10e85e2531e 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/utils/Constants.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/utils/Constants.kt @@ -136,7 +136,7 @@ const val PIPELINE_SETTING_MAX_QUEUE_SIZE_MIN = 0 /** * 流水线设置-最大排队数量-最大值 */ -const val PIPELINE_SETTING_MAX_QUEUE_SIZE_MAX = 20 +const val PIPELINE_SETTING_MAX_QUEUE_SIZE_MAX = 200 /** * 流水线设置-最大并发数量-默认值 diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingDao.kt index 63cbc941264..9dd9b7f40b1 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingDao.kt @@ -252,6 +252,10 @@ class PipelineSettingDao { } } + fun batchUpdate(dslContext: DSLContext, tPipelineSettingRecords: List) { + dslContext.batchUpdate(tPipelineSettingRecords).execute() + } + /** * 获取简单的数据(避免select大字段) * @@ -348,13 +352,15 @@ class PipelineSettingDao { fun updateMaxConRunningQueueSize( dslContext: DSLContext, - pipelineIdList: List, + projectId: String, + pipelineId: String, maxConRunningQueueSize: Int ): Int { with(TPipelineSetting.T_PIPELINE_SETTING) { return dslContext.update(this) .set(MAX_CON_RUNNING_QUEUE_SIZE, maxConRunningQueueSize) - .where(PIPELINE_ID.`in`(pipelineIdList)) + .where(PIPELINE_ID.eq(pipelineId)) + .and(PROJECT_ID.eq(projectId)) .execute() } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingVersionDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingVersionDao.kt index 91aec855d8d..b6ea3ee4a04 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingVersionDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineSettingVersionDao.kt @@ -34,6 +34,7 @@ import com.tencent.devops.process.pojo.setting.PipelineSetting import com.tencent.devops.process.util.NotifyTemplateUtils import com.tencent.devops.process.utils.PIPELINE_START_USER_NAME import org.jooq.DSLContext +import org.jooq.Result import org.springframework.stereotype.Repository @Suppress("LongParameterList") @@ -157,6 +158,21 @@ class PipelineSettingVersionDao { } } + fun getSettingByPipelineIds( + dslContext: DSLContext, + pipelineIds: List + ): Result { + with(TPipelineSettingVersion.T_PIPELINE_SETTING_VERSION) { + return dslContext.selectFrom(this) + .where(PIPELINE_ID.`in`(pipelineIds)) + .fetch() + } + } + + fun batchUpdate(dslContext: DSLContext, tPipelineSettingVersionRecords: List) { + dslContext.batchUpdate(tPipelineSettingVersionRecords).execute() + } + fun deleteAllVersion(dslContext: DSLContext, projectId: String, pipelineId: String): Int { with(TPipelineSettingVersion.T_PIPELINE_SETTING_VERSION) { return dslContext.deleteFrom(this) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt index 492601d95b4..8e599a44f79 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt @@ -31,6 +31,7 @@ import com.fasterxml.jackson.core.type.TypeReference import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.pipeline.enums.BuildRecordTimeStamp import com.tencent.devops.common.pipeline.enums.BuildStatus +import com.tencent.devops.common.pipeline.pojo.element.ElementPostInfo import com.tencent.devops.common.pipeline.pojo.time.BuildTimestampType import com.tencent.devops.model.process.tables.TPipelineBuildRecordTask import com.tencent.devops.model.process.tables.records.TPipelineBuildRecordTaskRecord @@ -39,7 +40,7 @@ import com.tencent.devops.process.pojo.KEY_TASK_ID import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask import org.jooq.Condition import org.jooq.DSLContext -import org.jooq.Record17 +import org.jooq.Record18 import org.jooq.RecordMapper import org.jooq.impl.DSL import org.jooq.util.mysql.MySQLDSL @@ -69,7 +70,8 @@ class BuildRecordTaskDao { STATUS, TASK_SEQ, ATOM_CODE, - TIMESTAMPS + TIMESTAMPS, + POST_INFO ).also { insert -> records.forEach { record -> insert.values( @@ -87,7 +89,8 @@ class BuildRecordTaskDao { record.status, record.taskSeq, record.atomCode, - JsonUtil.toJson(record.timestamps, false) + JsonUtil.toJson(record.timestamps, false), + record.elementPostInfo?.let { JsonUtil.toJson(it, false) } ) } }.onDuplicateKeyUpdate() @@ -200,7 +203,7 @@ class BuildRecordTaskDao { val result = dslContext.select( BUILD_ID, PROJECT_ID, PIPELINE_ID, RESOURCE_VERSION, STAGE_ID, CONTAINER_ID, TASK_ID, TASK_SEQ, EXECUTE_COUNT, TASK_VAR, CLASS_TYPE, ATOM_CODE, STATUS, ORIGIN_CLASS_TYPE, - START_TIME, END_TIME, TIMESTAMPS + START_TIME, END_TIME, TIMESTAMPS, POST_INFO ).from(this).join(max).on( TASK_ID.eq(max.field(KEY_TASK_ID, String::class.java)) .and(EXECUTE_COUNT.eq(max.field(KEY_EXECUTE_COUNT, Int::class.java))) @@ -227,7 +230,7 @@ class BuildRecordTaskDao { val result = dslContext.select( BUILD_ID, PROJECT_ID, PIPELINE_ID, RESOURCE_VERSION, STAGE_ID, CONTAINER_ID, TASK_ID, TASK_SEQ, EXECUTE_COUNT, TASK_VAR, CLASS_TYPE, ATOM_CODE, STATUS, ORIGIN_CLASS_TYPE, - START_TIME, END_TIME, TIMESTAMPS + START_TIME, END_TIME, TIMESTAMPS, POST_INFO ).from(this).where(conditions).orderBy(TASK_SEQ.asc()).fetch() return result.map { record -> generateBuildRecordTask(record) @@ -236,8 +239,9 @@ class BuildRecordTaskDao { } private fun TPipelineBuildRecordTask.generateBuildRecordTask( - record: Record17 + record: Record18 ) = BuildRecordTask( buildId = record[BUILD_ID], @@ -260,7 +264,10 @@ class BuildRecordTaskDao { endTime = record[END_TIME], timestamps = record[TIMESTAMPS]?.let { JsonUtil.to(it, object : TypeReference>() {}) - } ?: mapOf() + } ?: mapOf(), + elementPostInfo = record[POST_INFO]?.let { + JsonUtil.to(it, object : TypeReference() {}) + } ) fun getRecord( @@ -305,7 +312,10 @@ class BuildRecordTaskDao { endTime = endTime, timestamps = timestamps?.let { JsonUtil.to(it, object : TypeReference>() {}) - } ?: mapOf() + } ?: mapOf(), + elementPostInfo = postInfo?.let { + JsonUtil.to(it, object : TypeReference() {}) + } ) } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt index f7e5ba05422..a1215d02bcf 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt @@ -436,7 +436,7 @@ class PipelineBuildDao { /** * 取最近一次失败的构建 */ - fun getLatestSuccessedBuild( + fun getLatestSucceedBuild( dslContext: DSLContext, projectId: String, pipelineId: String @@ -478,6 +478,21 @@ class PipelineBuildDao { } } + fun updateExecuteCount( + dslContext: DSLContext, + projectId: String, + buildId: String, + executeCount: Int + ): Boolean { + with(T_PIPELINE_BUILD_HISTORY) { + return dslContext.update(this) + .set(EXECUTE_COUNT, executeCount) + .where(PROJECT_ID.eq(projectId)) + .and(BUILD_ID.eq(buildId)) + .execute() == 1 + } + } + fun convert(t: TPipelineBuildHistoryRecord?): BuildInfo? { return if (t == null) { null @@ -822,17 +837,17 @@ class PipelineBuildDao { } } - fun getBuildByBuildNo( + fun getBuildByBuildNum( dslContext: DSLContext, projectId: String, pipelineId: String, - buildNo: Int + buildNum: Int ): TPipelineBuildHistoryRecord? { return with(T_PIPELINE_BUILD_HISTORY) { dslContext.selectFrom(this) .where(PROJECT_ID.eq(projectId)) .and(PIPELINE_ID.eq(pipelineId)) - .and(BUILD_NUM.eq(buildNo)) + .and(BUILD_NUM.eq(buildNum)) .fetchAny() } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildSummaryDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildSummaryDao.kt index d0ca424bca8..0119c9928e8 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildSummaryDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildSummaryDao.kt @@ -471,9 +471,15 @@ class PipelineBuildSummaryDao { dslContext.update(this) .let { if (executeCount == 1) { - // 只有首次才写入LATEST_BUILD_ID和LATEST_STATUS,重试不写入 + // 只有首次才写入LATEST_BUILD_ID it.set(LATEST_BUILD_ID, buildId).set(LATEST_STATUS, status.ordinal) - } else it + } else { + // 重试时只有最新的构建才能刷新LATEST_STATUS + it.set( + LATEST_STATUS, + DSL.`when`(LATEST_BUILD_ID.eq(buildId), status.ordinal).otherwise(LATEST_STATUS) + ) + } } .set(LATEST_TASK_COUNT, taskCount) .set(LATEST_START_USER, userId) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt index eca02159112..32e38e52faf 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt @@ -329,6 +329,7 @@ class PipelineBuildTaskDao { val taskId = updateTaskInfo.taskId val baseStep = dslContext.update(this) .set(STATUS, updateTaskInfo.taskStatus.ordinal) + .set(EXECUTE_COUNT, updateTaskInfo.executeCount) updateTaskInfo.starter?.let { baseStep.set(STARTER, it) } updateTaskInfo.approver?.let { baseStep.set(APPROVER, it) } updateTaskInfo.startTime?.let { baseStep.set(START_TIME, it) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineInfoDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineInfoDao.kt index 4676e74a9ac..a36453ffd86 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineInfoDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineInfoDao.kt @@ -34,6 +34,7 @@ import com.tencent.devops.model.process.tables.records.TPipelineInfoRecord import com.tencent.devops.process.engine.pojo.PipelineInfo import com.tencent.devops.process.pojo.PipelineCollation import com.tencent.devops.process.pojo.PipelineSortType +import java.time.LocalDateTime import org.jooq.Condition import org.jooq.DSLContext import org.jooq.Field @@ -45,7 +46,6 @@ import org.jooq.SortField import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository -import java.time.LocalDateTime @Suppress("TooManyFunctions", "LongParameterList") @Repository @@ -735,6 +735,23 @@ class PipelineInfoDao { } } + fun getIdByCreateTimePeriod( + dslContext: DSLContext, + startTime: LocalDateTime, + endTime: LocalDateTime, + page: Int, + pageSize: Int + ): List { + with(T_PIPELINE_INFO) { + return dslContext.select(PIPELINE_ID) + .from(this) + .where(CREATE_TIME.between(startTime, endTime)) + .orderBy(CREATE_TIME, PIPELINE_ID) + .limit((page - 1) * pageSize, pageSize) + .fetchInto(String::class.java) + } + } + companion object { private val logger = LoggerFactory.getLogger(PipelineInfoDao::class.java) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/UpdateTaskInfo.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/UpdateTaskInfo.kt index 02447ed3765..a1ea823605d 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/UpdateTaskInfo.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/UpdateTaskInfo.kt @@ -36,6 +36,7 @@ data class UpdateTaskInfo( val projectId: String, // 项目ID val buildId: String, // 构建ID val taskId: String, // 任务ID + val executeCount: Int, val taskStatus: BuildStatus, // 构建状态 var starter: String? = null, // 启动人 var approver: String? = null, // 审批人 diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildQualityService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildQualityService.kt index 0cb33385585..c85c48630ee 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildQualityService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildQualityService.kt @@ -427,8 +427,10 @@ class PipelineBuildQualityService( ) taskBuildRecordService.updateTaskRecord( projectId = projectId, pipelineId = pipelineId, buildId = buildId, - taskId = taskId, executeCount = task.executeCount ?: 1, buildStatus = null, + taskId = taskId, executeCount = task.executeCount ?: 1, + buildStatus = null, taskVar = mapOf(QualityGateInElement::reviewUsers.name to auditUsers), + operation = "handleQualityResult#$taskId", timestamps = mapOf( BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to BuildRecordTimeStamp(LocalDateTime.now().timestampmilli(), null) @@ -457,7 +459,7 @@ class PipelineBuildQualityService( pipelineId = task.pipelineId, userId = task.starter, buildId = task.buildId, - refreshTypes = RefreshType.DETAIL.binary + refreshTypes = RefreshType.DETAIL.binary or RefreshType.RECORD.binary ) ) return AtomResponse(BuildStatus.RUNNING) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt index 9e0b04819f2..b04f71a973f 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt @@ -307,8 +307,7 @@ class PipelineContainerService @Autowired constructor( } } context.taskCount++ - addBuildTaskToList( - buildTaskList = buildTaskList, + val buildTask = genBuildTaskToList( projectId = projectId, pipelineId = pipelineId, buildId = buildId, @@ -321,6 +320,7 @@ class PipelineContainerService @Autowired constructor( executeCount = context.executeCount, postParentIdMap = postParentIdMap ) + buildTaskList.add(buildTask) resourceVersion?.let { recordTaskList.add( BuildRecordTask( @@ -337,6 +337,7 @@ class PipelineContainerService @Autowired constructor( originClassType = atomElement.getClassType(), resourceVersion = resourceVersion, timestamps = mapOf(), + elementPostInfo = buildTask.additionalOptions?.elementPostInfo, // 对矩阵产生的插件特殊表示类型 taskVar = mutableMapOf( "@type" to MatrixStatusElement.classType, @@ -433,7 +434,10 @@ class PipelineContainerService @Autowired constructor( taskId = atomElement.id!!, classType = atomElement.getClassType(), atomCode = atomElement.getTaskAtom(), executeCount = context.executeCount, resourceVersion = context.resourceVersion, taskSeq = taskSeq, status = status.name, - taskVar = mutableMapOf(), timestamps = mapOf() + taskVar = mutableMapOf(), timestamps = mapOf(), + elementPostInfo = atomElement.additionalOptions?.elementPostInfo?.takeIf { info -> + info.parentElementId != atomElement.id + } ) ) return@nextElement @@ -443,8 +447,7 @@ class PipelineContainerService @Autowired constructor( if (retryFlag) { if (container.matrixGroupFlag != true) { context.taskCount++ - addBuildTaskToList( - buildTaskList = buildTaskList, + val buildTask = genBuildTaskToList( projectId = context.projectId, pipelineId = context.pipelineId, buildId = context.buildId, @@ -457,6 +460,7 @@ class PipelineContainerService @Autowired constructor( executeCount = context.executeCount, postParentIdMap = emptyMap() ) + buildTaskList.add(buildTask) } needUpdateContainer = true } else { @@ -480,13 +484,16 @@ class PipelineContainerService @Autowired constructor( } // Rebuild/Stage-Retry/Fail-Task-Retry 重跑/Stage重试/失败的插件重试 + val skipWhenFailed = context.inSkipStage(stage, atomElement) + val buildStatus = if (skipWhenFailed) BuildStatus.SKIP else null + val recordStatus = if (skipWhenFailed) BuildStatus.FAILED.name else null val taskRecord = retryDetailModelStatus( lastTimeBuildTasks = lastTimeBuildTasks, container = container, retryStartTaskId = atomElement.id!!, executeCount = context.executeCount, atomElement = atomElement, // #4245 将失败跳过的插件置为跳过 - initialStatus = if (context.inSkipStage(stage, atomElement)) BuildStatus.SKIP else null + initialStatus = buildStatus ) if (taskRecord != null) { @@ -506,6 +513,21 @@ class PipelineContainerService @Autowired constructor( updateExistsTask.add(pair.first) } } + // #8955 针对被跳过或重试插件单独保留原状态 + taskBuildRecords.add( + BuildRecordTask( + projectId = context.projectId, pipelineId = context.pipelineId, + buildId = context.buildId, stageId = taskRecord.stageId, + containerId = taskRecord.containerId, taskSeq = taskRecord.taskSeq, + taskId = taskRecord.taskId, classType = taskRecord.taskType, + atomCode = taskRecord.atomCode ?: taskRecord.taskAtom, timestamps = mapOf(), + executeCount = taskRecord.executeCount ?: 1, taskVar = mutableMapOf(), + status = recordStatus, resourceVersion = context.resourceVersion, + elementPostInfo = taskRecord.additionalOptions?.elementPostInfo?.takeIf { info -> + info.parentElementId != taskRecord.taskId + } + ) + ) needUpdateContainer = true } else if (container.matrixGroupFlag == true && BuildStatus.parse(container.status).isFinish()) { // 构建矩阵没有对应的重试插件,单独进行重试判断 @@ -826,8 +848,7 @@ class PipelineContainerService @Autowired constructor( return -1 } - private fun addBuildTaskToList( - buildTaskList: MutableList, + private fun genBuildTaskToList( projectId: String, pipelineId: String, buildId: String, @@ -839,41 +860,37 @@ class PipelineContainerService @Autowired constructor( status: BuildStatus, executeCount: Int, postParentIdMap: Map - ) { - buildTaskList.add( - PipelineBuildTask( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - stageId = stage.id!!, - containerId = container.id!!, - containerHashId = container.containerHashId ?: "", - containerType = container.getClassType(), - taskSeq = taskSeq, - taskId = atomElement.id!!, - taskName = atomElement.name.coerceAtMaxLength(ELEMENT_NAME_MAX_LENGTH), - taskType = atomElement.getClassType(), - taskAtom = atomElement.getTaskAtom(), - status = status, - taskParams = atomElement.genTaskParams(), - // 由于遍历时对内部属性有修改,需要复制一个新对象赋值 - additionalOptions = postParentIdMap[atomElement.id]?.let { self -> - atomElement.additionalOptions?.copy( - elementPostInfo = atomElement.additionalOptions?.elementPostInfo?.copy( - parentElementId = self - ) - ) - } ?: atomElement.additionalOptions, - executeCount = executeCount, - starter = userId, - approver = null, - subProjectId = null, - subBuildId = null, - atomCode = atomElement.getAtomCode(), - stepId = atomElement.stepId + ) = PipelineBuildTask( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + stageId = stage.id!!, + containerId = container.id!!, + containerHashId = container.containerHashId ?: "", + containerType = container.getClassType(), + taskSeq = taskSeq, + taskId = atomElement.id!!, + taskName = atomElement.name.coerceAtMaxLength(ELEMENT_NAME_MAX_LENGTH), + taskType = atomElement.getClassType(), + taskAtom = atomElement.getTaskAtom(), + status = status, + taskParams = atomElement.genTaskParams(), + // 由于遍历时对内部属性有修改,需要复制一个新对象赋值 + additionalOptions = postParentIdMap[atomElement.id]?.let { self -> + atomElement.additionalOptions?.copy( + elementPostInfo = atomElement.additionalOptions?.elementPostInfo?.copy( + parentElementId = self + ) ) - ) - } + } ?: atomElement.additionalOptions, + executeCount = executeCount, + starter = userId, + approver = null, + subProjectId = null, + subBuildId = null, + atomCode = atomElement.getAtomCode(), + stepId = atomElement.stepId + ) fun setUpTriggerContainer( stage: Stage, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineElementService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineElementService.kt index cdbbd6ef262..2bd5fc306b4 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineElementService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineElementService.kt @@ -27,6 +27,7 @@ package com.tencent.devops.process.engine.service +import com.tencent.devops.common.api.util.Watcher import com.tencent.devops.common.api.util.timestamp import com.tencent.devops.common.client.Client import com.tencent.devops.common.pipeline.Model @@ -70,11 +71,14 @@ class PipelineElementService @Autowired constructor( startParamsMap: MutableMap? = null, handlePostFlag: Boolean = true ) { + val watcher = Watcher(id = "fillElementWhenNewBuild#$pipelineId") + watcher.start("getTemplateIdByPipeline") val templateId = if (model.instanceFromTemplate == true) { templateService.getTemplateIdByPipeline(projectId, pipelineId) } else { null } + watcher.start("getMatchRuleList") val ruleMatchList = getMatchRuleList(projectId, pipelineId, templateId) val qualityRuleFlag = ruleMatchList.isNotEmpty() var beforeElementSet: List? = null @@ -87,6 +91,7 @@ class PipelineElementService @Autowired constructor( elementRuleMap = triple.third } val qaSet = setOf(QualityGateInElement.classType, QualityGateOutElement.classType) + watcher.start("fillElement") model.stages.forEachIndexed { index, stage -> if (index == 0) { return@forEachIndexed @@ -170,6 +175,7 @@ class PipelineElementService @Autowired constructor( container.elements = finalElementList } } + watcher.stop() } fun getMatchRuleList(projectId: String, pipelineId: String, templateId: String?): List { diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelinePostElementService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelinePostElementService.kt index 8651483aaa2..53123285372 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelinePostElementService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelinePostElementService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.process.engine.service import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.api.util.JsonUtil.deepCopy import com.tencent.devops.common.client.Client import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.pojo.element.Element @@ -203,33 +204,22 @@ class PipelinePostElementService @Autowired constructor( if (originAtomElement.name.length > 128) originAtomElement.name.substring(0, 128) else originAtomElement.name val postCondition = elementPostInfo.postCondition - var postAtomRunCondition = RunCondition.PRE_TASK_SUCCESS - when (postCondition) { - "failed()" -> { - postAtomRunCondition = RunCondition.PRE_TASK_FAILED_ONLY - } - "always()" -> { - postAtomRunCondition = RunCondition.PRE_TASK_FAILED_BUT_CANCEL - } - "canceledOrTimeOut()" -> { - postAtomRunCondition = RunCondition.PARENT_TASK_CANCELED_OR_TIMEOUT - } + val postAtomRunCondition = getPostAtomRunCondition(postCondition) + val additionalOptions = originAtomElement.additionalOptions?.deepCopy() + additionalOptions?.let { + additionalOptions.enable = true + additionalOptions.continueWhenFailed = true + additionalOptions.retryWhenFailed = false + additionalOptions.runCondition = postAtomRunCondition + additionalOptions.pauseBeforeExec = null + additionalOptions.subscriptionPauseUser = null + additionalOptions.retryCount = 0 + additionalOptions.otherTask = null + additionalOptions.customCondition = null + additionalOptions.elementPostInfo = elementPostInfo } - val additionalOptions = ElementAdditionalOptions( - enable = true, - continueWhenFailed = true, - retryWhenFailed = false, - runCondition = postAtomRunCondition, - pauseBeforeExec = null, - subscriptionPauseUser = null, - customVariables = originAtomElement.additionalOptions?.customVariables, - retryCount = 0, - otherTask = null, - customCondition = null, - elementPostInfo = elementPostInfo - ) // 生成post操作的element - val postElementName = "$postPrompt$elementName" + val postElementName = getPostElementName(elementName) if (originAtomElement is MarketBuildAtomElement) { val marketBuildAtomElement = MarketBuildAtomElement( name = postElementName, @@ -254,4 +244,26 @@ class PipelinePostElementService @Autowired constructor( finalElementList.add(marketBuildLessAtomElement) } } + + fun getPostElementName(elementName: String): String { + return "$postPrompt$elementName" + } + + fun getPostAtomRunCondition(postCondition: String): RunCondition { + var postAtomRunCondition = RunCondition.PRE_TASK_SUCCESS + when (postCondition) { + "failed()" -> { + postAtomRunCondition = RunCondition.PRE_TASK_FAILED_ONLY + } + + "always()" -> { + postAtomRunCondition = RunCondition.PRE_TASK_FAILED_BUT_CANCEL + } + + "canceledOrTimeOut()" -> { + postAtomRunCondition = RunCondition.PARENT_TASK_CANCELED_OR_TIMEOUT + } + } + return postAtomRunCondition + } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt index 7ce5bb164ee..5fe32d9a445 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt @@ -89,13 +89,13 @@ import com.tencent.devops.process.pojo.setting.Subscription import com.tencent.devops.process.utils.PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX import com.tencent.devops.project.api.service.ServiceAllocIdResource import com.tencent.devops.project.api.service.ServiceProjectResource -import java.util.concurrent.atomic.AtomicInteger -import javax.ws.rs.core.Response import org.joda.time.LocalDateTime import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import java.util.concurrent.atomic.AtomicInteger +import javax.ws.rs.core.Response @Suppress( "LongParameterList", @@ -450,9 +450,9 @@ class PipelineRepositoryService constructor( if ((option.maxConcurrency ?: 0) > PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX) { throw InvalidParamException( "matrix maxConcurrency number(${option.maxConcurrency}) " + - "exceed $PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX /" + - "matrix maxConcurrency(${option.maxConcurrency}) " + - "is larger than $PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX" + "exceed $PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX /" + + "matrix maxConcurrency(${option.maxConcurrency}) " + + "is larger than $PIPELINE_MATRIX_CON_RUNNING_SIZE_MAX" ) } MatrixContextUtils.schemaCheck( @@ -1279,10 +1279,16 @@ class PipelineRepositoryService constructor( } } - fun updateMaxConRunningQueueSize(pipelineId: String, maxConRunningQueueSize: Int): Int { + fun updateMaxConRunningQueueSize( + userId: String, + projectId: String, + pipelineId: String, + maxConRunningQueueSize: Int + ): Int { return pipelineSettingDao.updateMaxConRunningQueueSize( dslContext = dslContext, - pipelineIdList = listOf(pipelineId), + projectId = projectId, + pipelineId = pipelineId, maxConRunningQueueSize = maxConRunningQueueSize ) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt index 624fadbc929..de03562c9e0 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt @@ -123,6 +123,7 @@ import com.tencent.devops.process.pojo.pipeline.record.BuildRecordModel import com.tencent.devops.process.pojo.pipeline.record.BuildRecordStage import com.tencent.devops.process.pojo.pipeline.record.BuildRecordStage.Companion.addRecords import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask +import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask.Companion.addRecords import com.tencent.devops.process.service.BuildVariableService import com.tencent.devops.process.service.StageTagService import com.tencent.devops.process.util.BuildMsgUtils @@ -725,7 +726,7 @@ class PipelineRuntimeService @Autowired constructor( ) context.containerSeq++ containerBuildRecords.addRecords( - stage = stage, + stageId = stage.id!!, container = container, context = context, buildStatus = null, @@ -736,7 +737,7 @@ class PipelineRuntimeService @Autowired constructor( if (!ContainerUtils.isNormalContainerEnable(container)) { context.containerSeq++ containerBuildRecords.addRecords( - stage = stage, + stageId = stage.id!!, container = container, context = context, buildStatus = BuildStatus.SKIP, @@ -748,7 +749,7 @@ class PipelineRuntimeService @Autowired constructor( if (!ContainerUtils.isVMBuildContainerEnable(container)) { context.containerSeq++ containerBuildRecords.addRecords( - stage = stage, + stageId = stage.id!!, container = container, context = context, buildStatus = BuildStatus.SKIP, @@ -1079,15 +1080,17 @@ class PipelineRuntimeService @Autowired constructor( BuildRecordTimeStamp(context.now.timestampmilli(), null) ), queueTime = context.now ) - + // #8955 针对单独写入的插件记录可以覆盖根据build数据生成的记录 + val taskBuildRecordResult = mutableListOf() if (updateExistsTask.isNotEmpty()) { pipelineTaskService.batchUpdate(transactionContext, updateExistsTask) - saveTaskRecords(updateExistsTask, taskBuildRecords, context.resourceVersion) + taskBuildRecordResult.addRecords(updateExistsTask, context.resourceVersion) } if (buildTaskList.isNotEmpty()) { pipelineTaskService.batchSave(transactionContext, buildTaskList) - saveTaskRecords(buildTaskList, taskBuildRecords, context.resourceVersion) + taskBuildRecordResult.addRecords(buildTaskList, context.resourceVersion) } + taskBuildRecordResult.addAll(taskBuildRecords) if (updateExistsContainer.isNotEmpty()) { pipelineContainerService.batchUpdate( transactionContext, updateExistsContainer.map { it.first } @@ -1111,30 +1114,10 @@ class PipelineRuntimeService @Autowired constructor( } pipelineBuildRecordService.batchSave( transactionContext, modelRecord, stageBuildRecords, - containerBuildRecords, taskBuildRecords + containerBuildRecords, taskBuildRecordResult ) } - private fun saveTaskRecords( - buildTaskList: MutableList, - taskBuildRecords: MutableList, - resourceVersion: Int - ) { - buildTaskList.forEach { - // 自动填充的构建机控制插件,不需要存入Record - if (EnvControlTaskType.parse(it.taskType) != null) return@forEach - taskBuildRecords.add( - BuildRecordTask( - projectId = it.projectId, pipelineId = it.pipelineId, buildId = it.buildId, - stageId = it.stageId, containerId = it.containerId, taskSeq = it.taskSeq, - taskId = it.taskId, classType = it.taskType, atomCode = it.atomCode ?: it.taskAtom, - executeCount = it.executeCount ?: 1, resourceVersion = resourceVersion, - taskVar = mutableMapOf(), timestamps = mapOf() - ) - ) - } - } - private fun saveContainerRecords( buildContainers: MutableList>, containerBuildRecords: MutableList, @@ -1158,6 +1141,9 @@ class PipelineRuntimeService @Autowired constructor( containerVar[VMBuildContainer::mutexGroup.name] = it } } + if (detail.matrixGroupFlag == true) { + containerVar[Container::elements.name] = detail.elements + } containerBuildRecords.add( BuildRecordContainer( projectId = build.projectId, pipelineId = build.pipelineId, resourceVersion = resourceVersion, @@ -1314,7 +1300,7 @@ class PipelineRuntimeService @Autowired constructor( userId = userId, buildId = buildId, // 刷新历史列表和详情页面 - refreshTypes = RefreshType.DETAIL.binary + refreshTypes = RefreshType.DETAIL.binary or RefreshType.RECORD.binary ), // 广播构建排队事件 PipelineBuildQueueBroadCastEvent( source = "startQueue", @@ -1413,6 +1399,7 @@ class PipelineRuntimeService @Autowired constructor( ManualReviewUserTaskElement::suggest.name to (params.suggest ?: ""), ManualReviewUserTaskElement::params.name to params.params ), + operation = "manualDealReview#$taskId", timestamps = mapOf( BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to BuildRecordTimeStamp(null, LocalDateTime.now().timestampmilli()) @@ -1519,6 +1506,14 @@ class PipelineRuntimeService @Autowired constructor( pipelineBuildSummaryDao.updateBuildNo(dslContext, projectId, pipelineId, buildNo) } + fun updateExecuteCount( + projectId: String, + buildId: String, + executeCount: Int + ) { + pipelineBuildDao.updateExecuteCount(dslContext, projectId, buildId, executeCount) + } + /** * 开始最新一次构建 */ @@ -1567,7 +1562,10 @@ class PipelineRuntimeService @Autowired constructor( userId = latestRunningBuild.userId, buildId = latestRunningBuild.buildId, // 刷新历史列表、详情、状态页面 - refreshTypes = RefreshType.HISTORY.binary or RefreshType.DETAIL.binary or RefreshType.STATUS.binary + refreshTypes = RefreshType.HISTORY.binary or + RefreshType.DETAIL.binary or + RefreshType.STATUS.binary or + RefreshType.RECORD.binary ) ) @@ -1633,7 +1631,9 @@ class PipelineRuntimeService @Autowired constructor( buildId = buildId, // 刷新详情、状态页面 // #3400 在BuildEnd处会有HISTORY,历史列表此处不需要,减少负载 - refreshTypes = RefreshType.DETAIL.binary or RefreshType.STATUS.binary + refreshTypes = RefreshType.DETAIL.binary or + RefreshType.STATUS.binary or + RefreshType.RECORD.binary ) ) logger.info("[$pipelineId]|finishLatestRunningBuild-$buildId|status=$status") @@ -1776,7 +1776,7 @@ class PipelineRuntimeService @Autowired constructor( // 获取流水线最后成功的构建号 fun getLatestSucceededBuildId(projectId: String, pipelineId: String): String? { - return pipelineBuildDao.getLatestSuccessedBuild(dslContext, projectId, pipelineId)?.buildId + return pipelineBuildDao.getLatestSucceedBuild(dslContext, projectId, pipelineId)?.buildId } // 获取流水线最后失败的构建号 @@ -1784,12 +1784,12 @@ class PipelineRuntimeService @Autowired constructor( return pipelineBuildDao.getLatestFailedBuild(dslContext, projectId, pipelineId)?.buildId } - fun getBuildIdbyBuildNo(projectId: String, pipelineId: String, buildNo: Int): String? { - return pipelineBuildDao.getBuildByBuildNo( + fun getBuildIdByBuildNum(projectId: String, pipelineId: String, buildNum: Int): String? { + return pipelineBuildDao.getBuildByBuildNum( dslContext = dslContext, projectId = projectId, pipelineId = pipelineId, - buildNo = buildNo + buildNum = buildNum )?.buildId } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineTaskService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineTaskService.kt index 25fec935a0e..67039490aaa 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineTaskService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineTaskService.kt @@ -644,6 +644,7 @@ class PipelineTaskService @Autowired constructor( val buildId = task.buildId val taskId = task.taskId val taskName = task.taskName + val executeCount = task.executeCount ?: 1 logger.info( "${task.buildId}|UPDATE_TASK_STATUS|$taskName|$taskStatus|$userId|$errorCode" + "|opt_change=${task.additionalOptions?.change}" @@ -655,6 +656,7 @@ class PipelineTaskService @Autowired constructor( projectId = projectId, buildId = buildId, taskId = taskId, + executeCount = executeCount, taskStatus = taskStatus, errorType = errorType, errorCode = errorCode, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt index 44472f1e843..9ff2e78c3c2 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt @@ -253,7 +253,8 @@ open class BaseBuildDetailService constructor( pipelineId = pipelineBuildInfo.pipelineId, userId = pipelineBuildInfo.startUser, buildId = buildId, - refreshTypes = RefreshType.DETAIL.binary + refreshTypes = RefreshType.DETAIL.binary or RefreshType.RECORD.binary, + executeCount = pipelineBuildInfo.executeCount ) ) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt index 8515a153279..37072e5fc1c 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt @@ -176,8 +176,13 @@ open class BaseBuildRecordService( return try { watcher.start("fillElementWhenNewBuild") val fullModel = JsonUtil.to(resourceStr, Model::class.java) - // 为model填充element - pipelineElementService.fillElementWhenNewBuild(fullModel, projectId, pipelineId) + // 为model填充质量红线element + pipelineElementService.fillElementWhenNewBuild( + model = fullModel, + projectId = projectId, + pipelineId = pipelineId, + handlePostFlag = false + ) val baseModelMap = JsonUtil.toMutableMap(fullModel) val mergeBuildRecordParam = MergeBuildRecordParam( projectId = projectId, @@ -194,10 +199,11 @@ open class BaseBuildRecordService( baseModelMap = baseModelMap, modelFieldRecordMap = recordMap ) - } catch (t: Throwable) { - PipelineBuildRecordService.logger.warn( + } catch (ignore: Throwable) { + logger.warn( "RECORD|parse record($buildId)-recordMap(${JsonUtil.toJson(recordMap ?: "")})" + - "-$executeCount with error: ", t + "-$executeCount with error: ", + ignore ) null } finally { diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/PipelineBuildRecordService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/PipelineBuildRecordService.kt index 18c2c02f93f..a0b01f5ed1a 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/PipelineBuildRecordService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/PipelineBuildRecordService.kt @@ -50,8 +50,8 @@ import com.tencent.devops.common.pipeline.pojo.time.BuildRecordTimeCost import com.tencent.devops.common.pipeline.pojo.time.BuildTimestampType import com.tencent.devops.common.pipeline.utils.ModelUtils import com.tencent.devops.common.redis.RedisOperation -import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.common.service.utils.LogUtils +import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.constant.ProcessMessageCode.BK_EVENT import com.tencent.devops.process.constant.ProcessMessageCode.BK_WAREHOUSE_EVENTS import com.tencent.devops.process.dao.record.BuildRecordContainerDao @@ -78,14 +78,14 @@ import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask import com.tencent.devops.process.service.StageTagService import com.tencent.devops.process.service.record.PipelineRecordModelService import com.tencent.devops.process.utils.PipelineVarUtil -import java.time.LocalDateTime -import java.util.concurrent.TimeUnit import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.util.concurrent.TimeUnit @Suppress( "LongParameterList", @@ -135,15 +135,16 @@ class PipelineBuildRecordService @Autowired constructor( fun batchSave( transactionContext: DSLContext?, - model: BuildRecordModel, - stageList: List, - containerList: List, - taskList: List + model: BuildRecordModel?, + stageList: List?, + containerList: List?, + taskList: List? ) { - recordModelDao.createRecord(transactionContext ?: dslContext, model) - recordStageDao.batchSave(transactionContext ?: dslContext, stageList) - recordTaskDao.batchSave(transactionContext ?: dslContext, taskList) - recordContainerDao.batchSave(transactionContext ?: dslContext, containerList) + val dsl = transactionContext ?: dslContext + model?.let { recordModelDao.createRecord(dsl, model) } + stageList?.let { recordStageDao.batchSave(dsl, stageList) } + containerList?.let { recordContainerDao.batchSave(dsl, containerList) } + taskList?.let { recordTaskDao.batchSave(dsl, taskList) } } private fun checkPassDays(startTime: Long?): Boolean { @@ -171,15 +172,6 @@ class PipelineBuildRecordService @Autowired constructor( logger.info("[$$buildId|$projectId|QUERY_BUILD_RECORD|$refreshStatus|executeCount=$executeCount") val watcher = Watcher(id = "getBuildRecord#$buildId") - // 如果请求的executeCount异常则直接返回错误,防止数据错乱 - if ( - executeCount?.let { - request -> request < 1 || buildInfo.executeCount?.let { request > it } == true - } == true - ) { - return null - } - // 如果请求的次数为空则填补为最新的次数,旧数据直接按第一次查询 var fixedExecuteCount = executeCount ?: buildInfo.executeCount ?: 1 watcher.start("buildRecordModel") diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt index b1cbfa3d758..97433340b82 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt @@ -119,21 +119,17 @@ class TaskBuildRecordService( buildStatus = BuildStatus.RUNNING, operation = operation ) - update( - projectId, pipelineId, buildId, executeCount, BuildStatus.RUNNING, - cancelUser = null, operation = operation - ) { - updateTaskRecord( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId, - executeCount = executeCount, - buildStatus = buildStatus, - taskVar = emptyMap(), - timestamps = timestamps - ) - } + updateTaskRecord( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + buildStatus = buildStatus, + taskVar = emptyMap(), + timestamps = timestamps, + operation = operation + ) } // TODO #7983 暂时保留和detail一致的方法,后续简化为updateTaskStatus @@ -154,25 +150,21 @@ class TaskBuildRecordService( taskId = taskId, buildStatus = BuildStatus.PAUSE ) - update( - projectId, pipelineId, buildId, executeCount, BuildStatus.RUNNING, - cancelUser = null, operation = "taskPause#$taskId" - ) { - updateTaskRecord( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId, - executeCount = executeCount, - buildStatus = BuildStatus.PAUSE, - taskVar = emptyMap(), - timestamps = mapOf( - BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to BuildRecordTimeStamp( - LocalDateTime.now().timestampmilli(), null - ) + updateTaskRecord( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + buildStatus = BuildStatus.PAUSE, + taskVar = emptyMap(), + operation = "taskPause#$taskId", + timestamps = mapOf( + BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to BuildRecordTimeStamp( + LocalDateTime.now().timestampmilli(), null ) ) - } + ) } fun taskStart( @@ -184,7 +176,8 @@ class TaskBuildRecordService( ) { taskBuildDetailService.taskStart(projectId, buildId, taskId) update( - projectId, pipelineId, buildId, executeCount, BuildStatus.RUNNING, + projectId = projectId, pipelineId = pipelineId, buildId = buildId, + executeCount = executeCount, buildStatus = BuildStatus.RUNNING, cancelUser = null, operation = "taskStart#$taskId" ) { val delimiters = "," @@ -279,24 +272,20 @@ class TaskBuildRecordService( taskId = taskId, cancelUser = cancelUser // fix me: 是否要直接更新取消人,暂时维护原有逻辑 ) - update( - projectId, pipelineId, buildId, executeCount, BuildStatus.RUNNING, - cancelUser = cancelUser, operation = "taskCancel#$taskId" - ) { - updateTaskRecord( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId, - executeCount = executeCount, - buildStatus = BuildStatus.CANCELED, - taskVar = emptyMap(), - timestamps = mapOf( - BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to - BuildRecordTimeStamp(null, LocalDateTime.now().timestampmilli()) - ) + updateTaskRecord( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + buildStatus = BuildStatus.CANCELED, + taskVar = emptyMap(), + operation = "taskCancel#$taskId", + timestamps = mapOf( + BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to + BuildRecordTimeStamp(null, LocalDateTime.now().timestampmilli()) ) - } + ) } fun taskPauseContinue( @@ -360,7 +349,8 @@ class TaskBuildRecordService( val executeCount = taskBuildEndParam.executeCount update( - projectId, pipelineId, buildId, executeCount, BuildStatus.RUNNING, + projectId = projectId, pipelineId = pipelineId, buildId = buildId, + executeCount = executeCount, buildStatus = BuildStatus.RUNNING, cancelUser = null, operation = "taskEnd#$taskId" ) { dslContext.transaction { configuration -> @@ -437,50 +427,57 @@ class TaskBuildRecordService( executeCount: Int, taskVar: Map, buildStatus: BuildStatus?, + operation: String, timestamps: Map? = null ) { - dslContext.transaction { configuration -> - val transactionContext = DSL.using(configuration) - val recordTask = recordTaskDao.getRecord( - dslContext = transactionContext, - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId, - executeCount = executeCount - ) ?: run { - logger.warn( - "ENGINE|$buildId|updateTaskByMap| get task($taskId) record failed." - ) - return@transaction - } - var startTime: LocalDateTime? = null - var endTime: LocalDateTime? = null - val now = LocalDateTime.now() - val newTimestamps = mutableMapOf() - if (buildStatus?.isRunning() == true && recordTask.startTime == null) { - startTime = now - } - if (buildStatus?.isFinish() == true && recordTask.endTime == null) { - endTime = now - if (BuildStatus.parse(recordTask.status) == BuildStatus.REVIEWING) { - newTimestamps[BuildTimestampType.TASK_REVIEW_PAUSE_WAITING] = - BuildRecordTimeStamp(null, now.timestampmilli()) + update( + projectId = projectId, pipelineId = pipelineId, buildId = buildId, + executeCount = executeCount, buildStatus = BuildStatus.RUNNING, + cancelUser = null, operation = operation + ) { + dslContext.transaction { configuration -> + val transactionContext = DSL.using(configuration) + val recordTask = recordTaskDao.getRecord( + dslContext = transactionContext, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount + ) ?: run { + logger.warn( + "ENGINE|$buildId|updateTaskByMap| get task($taskId) record failed." + ) + return@transaction + } + var startTime: LocalDateTime? = null + var endTime: LocalDateTime? = null + val now = LocalDateTime.now() + val newTimestamps = mutableMapOf() + if (buildStatus?.isRunning() == true && recordTask.startTime == null) { + startTime = now } + if (buildStatus?.isFinish() == true && recordTask.endTime == null) { + endTime = now + if (BuildStatus.parse(recordTask.status) == BuildStatus.REVIEWING) { + newTimestamps[BuildTimestampType.TASK_REVIEW_PAUSE_WAITING] = + BuildRecordTimeStamp(null, now.timestampmilli()) + } + } + recordTaskDao.updateRecord( + dslContext = transactionContext, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + taskVar = recordTask.taskVar.plus(taskVar), + buildStatus = buildStatus, + startTime = startTime, + endTime = endTime, + timestamps = timestamps?.let { mergeTimestamps(timestamps, recordTask.timestamps) } + ) } - recordTaskDao.updateRecord( - dslContext = transactionContext, - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId, - executeCount = executeCount, - taskVar = recordTask.taskVar.plus(taskVar), - buildStatus = buildStatus, - startTime = startTime, - endTime = endTime, - timestamps = timestamps?.let { mergeTimestamps(timestamps, recordTask.timestamps) } - ) } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt index 0eee6746468..5717fac02e4 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt @@ -734,6 +734,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( projectId = projectId, buildId = buildId, taskId = updateTaskStatusInfo.taskId, + executeCount = updateTaskStatusInfo.executeCount, taskStatus = updateTaskStatusInfo.buildStatus ) ) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt index 270b8e0d6c7..707eeb19878 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt @@ -27,16 +27,19 @@ package com.tencent.devops.process.service.record +import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.JsonUtil.deepCopy import com.tencent.devops.common.pipeline.Model import com.tencent.devops.common.pipeline.container.Container import com.tencent.devops.common.pipeline.container.Stage import com.tencent.devops.common.pipeline.container.VMBuildContainer import com.tencent.devops.common.pipeline.pojo.element.Element +import com.tencent.devops.common.pipeline.pojo.element.ElementAdditionalOptions import com.tencent.devops.common.pipeline.utils.ModelUtils import com.tencent.devops.process.dao.record.BuildRecordContainerDao import com.tencent.devops.process.dao.record.BuildRecordStageDao import com.tencent.devops.process.dao.record.BuildRecordTaskDao +import com.tencent.devops.process.engine.service.PipelinePostElementService import com.tencent.devops.process.pojo.pipeline.record.BuildRecordContainer import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask import com.tencent.devops.process.pojo.pipeline.record.MergeBuildRecordParam @@ -49,6 +52,7 @@ class PipelineRecordModelService @Autowired constructor( private val buildRecordStageDao: BuildRecordStageDao, private val buildRecordContainerDao: BuildRecordContainerDao, private val buildRecordTaskDao: BuildRecordTaskDao, + private val pipelinePostElementService: PipelinePostElementService, private val dslContext: DSLContext ) { @@ -150,15 +154,21 @@ class PipelineRecordModelService @Autowired constructor( containerVarMap[Container::status.name] = stageRecordContainer.status ?: "" containerVarMap[Container::executeCount.name] = stageRecordContainer.executeCount containerVarMap[Container::containPostTaskFlag.name] = stageRecordContainer.containPostTaskFlag ?: false - handleContainerRecordTask(stageRecordTasks, containerId, containerVarMap) + val stageBaseMap = (pipelineBaseModelMap[Model::stages.name] as List>).first { + it[Stage::id.name] == stageId + } + val containerBaseMap = (stageBaseMap[Stage::containers.name] as List>).first { + it[Container::id.name] == containerId + } + val containerBaseModelMap = containerBaseMap.deepCopy>() + handleContainerRecordTask( + stageRecordTasks = stageRecordTasks, + containerId = containerId, + containerVarMap = containerVarMap, + containerBaseMap = containerBaseModelMap + ) val matrixGroupFlag = stageRecordContainer.matrixGroupFlag if (matrixGroupFlag == true) { - val stageBaseMap = (pipelineBaseModelMap[Model::stages.name] as List>).first { - it[Stage::id.name] == stageId - } - val containerBaseMap = (stageBaseMap[Stage::containers.name] as List>).first { - it[Container::id.name] == containerId - } // 过滤出矩阵分裂出的job数据 val matrixRecordContainers = stageRecordContainers.filter { it.matrixGroupId == containerId }.sortedBy { it.containerId.toInt() } @@ -167,7 +177,6 @@ class PipelineRecordModelService @Autowired constructor( // 生成矩阵job的变量模型 var matrixContainerVarMap = matrixRecordContainer.containerVar val matrixContainerId = matrixRecordContainer.containerId - val containerBaseModelMap = containerBaseMap.deepCopy>() matrixContainerVarMap[Container::id.name] = matrixContainerId matrixContainerVarMap[Container::status.name] = matrixRecordContainer.status ?: "" matrixContainerVarMap[Container::executeCount.name] = matrixRecordContainer.executeCount @@ -177,12 +186,13 @@ class PipelineRecordModelService @Autowired constructor( stageRecordTasks = stageRecordTasks, containerId = matrixContainerId, containerVarMap = matrixContainerVarMap, - containerBaseMap = containerBaseModelMap + containerBaseMap = containerBaseModelMap, + matrixTaskFlag = true ) containerBaseModelMap.remove(VMBuildContainer::matrixControlOption.name) containerBaseModelMap.remove(VMBuildContainer::groupContainers.name) matrixContainerVarMap = ModelUtils.generateBuildModelDetail( - baseModelMap = containerBaseModelMap, + baseModelMap = containerBaseModelMap.deepCopy(), modelFieldRecordMap = matrixContainerVarMap ) groupContainers.add(matrixContainerVarMap) @@ -199,7 +209,8 @@ class PipelineRecordModelService @Autowired constructor( stageRecordTasks: List, containerId: String, containerVarMap: MutableMap, - containerBaseMap: Map? = null + containerBaseMap: Map, + matrixTaskFlag: Boolean = false ) { // 过滤出job下的task变量数据 val containerRecordTasks = stageRecordTasks.filter { it.containerId == containerId }.sortedBy { it.taskSeq } @@ -210,10 +221,39 @@ class PipelineRecordModelService @Autowired constructor( taskVarMap[Element::id.name] = taskId taskVarMap[Element::status.name] = containerRecordTask.status ?: "" taskVarMap[Element::executeCount.name] = containerRecordTask.executeCount - containerBaseMap?.let { + val elementPostInfo = containerRecordTask.elementPostInfo + if (elementPostInfo != null) { + // 生成post类型task的变量模型 + val additionalOptions = ElementAdditionalOptions( + enable = true, + continueWhenFailed = true, + retryWhenFailed = false, + runCondition = pipelinePostElementService.getPostAtomRunCondition(elementPostInfo.postCondition), + pauseBeforeExec = null, + subscriptionPauseUser = null, + otherTask = null, + customCondition = null, + elementPostInfo = elementPostInfo + ) + val additionalOptionsMap = taskVarMap[Element::additionalOptions.name] as? MutableMap + val finalAdditionalOptionsMap = if (additionalOptionsMap != null) { + ModelUtils.generateBuildModelDetail(additionalOptionsMap, JsonUtil.toMutableMap(additionalOptions)) + } else { + JsonUtil.toMutableMap(additionalOptions) + } + taskVarMap[Element::additionalOptions.name] = finalAdditionalOptionsMap + val parentElementJobIndex = elementPostInfo.parentElementJobIndex + val taskBaseMap = + (containerBaseMap[Container::elements.name] as List>)[parentElementJobIndex] + val taskName = taskBaseMap[Element::name.name]?.toString() ?: "" + taskVarMap[Element::name.name] = pipelinePostElementService.getPostElementName(taskName) + taskVarMap = ModelUtils.generateBuildModelDetail(taskBaseMap.deepCopy(), taskVarMap) + } + if (matrixTaskFlag && elementPostInfo == null) { // 生成矩阵task的变量模型 - val taskBaseMap = (it[Container::elements.name] as List>)[index].toMutableMap() - taskVarMap = ModelUtils.generateBuildModelDetail(taskBaseMap, taskVarMap) + val taskBaseMap = + (containerBaseMap[Container::elements.name] as List>)[index] + taskVarMap = ModelUtils.generateBuildModelDetail(taskBaseMap.deepCopy(), taskVarMap) } tasks.add(taskVarMap) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/util/NotifyTemplateUtils.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/util/NotifyTemplateUtils.kt index 66cd4cec58d..411eef859cd 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/util/NotifyTemplateUtils.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/util/NotifyTemplateUtils.kt @@ -37,34 +37,30 @@ import com.tencent.devops.process.utils.PROJECT_NAME_CHINESE object NotifyTemplateUtils { private const val COMMON_SHUTDOWN_SUCCESS_CONTENT = "commonShutdownSuccessContent" - // 【${0}】- 【${1}】#${2} 执行成功,耗时${3}, 触发人:${4}。 + // 【${%s}】- 【${%s}】#${%s} 执行成功,耗时${%s}, 触发人:${%s}。 private const val COMMON_SHUTDOWN_FAILURE_CONTENT = "commonShutdownFailureContent" - // 【${0}】- 【${1}】#${2} 执行失败,耗时${3}, 触发人:${4}。 + // 【${%s}】- 【${%s}】#${%s} 执行失败,耗时${%s}, 触发人:${%s}。 fun getCommonShutdownSuccessContent(): String { - return I18nUtil.getCodeLanMessage( - messageCode = COMMON_SHUTDOWN_SUCCESS_CONTENT, - params = arrayOf( - PROJECT_NAME_CHINESE, - PIPELINE_NAME, - PIPELINE_BUILD_NUM, - PIPELINE_TIME_DURATION, - PIPELINE_START_USER_NAME - ) + return String.format( + I18nUtil.getCodeLanMessage(COMMON_SHUTDOWN_SUCCESS_CONTENT), + PROJECT_NAME_CHINESE, + PIPELINE_NAME, + PIPELINE_BUILD_NUM, + PIPELINE_TIME_DURATION, + PIPELINE_START_USER_NAME ) } fun getCommonShutdownFailureContent(): String { - return I18nUtil.getCodeLanMessage( - messageCode = COMMON_SHUTDOWN_FAILURE_CONTENT, - params = arrayOf( - PROJECT_NAME_CHINESE, - PIPELINE_NAME, - PIPELINE_BUILD_NUM, - PIPELINE_TIME_DURATION, - PIPELINE_START_USER_NAME - ) + return String.format( + I18nUtil.getCodeLanMessage(COMMON_SHUTDOWN_FAILURE_CONTENT), + PROJECT_NAME_CHINESE, + PIPELINE_NAME, + PIPELINE_BUILD_NUM, + PIPELINE_TIME_DURATION, + PIPELINE_START_USER_NAME ) } } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt index dd3aa22a90e..36ad7c2858d 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt @@ -232,6 +232,7 @@ class TaskAtomService @Autowired(required = false) constructor( updateTaskInfo = UpdateTaskInfo( projectId = task.projectId, buildId = task.buildId, + executeCount = task.executeCount ?: 1, taskId = updateTaskStatusInfo.taskId, taskStatus = updateTaskStatusInfo.buildStatus ) diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/task/ManualReviewTaskAtom.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/task/ManualReviewTaskAtom.kt index 5bbd78231db..4fabdfe2a35 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/task/ManualReviewTaskAtom.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/task/ManualReviewTaskAtom.kt @@ -116,6 +116,7 @@ class ManualReviewTaskAtom( projectId = projectCode, pipelineId = pipelineId, buildId = buildId, taskId = taskId, executeCount = task.executeCount ?: 1, buildStatus = null, taskVar = mapOf(ManualReviewUserTaskElement::reviewUsers.name to reviewUsersList), + operation = "manualReviewTaskStart#${task.taskId}", timestamps = mapOf( BuildTimestampType.TASK_REVIEW_PAUSE_WAITING to BuildRecordTimeStamp(LocalDateTime.now().timestampmilli(), null) diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt index 217a429e9cb..297488e244f 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt @@ -259,6 +259,12 @@ class BuildStartControl @Autowired constructor( executeCount = executeCount ) broadcastStartEvent(buildInfo) + } else { + pipelineRuntimeService.updateExecuteCount( + projectId = projectId, + buildId = buildId, + executeCount = executeCount + ) } } finally { pipelineBuildLock.unlock() diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/InitializeMatrixGroupStageCmd.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/InitializeMatrixGroupStageCmd.kt index f5bc513bd51..b4864db50c4 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/InitializeMatrixGroupStageCmd.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/InitializeMatrixGroupStageCmd.kt @@ -33,7 +33,6 @@ import com.tencent.devops.common.api.exception.ExecuteException import com.tencent.devops.common.api.exception.InvalidParamException import com.tencent.devops.common.log.utils.BuildLogPrinter import com.tencent.devops.common.pipeline.EnvReplacementParser -import com.tencent.devops.common.pipeline.container.Container import com.tencent.devops.common.pipeline.container.NormalContainer import com.tencent.devops.common.pipeline.container.VMBuildContainer import com.tencent.devops.common.pipeline.enums.BuildStatus @@ -85,7 +84,8 @@ import kotlin.math.min "ReturnCount", "NestedBlockDepth", "ThrowsCount", - "LongParameterList" + "LongParameterList", + "LargeClass" ) @Service class InitializeMatrixGroupStageCmd( @@ -333,11 +333,13 @@ class InitializeMatrixGroupStageCmd( recordContainer?.let { val containerVar = mutableMapOf( "@type" to newContainer.getClassType(), - Container::containerHashId.name to (newContainer.containerHashId ?: ""), - Container::name.name to (newContainer.name) + newContainer::containerHashId.name to (newContainer.containerHashId ?: ""), + newContainer::name.name to (newContainer.name), + newContainer::matrixGroupId.name to matrixGroupId, + newContainer::matrixContext.name to contextCase ) modelContainer.mutexGroup?.let { - containerVar[VMBuildContainer::mutexGroup.name] = it + containerVar[newContainer::mutexGroup.name] = it } recordContainerList.add( BuildRecordContainer( @@ -445,11 +447,13 @@ class InitializeMatrixGroupStageCmd( recordContainer?.let { val containerVar = mutableMapOf( "@type" to newContainer.getClassType(), - Container::containerHashId.name to (newContainer.containerHashId ?: ""), - Container::name.name to (newContainer.name) + newContainer::containerHashId.name to (newContainer.containerHashId ?: ""), + newContainer::name.name to (newContainer.name), + newContainer::matrixGroupId.name to matrixGroupId, + newContainer::matrixContext.name to contextCase ) modelContainer.mutexGroup?.let { - containerVar[VMBuildContainer::mutexGroup.name] = it + containerVar[newContainer::mutexGroup.name] = it } recordContainerList.add( BuildRecordContainer( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt index 04bba270efb..b1f41a9973b 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt @@ -311,6 +311,27 @@ class UserBuildResourceImpl @Autowired constructor( ) } + override fun getBuildRecordByBuildNum( + userId: String, + projectId: String, + pipelineId: String, + buildNum: Int + ): Result { + checkParam(userId, projectId, pipelineId) + if (buildNum <= 0) { + throw ParamBlankException("Invalid buildNo") + } + return Result( + pipelineBuildFacadeService.getBuildRecordByBuildNum( + userId = userId, + projectId = projectId, + pipelineId = pipelineId, + buildNum = buildNum, + channelCode = ChannelCode.BS + ) + ) + } + override fun goToLatestFinishedBuild(userId: String, projectId: String, pipelineId: String): Response { checkParam(userId = userId, projectId = projectId, pipelineId = pipelineId) return pipelineBuildFacadeService.goToLatestFinishedBuild( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResourceImpl.kt index afd97288884..2d83d4027a5 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineSettingResourceImpl.kt @@ -73,10 +73,17 @@ class OpPipelineSettingResourceImpl @Autowired constructor( ) } - override fun updateMaxConRunningQueueSize(pipelineId: String, maxConRunningQueueSize: Int): Result { + override fun updateMaxConRunningQueueSize( + userId: String, + projectId: String, + pipelineId: String, + maxConRunningQueueSize: Int + ): Result { checkMaxConRunningQueueSize(maxConRunningQueueSize) return Result( pipelineSettingFacadeService.updateMaxConRunningQueueSize( + userId = userId, + projectId = projectId, pipelineId = pipelineId, maxConRunningQueueSize = maxConRunningQueueSize ) diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/notify/command/impl/NotifySendCmd.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/notify/command/impl/NotifySendCmd.kt index 1b7dacb0637..c9c0e4c1d13 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/notify/command/impl/NotifySendCmd.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/notify/command/impl/NotifySendCmd.kt @@ -4,12 +4,11 @@ import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.client.Client import com.tencent.devops.notify.api.service.ServiceNotifyMessageTemplateResource import com.tencent.devops.notify.pojo.SendNotifyMessageTemplateRequest -import com.tencent.devops.process.notify.command.NotifyCmd import com.tencent.devops.process.notify.command.BuildNotifyContext +import com.tencent.devops.process.notify.command.NotifyCmd import com.tencent.devops.process.pojo.PipelineNotifyTemplateEnum import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import java.lang.IllegalArgumentException @Service class NotifySendCmd @Autowired constructor( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt index eb47d9ca114..c46edd6bfb2 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt @@ -391,6 +391,8 @@ class PipelineBuildFacadeService( projectId = projectId, pipelineId = pipelineId, buildId = buildId, + executeCount = buildInfo.executeCount ?: 1, + resourceVersion = buildInfo.version, taskId = taskId, skipFailedTask = skipFailedTask ) @@ -1355,7 +1357,7 @@ class PipelineBuildFacadeService( arrayOf(userId, pipelineId, I18nUtil.getCodeLanMessage(BK_DETAIL)) ) ) - val buildId = pipelineRuntimeService.getBuildIdbyBuildNo(projectId, pipelineId, buildNo) + val buildId = pipelineRuntimeService.getBuildIdByBuildNum(projectId, pipelineId, buildNo) ?: throw ErrorCodeException( statusCode = Response.Status.NOT_FOUND.statusCode, errorCode = ProcessMessageCode.ERROR_NO_BUILD_EXISTS_BY_ID, @@ -1366,6 +1368,40 @@ class PipelineBuildFacadeService( ) } + fun getBuildRecordByBuildNum( + userId: String, + projectId: String, + pipelineId: String, + buildNum: Int, + channelCode: ChannelCode, + checkPermission: Boolean = true + ): ModelRecord { + pipelinePermissionService.validPipelinePermission( + userId = userId, + projectId = projectId, + pipelineId = pipelineId, + permission = AuthPermission.VIEW, + message = MessageUtil.getMessageByLocale( + ERROR_USER_NO_PERMISSION_GET_PIPELINE_INFO, + I18nUtil.getLanguage(userId), + arrayOf(userId, pipelineId, I18nUtil.getCodeLanMessage(BK_DETAIL)) + ) + ) + val buildId = pipelineRuntimeService.getBuildIdByBuildNum(projectId, pipelineId, buildNum) + ?: throw ErrorCodeException( + statusCode = Response.Status.NOT_FOUND.statusCode, + errorCode = ProcessMessageCode.ERROR_NO_BUILD_EXISTS_BY_ID, + params = arrayOf("buildNum=$buildNum") + ) + return getBuildRecord( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + executeCount = null, + channelCode = channelCode + ) + } + fun getBuildRecord( projectId: String, pipelineId: String, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineRetryFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineRetryFacadeService.kt index 609db3180f5..0c22a42164a 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineRetryFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineRetryFacadeService.kt @@ -31,7 +31,9 @@ import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.enums.ActionType import com.tencent.devops.common.log.utils.BuildLogPrinter +import com.tencent.devops.common.pipeline.container.Container import com.tencent.devops.common.pipeline.enums.BuildStatus +import com.tencent.devops.common.pipeline.pojo.time.BuildRecordTimeLine import com.tencent.devops.process.constant.ProcessMessageCode import com.tencent.devops.process.engine.common.VMUtils import com.tencent.devops.process.engine.pojo.PipelineBuildContainer @@ -39,17 +41,25 @@ import com.tencent.devops.process.engine.pojo.PipelineBuildTask import com.tencent.devops.process.engine.pojo.event.PipelineBuildContainerEvent import com.tencent.devops.process.engine.service.PipelineContainerService import com.tencent.devops.process.engine.service.PipelineTaskService +import com.tencent.devops.process.engine.service.record.ContainerBuildRecordService +import com.tencent.devops.process.engine.service.record.PipelineBuildRecordService +import com.tencent.devops.process.pojo.pipeline.record.BuildRecordContainer +import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask +import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask.Companion.addRecords import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @Service +@Suppress("ReturnCount", "LongParameterList", "LongMethod") class PipelineRetryFacadeService @Autowired constructor( val dslContext: DSLContext, val pipelineEventDispatcher: PipelineEventDispatcher, val pipelineTaskService: PipelineTaskService, val pipelineContainerService: PipelineContainerService, + val pipelineBuildRecordService: PipelineBuildRecordService, + val containerBuildRecordService: ContainerBuildRecordService, private val buildLogPrinter: BuildLogPrinter ) { @@ -63,6 +73,8 @@ class PipelineRetryFacadeService @Autowired constructor( projectId: String, pipelineId: String, buildId: String, + executeCount: Int, + resourceVersion: Int, taskId: String? = null, skipFailedTask: Boolean? = false ): Boolean { @@ -83,9 +95,18 @@ class PipelineRetryFacadeService @Autowired constructor( // 校验当前job的关机事件是否有完成 checkStopTask(projectId, buildId, containerInfo!!) - // 刷新当前job的开关机以及job状态, container状态, detail数据 - refreshTaskAndJob(userId, projectId, buildId, taskId, containerInfo, skipFailedTask) - // 发送container Refreash事件,重新开始task对应的调度 + // 刷新当前job的开关机以及job状态, container状态, detail数据, record数据 + refreshTaskAndJob( + userId = userId, + projectId = projectId, + buildId = buildId, + executeCount = executeCount, + resourceVersion = resourceVersion, + taskId = taskId, + containerInfo = containerInfo, + skipFailedTask = skipFailedTask + ) + // 发送container Refresh事件,重新开始task对应的调度 sendContainerEvent(taskInfo, userId) buildLogPrinter.addYellowLine( buildId = buildId, @@ -132,14 +153,18 @@ class PipelineRetryFacadeService @Autowired constructor( userId: String, projectId: String, buildId: String, + executeCount: Int, + resourceVersion: Int, taskId: String, containerInfo: PipelineBuildContainer, skipFailedTask: Boolean? = false ) { - val taskRecords = pipelineTaskService.listContainerBuildTasks(projectId, buildId, containerInfo.containerId) + val taskBuilds = pipelineTaskService.listContainerBuildTasks(projectId, buildId, containerInfo.containerId) + val taskRecords = mutableListOf() // 待重试task所属job对应的startVm,stopVm,endTask,对应task状态重置为Queue val startAndEndTask = mutableListOf() - taskRecords.forEach { task -> + taskBuilds.forEach { t -> + val task = t.copy(executeCount = executeCount) if (task.taskId == taskId) { // issues_6831: 若设置了手动跳过 且重试时选择了跳过当前插件则不刷新当前失败的插件,直接把task内状态改为SKIP if (task.additionalOptions?.manualSkip == true && skipFailedTask!!) { @@ -147,22 +172,60 @@ class PipelineRetryFacadeService @Autowired constructor( return@forEach } startAndEndTask.add(task) + taskRecords.addRecords(mutableListOf(task), resourceVersion) } else if (task.taskName.startsWith(VMUtils.getCleanVmLabel()) && - task.taskId.startsWith(VMUtils.getStopVmLabel())) { + task.taskId.startsWith(VMUtils.getStopVmLabel()) + ) { startAndEndTask.add(task) } else if (task.taskName.startsWith(VMUtils.getPrepareVmLabel()) && - task.taskId.startsWith(VMUtils.getStartVmLabel())) { + task.taskId.startsWith(VMUtils.getStartVmLabel()) + ) { startAndEndTask.add(task) } else if (task.taskName.startsWith(VMUtils.getWaitLabel()) && - task.taskId.startsWith(VMUtils.getEndLabel())) { + task.taskId.startsWith(VMUtils.getEndLabel()) + ) { startAndEndTask.add(task) } else if (task.status == BuildStatus.UNEXEC) { startAndEndTask.add(task) + taskRecords.addRecords(mutableListOf(task), resourceVersion) } } startAndEndTask.forEach { pipelineTaskService.updateTaskStatus(task = it, userId = userId, buildStatus = BuildStatus.QUEUE) } + + val lastContainerRecord = containerBuildRecordService.getRecord( + transactionContext = null, + projectId = containerInfo.projectId, + pipelineId = containerInfo.pipelineId, + buildId = containerInfo.buildId, + containerId = containerInfo.containerId, + executeCount = executeCount - 1 + ) + val containerRecord = if (lastContainerRecord != null) { + val containerVar = lastContainerRecord.containerVar + containerVar.remove(Container::timeCost.name) + containerVar.remove(Container::startVMStatus.name) + containerVar.remove(Container::startEpoch.name) + containerVar.remove(BuildRecordTimeLine::class.java.simpleName) + lastContainerRecord.copy( + status = BuildStatus.QUEUE.name, containerVar = containerVar, + executeCount = executeCount, timestamps = mapOf() + ) + } else { + BuildRecordContainer( + projectId = containerInfo.projectId, pipelineId = containerInfo.pipelineId, + resourceVersion = resourceVersion, buildId = containerInfo.buildId, + stageId = containerInfo.stageId, containerId = containerInfo.containerId, + containerType = containerInfo.containerType, executeCount = executeCount, + matrixGroupFlag = containerInfo.matrixGroupFlag, status = BuildStatus.QUEUE.name, + containerVar = mutableMapOf(), timestamps = mapOf() + ) + } + pipelineBuildRecordService.batchSave( + transactionContext = null, model = null, stageList = null, + containerList = listOf(containerRecord), taskList = taskRecords + ) // 修改容器状态位运行 pipelineContainerService.updateContainerStatus( projectId = containerInfo.projectId, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineSettingFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineSettingFacadeService.kt index c30edc79a20..421ef42a002 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineSettingFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineSettingFacadeService.kt @@ -330,10 +330,14 @@ class PipelineSettingFacadeService @Autowired constructor( } fun updateMaxConRunningQueueSize( + userId: String, + projectId: String, pipelineId: String, maxConRunningQueueSize: Int ): Int { return pipelineRepositoryService.updateMaxConRunningQueueSize( + userId = userId, + projectId = projectId, pipelineId = pipelineId, maxConRunningQueueSize = maxConRunningQueueSize ) diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt index 1abda49f153..e806b2fdf20 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt @@ -1063,7 +1063,7 @@ class TemplateFacadeService @Autowired constructor( ?: throw NotFoundException( I18nUtil.getCodeLanMessage( messageCode = ERROR_TEMPLATE_NOT_EXISTS, - language = userId + language = I18nUtil.getLanguage(userId) )) val template: Model = objectMapper.readValue( templateDao.getTemplate(dslContext = dslContext, version = templatePipelineRecord.version).template diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/listener/PipelineWebSocketListener.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/listener/PipelineWebSocketListener.kt index 14999c1ca84..1d82190eeea 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/listener/PipelineWebSocketListener.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/listener/PipelineWebSocketListener.kt @@ -90,12 +90,20 @@ class PipelineWebSocketListener @Autowired constructor( if (event.refreshTypes and RefreshType.RECORD.binary == RefreshType.RECORD.binary) { event.executeCount?.let { executeCount -> webSocketDispatcher.dispatch( + // #8955 增加对没有执行次数的默认页面的重复推送 pipelineWebsocketService.buildRecordMessage( buildId = event.buildId, projectId = event.projectId, pipelineId = event.pipelineId, userId = event.userId, executeCount = executeCount + ), + pipelineWebsocketService.buildRecordMessage( + buildId = event.buildId, + projectId = event.projectId, + pipelineId = event.pipelineId, + userId = event.userId, + executeCount = null ) ) } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/page/RecordPageBuild.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/page/RecordPageBuild.kt index 8357f680676..abb3269df98 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/page/RecordPageBuild.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/page/RecordPageBuild.kt @@ -32,8 +32,13 @@ import com.tencent.devops.common.websocket.pojo.BuildPageInfo abstract class RecordPageBuild : IPath { override fun buildPage(buildPageInfo: BuildPageInfo): String { - val defaultPage = "/console/pipeline/${buildPageInfo.projectId}/${buildPageInfo.pipelineId}" + - "/detail/${buildPageInfo.buildId}/executeDetail/${buildPageInfo.executeCount}" + val defaultPage = if (buildPageInfo.executeCount != null) { + "/console/pipeline/${buildPageInfo.projectId}/${buildPageInfo.pipelineId}" + + "/detail/${buildPageInfo.buildId}/executeDetail/${buildPageInfo.executeCount}" + } else { + "/console/pipeline/${buildPageInfo.projectId}/${buildPageInfo.pipelineId}" + + "/detail/${buildPageInfo.buildId}/executeDetail" + } if (!extRecordPage(buildPageInfo).isNullOrEmpty()) { return extRecordPage(buildPageInfo)!! } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/push/RecordWebsocketPush.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/push/RecordWebsocketPush.kt index 6b2a513f390..8961d139eea 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/push/RecordWebsocketPush.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/push/RecordWebsocketPush.kt @@ -46,7 +46,7 @@ data class RecordWebsocketPush( val buildId: String?, val pipelineId: String, val projectId: String, - val executeCount: Int, + val executeCount: Int?, override val userId: String, override val pushType: WebSocketType, override val redisOperation: RedisOperation, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/service/PipelineWebsocketService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/service/PipelineWebsocketService.kt index 86dc850705e..9439966ea5f 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/service/PipelineWebsocketService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/websocket/service/PipelineWebsocketService.kt @@ -165,7 +165,7 @@ class PipelineWebsocketService @Autowired constructor( projectId: String, pipelineId: String, userId: String, - executeCount: Int + executeCount: Int? ): RecordWebsocketPush { val page = recordPageBuild.buildPage( buildPageInfo = BuildPageInfo( @@ -176,7 +176,7 @@ class PipelineWebsocketService @Autowired constructor( executeCount = executeCount ) ) - logger.debug("status websocket: page[$page], buildId:[$buildId],pipelineId:[$pipelineId],project:[$projectId]") + logger.debug("record websocket: page[$page], buildId:[$buildId],pipelineId:[$pipelineId],project:[$projectId]") return RecordWebsocketPush( buildId = buildId, projectId = projectId, diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectTagService.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectTagService.kt index 7c49b75a0d0..1d0369ea20f 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectTagService.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectTagService.kt @@ -126,7 +126,7 @@ class ProjectTagService @Autowired constructor( fun updateTagByProject(projectTagUpdateDTO: ProjectTagUpdateDTO): Result { logger.info("updateTagByProject: $projectTagUpdateDTO") checkRouteTag(projectTagUpdateDTO.routerTag) - checkProject(projectTagUpdateDTO.projectCodeList) + // checkProject(projectTagUpdateDTO.projectCodeList) projectTagDao.updateProjectTags( dslContext = dslContext, englishNames = projectTagUpdateDTO.projectCodeList!!, diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt index 08d86efd78c..a5eaf18595d 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt @@ -847,6 +847,11 @@ abstract class AbsProjectServiceImpl @Autowired constructor( accessToken: String? ): Result { logger.info("Update the logo of project : englishName = $englishName") + val verify = validatePermission(englishName, userId, AuthPermission.EDIT) + if (!verify) { + logger.info("$englishName| $userId| ${AuthPermission.EDIT} validatePermission fail") + throw PermissionForbiddenException(I18nUtil.getCodeLanMessage(ProjectMessageCode.PEM_CHECK_FAIL)) + } val projectRecord = projectDao.getByEnglishName(dslContext, englishName) if (projectRecord != null) { var logoFile: File? = null diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityControlPointService.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityControlPointService.kt index 20442d8362d..627b460696d 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityControlPointService.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityControlPointService.kt @@ -33,7 +33,7 @@ import com.tencent.devops.common.api.util.HashUtil import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation -import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.model.quality.tables.records.TQualityControlPointRecord import com.tencent.devops.quality.api.v2.pojo.ControlPointPosition import com.tencent.devops.quality.api.v2.pojo.QualityControlPoint @@ -45,6 +45,7 @@ import com.tencent.devops.quality.dao.v2.QualityRuleBuildHisDao import com.tencent.devops.quality.dao.v2.QualityRuleDao import com.tencent.devops.quality.pojo.po.ControlPointPO import com.tencent.devops.quality.util.ElementUtils +import java.io.File import java.util.concurrent.Executors import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor @@ -63,7 +64,8 @@ class QualityControlPointService @Autowired constructor( private val controlPointDao: QualityControlPointDao, private val qualityRuleDao: QualityRuleDao, private val qualityRuleBuildHisDao: QualityRuleBuildHisDao, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + private val commonConfig: CommonConfig ) { @PostConstruct @@ -74,18 +76,20 @@ class QualityControlPointService @Autowired constructor( expiredTimeInSeconds = 60 ) - if (redisLock.tryLock()) { - Executors.newFixedThreadPool(1).submit { + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { try { logger.info("start init quality control point") val classPathResource = ClassPathResource( - "controlPoint_${I18nUtil.getDefaultLocaleLanguage()}.json" + "i18n${File.separator}controlPoint_${commonConfig.devopsDefaultLocaleLanguage}.json" ) val inputStream = classPathResource.inputStream val json = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } val controlPointPOs = JsonUtil.to(json, object : TypeReference>() {}) controlPointDao.batchCrateControlPoint(dslContext, controlPointPOs) logger.info("init quality control point end") + } catch (ignored: Throwable) { + logger.warn("init quality control point fail! error:${ignored.message}") } finally { redisLock.unlock() } diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityIndicatorService.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityIndicatorService.kt index b50ac3deb2f..ba8d5487088 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityIndicatorService.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityIndicatorService.kt @@ -37,6 +37,7 @@ import com.tencent.devops.common.client.Client import com.tencent.devops.common.quality.pojo.enums.QualityOperation import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.model.quality.tables.records.TQualityIndicatorRecord import com.tencent.devops.plugin.codecc.CodeccUtils @@ -89,6 +90,7 @@ import com.tencent.devops.quality.pojo.po.QualityIndicatorPO import com.tencent.devops.quality.util.ElementUtils import com.tencent.devops.store.api.atom.ServiceMarketAtomResource import com.tencent.devops.store.pojo.common.enums.StoreProjectTypeEnum +import java.io.File import java.util.Base64 import java.util.concurrent.Executors import javax.annotation.PostConstruct @@ -108,7 +110,8 @@ class QualityIndicatorService @Autowired constructor( private val indicatorDao: QualityIndicatorDao, private val metadataService: QualityMetadataService, private val templateIndicatorMapDao: QualityTemplateIndicatorMapDao, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + val commonConfig: CommonConfig ) { private val encoder = Base64.getEncoder() @@ -121,18 +124,20 @@ class QualityIndicatorService @Autowired constructor( expiredTimeInSeconds = 60 ) - if (redisLock.tryLock()) { - Executors.newFixedThreadPool(1).submit { + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { try { logger.info("start init quality indicator") val classPathResource = ClassPathResource( - "indicator_${I18nUtil.getDefaultLocaleLanguage()}.json" + "i18n${File.separator}indicator_${commonConfig.devopsDefaultLocaleLanguage}.json" ) val inputStream = classPathResource.inputStream val json = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } val qualityIndicatorPOs = JsonUtil.to(json, object : TypeReference>() {}) indicatorDao.batchCrateQualityIndicator(dslContext, qualityIndicatorPOs) logger.info("init quality indicator end") + } catch (ignored: Throwable) { + logger.warn("init quality indicator fail! error:${ignored.message}") } finally { redisLock.unlock() } @@ -337,7 +342,11 @@ class QualityIndicatorService @Autowired constructor( if (indicatorDao.create(userId, indicatorUpdate, dslContext) > 0) { return Msg(0, I18nUtil.getCodeLanMessage(BK_CREATE_SUCCESS), true) } - return Msg(-1, I18nUtil.getCodeLanMessage(messageCode = BK_CREATE_FAIL, language = userId), false) + return Msg( + -1, + I18nUtil.getCodeLanMessage(messageCode = BK_CREATE_FAIL, language = I18nUtil.getLanguage(userId)), + false + ) } fun userDelete(userId: String, id: Long): Boolean { diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityMetadataService.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityMetadataService.kt index e3df29614b3..18ce1783de1 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityMetadataService.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityMetadataService.kt @@ -33,7 +33,7 @@ import com.tencent.devops.common.api.util.HashUtil import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation -import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.model.quality.tables.records.TQualityMetadataRecord import com.tencent.devops.quality.api.v2.pojo.QualityIndicatorMetadata import com.tencent.devops.quality.api.v2.pojo.enums.QualityDataType @@ -41,6 +41,7 @@ import com.tencent.devops.quality.api.v2.pojo.op.ElementNameData import com.tencent.devops.quality.api.v2.pojo.op.QualityMetaData import com.tencent.devops.quality.dao.v2.QualityMetadataDao import com.tencent.devops.quality.pojo.po.QualityMetadataPO +import java.io.File import java.util.concurrent.Executors import javax.annotation.PostConstruct import org.jooq.DSLContext @@ -55,7 +56,8 @@ import org.springframework.stereotype.Service class QualityMetadataService @Autowired constructor( private val dslContext: DSLContext, private val metadataDao: QualityMetadataDao, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + val commonConfig: CommonConfig ) { companion object { private val logger = LoggerFactory.getLogger(QualityMetadataService::class.java) @@ -69,18 +71,20 @@ class QualityMetadataService @Autowired constructor( expiredTimeInSeconds = 60 ) - if (redisLock.tryLock()) { - Executors.newFixedThreadPool(1).submit { + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { try { logger.info("start init quality metadata") val classPathResource = ClassPathResource( - "metadata_${I18nUtil.getDefaultLocaleLanguage()}.json" + "i18n${File.separator}metadata_${commonConfig.devopsDefaultLocaleLanguage}.json" ) val inputStream = classPathResource.inputStream val json = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } val qualityMetadataPOs = JsonUtil.to(json, object : TypeReference>() {}) metadataDao.batchCrateQualityMetadata(dslContext, qualityMetadataPOs) logger.info("init quality metadata end") + } catch (ignored: Throwable) { + logger.warn("init quality metadata fail! error:${ignored.message}") } finally { redisLock.unlock() } diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityRuleService.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityRuleService.kt index 7e8600163b3..fc578554f6c 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityRuleService.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityRuleService.kt @@ -199,7 +199,7 @@ class QualityRuleService @Autowired constructor( fun userUpdateEnable(userId: String, projectId: String, ruleHashId: String, enable: Boolean) { val ruleId = HashUtil.decodeIdToLong(ruleHashId) - val permission = AuthPermission.EXECUTE + val permission = AuthPermission.ENABLE logger.info("user($userId) update the rule($ruleId) in project($projectId) to $enable") qualityPermissionService.validateRulePermission( userId = userId, diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityTemplateService.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityTemplateService.kt index f7302e955aa..1fd5e6f8a2b 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityTemplateService.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/service/v2/QualityTemplateService.kt @@ -33,7 +33,7 @@ import com.tencent.devops.common.api.util.HashUtil import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation -import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.quality.api.v2.pojo.ControlPointPosition import com.tencent.devops.quality.api.v2.pojo.RuleIndicatorSet import com.tencent.devops.quality.api.v2.pojo.RuleTemplate @@ -45,6 +45,7 @@ import com.tencent.devops.quality.dao.v2.QualityIndicatorDao import com.tencent.devops.quality.dao.v2.QualityRuleTemplateDao import com.tencent.devops.quality.dao.v2.QualityTemplateIndicatorMapDao import com.tencent.devops.quality.pojo.po.QualityRuleTemplatePO +import java.io.File import java.util.concurrent.Executors import javax.annotation.PostConstruct import org.jooq.DSLContext @@ -63,7 +64,8 @@ class QualityTemplateService @Autowired constructor( private val qualityControlPointDao: QualityControlPointDao, private val ruleTemplateIndicatorDao: QualityTemplateIndicatorMapDao, private val indicatorDao: QualityIndicatorDao, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + val commonConfig: CommonConfig ) { @PostConstruct @@ -74,12 +76,12 @@ class QualityTemplateService @Autowired constructor( expiredTimeInSeconds = 60 ) - if (redisLock.tryLock()) { - Executors.newFixedThreadPool(1).submit { + Executors.newFixedThreadPool(1).submit { + if (redisLock.tryLock()) { try { logger.info("start init quality rule template") val classPathResource = ClassPathResource( - "ruleTemplate_${I18nUtil.getDefaultLocaleLanguage()}.json" + "i18n${File.separator}ruleTemplate_${commonConfig.devopsDefaultLocaleLanguage}.json" ) val inputStream = classPathResource.inputStream val json = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } @@ -87,6 +89,8 @@ class QualityTemplateService @Autowired constructor( JsonUtil.to(json, object : TypeReference>() {}) ruleTemplateDao.batchCrateQualityRuleTemplate(dslContext, qualityRuleTemplatePOs) logger.info("init quality rule template end") + } catch (ignored: Throwable) { + logger.warn("init quality rule template fail! error:${ignored.message}") } finally { redisLock.unlock() } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/IGitService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/IGitService.kt index 4d368561c20..3937859d8fa 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/IGitService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/IGitService.kt @@ -170,7 +170,7 @@ interface IGitService { repoName: String, mrId: Long, tokenType: - TokenTypeEnum, + TokenTypeEnum, token: String, repoUrl: String? = null ): GitMrInfo diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/atom/ServiceMarketAtomArchiveResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/atom/ServiceMarketAtomArchiveResource.kt index 5a72e50ef3c..ec79ec39631 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/atom/ServiceMarketAtomArchiveResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/atom/ServiceMarketAtomArchiveResource.kt @@ -52,20 +52,20 @@ interface ServiceMarketAtomArchiveResource { @ApiOperation("校验用户上传的插件包是否合法") @GET - @Path("/users/{userId}/projects/{projectCode}/atoms/{atomCode}/versions/{version}/package/verify") + @Path("/users/{userId}/atoms/{atomCode}/versions/{version}/package/verify") fun verifyAtomPackageByUserId( @ApiParam("用户ID", required = true) @PathParam("userId") userId: String, - @ApiParam("项目代码", required = true) - @PathParam("projectCode") - projectCode: String, @ApiParam("插件代码", required = true) @PathParam("atomCode") atomCode: String, @ApiParam("版本号", required = true) @PathParam("version") version: String, + @ApiParam("项目代码", required = true) + @QueryParam("projectCode") + projectCode: String, @ApiParam("发布类型", required = false) @QueryParam("releaseType") releaseType: ReleaseTypeEnum?, @@ -76,38 +76,38 @@ interface ServiceMarketAtomArchiveResource { @ApiOperation("校验用户上传的插件包中的taskJson是否正确") @GET - @Path("/users/{userId}/projects/{projectCode}/atoms/{atomCode}/versions/{version}/json/verify") + @Path("/users/{userId}/atoms/{atomCode}/versions/{version}/json/verify") fun verifyAtomTaskJson( @ApiParam("用户ID", required = true) @PathParam("userId") userId: String, - @ApiParam("项目代码", required = true) - @PathParam("projectCode") - projectCode: String, @ApiParam("插件代码", required = true) @PathParam("atomCode") atomCode: String, @ApiParam("版本号", required = true) @PathParam("version") - version: String + version: String, + @ApiParam("项目代码", required = true) + @QueryParam("projectCode") + projectCode: String ): Result @ApiOperation("校验插件发布类型是否合法") @GET - @Path("/users/{userId}/projects/{projectCode}/atoms/{atomCode}/versions/{version}/releaseType/verify") + @Path("/users/{userId}/atoms/{atomCode}/versions/{version}/releaseType/verify") fun validateReleaseType( @ApiParam("用户ID", required = true) @PathParam("userId") userId: String, - @ApiParam("项目代码", required = true) - @PathParam("projectCode") - projectCode: String, @ApiParam("插件代码", required = true) @PathParam("atomCode") atomCode: String, @ApiParam("版本号", required = true) @PathParam("version") version: String, + @ApiParam("项目代码", required = true) + @QueryParam("projectCode") + projectCode: String, @ApiParam("插件字段校验确认标识", required = false) @QueryParam("fieldCheckConfirmFlag") fieldCheckConfirmFlag: Boolean? diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomConfigInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomConfigInfo.kt index 1c9d19e625c..6d8399399e2 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomConfigInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomConfigInfo.kt @@ -34,5 +34,7 @@ import io.swagger.annotations.ApiModelProperty @ApiModel("插件配置信息") data class AtomConfigInfo( @ApiModelProperty(value = "前端UI渲染方式", required = true) - val frontendType: FrontendTypeEnum = FrontendTypeEnum.NORMAL + val frontendType: FrontendTypeEnum = FrontendTypeEnum.NORMAL, + @ApiModelProperty(value = "是否为默认插件", required = true) + val defaultFlag: Boolean = false ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomCreateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomCreateRequest.kt index 33072fec424..8434671fbd1 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomCreateRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomCreateRequest.kt @@ -31,6 +31,7 @@ import com.tencent.devops.common.api.enums.FrontendTypeEnum import com.tencent.devops.common.web.annotation.BkField import com.tencent.devops.common.web.constant.BkStyleEnum import com.tencent.devops.repository.pojo.enums.VisibilityLevelEnum +import com.tencent.devops.store.pojo.common.enums.PackageSourceTypeEnum import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -57,5 +58,7 @@ data class MarketAtomCreateRequest( @field:BkField(patternStyle = BkStyleEnum.NOTE_STYLE, required = false) val privateReason: String? = null, @ApiModelProperty(value = "前端UI渲染方式", required = true) - val frontendType: FrontendTypeEnum = FrontendTypeEnum.NORMAL + val frontendType: FrontendTypeEnum = FrontendTypeEnum.NORMAL, + @ApiModelProperty(value = "插件包发布方式", required = false) + val packageSourceType: PackageSourceTypeEnum? = null ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/ReleaseInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/ReleaseInfo.kt index 01a03a88841..17041f6ff79 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/ReleaseInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/ReleaseInfo.kt @@ -38,7 +38,7 @@ import io.swagger.annotations.ApiModelProperty @ApiModel("插件发布部署模型") data class ReleaseInfo( @ApiModelProperty("项目编码", required = true) - var projectId: String, + var projectId: String = "", @ApiModelProperty("插件名称", required = true) @field:BkField(patternStyle = BkStyleEnum.NAME_STYLE) var name: String, @@ -59,7 +59,7 @@ data class ReleaseInfo( @ApiModelProperty("适用Job类型", required = true) val jobType: JobTypeEnum, @JsonProperty(value = "labelCodes", required = false) - @ApiModelProperty("标签id集合", name = "labelCodes") + @ApiModelProperty("标签标识集合", name = "labelCodes") val labelCodes: ArrayList? = null, @ApiModelProperty("版本信息", required = true) val versionInfo: VersionInfo, diff --git a/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/atom/impl/SampleAtomReleaseServiceImpl.kt b/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/atom/impl/SampleAtomReleaseServiceImpl.kt index 80b7b711710..02e237e5900 100644 --- a/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/atom/impl/SampleAtomReleaseServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/atom/impl/SampleAtomReleaseServiceImpl.kt @@ -46,7 +46,6 @@ import com.tencent.devops.store.constant.StoreMessageCode import com.tencent.devops.store.pojo.atom.AtomReleaseRequest import com.tencent.devops.store.pojo.atom.MarketAtomCreateRequest import com.tencent.devops.store.pojo.atom.MarketAtomUpdateRequest -import com.tencent.devops.store.pojo.common.enums.PackageSourceTypeEnum import com.tencent.devops.store.pojo.atom.enums.AtomStatusEnum import com.tencent.devops.store.pojo.common.ReleaseProcessItem import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum @@ -68,10 +67,6 @@ class SampleAtomReleaseServiceImpl : SampleAtomReleaseService, AtomReleaseServic return Result(data = null) } - override fun getAtomPackageSourceType(): PackageSourceTypeEnum { - return PackageSourceTypeEnum.UPLOAD - } - override fun getFileStr( projectCode: String, atomCode: String, diff --git a/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/common/impl/SampleStoreLogoServiceImpl.kt b/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/common/impl/SampleStoreLogoServiceImpl.kt index c3a325538fc..40c63a9addd 100644 --- a/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/common/impl/SampleStoreLogoServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store-sample/src/main/kotlin/com/tencent/devops/store/service/common/impl/SampleStoreLogoServiceImpl.kt @@ -31,9 +31,10 @@ import com.tencent.devops.artifactory.api.service.ServiceFileResource import com.tencent.devops.artifactory.pojo.enums.FileChannelTypeEnum import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.service.utils.CommonUtils +import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.store.utils.StoreUtils -import org.springframework.stereotype.Service import java.io.File +import org.springframework.stereotype.Service @Service class SampleStoreLogoServiceImpl : StoreLogoServiceImpl() { @@ -46,7 +47,7 @@ class SampleStoreLogoServiceImpl : StoreLogoServiceImpl() { file = file, fileChannelType = FileChannelTypeEnum.WEB_SHOW.name, logo = true, - language = userId + language = I18nUtil.getLanguage(userId) ).data // 开源版如果logoUrl的域名和ci域名一样,则logoUrl无需带上域名,防止域名变更影响图片显示(logoUrl会存db) return Result(if (logoUrl != null) StoreUtils.removeUrlHost(logoUrl) else logoUrl) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/resources/atom/ServiceMarketAtomArchiveResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/resources/atom/ServiceMarketAtomArchiveResourceImpl.kt index 0df551ccd05..0719a4ecdc8 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/resources/atom/ServiceMarketAtomArchiveResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/resources/atom/ServiceMarketAtomArchiveResourceImpl.kt @@ -43,9 +43,9 @@ class ServiceMarketAtomArchiveResourceImpl @Autowired constructor( override fun verifyAtomPackageByUserId( userId: String, - projectCode: String, atomCode: String, version: String, + projectCode: String, releaseType: ReleaseTypeEnum?, os: String? ): Result { @@ -61,18 +61,23 @@ class ServiceMarketAtomArchiveResourceImpl @Autowired constructor( override fun verifyAtomTaskJson( userId: String, - projectCode: String, atomCode: String, - version: String + version: String, + projectCode: String ): Result { - return marketAtomArchiveService.verifyAtomTaskJson(userId, projectCode, atomCode, version) + return marketAtomArchiveService.verifyAtomTaskJson( + userId = userId, + projectCode = projectCode, + atomCode = atomCode, + version = version + ) } override fun validateReleaseType( userId: String, - projectCode: String, atomCode: String, version: String, + projectCode: String, fieldCheckConfirmFlag: Boolean? ): Result { return marketAtomArchiveService.validateReleaseType( diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/AtomReleaseServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/AtomReleaseServiceImpl.kt index ba244cd7763..0ee6a3dd6c3 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/AtomReleaseServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/AtomReleaseServiceImpl.kt @@ -36,6 +36,7 @@ import com.tencent.devops.common.api.constant.KEY_REPOSITORY_HASH_ID import com.tencent.devops.common.api.constant.MASTER import com.tencent.devops.common.api.constant.SECURITY import com.tencent.devops.common.api.constant.TEST +import com.tencent.devops.common.api.enums.FrontendTypeEnum import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.JsonSchemaUtil @@ -93,6 +94,7 @@ import com.tencent.devops.store.pojo.common.KEY_INPUT import com.tencent.devops.store.pojo.common.KEY_INPUT_GROUPS import com.tencent.devops.store.pojo.common.KEY_LANGUAGE import com.tencent.devops.store.pojo.common.KEY_OUTPUT +import com.tencent.devops.store.pojo.common.KEY_PACKAGE_PATH import com.tencent.devops.store.pojo.common.KEY_RELEASE_INFO import com.tencent.devops.store.pojo.common.KEY_VERSION_INFO import com.tencent.devops.store.pojo.common.QUALITY_JSON_NAME @@ -192,8 +194,8 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ if (codeCount > 0) { // 抛出错误提示 return I18nUtil.generateResponseDataObject( - CommonMessageCode.PARAMETER_IS_EXIST, - arrayOf(atomCode) + messageCode = CommonMessageCode.PARAMETER_IS_EXIST, + params = arrayOf(atomCode) ) } val atomName = marketAtomCreateRequest.name @@ -202,8 +204,8 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ if (nameCount > 0) { // 抛出错误提示 return I18nUtil.generateResponseDataObject( - CommonMessageCode.PARAMETER_IS_EXIST, - arrayOf(atomName) + messageCode = CommonMessageCode.PARAMETER_IS_EXIST, + params = arrayOf(atomName) ) } return null @@ -240,21 +242,10 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ docsLink = storeCommonService.getStoreDetailUrl(StoreTypeEnum.ATOM, atomCode), marketAtomCreateRequest = marketAtomCreateRequest ) - // 添加插件与项目关联关系,type为0代表新增插件时关联的初始化项目 - storeProjectRelDao.addStoreProjectRel( + // 初始化插件统计表数据 + storeStatisticTotalDao.initStatisticData( dslContext = context, - userId = userId, storeCode = atomCode, - projectCode = marketAtomCreateRequest.projectCode, - type = StoreProjectTypeEnum.INIT.type.toByte(), - storeType = StoreTypeEnum.ATOM.type.toByte() - ) - storeProjectRelDao.addStoreProjectRel( - dslContext = context, - userId = userId, - storeCode = atomCode, - projectCode = marketAtomCreateRequest.projectCode, - type = StoreProjectTypeEnum.TEST.type.toByte(), storeType = StoreTypeEnum.ATOM.type.toByte() ) // 默认给新建插件的人赋予管理员权限 @@ -276,10 +267,24 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ yamlFlag = false ) ) - // 初始化插件统计表数据 - storeStatisticTotalDao.initStatisticData( + if (marketAtomCreateRequest.projectCode.isBlank()) { + return@transaction + } + // 添加插件与项目关联关系,type为0代表新增插件时关联的初始化项目 + storeProjectRelDao.addStoreProjectRel( + dslContext = context, + userId = userId, + storeCode = atomCode, + projectCode = marketAtomCreateRequest.projectCode, + type = StoreProjectTypeEnum.INIT.type.toByte(), + storeType = StoreTypeEnum.ATOM.type.toByte() + ) + storeProjectRelDao.addStoreProjectRel( dslContext = context, + userId = userId, storeCode = atomCode, + projectCode = marketAtomCreateRequest.projectCode, + type = StoreProjectTypeEnum.TEST.type.toByte(), storeType = StoreTypeEnum.ATOM.type.toByte() ) } @@ -292,7 +297,13 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ atomCode: String ): Result?> - abstract fun getAtomPackageSourceType(): PackageSourceTypeEnum + fun getAtomPackageSourceType(repositoryHashId: String?): PackageSourceTypeEnum { + return if (repositoryHashId.isNullOrBlank()) { + PackageSourceTypeEnum.UPLOAD + } else { + PackageSourceTypeEnum.REPO + } + } @Suppress("UNCHECKED_CAST") @BkTimed(extraTags = ["publish", "updateMarketAtom"], value = "store_publish_pipeline_atom") @@ -303,15 +314,19 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ ): Result { logger.info("updateMarketAtom userId is :$userId,marketAtomUpdateRequest is :$marketAtomUpdateRequest") val atomCode = marketAtomUpdateRequest.atomCode - val atomPackageSourceType = getAtomPackageSourceType() - logger.info("updateMarketAtom atomPackageSourceType is :$atomPackageSourceType") val version = marketAtomUpdateRequest.version // 判断插件是不是首次创建版本 val atomCount = atomDao.countByCode(dslContext, atomCode) if (atomCount < 1) { - return I18nUtil.generateResponseDataObject(CommonMessageCode.PARAMETER_IS_INVALID, arrayOf(atomCode)) + return I18nUtil.generateResponseDataObject( + messageCode = CommonMessageCode.PARAMETER_IS_INVALID, + params = arrayOf(atomCode), + language = I18nUtil.getLanguage(userId) + ) } val atomRecord = atomDao.getMaxVersionAtomByCode(dslContext, atomCode)!! + val atomPackageSourceType = getAtomPackageSourceType(atomRecord.repositoryHashId) + logger.info("updateMarketAtom atomPackageSourceType is :$atomPackageSourceType") val releaseType = marketAtomUpdateRequest.releaseType // 历史大版本下的小版本更新发布类型支持用户自定义分支,其他发布类型只支持master分支发布 val branch = if (marketAtomUpdateRequest.branch.isNullOrBlank() || @@ -331,8 +346,9 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ ) if (getAtomConfResult.errorCode != "0") { return I18nUtil.generateResponseDataObject( - getAtomConfResult.errorCode, - getAtomConfResult.errorParams + messageCode = getAtomConfResult.errorCode, + params = getAtomConfResult.errorParams, + language = I18nUtil.getLanguage(userId) ) } val taskJsonMap = getAtomConfResult.taskDataMap @@ -426,25 +442,37 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ logger.info("quality.json not found , skip...") } else if (getAtomQualityResult.errorCode != "0") { return I18nUtil.generateResponseDataObject( - getAtomQualityResult.errorCode, - getAtomQualityResult.errorParams + messageCode = getAtomQualityResult.errorCode, + params = getAtomQualityResult.errorParams, + language = I18nUtil.getLanguage(userId) ) } val atomEnvRequests = getAtomConfResult.atomEnvRequests ?: return I18nUtil.generateResponseDataObject( - StoreMessageCode.USER_REPOSITORY_TASK_JSON_FIELD_IS_NULL, arrayOf(KEY_EXECUTION) + messageCode = StoreMessageCode.USER_REPOSITORY_TASK_JSON_FIELD_IS_NULL, + params = arrayOf(KEY_EXECUTION), + language = I18nUtil.getLanguage(userId) ) - val propsMap = mutableMapOf() - propsMap[KEY_INPUT_GROUPS] = taskDataMap[KEY_INPUT_GROUPS] - propsMap[KEY_INPUT] = taskDataMap[KEY_INPUT] - propsMap[KEY_OUTPUT] = taskDataMap[KEY_OUTPUT] - propsMap[KEY_CONFIG] = taskDataMap[KEY_CONFIG] - val classType = if (convertUpdateRequest.os.isEmpty()) { + val packagePath = executionInfoMap[KEY_PACKAGE_PATH] as? String + val classType = if (packagePath.isNullOrBlank() && atomPackageSourceType == PackageSourceTypeEnum.UPLOAD) { + // 没有可执行文件的插件是老的内置插件,插件的classType为插件标识 + atomCode + } else if (convertUpdateRequest.os.isEmpty()) { MarketBuildLessAtomElement.classType } else { MarketBuildAtomElement.classType } + val propsMap = mutableMapOf() + val inputDataMap = taskDataMap[KEY_INPUT] as? Map + if (convertUpdateRequest.frontendType == FrontendTypeEnum.HISTORY) { + inputDataMap?.let { propsMap.putAll(inputDataMap) } + } else { + propsMap[KEY_INPUT_GROUPS] = taskDataMap[KEY_INPUT_GROUPS] + propsMap[KEY_INPUT] = inputDataMap + propsMap[KEY_OUTPUT] = taskDataMap[KEY_OUTPUT] + propsMap[KEY_CONFIG] = taskDataMap[KEY_CONFIG] + } convertUpdateRequest.os.sort() // 给操作系统排序 val atomStatus = if (atomPackageSourceType == PackageSourceTypeEnum.REPO) { @@ -467,7 +495,8 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ props = props, releaseType = releaseType.releaseType.toByte(), marketAtomUpdateRequest = convertUpdateRequest, - atomEnvRequests = atomEnvRequests + atomEnvRequests = atomEnvRequests, + repositoryHashId = atomRecord.repositoryHashId ) } else { // 升级插件 @@ -549,7 +578,8 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ props: String, releaseType: Byte, marketAtomUpdateRequest: MarketAtomUpdateRequest, - atomEnvRequests: List + atomEnvRequests: List, + repositoryHashId: String? ) { marketAtomDao.updateMarketAtom( dslContext = context, @@ -567,7 +597,7 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ releaseType = releaseType, versionContent = marketAtomUpdateRequest.versionContent ) - val atomPackageSourceType = getAtomPackageSourceType() + val atomPackageSourceType = getAtomPackageSourceType(repositoryHashId) if (atomPackageSourceType != PackageSourceTypeEnum.UPLOAD) { if (releaseType == ReleaseTypeEnum.CANCEL_RE_RELEASE.releaseType.toByte()) { marketAtomEnvInfoDao.deleteAtomEnvInfoById(context, atomId) @@ -589,11 +619,11 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ dslContext = dslContext, storeCode = atomCode, storeType = StoreTypeEnum.ATOM.type.toByte() - ) + ) ?: "" try { // 获取插件error.json文件内容 val errorJsonStr = getFileStr( - projectCode = projectCode!!, + projectCode = projectCode, atomCode = atomCode, atomVersion = atomVersion, fileName = ERROR_JSON_NAME, @@ -623,7 +653,7 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ projectCode = projectCode, errorCodes = errorCodes, fileDir = "$atomCode/$atomVersion", - i18nDir = StoreUtils.getStoreI18nDir(atomLanguage, getAtomPackageSourceType()), + i18nDir = StoreUtils.getStoreI18nDir(atomLanguage, getAtomPackageSourceType(repositoryHashId)), keyPrefix = "${StoreTypeEnum.ATOM.name}.$atomCode.$atomVersion", repositoryHashId = repositoryHashId ) @@ -916,7 +946,7 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ atomRecord = atomRecord, atomRequest = marketAtomUpdateRequest ) - val atomPackageSourceType = getAtomPackageSourceType() + val atomPackageSourceType = getAtomPackageSourceType(atomRecord.repositoryHashId) if (atomPackageSourceType != PackageSourceTypeEnum.UPLOAD) { marketAtomEnvInfoDao.addMarketAtomEnvInfo(context, atomId, atomEnvRequests) } @@ -938,7 +968,11 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ logger.info("getProcessInfo userId is $userId,atomId is $atomId") val record = marketAtomDao.getAtomRecordById(dslContext, atomId) return if (null == record) { - I18nUtil.generateResponseDataObject(CommonMessageCode.PARAMETER_IS_INVALID, arrayOf(atomId)) + I18nUtil.generateResponseDataObject( + messageCode = CommonMessageCode.PARAMETER_IS_INVALID, + params = arrayOf(atomId), + language = I18nUtil.getLanguage(userId) + ) } else { val atomCode = record.atomCode // 判断用户是否有查询权限 @@ -1031,7 +1065,8 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ ?: return I18nUtil.generateResponseDataObject( messageCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(atomId), - data = false + data = false, + language = I18nUtil.getLanguage(userId) ) val atomCode = atomRecord.atomCode // 查看当前版本之前的版本是否有已发布的,如果有已发布的版本则只是普通的升级操作而不需要审核 diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomArchiveServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomArchiveServiceImpl.kt index 83a4ff34335..cabc8f5e9fa 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomArchiveServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomArchiveServiceImpl.kt @@ -29,6 +29,7 @@ package com.tencent.devops.store.service.atom.impl import com.tencent.devops.artifactory.api.ServiceArchiveAtomResource import com.tencent.devops.common.api.constant.CommonMessageCode +import com.tencent.devops.common.api.enums.FrontendTypeEnum import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.JsonUtil @@ -39,12 +40,16 @@ import com.tencent.devops.store.dao.atom.MarketAtomDao import com.tencent.devops.store.dao.atom.MarketAtomEnvInfoDao import com.tencent.devops.store.dao.atom.MarketAtomVersionLogDao import com.tencent.devops.store.dao.common.StoreMemberDao +import com.tencent.devops.store.pojo.atom.AtomConfigInfo import com.tencent.devops.store.pojo.atom.AtomPkgInfoUpdateRequest import com.tencent.devops.store.pojo.atom.GetAtomConfigResult +import com.tencent.devops.store.pojo.atom.ReleaseInfo import com.tencent.devops.store.pojo.common.KEY_CONFIG +import com.tencent.devops.store.pojo.common.KEY_EXECUTION import com.tencent.devops.store.pojo.common.KEY_INPUT import com.tencent.devops.store.pojo.common.KEY_INPUT_GROUPS import com.tencent.devops.store.pojo.common.KEY_OUTPUT +import com.tencent.devops.store.pojo.common.KEY_RELEASE_INFO import com.tencent.devops.store.pojo.common.TASK_JSON_NAME import com.tencent.devops.store.pojo.common.enums.ReleaseTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum @@ -105,14 +110,7 @@ class MarketAtomArchiveServiceImpl : MarketAtomArchiveService { releaseType: ReleaseTypeEnum?, os: String? ): Result { - // 校验用户是否是该插件的开发成员 - val flag = storeMemberDao.isStoreMember(dslContext, userId, atomCode, StoreTypeEnum.ATOM.type.toByte()) - if (!flag) { - throw ErrorCodeException( - errorCode = CommonMessageCode.PERMISSION_DENIED, - params = arrayOf(atomCode) - ) - } + logger.info("verifyAtomPackageByUserId params[$userId|$projectCode|$atomCode|$version|$releaseType|$os]") val atomCount = atomDao.countByCode(dslContext, atomCode) if (atomCount < 0) { return I18nUtil.generateResponseDataObject( @@ -122,15 +120,30 @@ class MarketAtomArchiveServiceImpl : MarketAtomArchiveService { ) } val atomRecord = atomDao.getNewestAtomByCode(dslContext, atomCode)!! + if (atomRecord.classType != atomCode) { + // 校验用户是否是该插件的开发成员 + val flag = storeMemberDao.isStoreMember( + dslContext = dslContext, + userId = userId, + storeCode = atomCode, + storeType = StoreTypeEnum.ATOM.type.toByte() + ) + if (!flag) { + throw ErrorCodeException( + errorCode = CommonMessageCode.PERMISSION_DENIED, + params = arrayOf(atomCode) + ) + } + } // 不是重新上传的包才需要校验版本号 if (null != releaseType) { val osList = JsonUtil.getObjectMapper().readValue(os, ArrayList::class.java) as ArrayList val validateAtomVersionResult = marketAtomCommonService.validateAtomVersion( - atomRecord = atomRecord, - releaseType = releaseType, - osList = osList, - version = version - ) + atomRecord = atomRecord, + releaseType = releaseType, + osList = osList, + version = version + ) logger.info("validateAtomVersionResult is :$validateAtomVersionResult") if (validateAtomVersionResult.isNotOk()) { return validateAtomVersionResult @@ -210,17 +223,27 @@ class MarketAtomArchiveServiceImpl : MarketAtomArchiveService { return Result(true) } + @Suppress("UNCHECKED_CAST") override fun updateAtomPkgInfo( userId: String, atomId: String, atomPkgInfoUpdateRequest: AtomPkgInfoUpdateRequest ): Result { val taskDataMap = atomPkgInfoUpdateRequest.taskDataMap + val executionInfoMap = taskDataMap[KEY_EXECUTION] as Map val propsMap = mutableMapOf() - propsMap[KEY_INPUT_GROUPS] = taskDataMap[KEY_INPUT_GROUPS] - propsMap[KEY_INPUT] = taskDataMap[KEY_INPUT] - propsMap[KEY_OUTPUT] = taskDataMap[KEY_OUTPUT] - propsMap[KEY_CONFIG] = taskDataMap[KEY_CONFIG] + val releaseInfoMap = taskDataMap[KEY_RELEASE_INFO] as? Map + val configInfoMap = releaseInfoMap?.get(ReleaseInfo::configInfo.name) as? Map + val frontendType = configInfoMap?.get(AtomConfigInfo::frontendType.name) as? String + val inputDataMap = taskDataMap[KEY_INPUT] as? Map + if (frontendType == FrontendTypeEnum.HISTORY.name) { + inputDataMap?.let { propsMap.putAll(inputDataMap) } + } else { + propsMap[KEY_INPUT_GROUPS] = taskDataMap[KEY_INPUT_GROUPS] + propsMap[KEY_INPUT] = inputDataMap + propsMap[KEY_OUTPUT] = taskDataMap[KEY_OUTPUT] + propsMap[KEY_CONFIG] = taskDataMap[KEY_CONFIG] + } dslContext.transaction { t -> val context = DSL.using(t) val props = JsonUtil.toJson(propsMap) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomCommonServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomCommonServiceImpl.kt index 61c68cd5eea..1bc6edeb962 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomCommonServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomCommonServiceImpl.kt @@ -415,7 +415,7 @@ class MarketAtomCommonServiceImpl : MarketAtomCommonService { ) } val target = osExecutionInfoMap[KEY_TARGET] as? String - if (target.isNullOrBlank()) { + if (target == null) { // 执行入口为空则校验失败 throw ErrorCodeException( errorCode = StoreMessageCode.USER_REPOSITORY_TASK_JSON_FIELD_IS_NULL, @@ -438,7 +438,7 @@ class MarketAtomCommonServiceImpl : MarketAtomCommonService { pkgRepoPath = getPkgRepoPath(pkgLocalPath, projectCode, atomCode, version), language = language, minVersion = executionInfoMap[KEY_MINIMUM_VERSION] as? String, - target = osExecutionInfoMap[KEY_TARGET] as String, + target = target, shaContent = null, preCmd = JsonUtil.toJson(osExecutionInfoMap[KEY_DEMANDS] ?: ""), atomPostInfo = atomPostInfo, @@ -461,7 +461,7 @@ class MarketAtomCommonServiceImpl : MarketAtomCommonService { } } else { val target = executionInfoMap[KEY_TARGET] as? String - if (target.isNullOrBlank()) { + if (target == null) { // 执行入口为空则校验失败 throw ErrorCodeException( errorCode = StoreMessageCode.USER_REPOSITORY_TASK_JSON_FIELD_IS_NULL, @@ -475,7 +475,7 @@ class MarketAtomCommonServiceImpl : MarketAtomCommonService { pkgRepoPath = getPkgRepoPath(pkgLocalPath, projectCode, atomCode, version), language = language, minVersion = executionInfoMap[KEY_MINIMUM_VERSION] as? String, - target = executionInfoMap[KEY_TARGET] as String, + target = target, shaContent = null, preCmd = JsonUtil.toJson(executionInfoMap[KEY_DEMANDS] ?: ""), atomPostInfo = atomPostInfo, @@ -555,12 +555,12 @@ class MarketAtomCommonServiceImpl : MarketAtomCommonService { dslContext = dslContext, storeCode = atomCode, storeType = StoreTypeEnum.ATOM.type.toByte() - ) + ) ?: "" val atomRunInfo = AtomRunInfo( atomCode = atomCode, atomName = atom.name, version = atom.version, - initProjectCode = initProjectCode!!, + initProjectCode = initProjectCode, jobType = if (jobType == null) null else JobTypeEnum.valueOf(jobType), buildLessRunFlag = atom.buildLessRunFlag, inputTypeInfos = generateInputTypeInfos(atom.props) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomServiceImpl.kt index e14cd9aff6e..9f2c70bae6c 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/MarketAtomServiceImpl.kt @@ -1058,10 +1058,13 @@ abstract class MarketAtomServiceImpl @Autowired constructor() : MarketAtomServic ) } // 删除仓库插件包文件 - val initProjectCode = - storeProjectRelDao.getInitProjectCodeByStoreCode(dslContext, atomCode, StoreTypeEnum.ATOM.type.toByte()) + val initProjectCode = storeProjectRelDao.getInitProjectCodeByStoreCode( + dslContext = dslContext, + storeCode = atomCode, + storeType = StoreTypeEnum.ATOM.type.toByte() + ) ?: "" val deleteAtomFileResult = - client.get(ServiceArchiveAtomResource::class).deleteAtomFile(userId, initProjectCode!!, atomCode) + client.get(ServiceArchiveAtomResource::class).deleteAtomFile(userId, initProjectCode, atomCode) if (deleteAtomFileResult.isNotOk()) { return deleteAtomFileResult } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/OpAtomServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/OpAtomServiceImpl.kt index 8c39e6cfacb..fb9ef7f6f6f 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/OpAtomServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/atom/impl/OpAtomServiceImpl.kt @@ -70,6 +70,7 @@ import com.tencent.devops.store.pojo.common.PASS import com.tencent.devops.store.pojo.common.REJECT import com.tencent.devops.store.pojo.common.TASK_JSON_NAME import com.tencent.devops.store.pojo.common.enums.AuditTypeEnum +import com.tencent.devops.store.pojo.common.enums.PackageSourceTypeEnum import com.tencent.devops.store.pojo.common.enums.ReleaseTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.service.atom.AtomNotifyService @@ -391,30 +392,32 @@ class OpAtomServiceImpl @Autowired constructor( try { val taskJsonStr = taskJsonFile.readText(Charset.forName("UTF-8")) taskJsonMap = JsonUtil.toMap(taskJsonStr).toMutableMap() - val releaseInfoMap = taskJsonMap["releaseInfo"] + val releaseInfoMap = taskJsonMap[KEY_RELEASE_INFO] releaseInfo = JsonUtil.mapTo(releaseInfoMap as Map, ReleaseInfo::class.java) } catch (e: JsonProcessingException) { return I18nUtil.generateResponseDataObject( messageCode = StoreMessageCode.USER_REPOSITORY_TASK_JSON_FIELD_IS_INVALID, - params = arrayOf("releaseInfo"), + params = arrayOf(KEY_RELEASE_INFO), language = I18nUtil.getLanguage(userId) ) } - // 新增插件 - val addMarketAtomResult = atomReleaseService.addMarketAtom( - userId, - MarketAtomCreateRequest( - projectCode = releaseInfo.projectId, - atomCode = atomCode, - name = releaseInfo.name, - language = releaseInfo.language, - frontendType = releaseInfo.configInfo.frontendType + if (releaseInfo.versionInfo.releaseType == ReleaseTypeEnum.NEW) { + // 新增插件 + val addMarketAtomResult = atomReleaseService.addMarketAtom( + userId, + MarketAtomCreateRequest( + projectCode = releaseInfo.projectId, + atomCode = atomCode, + name = releaseInfo.name, + language = releaseInfo.language, + frontendType = releaseInfo.configInfo.frontendType, + packageSourceType = PackageSourceTypeEnum.UPLOAD + ) ) - ) - if (addMarketAtomResult.isNotOk()) { - return Result(data = false, message = addMarketAtomResult.message) + if (addMarketAtomResult.isNotOk()) { + return Result(data = false, message = addMarketAtomResult.message) + } } - val atomId = addMarketAtomResult.data!! // 远程logo资源不做处理 if (!releaseInfo.logoUrl.startsWith("http")) { // 解析logoUrl @@ -429,7 +432,7 @@ class OpAtomServiceImpl @Autowired constructor( val relativePath = logoUrlAnalysisResult.data val logoFile = File( "$atomPath${File.separator}file" + - "${File.separator}${relativePath?.removePrefix(File.separator)}" + "${File.separator}${relativePath?.removePrefix(File.separator)}" ) if (logoFile.exists()) { val result = storeLogoService.uploadStoreLogo( @@ -475,7 +478,6 @@ class OpAtomServiceImpl @Autowired constructor( val archiveAtomResult = AtomReleaseTxtAnalysisUtil.serviceArchiveAtomFile( userId = userId, projectCode = releaseInfo.projectId, - atomId = atomId, atomCode = atomCode, version = releaseInfo.versionInfo.version, serviceUrlPrefix = client.getServiceUrl(ServiceArchiveAtomFileResource::class), @@ -533,6 +535,10 @@ class OpAtomServiceImpl @Autowired constructor( message = updateMarketAtomResult.message ) } + if (releaseInfo.configInfo.defaultFlag) { + setDefault(userId, atomCode) + } + val atomId = updateMarketAtomResult.data!! // 确认测试通过 return atomReleaseService.passTest(userId, atomId) } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/common/impl/StoreIndexManageServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/common/impl/StoreIndexManageServiceImpl.kt index 169c7225e8a..19ab69fee1e 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/common/impl/StoreIndexManageServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/service/common/impl/StoreIndexManageServiceImpl.kt @@ -152,7 +152,7 @@ class StoreIndexManageServiceImpl @Autowired constructor( dslContext = dslContext, storeCode = atomCode, storeType = StoreTypeEnum.ATOM.type.toByte() - )!! + ) ?: "" val pipelineBuildInfo = client.get(ServiceBuildResource::class).getPipelineLatestBuildByIds( initProjectCode, listOf(pipelineId) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/utils/AtomReleaseTxtAnalysisUtil.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/utils/AtomReleaseTxtAnalysisUtil.kt index ab230e90b93..2da3463f509 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/utils/AtomReleaseTxtAnalysisUtil.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/utils/AtomReleaseTxtAnalysisUtil.kt @@ -197,7 +197,6 @@ object AtomReleaseTxtAnalysisUtil { fun serviceArchiveAtomFile( userId: String, projectCode: String, - atomId: String, atomCode: String, serviceUrlPrefix: String, releaseType: String, @@ -206,8 +205,8 @@ object AtomReleaseTxtAnalysisUtil { os: String ): Result { val serviceUrl = "$serviceUrlPrefix/service/artifactories/archiveAtom" + - "?userId=$userId&projectCode=$projectCode&atomId=$atomId&atomCode=$atomCode" + - "&version=$version&releaseType=$releaseType&os=$os" + "?userId=$userId&projectCode=$projectCode&atomCode=$atomCode" + + "&version=$version&releaseType=$releaseType&os=$os" OkhttpUtils.uploadFile(serviceUrl, file).use { response -> response.body!!.string() if (!response.isSuccessful) { diff --git a/src/backend/ci/core/stream/biz-stream/src/main/kotlin/com/tencent/devops/stream/service/StreamPipelineBadgeService.kt b/src/backend/ci/core/stream/biz-stream/src/main/kotlin/com/tencent/devops/stream/service/StreamPipelineBadgeService.kt index c7aaeb9d026..fe3403a620d 100644 --- a/src/backend/ci/core/stream/biz-stream/src/main/kotlin/com/tencent/devops/stream/service/StreamPipelineBadgeService.kt +++ b/src/backend/ci/core/stream/biz-stream/src/main/kotlin/com/tencent/devops/stream/service/StreamPipelineBadgeService.kt @@ -36,7 +36,6 @@ import org.jooq.DSLContext import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service -import java.lang.RuntimeException import java.net.URLDecoder @Service diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/AbstractBuildResourceApi.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/AbstractBuildResourceApi.kt index 2f3535a95ac..5c208b1d1ce 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/AbstractBuildResourceApi.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/AbstractBuildResourceApi.kt @@ -292,21 +292,11 @@ abstract class AbstractBuildResourceApi : WorkerRestApiSDK { map[AUTH_HEADER_DEVOPS_BUILD_TYPE] = buildType.name when (buildType) { - BuildType.AGENT -> { - map[AUTH_HEADER_DEVOPS_PROJECT_ID] = AgentEnv.getProjectId() - map[AUTH_HEADER_DEVOPS_AGENT_ID] = AgentEnv.getAgentId() - map[AUTH_HEADER_DEVOPS_AGENT_SECRET_KEY] = AgentEnv.getAgentSecretKey() - map[AUTH_HEADER_DEVOPS_PROJECT_ID] = AgentEnv.getProjectId() - map[AUTH_HEADER_DEVOPS_AGENT_ID] = AgentEnv.getAgentId() -// map[AUTH_HEADER_AGENT_SECRET_KEY] = AgentEnv.getAgentSecretKey() - } - - BuildType.DOCKER -> { + BuildType.DOCKER, BuildType.AGENT, BuildType.MACOS, BuildType.MACOS_NEW -> { map[AUTH_HEADER_DEVOPS_PROJECT_ID] = AgentEnv.getProjectId() map[AUTH_HEADER_DEVOPS_AGENT_ID] = AgentEnv.getAgentId() map[AUTH_HEADER_DEVOPS_AGENT_SECRET_KEY] = AgentEnv.getAgentSecretKey() } - else -> Unit } logger.info("Get the request header - $map") diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/AgentEnv.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/AgentEnv.kt index 84fd723024e..223e2b06c0b 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/AgentEnv.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/AgentEnv.kt @@ -47,19 +47,19 @@ object AgentEnv { private val logger = LoggerFactory.getLogger(AgentEnv::class.java) - private const val PROJECT_ID = "devops.project.id" - private const val DOCKER_PROJECT_ID = "devops_project_id" - private const val AGENT_ID = "devops.agent.id" - private const val DOCKER_AGENT_ID = "devops_agent_id" - private const val AGENT_SECRET_KEY = "devops.agent.secret.key" - private const val DOCKER_AGENT_SECRET_KEY = "devops_agent_secret_key" - private const val AGENT_GATEWAY = "landun.gateway" - private const val DOCKER_GATEWAY = "devops_gateway" - private const val AGENT_FILE_GATEWAY = "DEVOPS_FILE_GATEWAY" - private const val AGENT_ENV = "landun.env" - private const val AGENT_LOG_SAVE_MODE = "devops_log_save_mode" - private const val AGENT_PROPERTIES_FILE_NAME = ".agent.properties" - private const val BK_TAG = "devops_bk_tag" + const val PROJECT_ID = "devops.project.id" + const val DOCKER_PROJECT_ID = "devops_project_id" + const val AGENT_ID = "devops.agent.id" + const val DOCKER_AGENT_ID = "devops_agent_id" + const val AGENT_SECRET_KEY = "devops.agent.secret.key" + const val DOCKER_AGENT_SECRET_KEY = "devops_agent_secret_key" + const val AGENT_GATEWAY = "landun.gateway" + const val DOCKER_GATEWAY = "devops_gateway" + const val AGENT_FILE_GATEWAY = "DEVOPS_FILE_GATEWAY" + const val AGENT_ENV = "landun.env" + const val AGENT_LOG_SAVE_MODE = "devops_log_save_mode" + const val AGENT_PROPERTIES_FILE_NAME = ".agent.properties" + const val BK_TAG = "devops_bk_tag" private var projectId: String? = null private var agentId: String? = null @@ -80,9 +80,12 @@ object AgentEnv { if (projectId.isNullOrBlank()) { synchronized(this) { if (projectId.isNullOrBlank()) { - projectId = getProperty(if (isDockerEnv()) DOCKER_PROJECT_ID else PROJECT_ID) + projectId = getProperty(DOCKER_PROJECT_ID) if (projectId.isNullOrBlank()) { - throw PropertyNotExistException(PROJECT_ID, "Empty project Id") + projectId = getProperty(PROJECT_ID) + } + if (projectId.isNullOrBlank()) { + throw PropertyNotExistException("$PROJECT_ID|$DOCKER_PROJECT_ID", "Empty project Id") } logger.info("Get the project ID($projectId)") } @@ -95,9 +98,12 @@ object AgentEnv { if (agentId.isNullOrBlank()) { synchronized(this) { if (agentId.isNullOrBlank()) { - agentId = getProperty(if (isDockerEnv()) DOCKER_AGENT_ID else AGENT_ID) + agentId = getProperty(DOCKER_AGENT_ID) + if (agentId.isNullOrBlank()) { + agentId = getProperty(AGENT_ID) + } if (agentId.isNullOrBlank()) { - throw PropertyNotExistException(AGENT_ID, "Empty agent Id") + throw PropertyNotExistException("$AGENT_ID|$DOCKER_AGENT_ID", "Empty agent Id") } logger.info("Get the agent id($agentId)") } @@ -141,9 +147,12 @@ object AgentEnv { if (secretKey.isNullOrBlank()) { synchronized(this) { if (secretKey.isNullOrBlank()) { - secretKey = getProperty(if (isDockerEnv()) DOCKER_AGENT_SECRET_KEY else AGENT_SECRET_KEY) + secretKey = getProperty(DOCKER_AGENT_SECRET_KEY) if (secretKey.isNullOrBlank()) { - throw PropertyNotExistException(AGENT_SECRET_KEY, "Empty agent secret key") + secretKey = getProperty(AGENT_SECRET_KEY) + } + if (secretKey.isNullOrBlank()) { + throw PropertyNotExistException("$AGENT_SECRET_KEY|$DOCKER_AGENT_SECRET_KEY", "Empty agent secret key") } logger.info("Get the agent secret key($secretKey)") } @@ -157,7 +166,10 @@ object AgentEnv { synchronized(this) { if (gateway.isNullOrBlank()) { try { - gateway = getProperty(if (isDockerEnv()) DOCKER_GATEWAY else AGENT_GATEWAY) + gateway = getProperty(DOCKER_GATEWAY) + if (gateway.isNullOrBlank()) { + gateway = getProperty(AGENT_GATEWAY) + } if (gateway.isNullOrBlank()) { throw PropertyNotExistException(AGENT_GATEWAY, "Empty agent gateway") } @@ -223,7 +235,7 @@ object AgentEnv { private fun getProperty(prop: String): String? { val buildType = BuildEnv.getBuildType() - if (buildType == BuildType.DOCKER || buildType == BuildType.MACOS) { + if (buildType == BuildType.DOCKER || buildType == BuildType.MACOS || buildType == BuildType.MACOS_NEW) { logger.info("buildType is $buildType") return getEnvProp(prop) } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildEnv.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildEnv.kt index 7f4ff1af0a9..cf54f741386 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildEnv.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildEnv.kt @@ -47,6 +47,10 @@ object BuildEnv { return BuildType.valueOf(buildType!!) } + fun setBuildType(value: BuildType) { + this.buildType = value.name + } + fun isThirdParty() = getBuildType() == BuildType.AGENT fun getBuildId() = System.getProperty(BUILD_ID) diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildType.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildType.kt index 5f375d8b08d..be27cac83b2 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildType.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/env/BuildType.kt @@ -31,6 +31,7 @@ enum class BuildType { WORKER, AGENT, MACOS, + MACOS_NEW, DOCKER; companion object { diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/task/market/MarketAtomTask.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/task/market/MarketAtomTask.kt index fcce2304f90..ea4616adc70 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/task/market/MarketAtomTask.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/task/market/MarketAtomTask.kt @@ -510,7 +510,7 @@ open class MarketAtomTask : ITask() { private fun writeSdkEnv(workspace: File, buildTask: BuildTask, buildVariables: BuildVariables) { val inputFileFile = File(workspace, sdkFile) val sdkEnv: SdkEnv = when (BuildEnv.getBuildType()) { - BuildType.AGENT, BuildType.DOCKER, BuildType.MACOS -> { + BuildType.AGENT, BuildType.DOCKER, BuildType.MACOS, BuildType.MACOS_NEW -> { SdkEnv( buildType = BuildEnv.getBuildType(), projectId = buildVariables.projectId, diff --git a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js index a5276be380b..fd9a664b37c 100644 --- a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js +++ b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js @@ -1,2 +1,2 @@ /*! For license information please see bk-pipeline.min.js.LICENSE.txt */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("vue")):"function"==typeof define&&define.amd?define(["vue"],e):"object"==typeof exports?exports.bkPipeline=e(require("vue")):t.bkPipeline=e(t.Vue)}(self,(t=>(()=>{var e={8467:()=>{!function(){const t='';document.body?document.body.insertAdjacentHTML("afterbegin",t):document.addEventListener("DOMContentLoaded",(function(){document.body.insertAdjacentHTML("afterbegin",t)}))}()},8383:function(t,e){!function(t){"use strict";function e(){e=function(){return t};var t={},n=Object.prototype,i=n.hasOwnProperty,o=Object.defineProperty||function(t,e,n){t[e]=n.value},r="function"==typeof Symbol?Symbol:{},a=r.iterator||"@@iterator",s=r.asyncIterator||"@@asyncIterator",c=r.toStringTag||"@@toStringTag";function l(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{l({},"")}catch(t){l=function(t,e,n){return t[e]=n}}function p(t,e,n,i){var r=e&&e.prototype instanceof f?e:f,a=Object.create(r.prototype),s=new S(i||[]);return o(a,"_invoke",{value:k(t,n,s)}),a}function d(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(t){return{type:"throw",arg:t}}}t.wrap=p;var u={};function f(){}function m(){}function h(){}var b={};l(b,a,(function(){return this}));var g=Object.getPrototypeOf,v=g&&g(g(C([])));v&&v!==n&&i.call(v,a)&&(b=v);var y=h.prototype=f.prototype=Object.create(b);function x(t){["next","throw","return"].forEach((function(e){l(t,e,(function(t){return this._invoke(e,t)}))}))}function w(t,e){function n(o,r,a,s){var c=d(t[o],t,r);if("throw"!==c.type){var l=c.arg,p=l.value;return p&&"object"==typeof p&&i.call(p,"__await")?e.resolve(p.__await).then((function(t){n("next",t,a,s)}),(function(t){n("throw",t,a,s)})):e.resolve(p).then((function(t){l.value=t,a(l)}),(function(t){return n("throw",t,a,s)}))}s(c.arg)}var r;o(this,"_invoke",{value:function(t,i){function o(){return new e((function(e,o){n(t,i,e,o)}))}return r=r?r.then(o,o):o()}})}function k(t,e,n){var i="suspendedStart";return function(o,r){if("executing"===i)throw new Error("Generator is already running");if("completed"===i){if("throw"===o)throw r;return{value:void 0,done:!0}}for(n.method=o,n.arg=r;;){var a=n.delegate;if(a){var s=E(a,n);if(s){if(s===u)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===i)throw i="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);i="executing";var c=d(t,e,n);if("normal"===c.type){if(i=n.done?"completed":"suspendedYield",c.arg===u)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(i="completed",n.method="throw",n.arg=c.arg)}}}function E(t,e){var n=t.iterator[e.method];if(void 0===n){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=void 0,E(t,e),"throw"===e.method))return u;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return u}var i=d(n,t.iterator,e.arg);if("throw"===i.type)return e.method="throw",e.arg=i.arg,e.delegate=null,u;var o=i.arg;return o?o.done?(e[t.resultName]=o.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=void 0),e.delegate=null,u):o:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,u)}function T(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function I(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function S(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(T,this),this.reset(!0)}function C(t){if(t){var e=t[a];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var n=-1,o=function e(){for(;++n=0;--o){var r=this.tryEntries[o],a=r.completion;if("root"===r.tryLoc)return n("end");if(r.tryLoc<=this.prev){var s=i.call(r,"catchLoc"),c=i.call(r,"finallyLoc");if(s&&c){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&i.call(o,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),I(n),u}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var i=n.completion;if("throw"===i.type){var o=i.arg;I(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:C(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=void 0),u}},t}function n(t,e,n,i,o,r,a){try{var s=t[r](a),c=s.value}catch(t){return void n(t)}s.done?e(c):Promise.resolve(c).then(i,o)}var i=0;var o={name:"bk-checkbox",mixins:[{methods:{dispatch:function(t,e,n){for(var i=this.$parent||this.$root,o=i.$options.name;i&&(!o||o!==t);)(i=i.$parent)&&(o=i.$options.name);i&&i.$emit.apply(i,[e].concat(n))}}}],inject:{handleRemoveItem:{default:null},handleAddItem:{default:null}},props:{value:{type:[String,Number,Boolean],default:void 0},checked:{type:Boolean,default:void 0},trueValue:{type:[String,Number,Boolean],default:!0},falseValue:{type:[String,Number,Boolean],default:!1},label:{type:[String,Number]},name:{type:String,default:function(){return"bk-checkbox_".concat(i++)}},disabled:Boolean,indeterminate:Boolean,extCls:{type:String,default:""},beforeChange:Function},data:function(){return{parent:null,localValue:void 0}},computed:{selected:function(){return this.localValue===this.localTrueValue},parentValue:function(){return this.parent?this.parent.localValue:null},localTrueValue:function(){return this.parent?void 0===this.label&&void 0===this.value?this.trueValue:void 0!==this.value?this.value:this.label:void 0===this.label?this.trueValue:this.label}},watch:{value:function(t){this.setLocalValue(t)},checked:function(){this.setLocalValue()},parentValue:function(){this.setLocalValue()}},created:function(){this.handleAddItem&&"function"==typeof this.handleAddItem&&this.handleAddItem(this),this.init()},destroyed:function(){this.handleRemoveItem&&"function"==typeof this.handleRemoveItem&&this.handleRemoveItem(this)},methods:{getValue:function(){return this.selected?{isChecked:!0,value:this.localTrueValue}:{isChecked:!1,value:this.falseValue}},init:function(){for(var t=this.$parent;t&&!t.isCheckboxGroup&&"bk-checkbox-group"!==!t.$options.name;)t=t.$parent;this.parent=t,this.setLocalValue()},setLocalValue:function(t){if(void 0===t)if(this.parent){var e=this.localTrueValue,n=this.parent.localValue.includes(e);this.localValue=n?e:this.falseValue}else void 0!==this.checked?this.localValue=this.checked?this.localTrueValue:this.falseValue:void 0===this.value?this.localValue=this.falseValue:this.localValue=this.value;else this.localValue=t},handleClick:function(){var t=this;return function(t){return function(){var e=this,i=arguments;return new Promise((function(o,r){var a=t.apply(e,i);function s(t){n(a,o,r,s,c,"next",t)}function c(t){n(a,o,r,s,c,"throw",t)}s(void 0)}))}}(e().mark((function n(){var i,o,r;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!t.disabled){e.next=2;break}return e.abrupt("return",!1);case 2:if("function"!=typeof t.beforeChange){e.next=8;break}return e.next=5,t.beforeChange();case 5:if(!1!==e.sent){e.next=8;break}return e.abrupt("return");case 8:i=t.localValue,o=t.indeterminate?t.localTrueValue:i===t.localTrueValue?t.falseValue:t.localTrueValue,t.localValue=o,t.$emit("input",o,t.localTrueValue),t.$emit("change",o,i,t.localTrueValue),t.dispatch("bk-form-item","form-change"),t.parent&&(r=void 0===t.localTrueValue?o:t.localTrueValue,t.parent.handleChange(t.selected,r));case 15:case"end":return e.stop()}}),n)})))()}}};function r(t,e,n,i,o,r,a,s,c,l){"boolean"!=typeof a&&(c=s,s=a,a=!1);var p,d="function"==typeof n?n.options:n;if(t&&t.render&&(d.render=t.render,d.staticRenderFns=t.staticRenderFns,d._compiled=!0,o&&(d.functional=!0)),i&&(d._scopeId=i),r?(p=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),e&&e.call(this,c(t)),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=p):e&&(p=a?function(){e.call(this,l(this.$root.$options.shadowRoot))}:function(t){e.call(this,s(t))}),p)if(d.functional){var u=d.render;d.render=function(t,e){return p.call(e),u(t,e)}}else{var f=d.beforeCreate;d.beforeCreate=f?[].concat(f,p):[p]}return n}var a=r({render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("label",{staticClass:"bk-form-checkbox",class:[{"is-disabled":t.disabled,"is-indeterminate":t.indeterminate,"is-checked":t.selected},t.extCls],on:{click:t.handleClick,keydown:function(e){return!("button"in e)&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:(e.preventDefault(),t.handleClick(e))}}},[n("span",{staticClass:"bk-checkbox",attrs:{tabindex:!t.disabled&&0}}),n("input",{attrs:{type:"hidden",name:t.name},domProps:{value:void 0===t.label?t.localValue:t.label}}),t.$slots.default?n("span",{staticClass:"bk-checkbox-text"},[t._t("default")],2):t._e()])},staticRenderFns:[]},undefined,o,undefined,!1,undefined,!1,void 0,void 0,void 0),s=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},c={}.hasOwnProperty,l=function(t,e){return c.call(t,e)},p={}.toString,d=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==function(t){return p.call(t).slice(8,-1)}(t)?t.split(""):Object(t)},u=function(t){return d(s(t))},f=Math.ceil,m=Math.floor,h=function(t){return isNaN(t=+t)?0:(t>0?m:f)(t)},b=Math.min,g=Math.max,v=Math.min;function y(t,e){return t(e={exports:{}},e.exports),e.exports}var x,w,k=y((function(t){var e=t.exports={version:"2.6.12"};"number"==typeof __e&&(__e=e)})),E=(k.version,y((function(t){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)}))),T=y((function(t){var e="__core-js_shared__",n=E[e]||(E[e]={});(t.exports=function(t,e){return n[t]||(n[t]=void 0!==e?e:{})})("versions",[]).push({version:k.version,mode:"pure",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),I=0,S=Math.random(),C=T("keys"),O=(w=!1,function(t,e,n){var i,o,r=u(t),a=(o=r.length)>0?b(h(o),9007199254740991):0,s=function(t,e){return(t=h(t))<0?g(t+e,0):v(t,e)}(n,a);if(w&&e!=e){for(;a>s;)if((i=r[s++])!=i)return!0}else for(;a>s;s++)if((w||s in r)&&r[s]===e)return w||s||0;return!w&&-1}),A=C[x="IE_PROTO"]||(C[x]=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++I+S).toString(36))}(x)),_="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),L=Object.keys||function(t){return function(t,e){var n,i=u(t),o=0,r=[];for(n in i)n!=A&&l(i,n)&&r.push(n);for(;e.length>o;)l(i,n=e[o++])&&(~O(r,n)||r.push(n));return r}(t,_)},M=function(t,e,n){if(function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!")}(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)}}return function(){return t.apply(e,arguments)}},N=function(t){return"object"==typeof t?null!==t:"function"==typeof t},R=function(t){if(!N(t))throw TypeError(t+" is not an object!");return t},D=function(t){try{return!!t()}catch(t){return!0}},z=!D((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})),P=E.document,H=N(P)&&N(P.createElement),F=!z&&!D((function(){return 7!=Object.defineProperty((t="div",H?P.createElement(t):{}),"a",{get:function(){return 7}}).a;var t})),j=Object.defineProperty,B={f:z?Object.defineProperty:function(t,e,n){if(R(t),e=function(t,e){if(!N(t))return t;var n,i;if(e&&"function"==typeof(n=t.toString)&&!N(i=n.call(t)))return i;if("function"==typeof(n=t.valueOf)&&!N(i=n.call(t)))return i;if(!e&&"function"==typeof(n=t.toString)&&!N(i=n.call(t)))return i;throw TypeError("Can't convert object to primitive value")}(e,!0),R(n),F)try{return j(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},U=z?function(t,e,n){return B.f(t,e,function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}(1,n))}:function(t,e,n){return t[e]=n,t},V="prototype",Y=function(t,e,n){var i,o,r,a=t&Y.F,s=t&Y.G,c=t&Y.S,p=t&Y.P,d=t&Y.B,u=t&Y.W,f=s?k:k[e]||(k[e]={}),m=f[V],h=s?E:c?E[e]:(E[e]||{})[V];for(i in s&&(n=e),n)(o=!a&&h&&void 0!==h[i])&&l(f,i)||(r=o?h[i]:n[i],f[i]=s&&"function"!=typeof h[i]?n[i]:d&&o?M(r,E):u&&h[i]==r?function(t){var e=function(e,n,i){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(e);case 2:return new t(e,n)}return new t(e,n,i)}return t.apply(this,arguments)};return e[V]=t[V],e}(r):p&&"function"==typeof r?M(Function.call,r):r,p&&((f.virtual||(f.virtual={}))[i]=r,t&Y.R&&m&&!m[i]&&U(m,i,r)))};Y.F=1,Y.G=2,Y.S=4,Y.P=8,Y.B=16,Y.W=32,Y.U=64,Y.R=128;var W,$,G,X,q=Y;W="keys",$=function(){return function(t){return L(function(t){return Object(s(t))}(t))}},G=(k.Object||{})[W]||Object[W],(X={})[W]=$(),q(q.S+q.F*D((function(){G(1)})),"Object",X);var K,Z,Q=k.Object.keys;(K=a).install=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=K.props||{};Q(e).forEach((function(t){n.hasOwnProperty(t)&&("function"==typeof n[t]||n[t]instanceof Array?n[t]={type:n[t],default:e[t]}:n[t].default=e[t])})),K.name=e.namespace?K.name.replace("bk",e.namespace):K.name,t.component(K.name,K),"function"==typeof Z&&Z(t,e)},t.default=a,Object.defineProperty(t,"__esModule",{value:!0})}(e)},1409:function(t,e,n){!function(t,e){"use strict";function i(t){return i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){for(var n=0;n>>0||(H.test(n)?16:10))}:z;C(C.G+C.F*(parseInt!=F),{parseInt:F});var j=p.parseInt,B={}.toString,U=function(t){return B.call(t).slice(8,-1)},V=Array.isArray||function(t){return"Array"==U(t)};C(C.S,"Array",{isArray:V});var Y,W,$,G,X,q,K=p.Array.isArray,Z=function(t){return Object(O(t))},Q=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==U(t)?t.split(""):Object(t)},J=function(t){return Q(O(t))},tt=Math.ceil,et=Math.floor,nt=function(t){return isNaN(t=+t)?0:(t>0?et:tt)(t)},it=Math.min,ot=Math.max,rt=Math.min,at=c((function(t){var e="__core-js_shared__",n=l[e]||(l[e]={});(t.exports=function(t,e){return n[t]||(n[t]=void 0!==e?e:{})})("versions",[]).push({version:p.version,mode:"pure",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),st=0,ct=Math.random(),lt=at("keys"),pt=(q=!1,function(t,e,n){var i,o,r=J(t),a=(o=r.length)>0?it(nt(o),9007199254740991):0,s=function(t,e){return(t=nt(t))<0?ot(t+e,0):rt(t,e)}(n,a);if(q&&e!=e){for(;a>s;)if((i=r[s++])!=i)return!0}else for(;a>s;s++)if((q||s in r)&&r[s]===e)return q||s||0;return!q&&-1}),dt=lt[X="IE_PROTO"]||(lt[X]=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++st+ct).toString(36))}(X)),ut="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),ft=Object.keys||function(t){return function(t,e){var n,i=J(t),o=0,r=[];for(n in i)n!=dt&&T(i,n)&&r.push(n);for(;e.length>o;)T(i,n=e[o++])&&(~pt(r,n)||r.push(n));return r}(t,ut)};Y="keys",W=function(){return function(t){return ft(Z(t))}},$=(p.Object||{})[Y]||Object[Y],(G={})[Y]=W(),C(C.S+C.F*h((function(){$(1)})),"Object",G);var mt=p.Object.keys,ht={f:Object.getOwnPropertySymbols},bt={f:{}.propertyIsEnumerable},gt=Object.assign,vt=!gt||h((function(){var t={},e={},n=Symbol(),i="abcdefghijklmnopqrst";return t[n]=7,i.split("").forEach((function(t){e[t]=t})),7!=gt({},t)[n]||Object.keys(gt({},e)).join("")!=i}))?function(t,e){for(var n=Z(t),i=arguments.length,o=1,r=ht.f,a=bt.f;i>o;)for(var s,c=Q(arguments[o++]),l=r?ft(c).concat(r(c)):ft(c),p=l.length,d=0;p>d;)s=l[d++],b&&!a.call(c,s)||(n[s]=c[s]);return n}:gt;C(C.S+C.F,"Object",{assign:vt});var yt=p.Object.assign,xt=l.parseFloat,wt=D.trim,kt=1/xt(A+"-0")!=-1/0?function(t){var e=wt(String(t),3),n=xt(e);return 0===n&&"-"==e.charAt(0)?-0:n}:xt;C(C.G+C.F*(parseFloat!=kt),{parseFloat:kt});for(var Et=p.parseFloat,Tt="undefined"!=typeof window&&"undefined"!=typeof document,It=["Edge","Trident","Firefox"],St=0,Ct=0;Ct=0){St=1;break}var Ot=Tt&&window.Promise?function(t){var e=!1;return function(){e||(e=!0,window.Promise.resolve().then((function(){e=!1,t()})))}}:function(t){var e=!1;return function(){e||(e=!0,setTimeout((function(){e=!1,t()}),St))}};function At(t){return t&&"[object Function]"==={}.toString.call(t)}function _t(t,e){if(1!==t.nodeType)return[];var n=t.ownerDocument.defaultView.getComputedStyle(t,null);return e?n[e]:n}function Lt(t){return"HTML"===t.nodeName?t:t.parentNode||t.host}function Mt(t){if(!t)return document.body;switch(t.nodeName){case"HTML":case"BODY":return t.ownerDocument.body;case"#document":return t.body}var e=_t(t),n=e.overflow,i=e.overflowX,o=e.overflowY;return/(auto|scroll|overlay)/.test(n+o+i)?t:Mt(Lt(t))}var Nt=Tt&&!(!window.MSInputMethodContext||!document.documentMode),Rt=Tt&&/MSIE 10/.test(navigator.userAgent);function Dt(t){return 11===t?Nt:10===t?Rt:Nt||Rt}function zt(t){if(!t)return document.documentElement;for(var e=Dt(10)?document.body:null,n=t.offsetParent||null;n===e&&t.nextElementSibling;)n=(t=t.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&"BODY"!==i&&"HTML"!==i?-1!==["TH","TD","TABLE"].indexOf(n.nodeName)&&"static"===_t(n,"position")?zt(n):n:t?t.ownerDocument.documentElement:document.documentElement}function Pt(t){return null!==t.parentNode?Pt(t.parentNode):t}function Ht(t,e){if(!(t&&t.nodeType&&e&&e.nodeType))return document.documentElement;var n=t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING,i=n?t:e,o=n?e:t,r=document.createRange();r.setStart(i,0),r.setEnd(o,0);var a,s,c=r.commonAncestorContainer;if(t!==c&&e!==c||i.contains(o))return"BODY"===(s=(a=c).nodeName)||"HTML"!==s&&zt(a.firstElementChild)!==a?zt(c):c;var l=Pt(t);return l.host?Ht(l.host,e):Ht(t,Pt(e).host)}function Ft(t){var e="top"===(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top")?"scrollTop":"scrollLeft",n=t.nodeName;if("BODY"===n||"HTML"===n){var i=t.ownerDocument.documentElement;return(t.ownerDocument.scrollingElement||i)[e]}return t[e]}function jt(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=Ft(e,"top"),o=Ft(e,"left"),r=n?-1:1;return t.top+=i*r,t.bottom+=i*r,t.left+=o*r,t.right+=o*r,t}function Bt(t,e){var n="x"===e?"Left":"Top",i="Left"===n?"Right":"Bottom";return Et(t["border".concat(n,"Width")],10)+Et(t["border".concat(i,"Width")],10)}function Ut(t,e,n,i){return Math.max(e["offset".concat(t)],e["scroll".concat(t)],n["client".concat(t)],n["offset".concat(t)],n["scroll".concat(t)],Dt(10)?j(n["offset".concat(t)])+j(i["margin".concat("Height"===t?"Top":"Left")])+j(i["margin".concat("Height"===t?"Bottom":"Right")]):0)}function Vt(t){var e=t.body,n=t.documentElement,i=Dt(10)&&getComputedStyle(n);return{height:Ut("Height",e,n,i),width:Ut("Width",e,n,i)}}var Yt=yt||function(t){for(var e=1;e2&&void 0!==arguments[2]&&arguments[2],i=Dt(10),o="HTML"===e.nodeName,r=$t(t),a=$t(e),s=Mt(t),c=_t(e),l=Et(c.borderTopWidth,10),p=Et(c.borderLeftWidth,10);n&&o&&(a.top=Math.max(a.top,0),a.left=Math.max(a.left,0));var d=Wt({top:r.top-a.top-l,left:r.left-a.left-p,width:r.width,height:r.height});if(d.marginTop=0,d.marginLeft=0,!i&&o){var u=Et(c.marginTop,10),f=Et(c.marginLeft,10);d.top-=l-u,d.bottom-=l-u,d.left-=p-f,d.right-=p-f,d.marginTop=u,d.marginLeft=f}return(i&&!n?e.contains(s):e===s&&"BODY"!==s.nodeName)&&(d=jt(d,e)),d}function Xt(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t.ownerDocument.documentElement,i=Gt(t,n),o=Math.max(n.clientWidth,window.innerWidth||0),r=Math.max(n.clientHeight,window.innerHeight||0),a=e?0:Ft(n),s=e?0:Ft(n,"left");return Wt({top:a-i.top+i.marginTop,left:s-i.left+i.marginLeft,width:o,height:r})}function qt(t){var e=t.nodeName;if("BODY"===e||"HTML"===e)return!1;if("fixed"===_t(t,"position"))return!0;var n=Lt(t);return!!n&&qt(n)}function Kt(t){if(!t||!t.parentElement||Dt())return document.documentElement;for(var e=t.parentElement;e&&"none"===_t(e,"transform");)e=e.parentElement;return e||document.documentElement}function Zt(t,e,n,i){var o=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r={top:0,left:0},a=o?Kt(t):Ht(t,e);if("viewport"===i)r=Xt(a,o);else{var s;"scrollParent"===i?"BODY"===(s=Mt(Lt(e))).nodeName&&(s=t.ownerDocument.documentElement):s="window"===i?t.ownerDocument.documentElement:i;var c=Gt(s,a,o);if("HTML"!==s.nodeName||qt(a))r=c;else{var l=Vt(t.ownerDocument),p=l.height,d=l.width;r.top+=c.top-c.marginTop,r.bottom=p+c.top,r.left+=c.left-c.marginLeft,r.right=d+c.left}}var u="number"==typeof(n=n||0);return r.left+=u?n:n.left||0,r.top+=u?n:n.top||0,r.right-=u?n:n.right||0,r.bottom-=u?n:n.bottom||0,r}function Qt(t){return t.width*t.height}function Jt(t,e,n,i,o){var r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===t.indexOf("auto"))return t;var a=Zt(n,i,r,o),s={top:{width:a.width,height:e.top-a.top},right:{width:a.right-e.right,height:a.height},bottom:{width:a.width,height:a.bottom-e.bottom},left:{width:e.left-a.left,height:a.height}},c=mt(s).map((function(t){return Yt({key:t},s[t],{area:Qt(s[t])})})).sort((function(t,e){return e.area-t.area})),l=c.filter((function(t){var e=t.width,i=t.height;return e>=n.clientWidth&&i>=n.clientHeight})),p=l.length>0?l[0].key:c[0].key,d=t.split("-")[1];return p+(d?"-".concat(d):"")}function te(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;return Gt(n,i?Kt(e):Ht(e,n),i)}function ee(t){var e=t.ownerDocument.defaultView.getComputedStyle(t),n=Et(e.marginTop||0)+Et(e.marginBottom||0),i=Et(e.marginLeft||0)+Et(e.marginRight||0);return{width:t.offsetWidth+i,height:t.offsetHeight+n}}function ne(t){var e={left:"right",right:"left",bottom:"top",top:"bottom"};return t.replace(/left|right|bottom|top/g,(function(t){return e[t]}))}function ie(t,e,n){n=n.split("-")[0];var i=ee(t),o={width:i.width,height:i.height},r=-1!==["right","left"].indexOf(n),a=r?"top":"left",s=r?"left":"top",c=r?"height":"width",l=r?"width":"height";return o[a]=e[a]+e[c]/2-i[c]/2,o[s]=n===s?e[s]-i[l]:e[ne(s)],o}function oe(t,e){return Array.prototype.find?t.find(e):t.filter(e)[0]}function re(t,e,n){return(void 0===n?t:t.slice(0,function(t,e,n){if(Array.prototype.findIndex)return t.findIndex((function(t){return t[e]===n}));var i=oe(t,(function(t){return t[e]===n}));return t.indexOf(i)}(t,"name",n))).forEach((function(t){t.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=t.function||t.fn;t.enabled&&At(n)&&(e.offsets.popper=Wt(e.offsets.popper),e.offsets.reference=Wt(e.offsets.reference),e=n(e,t))})),e}function ae(){if(!this.state.isDestroyed){var t={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};t.offsets.reference=te(this.state,this.popper,this.reference,this.options.positionFixed),t.placement=Jt(this.options.placement,t.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),t.originalPlacement=t.placement,t.positionFixed=this.options.positionFixed,t.offsets.popper=ie(this.popper,t.offsets.reference,t.placement),t.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",t=re(this.modifiers,t),this.state.isCreated?this.options.onUpdate(t):(this.state.isCreated=!0,this.options.onCreate(t))}}function se(t,e){return t.some((function(t){var n=t.name;return t.enabled&&n===e}))}function ce(t){for(var e=[!1,"ms","Webkit","Moz","O"],n=t.charAt(0).toUpperCase()+t.slice(1),i=0;i1&&void 0!==arguments[1]&&arguments[1],n=xe.indexOf(t),i=xe.slice(n+1).concat(xe.slice(0,n));return e?i.reverse():i}var ke="flip",Ee="clockwise",Te="counterclockwise";function Ie(t,e,n,i){var o=[0,0],r=-1!==["right","left"].indexOf(i),a=t.split(/(\+|\-)/).map((function(t){return t.trim()})),s=a.indexOf(oe(a,(function(t){return-1!==t.search(/,|\s/)})));a[s]&&-1===a[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var c=/\s*,\s*|\s+/,l=-1!==s?[a.slice(0,s).concat([a[s].split(c)[0]]),[a[s].split(c)[1]].concat(a.slice(s+1))]:[a];return(l=l.map((function(t,i){var o=(1===i?!r:r)?"height":"width",a=!1;return t.reduce((function(t,e){return""===t[t.length-1]&&-1!==["+","-"].indexOf(e)?(t[t.length-1]=e,a=!0,t):a?(t[t.length-1]+=e,a=!1,t):t.concat(e)}),[]).map((function(t){return function(t,e,n,i){var o=t.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+o[1],a=o[2];return r?0===a.indexOf("%")?Wt("%p"===a?n:i)[e]/100*r:"vh"===a||"vw"===a?("vh"===a?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*r:r:t}(t,o,e,n)}))}))).forEach((function(t,e){t.forEach((function(n,i){he(n)&&(o[e]+=n*("-"===t[i-1]?-1:1))}))})),o}var Se={placement:"bottom",positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(t){var e=t.placement,n=e.split("-")[0],i=e.split("-")[1];if(i){var o=t.offsets,r=o.reference,s=o.popper,c=-1!==["bottom","top"].indexOf(n),l=c?"left":"top",p=c?"width":"height",d={start:a({},l,r[l]),end:a({},l,r[l]+r[p]-s[p])};t.offsets.popper=Yt({},s,d[i])}return t}},offset:{order:200,enabled:!0,fn:function(t,e){var n,i=e.offset,o=t.placement,r=t.offsets,a=r.popper,s=r.reference,c=o.split("-")[0];return n=he(+i)?[+i,0]:Ie(i,a,s,c),"left"===c?(a.top+=n[0],a.left-=n[1]):"right"===c?(a.top+=n[0],a.left+=n[1]):"top"===c?(a.left+=n[0],a.top-=n[1]):"bottom"===c&&(a.left+=n[0],a.top+=n[1]),t.popper=a,t},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(t,e){var n=e.boundariesElement||zt(t.instance.popper);t.instance.reference===n&&(n=zt(n));var i=ce("transform"),o=t.instance.popper.style,r=o.top,s=o.left,c=o[i];o.top="",o.left="",o[i]="";var l=Zt(t.instance.popper,t.instance.reference,e.padding,n,t.positionFixed);o.top=r,o.left=s,o[i]=c,e.boundaries=l;var p=e.priority,d=t.offsets.popper,u={primary:function(t){var n=d[t];return d[t]l[t]&&!e.escapeWithReference&&(i=Math.min(d[n],l[t]-("right"===t?d.width:d.height))),a({},n,i)}};return p.forEach((function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";d=Yt({},d,u[e](t))})),t.offsets.popper=d,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,a=-1!==["top","bottom"].indexOf(o),s=a?"right":"bottom",c=a?"left":"top",l=a?"width":"height";return n[s]r(i[s])&&(t.offsets.popper[c]=r(i[s])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!ve(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,s=r.popper,c=r.reference,l=-1!==["left","right"].indexOf(o),p=l?"height":"width",d=l?"Top":"Left",u=d.toLowerCase(),f=l?"left":"top",m=l?"bottom":"right",h=ee(i)[p];c[m]-hs[m]&&(t.offsets.popper[u]+=c[u]+h-s[m]),t.offsets.popper=Wt(t.offsets.popper);var b=c[u]+c[p]/2-h/2,g=_t(t.instance.popper),v=Et(g["margin".concat(d)],10),y=Et(g["border".concat(d,"Width")],10),x=b-t.offsets.popper[u]-v-y;return x=Math.max(Math.min(s[p]-h,x),0),t.arrowElement=i,t.offsets.arrow=(a(n={},u,Math.round(x)),a(n,f,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(t,e){if(se(t.instance.modifiers,"inner"))return t;if(t.flipped&&t.placement===t.originalPlacement)return t;var n=Zt(t.instance.popper,t.instance.reference,e.padding,e.boundariesElement,t.positionFixed),i=t.placement.split("-")[0],o=ne(i),r=t.placement.split("-")[1]||"",a=[];switch(e.behavior){case ke:a=[i,o];break;case Ee:a=we(i);break;case Te:a=we(i,!0);break;default:a=e.behavior}return a.forEach((function(s,c){if(i!==s||a.length===c+1)return t;i=t.placement.split("-")[0],o=ne(i);var l=t.offsets.popper,p=t.offsets.reference,d=Math.floor,u="left"===i&&d(l.right)>d(p.left)||"right"===i&&d(l.left)d(p.top)||"bottom"===i&&d(l.top)d(n.right),h=d(l.top)d(n.bottom),g="left"===i&&f||"right"===i&&m||"top"===i&&h||"bottom"===i&&b,v=-1!==["top","bottom"].indexOf(i),y=!!e.flipVariations&&(v&&"start"===r&&f||v&&"end"===r&&m||!v&&"start"===r&&h||!v&&"end"===r&&b),x=!!e.flipVariationsByContent&&(v&&"start"===r&&m||v&&"end"===r&&f||!v&&"start"===r&&b||!v&&"end"===r&&h),w=y||x;(u||g||w)&&(t.flipped=!0,(u||g)&&(i=a[c+1]),w&&(r=function(t){return"end"===t?"start":"start"===t?"end":t}(r)),t.placement=i+(r?"-"+r:""),t.offsets.popper=Yt({},t.offsets.popper,ie(t.instance.popper,t.offsets.reference,t.placement)),t=re(t.instance.modifiers,t,"flip"))})),t},behavior:"flip",padding:5,boundariesElement:"viewport",flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,a=-1!==["left","right"].indexOf(n),s=-1===["top","left"].indexOf(n);return o[a?"left":"top"]=r[n]-(s?o[a?"width":"height"]:0),t.placement=ne(e),t.offsets.popper=Wt(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!ve(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=oe(t.instance.modifiers,(function(t){return"preventOverflow"===t.name})).boundaries;if(e.bottomn.right||e.top>n.bottom||e.right2&&void 0!==arguments[2]?arguments[2]:{};o(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=Ot(this.update.bind(this)),this.options=Yt({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=e&&e.jquery?e[0]:e,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},mt(Yt({},t.Defaults.modifiers,r.modifiers)).forEach((function(e){i.options.modifiers[e]=Yt({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})})),this.modifiers=mt(this.options.modifiers).map((function(t){return Yt({name:t},i.options.modifiers[t])})).sort((function(t,e){return t.order-e.order})),this.modifiers.forEach((function(t){t.enabled&&At(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)})),this.update();var a=this.options.eventsEnabled;a&&this.enableEventListeners(),this.state.eventsEnabled=a}return e=t,(n=[{key:"update",value:function(){return ae.call(this)}},{key:"destroy",value:function(){return le.call(this)}},{key:"enableEventListeners",value:function(){return fe.call(this)}},{key:"disableEventListeners",value:function(){return me.call(this)}}])&&r(e.prototype,n),i&&r(e,i),Object.defineProperty(e,"prototype",{writable:!1}),t;var e,n,i}();function Oe(){return Oe=yt||function(t){for(var e=1;e-1}function gn(t){return t instanceof Element}function vn(t){return!(!t||!un(t,"isVirtual"))||gn(t)}function yn(t,e){return"function"==typeof t?t.apply(null,e):t}function xn(t,e){t.filter((function(t){return"flip"===t.name}))[0].enabled=e}function wn(){return document.createElement("div")}function kn(t,e){t.forEach((function(t){t&&(t.style.transitionDuration="".concat(e,"ms"))}))}function En(t,e){t.forEach((function(t){t&&t.setAttribute("data-state",e)}))}function Tn(t,e){var n=Oe({},e,{content:yn(e.content,[t])},e.ignoreAttributes?{}:function(t){return pn.reduce((function(e,n){var i=(t.getAttribute("data-tippy-".concat(n))||"").trim();if(!i)return e;if("content"===n)e[n]=i;else try{e[n]=JSON.parse(i)}catch(t){e[n]=i}return e}),{})}(t));return(n.arrow||Me)&&(n.animateFill=!1),n}function In(t,e){mt(t).forEach((function(t){if(!un(e,t))throw new Error("[tippy]: `".concat(t,"` is not a valid option"))}))}function Sn(t,e){t.innerHTML=gn(e)?e.innerHTML:e}function Cn(t,e){gn(e.content)?(Sn(t,""),t.appendChild(e.content)):"function"!=typeof e.content&&(t[e.allowHTML?"innerHTML":"textContent"]=e.content)}function On(t){return{tooltip:t.querySelector(Je),backdrop:t.querySelector(en),content:t.querySelector(tn),arrow:t.querySelector(nn)||t.querySelector(on)}}function An(t){t.setAttribute("data-inertia","")}function _n(t){var e=wn();return"round"===t?(e.className=Ze,Sn(e,'')):e.className=Ke,e}function Ln(){var t=wn();return t.className=qe,t.setAttribute("data-state","hidden"),t}function Mn(t,e){t.setAttribute("tabindex","-1"),e.setAttribute("data-interactive","")}function Nn(t,e,n){var i=Me&&void 0!==document.body.style.webkitTransition?"webkitTransitionEnd":"transitionend";t[e+"EventListener"](i,n)}function Rn(t){var e=t.getAttribute(Ue);return e?e.split("-")[0]:""}function Dn(t,e,n){n.split(" ").forEach((function(n){t.classList[e](n+"-theme")}))}function zn(t,e,n){var i=e[n],o=j(i);"number"==typeof i?t.style[n]=i+"px":isNaN(o)?t.style[n]=i:t.style[n]=o+"px"}function Pn(t,e){var n=wn();n.className=$e+(e.extCls?" ".concat(e.extCls):""),n.id="tippy-".concat(t),n.style.zIndex=""+e.zIndex,n.style.position="absolute",n.style.top="0",n.style.left="0",e.role&&n.setAttribute("role",e.role);var i=wn();i.className=Ge,zn(i,e,"maxWidth"),zn(i,e,"width"),i.setAttribute("data-size",e.size),i.setAttribute("data-animation",e.animation),i.setAttribute("data-state","hidden"),Dn(i,"add",e.theme);var o=wn();return o.className=Xe,o.setAttribute("data-state","hidden"),e.interactive&&Mn(n,i),e.arrow&&i.appendChild(_n(e.arrowType)),e.animateFill&&(i.appendChild(Ln()),i.setAttribute("data-animatefill","")),e.inertia&&An(i),Cn(o,e),i.appendChild(o),n.appendChild(i),n}function Hn(t,e,n){var i=On(t),o=i.tooltip,r=i.content,a=i.backdrop,s=i.arrow;t.style.zIndex=""+n.zIndex,o.setAttribute("data-size",n.size),o.setAttribute("data-animation",n.animation),o.style.maxWidth=n.maxWidth+("number"==typeof n.maxWidth?"px":""),n.role?t.setAttribute("role",n.role):t.removeAttribute("role"),e.content!==n.content&&Cn(r,n),!e.animateFill&&n.animateFill?(o.appendChild(Ln()),o.setAttribute("data-animatefill","")):e.animateFill&&!n.animateFill&&(o.removeChild(a),o.removeAttribute("data-animatefill")),!e.arrow&&n.arrow?o.appendChild(_n(n.arrowType)):e.arrow&&!n.arrow&&o.removeChild(s),e.arrow&&n.arrow&&e.arrowType!==n.arrowType&&o.replaceChild(_n(n.arrowType),s),!e.interactive&&n.interactive?Mn(t,o):e.interactive&&!n.interactive&&function(t,e){t.removeAttribute("tabindex"),e.removeAttribute("data-interactive")}(t,o),!e.inertia&&n.inertia?An(o):e.inertia&&!n.inertia&&function(t){t.removeAttribute("data-inertia")}(o),e.theme!==n.theme&&(Dn(o,"remove",e.theme),Dn(o,"add",n.theme))}var Fn=1,jn=[];function Bn(t,e){var n,i,o,r,a,s=Tn(t,e);if(!s.multiple&&t._tippy)return null;var c,l,p,d,u,f=!1,m=!1,h=!1,b=!1,g=[],v=mn(F,s.interactiveDebounce),y=Fn++,x=Pn(y,s),w=On(x),k={id:y,reference:t,popper:x,popperChildren:w,popperInstance:null,props:s,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},clearDelayTimeouts:Z,set:Q,setContent:function(t){Q({content:t})},show:J,hide:tt,enable:function(){k.state.isEnabled=!0},disable:function(){k.state.isEnabled=!1},destroy:function(e){if(!k.state.isDestroyed){m=!0,k.state.isMounted&&tt(0),z(),delete t._tippy;var n=k.props.target;n&&e&&gn(t)&&He(t.querySelectorAll(n)).forEach((function(t){t._tippy&&t._tippy.destroy()})),k.popperInstance&&k.popperInstance.destroy(),m=!1,k.state.isDestroyed=!0}}};return t._tippy=k,x._tippy=k,D(),s.lazy||$(),s.showOnInit&&X(),!s.a11y||s.target||(!gn(u=I())||Pe.call(u,"a[href],area[href],button,details,input,textarea,select,iframe,[tabindex]")&&!u.hasAttribute("disabled"))||I().setAttribute("tabindex","0"),x.addEventListener("mouseenter",(function(t){k.props.interactive&&k.state.isVisible&&"mouseenter"===n&&X(t,!0)})),x.addEventListener("mouseleave",(function(){k.props.interactive&&"mouseenter"===n&&document.addEventListener("mousemove",v)})),k;function E(){document.removeEventListener("mousemove",P)}function T(){document.body.removeEventListener("mouseleave",q),document.removeEventListener("mousemove",v),jn=jn.filter((function(t){return t!==v}))}function I(){return k.props.triggerTarget||t}function S(){document.addEventListener("click",K,!0)}function C(){document.removeEventListener("click",K,!0)}function O(){return[k.popperChildren.tooltip,k.popperChildren.backdrop,k.popperChildren.content]}function A(){var t=k.props.followCursor;return t&&"focus"!==n||rn&&"initial"===t}function _(){kn([x],Le?0:k.props.updateDuration),function t(){k.popperInstance.scheduleUpdate(),k.state.isMounted?requestAnimationFrame(t):kn([x],0)}()}function L(t,e){N(t,(function(){!k.state.isVisible&&x.parentNode&&x.parentNode.contains(x)&&e()}))}function M(t,e){N(t,e)}function N(t,e){var n=k.popperChildren.tooltip;function i(t){t.target===n&&(Nn(n,"remove",i),e())}if(0===t)return e();Nn(n,"remove",p),Nn(n,"add",i),p=i}function R(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];I().addEventListener(t,e,n),g.push({eventType:t,handler:e,options:n})}function D(){k.props.touchHold&&!k.props.target&&(R("touchstart",H,Be),R("touchend",j,Be)),k.props.trigger.trim().split(" ").forEach((function(t){if("manual"!==t)if(k.props.target)switch(t){case"mouseenter":R("mouseover",U),R("mouseout",V);break;case"focus":R("focusin",U),R("focusout",V);break;case"click":R(t,U)}else switch(R(t,H),t){case"mouseenter":R("mouseleave",j);break;case"focus":R(Le?"focusout":"blur",B)}}))}function z(){g.forEach((function(t){var e=t.eventType,n=t.handler,i=t.options;I().removeEventListener(e,n,i)})),g=[]}function P(e){var n=i=e,o=n.clientX,r=n.clientY;if(d){var a=je(e.target,(function(e){return e===t})),s=t.getBoundingClientRect(),c=k.props.followCursor,l="horizontal"===c,p="vertical"===c,u=bn(["top","bottom"],Rn(x)),f=x.getAttribute(Ue),m=!!f&&!!f.split("-")[1],h=u?x.offsetWidth:x.offsetHeight,b=h/2,g=u?0:m?h:b,v=u?m?h:b:0;!a&&k.props.interactive||(k.popperInstance.reference=Oe({},k.popperInstance.reference,{clientWidth:0,clientHeight:0,getBoundingClientRect:function(){return{width:u?h:0,height:u?0:h,top:(l?s.top:r)-g,bottom:(l?s.bottom:r)+g,left:(p?s.left:o)-v,right:(p?s.right:o)+v}}}),k.popperInstance.update()),"initial"===c&&k.state.isVisible&&E()}}function H(t){k.state.isEnabled&&!Y(t)&&(k.state.isVisible||(n=t.type,t instanceof MouseEvent&&(i=t,jn.forEach((function(e){return e(t)})))),"click"===t.type&&!1!==k.props.hideOnClick&&k.state.isVisible?q():X(t))}function F(e){var n=Fe(e.target,Qe)===x,i=je(e.target,(function(e){return e===t}));n||i||function(t,e,n,i){if(!t)return!0;var o=n.clientX,r=n.clientY,a=i.interactiveBorder,s=i.distance,c=e.top-r>("top"===t?a+s:a),l=r-e.bottom>("bottom"===t?a+s:a),p=e.left-o>("left"===t?a+s:a),d=o-e.right>("right"===t?a+s:a);return c||l||p||d}(Rn(x),x.getBoundingClientRect(),e,k.props)&&(T(),q())}function j(t){if(!Y(t))return k.props.interactive?(document.body.addEventListener("mouseleave",q),document.addEventListener("mousemove",v),void jn.push(v)):void q()}function B(t){t.target===I()&&(k.props.interactive&&t.relatedTarget&&x.contains(t.relatedTarget)||q())}function U(t){Fe(t.target,k.props.target)&&X(t)}function V(t){Fe(t.target,k.props.target)&&q()}function Y(t){var e="ontouchstart"in window,n=bn(t.type,"touch"),i=k.props.touchHold;return e&&rn&&i&&!n||rn&&!i&&n}function W(){!b&&l&&(b=!0,function(t){t.offsetHeight}(x),l())}function $(){var e=k.props.popperOptions,n=k.popperChildren,i=n.tooltip,o=n.arrow,r=hn(e,"preventOverflow");function a(t){k.props.flip&&!k.props.flipOnUpdate&&(t.flipped&&(k.popperInstance.options.placement=t.placement),xn(k.popperInstance.modifiers,!1)),i.setAttribute(Ue,t.placement),!1!==t.attributes[Ve]?i.setAttribute(Ve,""):i.removeAttribute(Ve),c&&c!==t.placement&&h&&(i.style.transition="none",requestAnimationFrame((function(){i.style.transition=""}))),c=t.placement,h=k.state.isVisible;var e=Rn(x),n=i.style;n.top=n.bottom=n.left=n.right="",n[e]=-(k.props.distance-10)+"px";var o=r&&void 0!==r.padding?r.padding:4,a="number"==typeof o,s=Oe({top:a?o:o.top,bottom:a?o:o.bottom,left:a?o:o.left,right:a?o:o.right},!a&&o);s[e]=a?o+k.props.distance:(o[e]||0)+k.props.distance,k.popperInstance.modifiers.filter((function(t){return"preventOverflow"===t.name}))[0].padding=s,d=s}var s=Oe({eventsEnabled:!1,placement:k.props.placement},e,{modifiers:Oe({},e?e.modifiers:{},{preventOverflow:Oe({boundariesElement:k.props.boundary,padding:4},r),arrow:Oe({element:o,enabled:!!o},hn(e,"arrow")),flip:Oe({enabled:k.props.flip,padding:k.props.distance+4,behavior:k.props.flipBehavior},hn(e,"flip")),offset:Oe({offset:k.props.offset},hn(e,"offset"))}),onCreate:function(t){a(t),W(),e&&e.onCreate&&e.onCreate(t)},onUpdate:function(t){a(t),W(),e&&e.onUpdate&&e.onUpdate(t)}});k.popperInstance=new Ce(t,x,s)}function G(){b=!1;var e=A();k.popperInstance?(xn(k.popperInstance.modifiers,k.props.flip),e||(k.popperInstance.reference=t,k.popperInstance.enableEventListeners()),k.popperInstance.scheduleUpdate()):($(),e||k.popperInstance.enableEventListeners());var n=k.props.appendTo,i="parent"===n?t.parentNode:yn(n,[t]);i.contains(x)||(i.appendChild(x),k.props.onMount(k),k.state.isMounted=!0)}function X(t,n){if(Z(),!k.state.isVisible){if(k.props.target)return function(t){if(t){var n=Fe(t.target,k.props.target);n&&!n._tippy&&Bn(n,Oe({},k.props,{content:yn(e.content,[n]),appendTo:e.appendTo,target:"",showOnInit:!0}))}}(t);if(f=!0,t&&!n&&k.props.onTrigger(k,t),k.props.wait)return k.props.wait(k,t);A()&&!k.state.isMounted&&(k.popperInstance||$(),document.addEventListener("mousemove",P)),S();var i=fn(k.props.delay,0,Re.delay);i?o=setTimeout((function(){J()}),i):J()}}function q(){if(Z(),!k.state.isVisible)return E();f=!1;var t=fn(k.props.delay,1,Re.delay);t?r=setTimeout((function(){k.state.isVisible&&tt()}),t):a=requestAnimationFrame((function(){tt()}))}function K(t){if(!k.props.interactive||!x.contains(t.target)){if(I().contains(t.target)){if(rn)return;if(k.state.isVisible&&bn(k.props.trigger,"click"))return}!0===k.props.hideOnClick&&(Z(),tt())}}function Z(){clearTimeout(o),clearTimeout(r),cancelAnimationFrame(a)}function Q(e){In(e=e||{},Re),z();var n=k.props,o=Tn(t,Oe({},k.props,e,{ignoreAttributes:!0}));o.ignoreAttributes=un(e,"ignoreAttributes")?e.ignoreAttributes||!1:n.ignoreAttributes,k.props=o,D(),T(),v=mn(F,o.interactiveDebounce),Hn(x,n,o),k.popperChildren=On(x),k.popperInstance&&(De.some((function(t){return un(e,t)&&e[t]!==n[t]}))?(k.popperInstance.destroy(),$(),k.state.isVisible&&k.popperInstance.enableEventListeners(),k.props.followCursor&&i&&P(i)):k.popperInstance.update())}function J(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:fn(k.props.duration,0,Re.duration[1]);if(!k.state.isDestroyed&&k.state.isEnabled&&(!rn||k.props.touch)&&!I().hasAttribute("disabled")&&!1!==k.props.onShow(k)){S(),x.style.visibility="visible",k.state.isVisible=!0,k.props.interactive&&I().classList.add(We);var e=O();kn(e.concat(x),0),l=function(){if(k.state.isVisible){var n=A();n&&i?P(i):n||k.popperInstance.update(),k.popperChildren.backdrop&&(k.popperChildren.content.style.transitionDelay=Math.round(t/12)+"ms"),k.props.sticky&&_(),kn([x],k.props.updateDuration),kn(e,t),En(e,"visible"),M(t,(function(){k.props.aria&&I().setAttribute("aria-".concat(k.props.aria),x.id),k.props.onShown(k),k.state.isShown=!0}))}},G()}}function tt(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:fn(k.props.duration,1,Re.duration[1]);if(!k.state.isDestroyed&&(k.state.isEnabled||m)&&(!1!==k.props.onHide(k)||m)){C(),x.style.visibility="hidden",k.state.isVisible=!1,k.state.isShown=!1,h=!1,k.props.interactive&&I().classList.remove(We);var e=O();kn(e,t),En(e,"hidden"),L(t,(function(){f||E(),k.props.aria&&I().removeAttribute("aria-".concat(k.props.aria)),k.popperInstance.disableEventListeners(),k.popperInstance.options.placement=k.props.placement,x.parentNode.removeChild(x),k.props.onHidden(k),k.state.isMounted=!1}))}}}var Un=!1;function Vn(t,e){In(e||{},Re),Un||(document.addEventListener("touchstart",an,Be),window.addEventListener("blur",ln),Un=!0);var n,i=Oe({},Re,e);n=t,"[object Object]"==={}.toString.call(n)&&!n.addEventListener&&function(t){var e={isVirtual:!0,attributes:t.attributes||{},contains:function(){},setAttribute:function(e,n){t.attributes[e]=n},getAttribute:function(e){return t.attributes[e]},removeAttribute:function(e){delete t.attributes[e]},hasAttribute:function(e){return e in t.attributes},addEventListener:function(){},removeEventListener:function(){},classList:{classNames:{},add:function(e){t.classList.classNames[e]=!0},remove:function(e){delete t.classList.classNames[e]},contains:function(e){return e in t.classList.classNames}}};for(var n in e)t[n]=e[n]}(t);var o=function(t){if(vn(t))return[t];if(t instanceof NodeList)return He(t);if(K(t))return t;try{return He(document.querySelectorAll(t))}catch(t){return[]}}(t).reduce((function(t,e){var n=e&&Bn(e,i);return n&&t.push(n),t}),[]);return vn(t)?o[0]:o}Vn.version="4.3.4",Vn.defaults=Re,Vn.setDefaults=function(t){mt(t).forEach((function(e){Re[e]=t[e]}))},Vn.hideAll=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.exclude,n=t.duration;He(document.querySelectorAll(Qe)).forEach((function(t){var i=t._tippy;if(i){var o=!1;e&&(o=dn(e)?i.reference===e:t===e.popper),o||i.hide(n)}}))},Vn.group=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.delay,i=void 0===n?t[0].props.delay:n,o=e.duration,r=void 0===o?0:o,a=!1;function s(t){a=t,d()}function c(e){e._originalProps.onShow(e),t.forEach((function(t){t.set({duration:r}),t.state.isVisible&&t.hide()})),s(!0)}function l(t){t._originalProps.onHide(t),s(!1)}function p(t){t._originalProps.onShown(t),t.set({duration:t._originalProps.duration})}function d(){t.forEach((function(t){t.set({onShow:c,onShown:p,onHide:l,delay:a?[0,K(i)?i[1]:i]:i,duration:a?r:t._originalProps.duration})}))}t.forEach((function(t){t._originalProps?t.set(t._originalProps):t._originalProps=Oe({},t.props)})),d()},Ae&&setTimeout((function(){He(document.querySelectorAll("[data-tippy]")).forEach((function(t){var e=t.getAttribute("data-tippy");e&&Vn(t,{content:e})}))}));var Yn={duration:0,arrow:!0,size:"small",trigger:"mouseenter focus",theme:"dark",interactive:!0,boundary:"window",content:"",allowHTML:!0,extCls:""},Wn=["auto-start","auto","auto-end","top-start","top","top-end","right-start","right","right-end","bottom-end","bottom","bottom-start","left-end","left","left-start"],$n=function(t,n){var o=n.value,r=function(t){for(var e=1;e>>0||(P.test(n)?16:10))}:D;S(S.G+S.F*(parseInt!=H),{parseInt:H});var F=l.parseInt,j={}.toString,B=function(t){return j.call(t).slice(8,-1)},U=Array.isArray||function(t){return"Array"==B(t)};S(S.S,"Array",{isArray:U});var V,Y,W,$,G,X,q=l.Array.isArray,K=function(t){return Object(C(t))},Z=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==B(t)?t.split(""):Object(t)},Q=function(t){return Z(C(t))},J=Math.ceil,tt=Math.floor,et=function(t){return isNaN(t=+t)?0:(t>0?tt:J)(t)},nt=Math.min,it=Math.max,ot=Math.min,rt=s((function(t){var e="__core-js_shared__",n=c[e]||(c[e]={});(t.exports=function(t,e){return n[t]||(n[t]=void 0!==e?e:{})})("versions",[]).push({version:l.version,mode:"pure",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),at=0,st=Math.random(),ct=rt("keys"),lt=(X=!1,function(t,e,n){var i,o,r=Q(t),a=(o=r.length)>0?nt(et(o),9007199254740991):0,s=function(t,e){return(t=et(t))<0?it(t+e,0):ot(t,e)}(n,a);if(X&&e!=e){for(;a>s;)if((i=r[s++])!=i)return!0}else for(;a>s;s++)if((X||s in r)&&r[s]===e)return X||s||0;return!X&&-1}),pt=ct[G="IE_PROTO"]||(ct[G]=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++at+st).toString(36))}(G)),dt="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),ut=Object.keys||function(t){return function(t,e){var n,i=Q(t),o=0,r=[];for(n in i)n!=pt&&E(i,n)&&r.push(n);for(;e.length>o;)E(i,n=e[o++])&&(~lt(r,n)||r.push(n));return r}(t,dt)};V="keys",Y=function(){return function(t){return ut(K(t))}},W=(l.Object||{})[V]||Object[V],($={})[V]=Y(),S(S.S+S.F*m((function(){W(1)})),"Object",$);var ft=l.Object.keys,mt={f:Object.getOwnPropertySymbols},ht={f:{}.propertyIsEnumerable},bt=Object.assign,gt=!bt||m((function(){var t={},e={},n=Symbol(),i="abcdefghijklmnopqrst";return t[n]=7,i.split("").forEach((function(t){e[t]=t})),7!=bt({},t)[n]||Object.keys(bt({},e)).join("")!=i}))?function(t,e){for(var n=K(t),i=arguments.length,o=1,r=mt.f,a=ht.f;i>o;)for(var s,c=Z(arguments[o++]),l=r?ut(c).concat(r(c)):ut(c),p=l.length,d=0;p>d;)s=l[d++],h&&!a.call(c,s)||(n[s]=c[s]);return n}:bt;S(S.S+S.F,"Object",{assign:gt});var vt=l.Object.assign,yt=c.parseFloat,xt=R.trim,wt=1/yt(O+"-0")!=-1/0?function(t){var e=xt(String(t),3),n=yt(e);return 0===n&&"-"==e.charAt(0)?-0:n}:yt;S(S.G+S.F*(parseFloat!=wt),{parseFloat:wt});for(var kt=l.parseFloat,Et="undefined"!=typeof window&&"undefined"!=typeof document,Tt=["Edge","Trident","Firefox"],It=0,St=0;St=0){It=1;break}var Ct=Et&&window.Promise?function(t){var e=!1;return function(){e||(e=!0,window.Promise.resolve().then((function(){e=!1,t()})))}}:function(t){var e=!1;return function(){e||(e=!0,setTimeout((function(){e=!1,t()}),It))}};function Ot(t){return t&&"[object Function]"==={}.toString.call(t)}function At(t,e){if(1!==t.nodeType)return[];var n=t.ownerDocument.defaultView.getComputedStyle(t,null);return e?n[e]:n}function _t(t){return"HTML"===t.nodeName?t:t.parentNode||t.host}function Lt(t){if(!t)return document.body;switch(t.nodeName){case"HTML":case"BODY":return t.ownerDocument.body;case"#document":return t.body}var e=At(t),n=e.overflow,i=e.overflowX,o=e.overflowY;return/(auto|scroll|overlay)/.test(n+o+i)?t:Lt(_t(t))}var Mt=Et&&!(!window.MSInputMethodContext||!document.documentMode),Nt=Et&&/MSIE 10/.test(navigator.userAgent);function Rt(t){return 11===t?Mt:10===t?Nt:Mt||Nt}function Dt(t){if(!t)return document.documentElement;for(var e=Rt(10)?document.body:null,n=t.offsetParent||null;n===e&&t.nextElementSibling;)n=(t=t.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&"BODY"!==i&&"HTML"!==i?-1!==["TH","TD","TABLE"].indexOf(n.nodeName)&&"static"===At(n,"position")?Dt(n):n:t?t.ownerDocument.documentElement:document.documentElement}function zt(t){return null!==t.parentNode?zt(t.parentNode):t}function Pt(t,e){if(!(t&&t.nodeType&&e&&e.nodeType))return document.documentElement;var n=t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING,i=n?t:e,o=n?e:t,r=document.createRange();r.setStart(i,0),r.setEnd(o,0);var a,s,c=r.commonAncestorContainer;if(t!==c&&e!==c||i.contains(o))return"BODY"===(s=(a=c).nodeName)||"HTML"!==s&&Dt(a.firstElementChild)!==a?Dt(c):c;var l=zt(t);return l.host?Pt(l.host,e):Pt(t,zt(e).host)}function Ht(t){var e="top"===(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top")?"scrollTop":"scrollLeft",n=t.nodeName;if("BODY"===n||"HTML"===n){var i=t.ownerDocument.documentElement;return(t.ownerDocument.scrollingElement||i)[e]}return t[e]}function Ft(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=Ht(e,"top"),o=Ht(e,"left"),r=n?-1:1;return t.top+=i*r,t.bottom+=i*r,t.left+=o*r,t.right+=o*r,t}function jt(t,e){var n="x"===e?"Left":"Top",i="Left"===n?"Right":"Bottom";return kt(t["border".concat(n,"Width")],10)+kt(t["border".concat(i,"Width")],10)}function Bt(t,e,n,i){return Math.max(e["offset".concat(t)],e["scroll".concat(t)],n["client".concat(t)],n["offset".concat(t)],n["scroll".concat(t)],Rt(10)?F(n["offset".concat(t)])+F(i["margin".concat("Height"===t?"Top":"Left")])+F(i["margin".concat("Height"===t?"Bottom":"Right")]):0)}function Ut(t){var e=t.body,n=t.documentElement,i=Rt(10)&&getComputedStyle(n);return{height:Bt("Height",e,n,i),width:Bt("Width",e,n,i)}}var Vt=vt||function(t){for(var e=1;e2&&void 0!==arguments[2]&&arguments[2],i=Rt(10),o="HTML"===e.nodeName,r=Wt(t),a=Wt(e),s=Lt(t),c=At(e),l=kt(c.borderTopWidth,10),p=kt(c.borderLeftWidth,10);n&&o&&(a.top=Math.max(a.top,0),a.left=Math.max(a.left,0));var d=Yt({top:r.top-a.top-l,left:r.left-a.left-p,width:r.width,height:r.height});if(d.marginTop=0,d.marginLeft=0,!i&&o){var u=kt(c.marginTop,10),f=kt(c.marginLeft,10);d.top-=l-u,d.bottom-=l-u,d.left-=p-f,d.right-=p-f,d.marginTop=u,d.marginLeft=f}return(i&&!n?e.contains(s):e===s&&"BODY"!==s.nodeName)&&(d=Ft(d,e)),d}function Gt(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t.ownerDocument.documentElement,i=$t(t,n),o=Math.max(n.clientWidth,window.innerWidth||0),r=Math.max(n.clientHeight,window.innerHeight||0),a=e?0:Ht(n),s=e?0:Ht(n,"left");return Yt({top:a-i.top+i.marginTop,left:s-i.left+i.marginLeft,width:o,height:r})}function Xt(t){var e=t.nodeName;if("BODY"===e||"HTML"===e)return!1;if("fixed"===At(t,"position"))return!0;var n=_t(t);return!!n&&Xt(n)}function qt(t){if(!t||!t.parentElement||Rt())return document.documentElement;for(var e=t.parentElement;e&&"none"===At(e,"transform");)e=e.parentElement;return e||document.documentElement}function Kt(t,e,n,i){var o=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r={top:0,left:0},a=o?qt(t):Pt(t,e);if("viewport"===i)r=Gt(a,o);else{var s;"scrollParent"===i?"BODY"===(s=Lt(_t(e))).nodeName&&(s=t.ownerDocument.documentElement):s="window"===i?t.ownerDocument.documentElement:i;var c=$t(s,a,o);if("HTML"!==s.nodeName||Xt(a))r=c;else{var l=Ut(t.ownerDocument),p=l.height,d=l.width;r.top+=c.top-c.marginTop,r.bottom=p+c.top,r.left+=c.left-c.marginLeft,r.right=d+c.left}}var u="number"==typeof(n=n||0);return r.left+=u?n:n.left||0,r.top+=u?n:n.top||0,r.right-=u?n:n.right||0,r.bottom-=u?n:n.bottom||0,r}function Zt(t){return t.width*t.height}function Qt(t,e,n,i,o){var r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===t.indexOf("auto"))return t;var a=Kt(n,i,r,o),s={top:{width:a.width,height:e.top-a.top},right:{width:a.right-e.right,height:a.height},bottom:{width:a.width,height:a.bottom-e.bottom},left:{width:e.left-a.left,height:a.height}},c=ft(s).map((function(t){return Vt({key:t},s[t],{area:Zt(s[t])})})).sort((function(t,e){return e.area-t.area})),l=c.filter((function(t){var e=t.width,i=t.height;return e>=n.clientWidth&&i>=n.clientHeight})),p=l.length>0?l[0].key:c[0].key,d=t.split("-")[1];return p+(d?"-".concat(d):"")}function Jt(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;return $t(n,i?qt(e):Pt(e,n),i)}function te(t){var e=t.ownerDocument.defaultView.getComputedStyle(t),n=kt(e.marginTop||0)+kt(e.marginBottom||0),i=kt(e.marginLeft||0)+kt(e.marginRight||0);return{width:t.offsetWidth+i,height:t.offsetHeight+n}}function ee(t){var e={left:"right",right:"left",bottom:"top",top:"bottom"};return t.replace(/left|right|bottom|top/g,(function(t){return e[t]}))}function ne(t,e,n){n=n.split("-")[0];var i=te(t),o={width:i.width,height:i.height},r=-1!==["right","left"].indexOf(n),a=r?"top":"left",s=r?"left":"top",c=r?"height":"width",l=r?"width":"height";return o[a]=e[a]+e[c]/2-i[c]/2,o[s]=n===s?e[s]-i[l]:e[ee(s)],o}function ie(t,e){return Array.prototype.find?t.find(e):t.filter(e)[0]}function oe(t,e,n){return(void 0===n?t:t.slice(0,function(t,e,n){if(Array.prototype.findIndex)return t.findIndex((function(t){return t[e]===n}));var i=ie(t,(function(t){return t[e]===n}));return t.indexOf(i)}(t,"name",n))).forEach((function(t){t.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=t.function||t.fn;t.enabled&&Ot(n)&&(e.offsets.popper=Yt(e.offsets.popper),e.offsets.reference=Yt(e.offsets.reference),e=n(e,t))})),e}function re(){if(!this.state.isDestroyed){var t={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};t.offsets.reference=Jt(this.state,this.popper,this.reference,this.options.positionFixed),t.placement=Qt(this.options.placement,t.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),t.originalPlacement=t.placement,t.positionFixed=this.options.positionFixed,t.offsets.popper=ne(this.popper,t.offsets.reference,t.placement),t.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",t=oe(this.modifiers,t),this.state.isCreated?this.options.onUpdate(t):(this.state.isCreated=!0,this.options.onCreate(t))}}function ae(t,e){return t.some((function(t){var n=t.name;return t.enabled&&n===e}))}function se(t){for(var e=[!1,"ms","Webkit","Moz","O"],n=t.charAt(0).toUpperCase()+t.slice(1),i=0;i1&&void 0!==arguments[1]&&arguments[1],n=ye.indexOf(t),i=ye.slice(n+1).concat(ye.slice(0,n));return e?i.reverse():i}var we="flip",ke="clockwise",Ee="counterclockwise";function Te(t,e,n,i){var o=[0,0],r=-1!==["right","left"].indexOf(i),a=t.split(/(\+|\-)/).map((function(t){return t.trim()})),s=a.indexOf(ie(a,(function(t){return-1!==t.search(/,|\s/)})));a[s]&&-1===a[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var c=/\s*,\s*|\s+/,l=-1!==s?[a.slice(0,s).concat([a[s].split(c)[0]]),[a[s].split(c)[1]].concat(a.slice(s+1))]:[a];return(l=l.map((function(t,i){var o=(1===i?!r:r)?"height":"width",a=!1;return t.reduce((function(t,e){return""===t[t.length-1]&&-1!==["+","-"].indexOf(e)?(t[t.length-1]=e,a=!0,t):a?(t[t.length-1]+=e,a=!1,t):t.concat(e)}),[]).map((function(t){return function(t,e,n,i){var o=t.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+o[1],a=o[2];return r?0===a.indexOf("%")?Yt("%p"===a?n:i)[e]/100*r:"vh"===a||"vw"===a?("vh"===a?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*r:r:t}(t,o,e,n)}))}))).forEach((function(t,e){t.forEach((function(n,i){me(n)&&(o[e]+=n*("-"===t[i-1]?-1:1))}))})),o}var Ie={placement:"bottom",positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(t){var e=t.placement,n=e.split("-")[0],i=e.split("-")[1];if(i){var o=t.offsets,a=o.reference,s=o.popper,c=-1!==["bottom","top"].indexOf(n),l=c?"left":"top",p=c?"width":"height",d={start:r({},l,a[l]),end:r({},l,a[l]+a[p]-s[p])};t.offsets.popper=Vt({},s,d[i])}return t}},offset:{order:200,enabled:!0,fn:function(t,e){var n,i=e.offset,o=t.placement,r=t.offsets,a=r.popper,s=r.reference,c=o.split("-")[0];return n=me(+i)?[+i,0]:Te(i,a,s,c),"left"===c?(a.top+=n[0],a.left-=n[1]):"right"===c?(a.top+=n[0],a.left+=n[1]):"top"===c?(a.left+=n[0],a.top-=n[1]):"bottom"===c&&(a.left+=n[0],a.top+=n[1]),t.popper=a,t},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(t,e){var n=e.boundariesElement||Dt(t.instance.popper);t.instance.reference===n&&(n=Dt(n));var i=se("transform"),o=t.instance.popper.style,a=o.top,s=o.left,c=o[i];o.top="",o.left="",o[i]="";var l=Kt(t.instance.popper,t.instance.reference,e.padding,n,t.positionFixed);o.top=a,o.left=s,o[i]=c,e.boundaries=l;var p=e.priority,d=t.offsets.popper,u={primary:function(t){var n=d[t];return d[t]l[t]&&!e.escapeWithReference&&(i=Math.min(d[n],l[t]-("right"===t?d.width:d.height))),r({},n,i)}};return p.forEach((function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";d=Vt({},d,u[e](t))})),t.offsets.popper=d,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,a=-1!==["top","bottom"].indexOf(o),s=a?"right":"bottom",c=a?"left":"top",l=a?"width":"height";return n[s]r(i[s])&&(t.offsets.popper[c]=r(i[s])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!ge(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],a=t.offsets,s=a.popper,c=a.reference,l=-1!==["left","right"].indexOf(o),p=l?"height":"width",d=l?"Top":"Left",u=d.toLowerCase(),f=l?"left":"top",m=l?"bottom":"right",h=te(i)[p];c[m]-hs[m]&&(t.offsets.popper[u]+=c[u]+h-s[m]),t.offsets.popper=Yt(t.offsets.popper);var b=c[u]+c[p]/2-h/2,g=At(t.instance.popper),v=kt(g["margin".concat(d)],10),y=kt(g["border".concat(d,"Width")],10),x=b-t.offsets.popper[u]-v-y;return x=Math.max(Math.min(s[p]-h,x),0),t.arrowElement=i,t.offsets.arrow=(r(n={},u,Math.round(x)),r(n,f,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(t,e){if(ae(t.instance.modifiers,"inner"))return t;if(t.flipped&&t.placement===t.originalPlacement)return t;var n=Kt(t.instance.popper,t.instance.reference,e.padding,e.boundariesElement,t.positionFixed),i=t.placement.split("-")[0],o=ee(i),r=t.placement.split("-")[1]||"",a=[];switch(e.behavior){case we:a=[i,o];break;case ke:a=xe(i);break;case Ee:a=xe(i,!0);break;default:a=e.behavior}return a.forEach((function(s,c){if(i!==s||a.length===c+1)return t;i=t.placement.split("-")[0],o=ee(i);var l=t.offsets.popper,p=t.offsets.reference,d=Math.floor,u="left"===i&&d(l.right)>d(p.left)||"right"===i&&d(l.left)d(p.top)||"bottom"===i&&d(l.top)d(n.right),h=d(l.top)d(n.bottom),g="left"===i&&f||"right"===i&&m||"top"===i&&h||"bottom"===i&&b,v=-1!==["top","bottom"].indexOf(i),y=!!e.flipVariations&&(v&&"start"===r&&f||v&&"end"===r&&m||!v&&"start"===r&&h||!v&&"end"===r&&b),x=!!e.flipVariationsByContent&&(v&&"start"===r&&m||v&&"end"===r&&f||!v&&"start"===r&&b||!v&&"end"===r&&h),w=y||x;(u||g||w)&&(t.flipped=!0,(u||g)&&(i=a[c+1]),w&&(r=function(t){return"end"===t?"start":"start"===t?"end":t}(r)),t.placement=i+(r?"-"+r:""),t.offsets.popper=Vt({},t.offsets.popper,ne(t.instance.popper,t.offsets.reference,t.placement)),t=oe(t.instance.modifiers,t,"flip"))})),t},behavior:"flip",padding:5,boundariesElement:"viewport",flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,a=-1!==["left","right"].indexOf(n),s=-1===["top","left"].indexOf(n);return o[a?"left":"top"]=r[n]-(s?o[a?"width":"height"]:0),t.placement=ee(e),t.offsets.popper=Yt(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!ge(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=ie(t.instance.modifiers,(function(t){return"preventOverflow"===t.name})).boundaries;if(e.bottomn.right||e.top>n.bottom||e.right2&&void 0!==arguments[2]?arguments[2]:{};i(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(o.update)},this.update=Ct(this.update.bind(this)),this.options=Vt({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=e&&e.jquery?e[0]:e,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},ft(Vt({},t.Defaults.modifiers,r.modifiers)).forEach((function(e){o.options.modifiers[e]=Vt({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})})),this.modifiers=ft(this.options.modifiers).map((function(t){return Vt({name:t},o.options.modifiers[t])})).sort((function(t,e){return t.order-e.order})),this.modifiers.forEach((function(t){t.enabled&&Ot(t.onLoad)&&t.onLoad(o.reference,o.popper,o.options,t,o.state)})),this.update();var a=this.options.eventsEnabled;a&&this.enableEventListeners(),this.state.eventsEnabled=a}return e=t,(n=[{key:"update",value:function(){return re.call(this)}},{key:"destroy",value:function(){return ce.call(this)}},{key:"enableEventListeners",value:function(){return ue.call(this)}},{key:"disableEventListeners",value:function(){return fe.call(this)}}])&&o(e.prototype,n),r&&o(e,r),Object.defineProperty(e,"prototype",{writable:!1}),t;var e,n,r}();function Ce(){return Ce=vt||function(t){for(var e=1;e-1}function bn(t){return t instanceof Element}function gn(t){return!(!t||!dn(t,"isVirtual"))||bn(t)}function vn(t,e){return"function"==typeof t?t.apply(null,e):t}function yn(t,e){t.filter((function(t){return"flip"===t.name}))[0].enabled=e}function xn(){return document.createElement("div")}function wn(t,e){t.forEach((function(t){t&&(t.style.transitionDuration="".concat(e,"ms"))}))}function kn(t,e){t.forEach((function(t){t&&t.setAttribute("data-state",e)}))}function En(t,e){var n=Ce({},e,{content:vn(e.content,[t])},e.ignoreAttributes?{}:function(t){return ln.reduce((function(e,n){var i=(t.getAttribute("data-tippy-".concat(n))||"").trim();if(!i)return e;if("content"===n)e[n]=i;else try{e[n]=JSON.parse(i)}catch(t){e[n]=i}return e}),{})}(t));return(n.arrow||Le)&&(n.animateFill=!1),n}function Tn(t,e){ft(t).forEach((function(t){if(!dn(e,t))throw new Error("[tippy]: `".concat(t,"` is not a valid option"))}))}function In(t,e){t.innerHTML=bn(e)?e.innerHTML:e}function Sn(t,e){bn(e.content)?(In(t,""),t.appendChild(e.content)):"function"!=typeof e.content&&(t[e.allowHTML?"innerHTML":"textContent"]=e.content)}function Cn(t){return{tooltip:t.querySelector(Qe),backdrop:t.querySelector(tn),content:t.querySelector(Je),arrow:t.querySelector(en)||t.querySelector(nn)}}function On(t){t.setAttribute("data-inertia","")}function An(t){var e=xn();return"round"===t?(e.className=Ke,In(e,'')):e.className=qe,e}function _n(){var t=xn();return t.className=Xe,t.setAttribute("data-state","hidden"),t}function Ln(t,e){t.setAttribute("tabindex","-1"),e.setAttribute("data-interactive","")}function Mn(t,e,n){var i=Le&&void 0!==document.body.style.webkitTransition?"webkitTransitionEnd":"transitionend";t[e+"EventListener"](i,n)}function Nn(t){var e=t.getAttribute(Be);return e?e.split("-")[0]:""}function Rn(t,e,n){n.split(" ").forEach((function(n){t.classList[e](n+"-theme")}))}function Dn(t,e,n){var i=e[n],o=F(i);"number"==typeof i?t.style[n]=i+"px":isNaN(o)?t.style[n]=i:t.style[n]=o+"px"}function zn(t,e){var n=xn();n.className=We+(e.extCls?" ".concat(e.extCls):""),n.id="tippy-".concat(t),n.style.zIndex=""+e.zIndex,n.style.position="absolute",n.style.top="0",n.style.left="0",e.role&&n.setAttribute("role",e.role);var i=xn();i.className=$e,Dn(i,e,"maxWidth"),Dn(i,e,"width"),i.setAttribute("data-size",e.size),i.setAttribute("data-animation",e.animation),i.setAttribute("data-state","hidden"),Rn(i,"add",e.theme);var o=xn();return o.className=Ge,o.setAttribute("data-state","hidden"),e.interactive&&Ln(n,i),e.arrow&&i.appendChild(An(e.arrowType)),e.animateFill&&(i.appendChild(_n()),i.setAttribute("data-animatefill","")),e.inertia&&On(i),Sn(o,e),i.appendChild(o),n.appendChild(i),n}function Pn(t,e,n){var i=Cn(t),o=i.tooltip,r=i.content,a=i.backdrop,s=i.arrow;t.style.zIndex=""+n.zIndex,o.setAttribute("data-size",n.size),o.setAttribute("data-animation",n.animation),o.style.maxWidth=n.maxWidth+("number"==typeof n.maxWidth?"px":""),n.role?t.setAttribute("role",n.role):t.removeAttribute("role"),e.content!==n.content&&Sn(r,n),!e.animateFill&&n.animateFill?(o.appendChild(_n()),o.setAttribute("data-animatefill","")):e.animateFill&&!n.animateFill&&(o.removeChild(a),o.removeAttribute("data-animatefill")),!e.arrow&&n.arrow?o.appendChild(An(n.arrowType)):e.arrow&&!n.arrow&&o.removeChild(s),e.arrow&&n.arrow&&e.arrowType!==n.arrowType&&o.replaceChild(An(n.arrowType),s),!e.interactive&&n.interactive?Ln(t,o):e.interactive&&!n.interactive&&function(t,e){t.removeAttribute("tabindex"),e.removeAttribute("data-interactive")}(t,o),!e.inertia&&n.inertia?On(o):e.inertia&&!n.inertia&&function(t){t.removeAttribute("data-inertia")}(o),e.theme!==n.theme&&(Rn(o,"remove",e.theme),Rn(o,"add",n.theme))}var Hn=1,Fn=[];function jn(t,e){var n,i,o,r,a,s=En(t,e);if(!s.multiple&&t._tippy)return null;var c,l,p,d,u,f=!1,m=!1,h=!1,b=!1,g=[],v=fn(F,s.interactiveDebounce),y=Hn++,x=zn(y,s),w=Cn(x),k={id:y,reference:t,popper:x,popperChildren:w,popperInstance:null,props:s,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},clearDelayTimeouts:Z,set:Q,setContent:function(t){Q({content:t})},show:J,hide:tt,enable:function(){k.state.isEnabled=!0},disable:function(){k.state.isEnabled=!1},destroy:function(e){if(!k.state.isDestroyed){m=!0,k.state.isMounted&&tt(0),z(),delete t._tippy;var n=k.props.target;n&&e&&bn(t)&&Pe(t.querySelectorAll(n)).forEach((function(t){t._tippy&&t._tippy.destroy()})),k.popperInstance&&k.popperInstance.destroy(),m=!1,k.state.isDestroyed=!0}}};return t._tippy=k,x._tippy=k,D(),s.lazy||$(),s.showOnInit&&X(),!s.a11y||s.target||(!bn(u=I())||ze.call(u,"a[href],area[href],button,details,input,textarea,select,iframe,[tabindex]")&&!u.hasAttribute("disabled"))||I().setAttribute("tabindex","0"),x.addEventListener("mouseenter",(function(t){k.props.interactive&&k.state.isVisible&&"mouseenter"===n&&X(t,!0)})),x.addEventListener("mouseleave",(function(){k.props.interactive&&"mouseenter"===n&&document.addEventListener("mousemove",v)})),k;function E(){document.removeEventListener("mousemove",P)}function T(){document.body.removeEventListener("mouseleave",q),document.removeEventListener("mousemove",v),Fn=Fn.filter((function(t){return t!==v}))}function I(){return k.props.triggerTarget||t}function S(){document.addEventListener("click",K,!0)}function C(){document.removeEventListener("click",K,!0)}function O(){return[k.popperChildren.tooltip,k.popperChildren.backdrop,k.popperChildren.content]}function A(){var t=k.props.followCursor;return t&&"focus"!==n||on&&"initial"===t}function _(){wn([x],_e?0:k.props.updateDuration),function t(){k.popperInstance.scheduleUpdate(),k.state.isMounted?requestAnimationFrame(t):wn([x],0)}()}function L(t,e){N(t,(function(){!k.state.isVisible&&x.parentNode&&x.parentNode.contains(x)&&e()}))}function M(t,e){N(t,e)}function N(t,e){var n=k.popperChildren.tooltip;function i(t){t.target===n&&(Mn(n,"remove",i),e())}if(0===t)return e();Mn(n,"remove",p),Mn(n,"add",i),p=i}function R(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];I().addEventListener(t,e,n),g.push({eventType:t,handler:e,options:n})}function D(){k.props.touchHold&&!k.props.target&&(R("touchstart",H,je),R("touchend",j,je)),k.props.trigger.trim().split(" ").forEach((function(t){if("manual"!==t)if(k.props.target)switch(t){case"mouseenter":R("mouseover",U),R("mouseout",V);break;case"focus":R("focusin",U),R("focusout",V);break;case"click":R(t,U)}else switch(R(t,H),t){case"mouseenter":R("mouseleave",j);break;case"focus":R(_e?"focusout":"blur",B)}}))}function z(){g.forEach((function(t){var e=t.eventType,n=t.handler,i=t.options;I().removeEventListener(e,n,i)})),g=[]}function P(e){var n=i=e,o=n.clientX,r=n.clientY;if(d){var a=Fe(e.target,(function(e){return e===t})),s=t.getBoundingClientRect(),c=k.props.followCursor,l="horizontal"===c,p="vertical"===c,u=hn(["top","bottom"],Nn(x)),f=x.getAttribute(Be),m=!!f&&!!f.split("-")[1],h=u?x.offsetWidth:x.offsetHeight,b=h/2,g=u?0:m?h:b,v=u?m?h:b:0;!a&&k.props.interactive||(k.popperInstance.reference=Ce({},k.popperInstance.reference,{clientWidth:0,clientHeight:0,getBoundingClientRect:function(){return{width:u?h:0,height:u?0:h,top:(l?s.top:r)-g,bottom:(l?s.bottom:r)+g,left:(p?s.left:o)-v,right:(p?s.right:o)+v}}}),k.popperInstance.update()),"initial"===c&&k.state.isVisible&&E()}}function H(t){k.state.isEnabled&&!Y(t)&&(k.state.isVisible||(n=t.type,t instanceof MouseEvent&&(i=t,Fn.forEach((function(e){return e(t)})))),"click"===t.type&&!1!==k.props.hideOnClick&&k.state.isVisible?q():X(t))}function F(e){var n=He(e.target,Ze)===x,i=Fe(e.target,(function(e){return e===t}));n||i||function(t,e,n,i){if(!t)return!0;var o=n.clientX,r=n.clientY,a=i.interactiveBorder,s=i.distance,c=e.top-r>("top"===t?a+s:a),l=r-e.bottom>("bottom"===t?a+s:a),p=e.left-o>("left"===t?a+s:a),d=o-e.right>("right"===t?a+s:a);return c||l||p||d}(Nn(x),x.getBoundingClientRect(),e,k.props)&&(T(),q())}function j(t){if(!Y(t))return k.props.interactive?(document.body.addEventListener("mouseleave",q),document.addEventListener("mousemove",v),void Fn.push(v)):void q()}function B(t){t.target===I()&&(k.props.interactive&&t.relatedTarget&&x.contains(t.relatedTarget)||q())}function U(t){He(t.target,k.props.target)&&X(t)}function V(t){He(t.target,k.props.target)&&q()}function Y(t){var e="ontouchstart"in window,n=hn(t.type,"touch"),i=k.props.touchHold;return e&&on&&i&&!n||on&&!i&&n}function W(){!b&&l&&(b=!0,function(t){t.offsetHeight}(x),l())}function $(){var e=k.props.popperOptions,n=k.popperChildren,i=n.tooltip,o=n.arrow,r=mn(e,"preventOverflow");function a(t){k.props.flip&&!k.props.flipOnUpdate&&(t.flipped&&(k.popperInstance.options.placement=t.placement),yn(k.popperInstance.modifiers,!1)),i.setAttribute(Be,t.placement),!1!==t.attributes[Ue]?i.setAttribute(Ue,""):i.removeAttribute(Ue),c&&c!==t.placement&&h&&(i.style.transition="none",requestAnimationFrame((function(){i.style.transition=""}))),c=t.placement,h=k.state.isVisible;var e=Nn(x),n=i.style;n.top=n.bottom=n.left=n.right="",n[e]=-(k.props.distance-10)+"px";var o=r&&void 0!==r.padding?r.padding:4,a="number"==typeof o,s=Ce({top:a?o:o.top,bottom:a?o:o.bottom,left:a?o:o.left,right:a?o:o.right},!a&&o);s[e]=a?o+k.props.distance:(o[e]||0)+k.props.distance,k.popperInstance.modifiers.filter((function(t){return"preventOverflow"===t.name}))[0].padding=s,d=s}var s=Ce({eventsEnabled:!1,placement:k.props.placement},e,{modifiers:Ce({},e?e.modifiers:{},{preventOverflow:Ce({boundariesElement:k.props.boundary,padding:4},r),arrow:Ce({element:o,enabled:!!o},mn(e,"arrow")),flip:Ce({enabled:k.props.flip,padding:k.props.distance+4,behavior:k.props.flipBehavior},mn(e,"flip")),offset:Ce({offset:k.props.offset},mn(e,"offset"))}),onCreate:function(t){a(t),W(),e&&e.onCreate&&e.onCreate(t)},onUpdate:function(t){a(t),W(),e&&e.onUpdate&&e.onUpdate(t)}});k.popperInstance=new Se(t,x,s)}function G(){b=!1;var e=A();k.popperInstance?(yn(k.popperInstance.modifiers,k.props.flip),e||(k.popperInstance.reference=t,k.popperInstance.enableEventListeners()),k.popperInstance.scheduleUpdate()):($(),e||k.popperInstance.enableEventListeners());var n=k.props.appendTo,i="parent"===n?t.parentNode:vn(n,[t]);i.contains(x)||(i.appendChild(x),k.props.onMount(k),k.state.isMounted=!0)}function X(t,n){if(Z(),!k.state.isVisible){if(k.props.target)return function(t){if(t){var n=He(t.target,k.props.target);n&&!n._tippy&&jn(n,Ce({},k.props,{content:vn(e.content,[n]),appendTo:e.appendTo,target:"",showOnInit:!0}))}}(t);if(f=!0,t&&!n&&k.props.onTrigger(k,t),k.props.wait)return k.props.wait(k,t);A()&&!k.state.isMounted&&(k.popperInstance||$(),document.addEventListener("mousemove",P)),S();var i=un(k.props.delay,0,Ne.delay);i?o=setTimeout((function(){J()}),i):J()}}function q(){if(Z(),!k.state.isVisible)return E();f=!1;var t=un(k.props.delay,1,Ne.delay);t?r=setTimeout((function(){k.state.isVisible&&tt()}),t):a=requestAnimationFrame((function(){tt()}))}function K(t){if(!k.props.interactive||!x.contains(t.target)){if(I().contains(t.target)){if(on)return;if(k.state.isVisible&&hn(k.props.trigger,"click"))return}!0===k.props.hideOnClick&&(Z(),tt())}}function Z(){clearTimeout(o),clearTimeout(r),cancelAnimationFrame(a)}function Q(e){Tn(e=e||{},Ne),z();var n=k.props,o=En(t,Ce({},k.props,e,{ignoreAttributes:!0}));o.ignoreAttributes=dn(e,"ignoreAttributes")?e.ignoreAttributes||!1:n.ignoreAttributes,k.props=o,D(),T(),v=fn(F,o.interactiveDebounce),Pn(x,n,o),k.popperChildren=Cn(x),k.popperInstance&&(Re.some((function(t){return dn(e,t)&&e[t]!==n[t]}))?(k.popperInstance.destroy(),$(),k.state.isVisible&&k.popperInstance.enableEventListeners(),k.props.followCursor&&i&&P(i)):k.popperInstance.update())}function J(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:un(k.props.duration,0,Ne.duration[1]);if(!k.state.isDestroyed&&k.state.isEnabled&&(!on||k.props.touch)&&!I().hasAttribute("disabled")&&!1!==k.props.onShow(k)){S(),x.style.visibility="visible",k.state.isVisible=!0,k.props.interactive&&I().classList.add(Ye);var e=O();wn(e.concat(x),0),l=function(){if(k.state.isVisible){var n=A();n&&i?P(i):n||k.popperInstance.update(),k.popperChildren.backdrop&&(k.popperChildren.content.style.transitionDelay=Math.round(t/12)+"ms"),k.props.sticky&&_(),wn([x],k.props.updateDuration),wn(e,t),kn(e,"visible"),M(t,(function(){k.props.aria&&I().setAttribute("aria-".concat(k.props.aria),x.id),k.props.onShown(k),k.state.isShown=!0}))}},G()}}function tt(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:un(k.props.duration,1,Ne.duration[1]);if(!k.state.isDestroyed&&(k.state.isEnabled||m)&&(!1!==k.props.onHide(k)||m)){C(),x.style.visibility="hidden",k.state.isVisible=!1,k.state.isShown=!1,h=!1,k.props.interactive&&I().classList.remove(Ye);var e=O();wn(e,t),kn(e,"hidden"),L(t,(function(){f||E(),k.props.aria&&I().removeAttribute("aria-".concat(k.props.aria)),k.popperInstance.disableEventListeners(),k.popperInstance.options.placement=k.props.placement,x.parentNode.removeChild(x),k.props.onHidden(k),k.state.isMounted=!1}))}}}var Bn=!1;function Un(t,e){Tn(e||{},Ne),Bn||(document.addEventListener("touchstart",rn,je),window.addEventListener("blur",cn),Bn=!0);var n,i=Ce({},Ne,e);n=t,"[object Object]"==={}.toString.call(n)&&!n.addEventListener&&function(t){var e={isVirtual:!0,attributes:t.attributes||{},contains:function(){},setAttribute:function(e,n){t.attributes[e]=n},getAttribute:function(e){return t.attributes[e]},removeAttribute:function(e){delete t.attributes[e]},hasAttribute:function(e){return e in t.attributes},addEventListener:function(){},removeEventListener:function(){},classList:{classNames:{},add:function(e){t.classList.classNames[e]=!0},remove:function(e){delete t.classList.classNames[e]},contains:function(e){return e in t.classList.classNames}}};for(var n in e)t[n]=e[n]}(t);var o=function(t){if(gn(t))return[t];if(t instanceof NodeList)return Pe(t);if(q(t))return t;try{return Pe(document.querySelectorAll(t))}catch(t){return[]}}(t).reduce((function(t,e){var n=e&&jn(e,i);return n&&t.push(n),t}),[]);return gn(t)?o[0]:o}Un.version="4.3.4",Un.defaults=Ne,Un.setDefaults=function(t){ft(t).forEach((function(e){Ne[e]=t[e]}))},Un.hideAll=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.exclude,n=t.duration;Pe(document.querySelectorAll(Ze)).forEach((function(t){var i=t._tippy;if(i){var o=!1;e&&(o=pn(e)?i.reference===e:t===e.popper),o||i.hide(n)}}))},Un.group=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.delay,i=void 0===n?t[0].props.delay:n,o=e.duration,r=void 0===o?0:o,a=!1;function s(t){a=t,d()}function c(e){e._originalProps.onShow(e),t.forEach((function(t){t.set({duration:r}),t.state.isVisible&&t.hide()})),s(!0)}function l(t){t._originalProps.onHide(t),s(!1)}function p(t){t._originalProps.onShown(t),t.set({duration:t._originalProps.duration})}function d(){t.forEach((function(t){t.set({onShow:c,onShown:p,onHide:l,delay:a?[0,q(i)?i[1]:i]:i,duration:a?r:t._originalProps.duration})}))}t.forEach((function(t){t._originalProps?t.set(t._originalProps):t._originalProps=Ce({},t.props)})),d()},Oe&&setTimeout((function(){Pe(document.querySelectorAll("[data-tippy]")).forEach((function(t){var e=t.getAttribute("data-tippy");e&&Un(t,{content:e})}))}));S(S.S+S.F*!h,"Object",{defineProperty:x.f});var Vn,Yn=l.Object,Wn=function(t,e,n){return Yn.defineProperty(t,e,n)},$n=!1;!function(){if(!window.__bk_zIndex_manager){var t={nextZIndex:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return"default"===e?t.zIndex++:e},nextTickIndex:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"default";return"default"===n?(t.zIndex+=e,t.zIndex):n}};Wn(t,"zIndex",{configurable:!0,get:function(){return $n||(Vn=Vn||(e.prototype.$BK_EL||{}).zIndex||2e3,$n=!0),Vn},set:function(t){Vn=t}}),window.__bk_zIndex_manager=t}}();var Gn=window.__bk_zIndex_manager,Xn={props:{zIndex:{type:[Number,String],default:"default"}},methods:{getLocalZIndex:function(t){return Gn.nextTickIndex(2,t)}}};S(S.S,"Number",{isNaN:function(t){return t!=t}});var qn=l.Number.isNaN;function Kn(){for(var t="",e=Math.floor(65536*(1+Math.random())).toString(16).substring(1),n=0;n<7;n++)t+=e;return t}window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame,window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame,function(){if(!window.__bk_pop_manager){var t="bk_pop_".concat((new Date).getTime()),e="bk_pop_mask_".concat((new Date).getTime()),n="bk_pop_key_".concat((new Date).getTime()),i=[],o={containerId:t,maskId:e,defaultKey:n,formatKey:function(t){return String(t).replace(/#/g,"_")},show:function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{tplAction:"keepAll",zIndex:"",beforeHide:function(){},ignoreExistMask:!1,appendToBody:!1};t=null==t||qn(t)?this.defaultKey:t,t=this.formatKey(t);var i=Kn(),o=!1;if("onlyone"===n.tplAction&&this.clearByTpl(t),this.shouldAppendToBody(t))o=!0,this.showModalMask(n.zIndex),!n.appendToBody&&this.container.append(e);else{o=!1;var r=n.zIndex;this.showModalMask(r)}var a=n.beforeHide,s=n.zIndex,c=n.ignoreExistMask;return this.instances.push({uid:i,ins:e,tplName:t,beforeHide:a,zIndex:s,ignoreExistMask:c,isAppendToBody:o}),"".concat(i,"#").concat(t)},shouldAppendToBody:function(t){return this.instances.some((function(e){return e.tplName===t}))},hide:function(t,e){switch((e=e||{action:"hideUid"}).action){case"hideAll":this.clearAll();break;case"hideUid":this.clearByUid(t);break;case"hideSameTpl":this.clearByTpl(t);break;default:this.autoClear(t)}this.updateModalMaskIndex()},updateModalMaskIndex:function(){if(this.instances.length){var t=this.instances.slice(-1)[0],e=t.isAppendToBody?t.zIndex:t.zIndex-1;this.showModalMask(e)}else this.hideModalMask()},autoClear:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";t.indexOf("#")>0?this.clearByUid(t):this.clearByTpl()},clearByTpl:function(t){var e=this,n=t.split("#").slice(-1)[0];this.instances=this.instances.filter((function(t){return t.tplName!==n||(e.__hideInstance(t),!1)}))},clearByUid:function(t){var e=t.split("#"),n=e[0],i=e[1],o=this.instances.findIndex((function(t){return t.uid===n&&t.tplName===i}));o>=0?(this.__hideInstance(this.instances[o]),this.instances.splice(o,1)):console.warn("Can not find pop instance with index "+o)},clearAll:function(){var t=this;this.instances.forEach((function(e){return t.__hideInstance(e)})),this.instances=[]},showModalMask:function(t){this.dialogMask.setAttribute("style","z-index:".concat(t,";")),document.body.style.overflow="hidden",this.dialogMask.classList.remove("hide-active"),this.dialogMask.classList.add("show-active")},hideModalMask:function(){this.dialogMask.classList.remove("show-active"),this.dialogMask.classList.add("hide-active"),document.body.style.overflow=""},__hideInstance:function(t){"function"==typeof t.beforeHide?(t.beforeHide(),setTimeout((function(){t.isAppendToBody&&t.ins.remove()}))):t.isAppendToBody&&t.ins.remove()}};Wn(o,"container",{get:function(){var t=document.querySelector("[data-bkpop-container]");return t||((t=document.createElement("div")).setAttribute("id",this.containerId),t.setAttribute("data-bkpop-container","true"),document.body.append(t)),t}}),Wn(o,"dialogMask",{get:function(){var t=document.querySelector("[data-bkpop-mask]");return t||((t=document.createElement("div")).setAttribute("id",this.maskId),t.setAttribute("class","bk-dialog-mask"),t.setAttribute("data-bkpop-mask","true"),this.container.append(t)),t}}),Wn(o,"instances",{get:function(){return i},set:function(t){i=t}}),window.__bk_pop_manager=o,window.__bk_pop_manager.__container=o.container}}();var Zn=window.__bk_pop_manager,Qn={name:"bk-popover",mixins:[Xn],props:{placement:{type:String,default:"top"},content:{type:String,default:""},theme:{type:String,default:"dark"},interactive:{type:[Boolean,String],default:!0},arrow:{type:[Boolean,String],default:!0},arrowType:{type:String,default:"sharp"},boundary:{type:String,default:"window"},showOnInit:{type:Boolean,default:!1},arrowTransform:{type:String,default:""},trigger:{type:String,default:"mouseenter focus"},animation:{type:String,default:"shift-away"},distance:{type:Number,default:10},width:{type:[String,Number],default:"auto"},maxWidth:{type:[String,Number],default:"auto"},offset:{type:[Number,String],default:0},always:{type:Boolean,default:!1},followCursor:{type:[Boolean,String],default:!1},sticky:{type:[Boolean,String],default:!1},delay:{type:Number,default:100},size:{type:String,default:"small"},onShow:{type:Function,default:function(){}},onHide:{type:Function,default:function(){}},tippyOptions:{type:Object,default:function(){return{}}},extCls:{type:String,default:""},disabled:Boolean},data:function(){return{instance:null}},watch:{disabled:function(t){this.instance&&(t?this.instance.disable():this.instance.enable())},tippyOptions:function(t){this.instance&&this.instance.set(t)}},mounted:function(){var t=this,e=function(t){var e={};for(var n in t)Ne.hasOwnProperty(n)&&(e[n]=t[n]);return e}(a({},{appendTo:Zn.container},this.$props,this.tippyOptions)),n=e.onShow,i=e.onHide,o="number"==typeof e.zIndex?e.zIndex:null;e.onShow=function(e){e.set({zIndex:t.getLocalZIndex(o||t.zIndex)}),n&&n(e),t.$emit("show")},e.onHide=function(e){i&&i(e),t.$emit("hide")},e.content=this.$refs.html,this.always&&(e.showOnInit=!0,e.hideOnClick=!1,e.trigger="manual"),this.instance=Un(this.$refs.reference,e),this.disabled&&this.instance.disable()},updated:function(){this.instance.setContent(this.$refs.html),this.instance.popperInstance&&this.instance.popperInstance.update()},beforeDestroy:function(){this.instance.destroy()},methods:{showHandler:function(){this.instance.show()},hideHandler:function(){this.instance.hide()}}};function Jn(t,e,n,i,o,r,a,s,c,l){"boolean"!=typeof a&&(c=s,s=a,a=!1);var p,d="function"==typeof n?n.options:n;if(t&&t.render&&(d.render=t.render,d.staticRenderFns=t.staticRenderFns,d._compiled=!0,o&&(d.functional=!0)),i&&(d._scopeId=i),r?(p=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),e&&e.call(this,c(t)),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=p):e&&(p=a?function(){e.call(this,l(this.$root.$options.shadowRoot))}:function(t){e.call(this,s(t))}),p)if(d.functional){var u=d.render;d.render=function(t,e){return p.call(e),u(t,e)}}else{var f=d.beforeCreate;d.beforeCreate=f?[].concat(f,p):[p]}return n}var ti,ei,ni=Jn({render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"bk-tooltip"},[n("div",{ref:"html",staticClass:"bk-tooltip-content"},[t._t("content",[t._v(t._s(t.content))])],2),n("div",{ref:"reference",staticClass:"bk-tooltip-ref",attrs:{tabindex:"-1"}},[t._t("default")],2)])},staticRenderFns:[]},undefined,Qn,undefined,!1,undefined,!1,void 0,void 0,void 0);ei=function(t,e){t.prototype.$bkPopover=function(t,e){return Un(t,e)}},(ti=ni).install=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=ti.props||{};ft(e).forEach((function(t){n.hasOwnProperty(t)&&("function"==typeof n[t]||n[t]instanceof Array?n[t]={type:n[t],default:e[t]}:n[t].default=e[t])})),ti.name=e.namespace?ti.name.replace("bk",e.namespace):ti.name,t.component(ti.name,ti),"function"==typeof ei&&ei(t,e)},t.default=ni,Object.defineProperty(t,"__esModule",{value:!0})}(e,n(8976))},5768:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'.bk-form-checkbox{position:relative;display:inline-block;vertical-align:middle;font-size:0;line-height:18px;overflow:hidden;cursor:pointer}.bk-form-checkbox.is-indeterminate:not(.is-checked) .bk-checkbox{border-color:#3a84ff;background-color:#3a84ff;background-clip:content-box;position:relative}.bk-form-checkbox.is-indeterminate:not(.is-checked) .bk-checkbox:after{content:"";width:8px;height:2px;background:#fff;position:absolute;top:50%;left:50%;margin-left:-4px;border-radius:2px;margin-top:-1px;display:inline-block}.bk-form-checkbox.is-disabled{cursor:not-allowed}.bk-form-checkbox.is-disabled .bk-checkbox{border-color:#dcdee5;background-color:#fafbfd}.bk-form-checkbox.is-disabled.is-checked .bk-checkbox{border-color:#dcdee5;background-color:#dcdee5}.bk-form-checkbox.is-checked .bk-checkbox{border-color:#3a84ff;background-color:#3a84ff;background-clip:border-box}.bk-form-checkbox.is-checked .bk-checkbox:after{content:"";position:absolute;top:1px;left:4px;width:4px;height:8px;border:2px solid #fff;border-left:0;border-top:0;-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.bk-form-checkbox .bk-checkbox{position:relative;display:inline-block;vertical-align:middle;width:16px;height:16px;border:1px solid #979ba5;border-radius:2px}.bk-form-checkbox .bk-checkbox:focus{border-color:#3c96ff;outline:none}.bk-form-checkbox .bk-checkbox-text{display:inline-block;margin:0 0 0 6px;vertical-align:middle;font-size:14px;color:#63656e}',""]);const s=a},5861:(t,e,n)=>{"use strict";n.d(e,{Z:()=>v});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r),s=n(3626),c=n.n(s),l=new URL(n(5817),n.b),p=new URL(n(1365),n.b),d=new URL(n(8210),n.b),u=new URL(n(3385),n.b),f=a()(o()),m=c()(l,{hash:"#iconcool"}),h=c()(p),b=c()(d),g=c()(u,{hash:"?#iefix"});f.push([t.id,"body,html{margin:0;padding:0}*{-webkit-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:rgba(0,0,0,0)}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}::-ms-clear,::-ms-reveal{display:none}input[type=text]::-ms-clear{display:none}input[type=text]::-ms-reveal{display:none}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}@font-face{font-family:bk;src:url("+m+') format("svg"),url('+h+') format("truetype"),url('+b+') format("woff"),url('+g+') format("embedded-opentype");font-weight:400;font-style:normal}.bk-icon{font-family:bk !important;speak:none;font-style:normal;font-weight:400;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;text-transform:none;line-height:1;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-angle-double-down:before{content:""}.icon-angle-double-left:before{content:""}.icon-angle-double-right:before{content:""}.icon-angle-double-up:before{content:""}.icon-angle-left:before{content:""}.icon-angle-down:before{content:""}.icon-angle-right:before{content:""}.icon-angle-up:before{content:""}.icon-apps-shape:before{content:""}.icon-apps:before{content:""}.icon-area-chart:before{content:""}.icon-arrows-down-circle-shape:before{content:""}.icon-arrows-down-circle:before{content:""}.icon-arrows-down-shape:before{content:""}.icon-arrows-down:before{content:""}.icon-arrows-left-circle-shape:before{content:""}.icon-arrows-left-circle:before{content:""}.icon-arrows-left-shape:before{content:""}.icon-arrows-left:before{content:""}.icon-arrows-m-down-shape:before{content:""}.icon-arrows-m-left-shape:before{content:""}.icon-arrows-m-right-shape:before{content:""}.icon-arrows-m-up-shape:before{content:""}.icon-arrows-right-circle-shape:before{content:""}.icon-arrows-right-circle:before{content:""}.icon-arrows-right-shape:before{content:""}.icon-arrows-right:before{content:""}.icon-arrows-up-circle-shape:before{content:""}.icon-arrows-up-circle:before{content:""}.icon-arrows-up-shape:before{content:""}.icon-arrows-up:before{content:""}.icon-back-shape:before{content:""}.icon-back:before{content:""}.icon-back2:before{content:""}.icon-bar-chart:before{content:""}.icon-bk:before{content:""}.icon-block-shape:before{content:""}.icon-calendar-shape:before{content:""}.icon-calendar:before{content:""}.icon-chain:before{content:""}.icon-check-1:before{content:""}.icon-check-circle-shape:before{content:""}.icon-check-circle:before{content:""}.icon-circle-2-1:before{content:""}.icon-circle-4-1:before{content:""}.icon-circle-shape:before{content:""}.icon-circle:before{content:""}.icon-clipboard-shape:before{content:""}.icon-clipboard:before{content:""}.icon-clock-shape:before{content:""}.icon-clock:before{content:""}.icon-close-circle-shape:before{content:""}.icon-close-circle:before{content:""}.icon-close:before{content:""}.icon-close3-shape:before{content:""}.icon-code:before{content:""}.icon-cog-shape:before{content:""}.icon-cog:before{content:""}.icon-cry-shape:before{content:""}.icon-cry:before{content:""}.icon-dashboard-2-shape:before{content:""}.icon-dashboard-2:before{content:""}.icon-dashboard-shape:before{content:""}.icon-dashboard:before{content:""}.icon-data-shape:before{content:""}.icon-data:before{content:""}.icon-data2-shape:before{content:""}.icon-data2:before{content:""}.icon-dedent:before{content:""}.icon-delete:before{content:""}.icon-dialogue-empty-shape:before{content:""}.icon-dialogue-empty:before{content:""}.icon-dialogue-shape:before{content:""}.icon-dialogue:before{content:""}.icon-dispirited-shape:before{content:""}.icon-dispirited:before{content:""}.icon-docker:before{content:""}.icon-down-shape:before{content:""}.icon-download:before{content:""}.icon-edit:before{content:""}.icon-edit2:before{content:""}.icon-ellipsis:before{content:""}.icon-email-shape:before{content:""}.icon-email:before{content:""}.icon-empty-shape:before{content:""}.icon-empty:before{content:""}.icon-end:before{content:""}.icon-exclamation-circle-shape:before{content:""}.icon-exclamation-circle:before{content:""}.icon-exclamation-triangle-shape:before{content:""}.icon-exclamation-triangle:before{content:""}.icon-exclamation:before{content:""}.icon-execute:before{content:""}.icon-eye-shape:before{content:""}.icon-eye-slash-shape:before{content:""}.icon-eye-slash:before{content:""}.icon-eye:before{content:""}.icon-file-plus-shape:before{content:""}.icon-file-plus:before{content:""}.icon-file-shape:before{content:""}.icon-file:before{content:""}.icon-folder-open-shape:before{content:""}.icon-folder-open:before{content:""}.icon-folder-plus-shape:before{content:""}.icon-folder-plus:before{content:""}.icon-folder-shape:before{content:""}.icon-folder:before{content:""}.icon-full-screen:before{content:""}.icon-heart-shape:before{content:""}.icon-heart:before{content:""}.icon-hide:before{content:""}.icon-home-shape:before{content:""}.icon-home:before{content:""}.icon-id-shape:before{content:""}.icon-id:before{content:""}.icon-image-shape:before{content:""}.icon-image:before{content:""}.icon-indent:before{content:""}.icon-info-circle-shape:before{content:""}.icon-info-circle:before{content:""}.icon-info:before{content:""}.icon-key:before{content:""}.icon-left-shape:before{content:""}.icon-line-chart:before{content:""}.icon-list:before{content:""}.icon-lock-shape:before{content:""}.icon-lock:before{content:""}.icon-minus-circle-shape:before{content:""}.icon-minus-circle:before{content:""}.icon-minus-square-shape:before{content:""}.icon-minus-square:before{content:""}.icon-minus:before{content:""}.icon-mobile-shape:before{content:""}.icon-mobile:before{content:""}.icon-monitors-cog:before{content:""}.icon-monitors:before{content:""}.icon-more:before{content:""}.icon-move:before{content:""}.icon-next-shape:before{content:""}.icon-next:before{content:""}.icon-order-shape:before{content:""}.icon-order:before{content:""}.icon-panel-permission:before{content:""}.icon-panel-shape:before{content:""}.icon-panel:before{content:""}.icon-panels:before{content:""}.icon-password-shape:before{content:""}.icon-password:before{content:""}.icon-pause:before{content:""}.icon-pc-shape:before{content:""}.icon-pc:before{content:""}.icon-pie-chart-shape:before{content:""}.icon-pie-chart:before{content:""}.icon-pipeline-shape:before{content:""}.icon-pipeline:before{content:""}.icon-play-circle-shape:before{content:""}.icon-play-shape:before{content:""}.icon-play:before{content:""}.icon-play2:before{content:""}.icon-play3:before{content:""}.icon-plus-circle-shape:before{content:""}.icon-plus-circle:before{content:""}.icon-plus-square-shape:before{content:""}.icon-plus-square:before{content:""}.icon-plus:before{content:""}.icon-project:before{content:""}.icon-qq-shape:before{content:""}.icon-qq:before{content:""}.icon-question-circle-shape:before{content:""}.icon-question-circle:before{content:""}.icon-question:before{content:""}.icon-refresh:before{content:""}.icon-right-shape:before{content:""}.icon-rtx:before{content:""}.icon-save-shape:before{content:""}.icon-save:before{content:""}.icon-script-file:before{content:""}.icon-script-files:before{content:""}.icon-search:before{content:""}.icon-sina-shape:before{content:""}.icon-sina:before{content:""}.icon-sitemap-shape:before{content:""}.icon-sitemap:before{content:""}.icon-smile-shape:before{content:""}.icon-smile:before{content:""}.icon-sort:before{content:""}.icon-star-shape:before{content:""}.icon-star:before{content:""}.icon-stop-shape:before{content:""}.icon-stop:before{content:""}.icon-tree-application-shape:before{content:""}.icon-tree-application:before{content:""}.icon-tree-group-shape:before{content:""}.icon-tree-group:before{content:""}.icon-tree-module-shape:before{content:""}.icon-tree-module:before{content:""}.icon-tree-process-shape:before{content:""}.icon-tree-process:before{content:""}.icon-un-full-screen:before{content:""}.icon-unlock-shape:before{content:""}.icon-unlock:before{content:""}.icon-up-shape:before{content:""}.icon-upload:before{content:""}.icon-user-shape:before{content:""}.icon-user:before{content:""}.icon-weixin-shape:before{content:""}.icon-weixin:before{content:""}.icon-work-manage:before{content:""}.icon-funnel:before{content:""}.icon-user-group:before{content:""}.icon-user-3:before{content:""}.icon-copy:before{content:""}.icon-batch-edit-line:before{content:""}.icon-refresh-line:before{content:""}.icon-close-line:before{content:""}.icon-1_up:before{content:""}.icon-arrows-right--line:before{content:""}.icon-arrows-left-line:before{content:""}.icon-arrows-down-line:before{content:""}.icon-arrows-up-line:before{content:""}.icon-angle-double-right-line:before{content:""}.icon-angle-double-down-line:before{content:""}.icon-angle-double-up-line:before{content:""}.icon-angle-double-left-line:before{content:""}.icon-angle-left-line:before{content:""}.icon-angle-right-line:before{content:""}.icon-angle-up-line:before{content:""}.icon-angle-down-line:before{content:""}.icon-check-line:before{content:""}.icon-close-line-2:before{content:""}.icon-edit-line:before{content:""}.icon-list-line:before{content:""}.icon-plus-line:before{content:""}.icon-angle-up-fill:before{content:""}.icon-angle-down-fill:before{content:""}.icon-grag-fill:before{content:""}.icon-template-fill-49:before{content:""}.icon-folder-fill:before{content:""}.icon-expand-line:before{content:""}.icon-shrink-line:before{content:""}.icon-minus-line:before{content:""}.icon-compressed-file:before{content:""}.icon-upload-cloud:before{content:""}.icon-text-file:before{content:""}.icon-filliscreen-line:before{content:""}.icon-left-turn-line:before{content:""}.icon-right-turn-line:before{content:""}.icon-enlarge-line:before{content:""}.icon-narrow-line:before{content:""}.icon-unfull-screen:before{content:""}.icon-image:before{content:""}.icon-image-fail:before{content:""}.icon-normalized:before{content:""}.icon-chinese:before{content:""}.icon-english:before{content:""}.icon-japanese:before{content:""}.transition-effect{-webkit-transition:all .2s linear;transition:all .2s linear}.bk-fade-in-linear-enter-active,.bk-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.bk-fade-in-linear-enter,.bk-fade-in-linear-leave-active{opacity:0}.bk-fade-in-ease-enter-active,.bk-fade-in-ease-leave-active{-webkit-transition:opacity .2s cubic-bezier(0.55, 0, 0.1, 1);transition:opacity .2s cubic-bezier(0.55, 0, 0.1, 1)}.bk-fade-in-ease-enter,.bk-fade-in-ease-leave-active{opacity:0}.bk-slide-fade-right-enter-active,.bk-slide-fade-right-leave-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:transform .2s linear,opacity .2s linear;transition:transform .2s linear,opacity .2s linear,-webkit-transform .2s linear}.bk-slide-fade-right-enter{opacity:0;-webkit-transform:translate3d(20%, 0, 0);transform:translate3d(20%, 0, 0)}.bk-slide-fade-right-leave-active{opacity:0;-webkit-transform:translate3d(-20%, 0, 0);transform:translate3d(-20%, 0, 0)}.bk-slide-fade-left-enter-active,.bk-slide-fade-left-leave-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:transform .2s linear,opacity .2s linear;transition:transform .2s linear,opacity .2s linear,-webkit-transform .2s linear}.bk-slide-fade-left-enter{opacity:0;-webkit-transform:translate3d(-20%, 0, 0);transform:translate3d(-20%, 0, 0)}.bk-slide-fade-left-leave-active{opacity:0;-webkit-transform:translate3d(20%, 0, 0);transform:translate3d(20%, 0, 0)}.bk-slide-fade-up-enter-active,.bk-slide-fade-up-leave-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:transform .2s linear,opacity .2s linear;transition:transform .2s linear,opacity .2s linear,-webkit-transform .2s linear}.bk-slide-fade-up-enter{opacity:0;-webkit-transform:translate3d(0, 20%, 0);transform:translate3d(0, 20%, 0)}.bk-slide-fade-up-leave-active{opacity:0;-webkit-transform:translate3d(0, -20%, 0);transform:translate3d(0, -20%, 0)}.bk-slide-fade-down-enter-active,.bk-slide-fade-down-leave-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:transform .2s linear,opacity .2s linear;transition:transform .2s linear,opacity .2s linear,-webkit-transform .2s linear}.bk-slide-fade-down-enter{opacity:0;-webkit-transform:translate3d(0, -20%, 0);transform:translate3d(0, -20%, 0)}.bk-slide-fade-down-leave-active{opacity:0;-webkit-transform:translate3d(0, 20%, 0);transform:translate3d(0, 20%, 0)}.bk-zoom-enter,.bk-zoom-leave-to{-webkit-transform:scale(0);transform:scale(0)}.bk-zoom-enter-to,.bk-zoom-leave{-webkit-transform:scale(1);transform:scale(1)}.bk-zoom-enter-active,.bk-zoom-leave-active{-webkit-transition:all .2s;transition:all .2s}.bk-move-in-left-enter,.bk-move-in-left-leave-to{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);opacity:0}.bk-move-in-left-enter-to,.bk-move-in-left-leave{-webkit-transform:translateZ(0);transform:translateZ(0)}.bk-move-in-left-enter-active,.bk-move-in-left-leave-active{-webkit-transition:all .2s;transition:all .2s}.bk-move-in-right-enter,.bk-move-in-right-leave-to{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);opacity:0}.bk-move-in-right-enter-to,.bk-move-in-right-leave{-webkit-transform:translateZ(0);transform:translateZ(0)}.bk-move-in-right-enter-active,.bk-move-in-right-leave-active{-webkit-transition:all .2s;transition:all .2s}.bk-move-in-up-enter,.bk-move-in-up-leave-to{-webkit-transform:translate3d(0, -100%, 0);transform:translate3d(0, -100%, 0);opacity:0}.bk-move-in-up-enter-to,.bk-move-in-up-leave{-webkit-transform:translateZ(0);transform:translateZ(0)}.bk-move-in-up-enter-active,.bk-move-in-up-leave-active{-webkit-transition:all .2s;transition:all .2s}.bk-move-in-down-enter,.bk-move-in-down-leave-to{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0);opacity:0}.bk-move-in-down-enter-to,.bk-move-in-down-leave{-webkit-transform:translateZ(0);transform:translateZ(0)}.bk-move-in-down-enter-active,.bk-move-in-down-leave-active{-webkit-transition:all .2s;transition:all .2s}.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.fade-enter,.fade-leave-active{opacity:0}.fade-show-enter-active,.fade-show-leave-active{-webkit-transition:opacity .2s linear,margin .2s linear;transition:opacity .2s linear,margin .2s linear}.fade-show-enter,.fade-show-leave-active{opacity:0;margin-top:-20px}.displacement-fade-show-enter-active,.displacement-fade-show-leave-active{-webkit-transition:opacity .2s linear,margin .2s linear;transition:opacity .2s linear,margin .2s linear}.displacement-fade-show-enter,.displacement-fade-show-leave-active{opacity:0;margin-top:-50px}.fade-center-enter-active,.fade-center-leave-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,transform .2s linear;transition:opacity .2s linear,transform .2s linear,-webkit-transform .2s linear;-webkit-transform-origin:center center;transform-origin:center center}.fade-center-enter,.fade-center-leave-active{opacity:0;-webkit-transform:translate(50%, -50%) scale(0) !important;transform:translate(50%, -50%) scale(0) !important}.slide-enter-active .bk-sideslider-wrapper,.slide-leave-active .bk-sideslider-wrapper{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:-webkit-transform .25s;transition:-webkit-transform .25s;transition:transform .25s;transition:transform .25s,-webkit-transform .25s}.slide-enter-active{-webkit-animation:slider-fade-in .25s;animation:slider-fade-in .25s}.slide-leave-active{animation:slider-fade-in .25s reverse}.slide-enter .bk-sideslider-wrapper.left,.slide-leave-to .bk-sideslider-wrapper.left{-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-transition:-webkit-transform .25s;transition:-webkit-transform .25s;transition:transform .25s;transition:transform .25s,-webkit-transform .25s}.slide-enter .bk-sideslider-wrapper.right,.slide-leave-to .bk-sideslider-wrapper.right{-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transition:-webkit-transform .25s;transition:-webkit-transform .25s;transition:transform .25s;transition:transform .25s,-webkit-transform .25s}@-webkit-keyframes slider-fade-in{0%{opacity:0}to{opacity:1}}@keyframes slider-fade-in{0%{opacity:0}to{opacity:1}}.fade-leave-active,.slide-fade-enter-active{-webkit-transition:opacity .2s linear,-webkit-transform .2s linear;transition:opacity .2s linear,-webkit-transform .2s linear;transition:transform .2s linear,opacity .2s linear;transition:transform .2s linear,opacity .2s linear,-webkit-transform .2s linear}.slide-fade-enter{opacity:0;-webkit-transform:translateX(20%);transform:translateX(20%)}.slide-fade-leave-active{opacity:0;-webkit-transform:translateX(-20%);transform:translateX(-20%)}.toggle-slide-enter-active,.toggle-slide-leave-active{-webkit-transition:opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);transition:opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);transition:transform .3s cubic-bezier(0.23, 1, 0.23, 1),opacity .5s cubic-bezier(0.23, 1, 0.23, 1);transition:transform .3s cubic-bezier(0.23, 1, 0.23, 1),opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);-webkit-transform-origin:center top;transform-origin:center top}.toggle-slide-enter,.toggle-slide-leave-active{-webkit-transform:translateZ(0) scaleY(0);transform:translateZ(0) scaleY(0);opacity:0}.toggle-slide2-enter-active,.toggle-slide2-leave-active{-webkit-transition:opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);transition:opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);transition:transform .3s cubic-bezier(0.23, 1, 0.23, 1),opacity .5s cubic-bezier(0.23, 1, 0.23, 1);transition:transform .3s cubic-bezier(0.23, 1, 0.23, 1),opacity .5s cubic-bezier(0.23, 1, 0.23, 1),-webkit-transform .3s cubic-bezier(0.23, 1, 0.23, 1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.toggle-slide2-enter,.toggle-slide2-leave-active{-webkit-transform:translateZ(0) scaleY(0);transform:translateZ(0) scaleY(0);opacity:0}.fade-appear,.fade-enter-active,.fade-leave-active{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.fade-appear,.fade-enter-active{-webkit-animation-name:fadeIn;animation-name:fadeIn;-webkit-animation-play-state:running;animation-play-state:running}.fade-leave-active{-webkit-animation-name:fadeOut;animation-name:fadeOut;-webkit-animation-play-state:running;animation-play-state:running}.fade-appear,.fade-enter-active{opacity:0}.fade-appear,.fade-enter-active,.fade-leave-active{-webkit-animation-timing-function:linear;animation-timing-function:linear}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.ease-appear,.ease-enter-active,.ease-leave-active{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.ease-appear,.ease-enter-active{-webkit-animation-name:easeIn;animation-name:easeIn;-webkit-animation-play-state:running;animation-play-state:running}.ease-leave-active{-webkit-animation-name:easeOut;animation-name:easeOut;-webkit-animation-play-state:running;animation-play-state:running}.ease-appear,.ease-enter-active{opacity:0}.ease-appear,.ease-enter-active,.ease-leave-active{-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes easeIn{0%{opacity:0;-webkit-transform:scale(0.9);transform:scale(0.9)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes easeIn{0%{opacity:0;-webkit-transform:scale(0.9);transform:scale(0.9)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes easeOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform:scale(0.9);transform:scale(0.9)}}@keyframes easeOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform:scale(0.9);transform:scale(0.9)}}.transition-drop-appear,.transition-drop-enter-active,.transition-drop-leave-active{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.transition-drop-appear,.transition-drop-enter-active{-webkit-animation-name:transitionDropIn;animation-name:transitionDropIn;-webkit-animation-play-state:running;animation-play-state:running}.transition-drop-leave-active{-webkit-animation-name:transitionDropOut;animation-name:transitionDropOut;-webkit-animation-play-state:running;animation-play-state:running}.transition-drop-appear,.transition-drop-enter-active{opacity:0}.transition-drop-appear,.transition-drop-enter-active,.transition-drop-leave-active{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}@-webkit-keyframes transitionDropIn{0%{opacity:0;-webkit-transform:scaleY(0.8);transform:scaleY(0.8)}to{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}}@keyframes transitionDropIn{0%{opacity:0;-webkit-transform:scaleY(0.8);transform:scaleY(0.8)}to{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}}@-webkit-keyframes transitionDropOut{0%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}to{opacity:0;-webkit-transform:scaleY(0.8);transform:scaleY(0.8)}}@keyframes transitionDropOut{0%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}to{opacity:0;-webkit-transform:scaleY(0.8);transform:scaleY(0.8)}}.f10{font-size:10px !important}.f11{font-size:11px !important}.f12{font-size:12px !important}.f13{font-size:13px !important}.f14{font-size:14px !important}.f15{font-size:15px !important}.f16{font-size:16px !important}.f17{font-size:17px !important}.f18{font-size:18px !important}.f19{font-size:19px !important}.f20{font-size:20px !important}.f21{font-size:21px !important}.f22{font-size:22px !important}.f23{font-size:23px !important}.f24{font-size:24px !important}.f25{font-size:25px !important}.f26{font-size:26px !important}.f27{font-size:27px !important}.f28{font-size:28px !important}.f29{font-size:29px !important}.f30{font-size:30px !important}.f31{font-size:31px !important}.f32{font-size:32px !important}.f33{font-size:33px !important}.f34{font-size:34px !important}.f35{font-size:35px !important}.f36{font-size:36px !important}.f37{font-size:37px !important}.f38{font-size:38px !important}.f39{font-size:39px !important}.f40{font-size:40px !important}.m0{margin:0 !important}.m5{margin:5px !important}.m10{margin:10px !important}.m15{margin:15px !important}.m20{margin:20px !important}.m25{margin:25px !important}.m30{margin:30px !important}.m35{margin:35px !important}.m40{margin:40px !important}.m45{margin:45px !important}.m50{margin:50px !important}.mt0{margin-top:0 !important}.mt5{margin-top:5px !important}.mt10{margin-top:10px !important}.mt15{margin-top:15px !important}.mt20{margin-top:20px !important}.mt25{margin-top:25px !important}.mt30{margin-top:30px !important}.mt35{margin-top:35px !important}.mt40{margin-top:40px !important}.mt45{margin-top:45px !important}.mt50{margin-top:50px !important}.mb0{margin-bottom:0 !important}.mb5{margin-bottom:5px !important}.mb10{margin-bottom:10px !important}.mb15{margin-bottom:15px !important}.mb20{margin-bottom:20px !important}.mb25{margin-bottom:25px !important}.mb30{margin-bottom:30px !important}.mb35{margin-bottom:35px !important}.mb40{margin-bottom:40px !important}.mb45{margin-bottom:45px !important}.mb50{margin-bottom:50px !important}.ml0{margin-left:0 !important}.ml5{margin-left:5px !important}.ml10{margin-left:10px !important}.ml15{margin-left:15px !important}.ml20{margin-left:20px !important}.ml25{margin-left:25px !important}.ml30{margin-left:30px !important}.ml35{margin-left:35px !important}.ml40{margin-left:40px !important}.ml45{margin-left:45px !important}.ml50{margin-left:50px !important}.mr0{margin-right:0 !important}.mr5{margin-right:5px !important}.mr10{margin-right:10px !important}.mr15{margin-right:15px !important}.mr20{margin-right:20px !important}.mr25{margin-right:25px !important}.mr30{margin-right:30px !important}.mr35{margin-right:35px !important}.mr40{margin-right:40px !important}.mr45{margin-right:45px !important}.mr50{margin-right:50px !important}.p0{padding:0 !important}.p5{padding:5px !important}.p10{padding:10px !important}.p15{padding:15px !important}.p20{padding:20px !important}.p25{padding:25px !important}.p30{padding:30px !important}.p35{padding:35px !important}.p40{padding:40px !important}.p45{padding:45px !important}.p50{padding:50px !important}.pt0{padding-top:0 !important}.pt5{padding-top:5px !important}.pt10{padding-top:10px !important}.pt15{padding-top:15px !important}.pt20{padding-top:20px !important}.pt25{padding-top:25px !important}.pt30{padding-top:30px !important}.pt35{padding-top:35px !important}.pt40{padding-top:40px !important}.pt45{padding-top:45px !important}.pt50{padding-top:50px !important}.pb0{padding-bottom:0 !important}.pb5{padding-bottom:5px !important}.pb10{padding-bottom:10px !important}.pb15{padding-bottom:15px !important}.pb20{padding-bottom:20px !important}.pb25{padding-bottom:25px !important}.pb30{padding-bottom:30px !important}.pb35{padding-bottom:35px !important}.pb40{padding-bottom:40px !important}.pb45{padding-bottom:45px !important}.pb50{padding-bottom:50px !important}.pl0{padding-left:0 !important}.pl5{padding-left:5px !important}.pl10{padding-left:10px !important}.pl15{padding-left:15px !important}.pl20{padding-left:20px !important}.pl25{padding-left:25px !important}.pl30{padding-left:30px !important}.pl35{padding-left:35px !important}.pl40{padding-left:40px !important}.pl45{padding-left:45px !important}.pl50{padding-left:50px !important}.pr0{padding-right:0 !important}.pr5{padding-right:5px !important}.pr10{padding-right:10px !important}.pr15{padding-right:15px !important}.pr20{padding-right:20px !important}.pr25{padding-right:25px !important}.pr30{padding-right:30px !important}.pr35{padding-right:35px !important}.pr40{padding-right:40px !important}.pr45{padding-right:45px !important}.pr50{padding-right:50px !important}.bk-bg-default{background-color:#fafbfd}.bk-bg-primary{background-color:#3a84ff}.bk-bg-warning{background-color:#ff9c01}.bk-bg-danger{background-color:#ea3636}.bk-bg-success{background-color:#2dcb56}.bk-text-default{color:#63656e}.bk-text-primary{color:#3a84ff}.bk-text-warning{color:#ff9c01}.bk-text-danger{color:#ea3636}.bk-text-success{color:#2dcb56}.bk-text-main{color:#313238}.bk-text-minor{color:#979ba5}.bk-text-yahei{font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Microsoft Yahei,Helvetica,Aria}.fb{font-weight:700 !important}.fn{font-weight:400 !important}.lh150{line-height:150% !important}.lh180{line-height:180% !important}.lh200{line-height:200% !important}.unl{text-decoration:underline !important}.no_unl{text-decoration:none !important}.tl{text-align:left !important}.tc{text-align:center !important}.tr{text-align:right !important}.bc{margin-left:auto !important;margin-right:auto !important}.fl{float:left !important}.fr{float:right !important}.cb{clear:both !important}.cl{clear:left !important}.cr{clear:right !important}.vm{vertical-align:middle !important}.pr{position:relative !important}.abs-right,.pa{position:absolute !important}.zoom{zoom:1}.hidden{visibility:hidden !important}.none{display:none !important}.h50{height:50px !important}.h80{height:80px !important}.h100{height:100px !important}.h200{height:200px !important}.h{height:100% !important}.bk-has-submenu{position:relative}.bk-has-submenu:hover .bk-submenu{display:block}.bk-has-submenu .bk-submenu{display:none;position:absolute;width:100%;left:0;right:0;padding:0}.bk-has-submenu .bk-submenu>li{display:block}',""]);const v=f},2979:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'.bk-tooltip,.bk-tooltip-ref{display:inline-block}.bk-tooltip-ref{position:relative;outline:0}.tippy-iOS{cursor:pointer !important;-webkit-tap-highlight-color:rgba(0,0,0,0)}.tippy-popper{-webkit-transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);max-width:calc(100% - 8px);pointer-events:none;outline:0}.tippy-popper[x-placement^=top] .tippy-backdrop{border-radius:40% 40% 0 0}.tippy-popper[x-placement^=top] .tippy-roundarrow{bottom:-7px;bottom:-6.5px;-webkit-transform-origin:50% 0;transform-origin:50% 0;margin:0 3px}.tippy-popper[x-placement^=top] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.tippy-popper[x-placement^=top] .tippy-arrow{border-top:8px solid #333;border-right:8px solid rgba(0,0,0,0);border-left:8px solid rgba(0,0,0,0);bottom:-7px;margin:0 3px;-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=top] .tippy-backdrop{-webkit-transform-origin:0 25%;transform-origin:0 25%}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -55%);transform:scale(1) translate(-50%, -55%)}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-50%, -45%);transform:scale(0.2) translate(-50%, -45%);opacity:0}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(-10px) rotateX(0);transform:perspective(700px) translateY(-10px) rotateX(0)}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateY(0) rotateX(60deg);transform:perspective(700px) translateY(0) rotateX(60deg)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=top] [data-animation=scale]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(-10px) scale(1);transform:translateY(-10px) scale(1)}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px) scale(0.5);transform:translateY(-10px) scale(0.5)}.tippy-popper[x-placement^=top] [data-animation=slide-toggle]{-webkit-transform-origin:center bottom;transform-origin:center bottom}.tippy-popper[x-placement^=top] [data-animation=slide-toggle][data-state=visible]{-webkit-transform:scaleY(1);transform:scaleY(1);opacity:1}.tippy-popper[x-placement^=top] [data-animation=slide-toggle][data-state=hidden]{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.tippy-popper[x-placement^=bottom] .tippy-backdrop{border-radius:0 0 30% 30%}.tippy-popper[x-placement^=bottom] .tippy-roundarrow{top:-7px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;margin:0 3px}.tippy-popper[x-placement^=bottom] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(0);transform:rotate(0)}.tippy-popper[x-placement^=bottom] .tippy-arrow{border-bottom:8px solid #333;border-right:8px solid rgba(0,0,0,0);border-left:8px solid rgba(0,0,0,0);top:-7px;margin:0 3px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%}.tippy-popper[x-placement^=bottom] .tippy-backdrop{-webkit-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -45%);transform:scale(1) translate(-50%, -45%)}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-50%);transform:scale(0.2) translate(-50%);opacity:0}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(10px) rotateX(0);transform:perspective(700px) translateY(10px) rotateX(0)}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateY(0) rotateX(-60deg);transform:perspective(700px) translateY(0) rotateX(-60deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=bottom] [data-animation=scale]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(10px) scale(1);transform:translateY(10px) scale(1)}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px) scale(0.5);transform:translateY(10px) scale(0.5)}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle]{-webkit-transform-origin:center top;transform-origin:center top}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle][data-state=visible]{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle][data-state=hidden]{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.tippy-popper[x-placement^=left] .tippy-backdrop{border-radius:50% 0 0 50%}.tippy-popper[x-placement^=left] .tippy-roundarrow{right:-12px;-webkit-transform-origin:33.33333333% 50%;transform-origin:33.33333333% 50%;margin:3px 0}.tippy-popper[x-placement^=left] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.tippy-popper[x-placement^=left] .tippy-arrow{border-left:8px solid #333;border-top:8px solid rgba(0,0,0,0);border-bottom:8px solid rgba(0,0,0,0);right:-7px;margin:3px 0;-webkit-transform-origin:0 50%;transform-origin:0 50%}.tippy-popper[x-placement^=left] .tippy-backdrop{-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -50%);transform:scale(1) translate(-50%, -50%)}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-75%, -50%);transform:scale(0.2) translate(-75%, -50%);opacity:0}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(-20px);transform:translateX(-20px)}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(-10px) rotateY(0);transform:perspective(700px) translateX(-10px) rotateY(0)}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateX(0) rotateY(-60deg);transform:perspective(700px) translateX(0) rotateY(-60deg)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=left] [data-animation=scale]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(-10px) scale(1);transform:translateX(-10px) scale(1)}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px) scale(0.5);transform:translateX(-10px) scale(0.5)}.tippy-popper[x-placement^=right] .tippy-backdrop{border-radius:0 50% 50% 0}.tippy-popper[x-placement^=right] .tippy-roundarrow{left:-12px;-webkit-transform-origin:66.66666666% 50%;transform-origin:66.66666666% 50%;margin:3px 0}.tippy-popper[x-placement^=right] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.tippy-popper[x-placement^=right] .tippy-arrow{border-right:8px solid #333;border-top:8px solid rgba(0,0,0,0);border-bottom:8px solid rgba(0,0,0,0);left:-7px;margin:3px 0;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tippy-popper[x-placement^=right] .tippy-backdrop{-webkit-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -50%);transform:scale(1) translate(-50%, -50%)}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-25%, -50%);transform:scale(0.2) translate(-25%, -50%);opacity:0}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(10px) rotateY(0);transform:perspective(700px) translateX(10px) rotateY(0)}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateX(0) rotateY(60deg);transform:perspective(700px) translateX(0) rotateY(60deg)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=right] [data-animation=scale]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(10px) scale(1);transform:translateX(10px) scale(1)}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px) scale(0.5);transform:translateX(10px) scale(0.5)}.tippy-tooltip{position:relative;color:#fff;border-radius:4px;font-size:.9rem;padding:.3rem .6rem;text-align:left;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:rgba(0,0,0,.8)}.tippy-tooltip[data-size=small]{padding:7px 14px;font-size:12px}.tippy-tooltip[data-size=large]{padding:.4rem .8rem;font-size:1rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:rgba(0,0,0,0)}.tippy-tooltip[data-interactive],.tippy-tooltip[data-interactive] .tippy-roundarrow path{pointer-events:auto}.tippy-tooltip[data-inertia][data-state=visible]{-webkit-transition-timing-function:cubic-bezier(0.54, 1.5, 0.38, 1.11);transition-timing-function:cubic-bezier(0.54, 1.5, 0.38, 1.11)}.tippy-tooltip[data-inertia][data-state=hidden]{-webkit-transition-timing-function:ease;transition-timing-function:ease}.tippy-arrow,.tippy-roundarrow{position:absolute;width:0;height:0}.tippy-roundarrow{width:18px;height:7px;fill:#333;pointer-events:none}.tippy-backdrop{position:absolute;background-color:#333;border-radius:50%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;-webkit-transition:all cubic-bezier(0.46, 0.1, 0.52, 0.98);transition:all cubic-bezier(0.46, 0.1, 0.52, 0.98);-webkit-backface-visibility:hidden;backface-visibility:hidden}.tippy-backdrop:after{content:"";float:left;padding-top:100%}.tippy-backdrop+.tippy-content{-webkit-transition-property:opacity;transition-property:opacity}.tippy-backdrop+.tippy-content[data-state=visible]{opacity:1}.tippy-backdrop+.tippy-content[data-state=hidden]{opacity:0}.tippy-tooltip.light-theme{color:#26323d;-webkit-box-shadow:0 0 6px 0 #dcdee5;box-shadow:0 0 6px 0 #dcdee5;background-color:#fff}.tippy-tooltip.light-theme:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;background:inherit}.tippy-tooltip.light-theme .tippy-arrow{z-index:-2;width:11px;height:11px;border:none !important;-webkit-box-shadow:inherit;box-shadow:inherit;background:inherit;-webkit-transform-origin:center center;transform-origin:center center;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.tippy-tooltip.light-theme[x-placement^=top] .tippy-arrow{bottom:-5px}.tippy-tooltip.light-theme[x-placement^=bottom] .tippy-arrow{top:-5px}.tippy-tooltip.light-theme[x-placement^=left] .tippy-arrow{right:-5px}.tippy-tooltip.light-theme[x-placement^=right] .tippy-arrow{left:-5px}.tippy-tooltip.light-theme .tippy-backdrop{background-color:#fff}.tippy-tooltip.light-theme .tippy-roundarrow{fill:#fff}.tippy-tooltip.light-theme[data-animatefill]{background-color:rgba(0,0,0,0)}.tippy-tooltip.light-border-theme{color:#26323d;-webkit-box-shadow:0 0 6px 0 #dcdee5;box-shadow:0 0 6px 0 #dcdee5;background-color:#fff;border:1px solid #dcdee5}.tippy-tooltip.light-border-theme:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;background:inherit}.tippy-tooltip.light-border-theme .tippy-arrow{z-index:-2;width:11px;height:11px;border:1px solid #dcdee5;-webkit-box-shadow:inherit;box-shadow:inherit;background:inherit;-webkit-transform-origin:center center;transform-origin:center center;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.tippy-tooltip.light-border-theme[x-placement^=top] .tippy-arrow{bottom:-5px}.tippy-tooltip.light-border-theme[x-placement^=bottom] .tippy-arrow{top:-5px}.tippy-tooltip.light-border-theme[x-placement^=left] .tippy-arrow{right:-5px}.tippy-tooltip.light-border-theme[x-placement^=right] .tippy-arrow{left:-5px}.tippy-tooltip.light-border-theme .tippy-backdrop{background-color:#fff}.tippy-tooltip.light-border-theme .tippy-roundarrow{fill:#fff}.tippy-tooltip.light-border-theme[data-animatefill]{background-color:rgba(0,0,0,0)}',""]);const s=a},3545:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'.bk-tooltip,.bk-tooltip-ref{display:inline-block}.bk-tooltip-ref{position:relative;outline:0}.tippy-iOS{cursor:pointer !important;-webkit-tap-highlight-color:rgba(0,0,0,0)}.tippy-popper{-webkit-transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);max-width:calc(100% - 8px);pointer-events:none;outline:0}.tippy-popper[x-placement^=top] .tippy-backdrop{border-radius:40% 40% 0 0}.tippy-popper[x-placement^=top] .tippy-roundarrow{bottom:-7px;bottom:-6.5px;-webkit-transform-origin:50% 0;transform-origin:50% 0;margin:0 3px}.tippy-popper[x-placement^=top] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.tippy-popper[x-placement^=top] .tippy-arrow{border-top:8px solid #333;border-right:8px solid rgba(0,0,0,0);border-left:8px solid rgba(0,0,0,0);bottom:-7px;margin:0 3px;-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=top] .tippy-backdrop{-webkit-transform-origin:0 25%;transform-origin:0 25%}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -55%);transform:scale(1) translate(-50%, -55%)}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-50%, -45%);transform:scale(0.2) translate(-50%, -45%);opacity:0}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(-10px) rotateX(0);transform:perspective(700px) translateY(-10px) rotateX(0)}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateY(0) rotateX(60deg);transform:perspective(700px) translateY(0) rotateX(60deg)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=top] [data-animation=scale]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(-10px) scale(1);transform:translateY(-10px) scale(1)}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px) scale(0.5);transform:translateY(-10px) scale(0.5)}.tippy-popper[x-placement^=top] [data-animation=slide-toggle]{-webkit-transform-origin:center bottom;transform-origin:center bottom}.tippy-popper[x-placement^=top] [data-animation=slide-toggle][data-state=visible]{-webkit-transform:scaleY(1);transform:scaleY(1);opacity:1}.tippy-popper[x-placement^=top] [data-animation=slide-toggle][data-state=hidden]{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.tippy-popper[x-placement^=bottom] .tippy-backdrop{border-radius:0 0 30% 30%}.tippy-popper[x-placement^=bottom] .tippy-roundarrow{top:-7px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;margin:0 3px}.tippy-popper[x-placement^=bottom] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(0);transform:rotate(0)}.tippy-popper[x-placement^=bottom] .tippy-arrow{border-bottom:8px solid #333;border-right:8px solid rgba(0,0,0,0);border-left:8px solid rgba(0,0,0,0);top:-7px;margin:0 3px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%}.tippy-popper[x-placement^=bottom] .tippy-backdrop{-webkit-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -45%);transform:scale(1) translate(-50%, -45%)}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-50%);transform:scale(0.2) translate(-50%);opacity:0}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(10px) rotateX(0);transform:perspective(700px) translateY(10px) rotateX(0)}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateY(0) rotateX(-60deg);transform:perspective(700px) translateY(0) rotateX(-60deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=bottom] [data-animation=scale]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(10px) scale(1);transform:translateY(10px) scale(1)}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px) scale(0.5);transform:translateY(10px) scale(0.5)}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle]{-webkit-transform-origin:center top;transform-origin:center top}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle][data-state=visible]{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}.tippy-popper[x-placement^=bottom] [data-animation=slide-toggle][data-state=hidden]{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.tippy-popper[x-placement^=left] .tippy-backdrop{border-radius:50% 0 0 50%}.tippy-popper[x-placement^=left] .tippy-roundarrow{right:-12px;-webkit-transform-origin:33.33333333% 50%;transform-origin:33.33333333% 50%;margin:3px 0}.tippy-popper[x-placement^=left] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.tippy-popper[x-placement^=left] .tippy-arrow{border-left:8px solid #333;border-top:8px solid rgba(0,0,0,0);border-bottom:8px solid rgba(0,0,0,0);right:-7px;margin:3px 0;-webkit-transform-origin:0 50%;transform-origin:0 50%}.tippy-popper[x-placement^=left] .tippy-backdrop{-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -50%);transform:scale(1) translate(-50%, -50%)}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-75%, -50%);transform:scale(0.2) translate(-75%, -50%);opacity:0}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(-20px);transform:translateX(-20px)}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(-10px) rotateY(0);transform:perspective(700px) translateX(-10px) rotateY(0)}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateX(0) rotateY(-60deg);transform:perspective(700px) translateX(0) rotateY(-60deg)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=left] [data-animation=scale]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(-10px) scale(1);transform:translateX(-10px) scale(1)}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px) scale(0.5);transform:translateX(-10px) scale(0.5)}.tippy-popper[x-placement^=right] .tippy-backdrop{border-radius:0 50% 50% 0}.tippy-popper[x-placement^=right] .tippy-roundarrow{left:-12px;-webkit-transform-origin:66.66666666% 50%;transform-origin:66.66666666% 50%;margin:3px 0}.tippy-popper[x-placement^=right] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.tippy-popper[x-placement^=right] .tippy-arrow{border-right:8px solid #333;border-top:8px solid rgba(0,0,0,0);border-bottom:8px solid rgba(0,0,0,0);left:-7px;margin:3px 0;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tippy-popper[x-placement^=right] .tippy-backdrop{-webkit-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%, -50%);transform:scale(1) translate(-50%, -50%)}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(0.2) translate(-25%, -50%);transform:scale(0.2) translate(-25%, -50%);opacity:0}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(10px) rotateY(0);transform:perspective(700px) translateX(10px) rotateY(0)}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) translateX(0) rotateY(60deg);transform:perspective(700px) translateX(0) rotateY(60deg)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=hidden]{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=right] [data-animation=scale]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(10px) scale(1);transform:translateX(10px) scale(1)}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px) scale(0.5);transform:translateX(10px) scale(0.5)}.tippy-tooltip{position:relative;color:#fff;border-radius:4px;font-size:.9rem;padding:.3rem .6rem;text-align:left;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:rgba(0,0,0,.8)}.tippy-tooltip[data-size=small]{padding:7px 14px;font-size:12px}.tippy-tooltip[data-size=large]{padding:.4rem .8rem;font-size:1rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:rgba(0,0,0,0)}.tippy-tooltip[data-interactive],.tippy-tooltip[data-interactive] .tippy-roundarrow path{pointer-events:auto}.tippy-tooltip[data-inertia][data-state=visible]{-webkit-transition-timing-function:cubic-bezier(0.54, 1.5, 0.38, 1.11);transition-timing-function:cubic-bezier(0.54, 1.5, 0.38, 1.11)}.tippy-tooltip[data-inertia][data-state=hidden]{-webkit-transition-timing-function:ease;transition-timing-function:ease}.tippy-arrow,.tippy-roundarrow{position:absolute;width:0;height:0}.tippy-roundarrow{width:18px;height:7px;fill:#333;pointer-events:none}.tippy-backdrop{position:absolute;background-color:#333;border-radius:50%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;-webkit-transition:all cubic-bezier(0.46, 0.1, 0.52, 0.98);transition:all cubic-bezier(0.46, 0.1, 0.52, 0.98);-webkit-backface-visibility:hidden;backface-visibility:hidden}.tippy-backdrop:after{content:"";float:left;padding-top:100%}.tippy-backdrop+.tippy-content{-webkit-transition-property:opacity;transition-property:opacity}.tippy-backdrop+.tippy-content[data-state=visible]{opacity:1}.tippy-backdrop+.tippy-content[data-state=hidden]{opacity:0}.tippy-tooltip.light-theme{color:#26323d;-webkit-box-shadow:0 0 6px 0 #dcdee5;box-shadow:0 0 6px 0 #dcdee5;background-color:#fff}.tippy-tooltip.light-theme:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;background:inherit}.tippy-tooltip.light-theme .tippy-arrow{z-index:-2;width:11px;height:11px;border:none !important;-webkit-box-shadow:inherit;box-shadow:inherit;background:inherit;-webkit-transform-origin:center center;transform-origin:center center;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.tippy-tooltip.light-theme[x-placement^=top] .tippy-arrow{bottom:-5px}.tippy-tooltip.light-theme[x-placement^=bottom] .tippy-arrow{top:-5px}.tippy-tooltip.light-theme[x-placement^=left] .tippy-arrow{right:-5px}.tippy-tooltip.light-theme[x-placement^=right] .tippy-arrow{left:-5px}.tippy-tooltip.light-theme .tippy-backdrop{background-color:#fff}.tippy-tooltip.light-theme .tippy-roundarrow{fill:#fff}.tippy-tooltip.light-theme[data-animatefill]{background-color:rgba(0,0,0,0)}.tippy-tooltip.light-border-theme{color:#26323d;-webkit-box-shadow:0 0 6px 0 #dcdee5;box-shadow:0 0 6px 0 #dcdee5;background-color:#fff;border:1px solid #dcdee5}.tippy-tooltip.light-border-theme:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;background:inherit}.tippy-tooltip.light-border-theme .tippy-arrow{z-index:-2;width:11px;height:11px;border:1px solid #dcdee5;-webkit-box-shadow:inherit;box-shadow:inherit;background:inherit;-webkit-transform-origin:center center;transform-origin:center center;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.tippy-tooltip.light-border-theme[x-placement^=top] .tippy-arrow{bottom:-5px}.tippy-tooltip.light-border-theme[x-placement^=bottom] .tippy-arrow{top:-5px}.tippy-tooltip.light-border-theme[x-placement^=left] .tippy-arrow{right:-5px}.tippy-tooltip.light-border-theme[x-placement^=right] .tippy-arrow{left:-5px}.tippy-tooltip.light-border-theme .tippy-backdrop{background-color:#fff}.tippy-tooltip.light-border-theme .tippy-roundarrow{fill:#fff}.tippy-tooltip.light-border-theme[data-animatefill]{background-color:rgba(0,0,0,0)}',""]);const s=a},4800:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n *//*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.connect-line{position:absolute;width:58px;top:19px;stroke:#3c96ff;stroke-width:1;fill:none}.insert-tip{position:absolute;display:block;padding:0 10px;max-width:100px;height:24px;display:flex;align-items:center;border:1px solid #addaff;border-radius:22px;color:#3c96ff;font-size:10px;cursor:pointer;background-color:#fff;box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.insert-tip.direction:after{content:"";position:absolute;height:6px;width:6px;background-color:#fff;transform:rotate(45deg);bottom:-4px;left:20px;border-right:1px solid #addaff;border-bottom:1px solid #addaff}.insert-tip .tip-icon{margin:0 5px 0 0;cursor:pointer;position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip .tip-icon:before,.insert-tip .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#3c96ff}.insert-tip .tip-icon:after{transform:rotate(90deg)}.insert-tip>span{white-space:nowrap}.insert-tip:hover{background-color:#3c96ff;color:#fff;border-color:#3c96ff}.insert-tip:hover.direction:after{background-color:#3c96ff;border-right-color:#3c96ff;border-bottom-color:#3c96ff}.insert-tip:hover .tip-icon{position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip:hover .tip-icon:before,.insert-tip:hover .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#fff}.insert-tip:hover .tip-icon:after{transform:rotate(90deg)}.pointer{cursor:pointer}span.skip-name{text-decoration:line-through;color:#c4cdd6}span.skip-name:hover{color:#c4cdd6}.spin-icon{display:inline-block;animation:loading .8s linear infinite}.add-plus-icon,.minus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #addaff;background-color:#fff;border-radius:50%;transition:all .3s ease}.add-plus-icon:before,.minus-icon:before,.add-plus-icon:after,.minus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#3c96ff}.add-plus-icon:after,.minus-icon:after{transform:rotate(90deg)}.add-plus-icon.active,.active.minus-icon{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon.active:before,.active.minus-icon:before,.add-plus-icon.active:after,.active.minus-icon:after{background-color:#fff}.add-plus-icon:hover,.minus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon:hover:before,.minus-icon:hover:before,.add-plus-icon:hover:after,.minus-icon:hover:after{background-color:#fff}.minus-icon:before{display:none}.un-exec-this-time{opacity:.5}.un-exec-this-time .un-exec-this-time{opacity:1}.readonly .stage-connector{background:#c3cdd7;color:#c3cdd7}.readonly .stage-connector:before{background:#c3cdd7}.readonly .connect-line.left,.readonly .connect-line.right{stroke:#c3cdd7}.readonly .connect-line.left:before,.readonly .connect-line.right:before{stroke:#c3cdd7}.readonly .connect-line.left:after,.readonly .connect-line.right:after{stroke:#c3cdd7;background-color:#c3cdd7}.readonly:after{background:#c3cdd7}.readonly .container-connect-triangle{color:#c3cdd7}.readonly .container-title{cursor:pointer;background-color:#63656e}.readonly .container-title:before,.readonly .container-title:after{border-top-color:#c3cdd7}.readonly .container-title>.container-name span{color:#fff}.editing .stage-connector{background:#3c96ff;color:#3c96ff}.editing .stage-connector:before{background:#3c96ff}.editing .connect-line.left,.editing .connect-line.right{stroke:#3c96ff}.editing .connect-line.left:before,.editing .connect-line.right:before{stroke:#3c96ff}.editing .connect-line.left:after,.editing .connect-line.right:after{stroke:#3c96ff;background-color:#3c96ff}.editing:after{background:#3c96ff}.editing:before{color:#3c96ff}.container-type{font-size:12px;margin-right:12px;font-style:normal}.container-type .devops-icon{font-size:18px}.container-type .devops-icon.icon-exclamation-triangle-shape{font-size:14px}.container-type .devops-icon.icon-exclamation-triangle-shape.is-danger{color:#ff5656}.container-type i{font-style:normal}.bk-pipeline .stage-status:not(.readonly){background-color:#3c96ff}.bk-pipeline .stage-status:not(.readonly).WARNING{background-color:#ffb400;color:#fff}.bk-pipeline .stage-status:not(.readonly).FAILED{background-color:#ff5656;color:#fff}.bk-pipeline .stage-status:not(.readonly).SUCCEED{background-color:#34d97b;color:#fff}.bk-pipeline .stage-status.CANCELED,.bk-pipeline .stage-status.REVIEW_ABORT,.bk-pipeline .stage-status.REVIEWING,.bk-pipeline .stage-name-status-icon.CANCELED,.bk-pipeline .stage-name-status-icon.REVIEW_ABORT,.bk-pipeline .stage-name-status-icon.REVIEWING{color:#ffb400}.bk-pipeline .stage-status.FAILED,.bk-pipeline .stage-status.QUALITY_CHECK_FAIL,.bk-pipeline .stage-status.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-status.QUEUE_TIMEOUT,.bk-pipeline .stage-status.EXEC_TIMEOUT,.bk-pipeline .stage-name-status-icon.FAILED,.bk-pipeline .stage-name-status-icon.QUALITY_CHECK_FAIL,.bk-pipeline .stage-name-status-icon.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-name-status-icon.QUEUE_TIMEOUT,.bk-pipeline .stage-name-status-icon.EXEC_TIMEOUT{color:#ff5656}.bk-pipeline .stage-status.SUCCEED,.bk-pipeline .stage-status.REVIEW_PROCESSED,.bk-pipeline .stage-name-status-icon.SUCCEED,.bk-pipeline .stage-name-status-icon.REVIEW_PROCESSED{color:#34d97b}.bk-pipeline .stage-status.PAUSE,.bk-pipeline .stage-name-status-icon.PAUSE{color:#63656e}.bk-pipeline .container-title .stage-status{color:#fff}.bk-pipeline .container-title.UNEXEC,.bk-pipeline .container-title.SKIP,.bk-pipeline .container-title.DISABLED{color:#fff;background-color:#63656e}.bk-pipeline .container-title.UNEXEC .fold-atom-icon,.bk-pipeline .container-title.SKIP .fold-atom-icon,.bk-pipeline .container-title.DISABLED .fold-atom-icon{color:#63656e}.bk-pipeline .container-title.QUEUE,.bk-pipeline .container-title.RUNNING,.bk-pipeline .container-title.REVIEWING,.bk-pipeline .container-title.PREPARE_ENV,.bk-pipeline .container-title.LOOP_WAITING,.bk-pipeline .container-title.DEPENDENT_WAITING,.bk-pipeline .container-title.CALL_WAITING{background-color:#459fff;color:#fff}.bk-pipeline .container-title.QUEUE .fold-atom-icon,.bk-pipeline .container-title.RUNNING .fold-atom-icon,.bk-pipeline .container-title.REVIEWING .fold-atom-icon,.bk-pipeline .container-title.PREPARE_ENV .fold-atom-icon,.bk-pipeline .container-title.LOOP_WAITING .fold-atom-icon,.bk-pipeline .container-title.DEPENDENT_WAITING .fold-atom-icon,.bk-pipeline .container-title.CALL_WAITING .fold-atom-icon{color:#459fff}.bk-pipeline .container-title.CANCELED,.bk-pipeline .container-title.REVIEW_ABORT,.bk-pipeline .container-title.TRY_FINALLY,.bk-pipeline .container-title.QUEUE_CACHE{background-color:#f6b026}.bk-pipeline .container-title.CANCELED span.skip-name,.bk-pipeline .container-title.REVIEW_ABORT span.skip-name,.bk-pipeline .container-title.TRY_FINALLY span.skip-name,.bk-pipeline .container-title.QUEUE_CACHE span.skip-name{color:#fff}.bk-pipeline .container-title.CANCELED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_ABORT .fold-atom-icon,.bk-pipeline .container-title.TRY_FINALLY .fold-atom-icon,.bk-pipeline .container-title.QUEUE_CACHE .fold-atom-icon{color:#f6b026}.bk-pipeline .container-title.FAILED,.bk-pipeline .container-title.TERMINATE,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT,.bk-pipeline .container-title.QUALITY_CHECK_FAIL,.bk-pipeline .container-title.QUEUE_TIMEOUT,.bk-pipeline .container-title.EXEC_TIMEOUT{color:#fff;background-color:#ff5656}.bk-pipeline .container-title.FAILED .fold-atom-icon,.bk-pipeline .container-title.TERMINATE .fold-atom-icon,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.QUALITY_CHECK_FAIL .fold-atom-icon,.bk-pipeline .container-title.QUEUE_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.EXEC_TIMEOUT .fold-atom-icon{color:#ff5656}.bk-pipeline .container-title.SUCCEED,.bk-pipeline .container-title.REVIEW_PROCESSED,.bk-pipeline .container-title.STAGE_SUCCESS{color:#fff;background-color:#34d97b}.bk-pipeline .container-title.SUCCEED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_PROCESSED .fold-atom-icon,.bk-pipeline .container-title.STAGE_SUCCESS .fold-atom-icon{color:#34d97b}.bk-pipeline .container-title.PAUSE{color:#fff;background-color:#ff9801}.bk-pipeline .container-title.PAUSE .fold-atom-icon{color:#ff9801}.bk-pipeline .bk-pipeline-atom.UNEXEC,.bk-pipeline .bk-pipeline-atom.SKIP,.bk-pipeline .bk-pipeline-atom.DISABLED{color:#63656e}.bk-pipeline .bk-pipeline-atom.CANCELED,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT,.bk-pipeline .bk-pipeline-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:before,.bk-pipeline .bk-pipeline-atom.REVIEWING:before{background-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:after,.bk-pipeline .bk-pipeline-atom.REVIEWING:after{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED .atom-icon,.bk-pipeline .bk-pipeline-atom.CANCELED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.CANCELED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEWING .stage-check-icon{color:#ffb400}.bk-pipeline .bk-pipeline-atom.FAILED,.bk-pipeline .bk-pipeline-atom.TERMINATE,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED:before,.bk-pipeline .bk-pipeline-atom.TERMINATE:before,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:before,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:before{background-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED:after,.bk-pipeline .bk-pipeline-atom.TERMINATE:after,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:after,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:after{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED .atom-icon,.bk-pipeline .bk-pipeline-atom.FAILED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.FAILED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-execute-time,.bk-pipeline .bk-pipeline-atom.TERMINATE .stage-check-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .stage-check-icon{color:#ff5656}.bk-pipeline .bk-pipeline-atom.SUCCEED,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:before{background-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:after{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-icon,.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.PAUSE{border-color:#ff9801;color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE:before{background-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE:after{border-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE .atom-icon,.bk-pipeline .bk-pipeline-atom.PAUSE .atom-execute-time{color:#63656e}.bk-pipeline .bk-pipeline-atom.template-compare-atom{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:before{background-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:after{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom{background-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title{color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title>i{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title{color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i:last-child{border-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title{color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title>i{border-top:2px solid #ff5656}',""]);const s=a},8998:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline .bk-pipeline-atom{cursor:pointer;position:relative;display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;border:1px solid #c4cdd6}.bk-pipeline .bk-pipeline-atom .active-atom-location-icon{position:absolute;color:#3c96ff;left:-30px}.bk-pipeline .bk-pipeline-atom.trigger-atom:before,.bk-pipeline .bk-pipeline-atom.trigger-atom:after{display:none}.bk-pipeline .bk-pipeline-atom:first-child:before{top:-16px}.bk-pipeline .bk-pipeline-atom:before{content:"";position:absolute;height:14px;width:2px;background:#c4cdd6;top:-12px;left:22px;z-index:1}.bk-pipeline .bk-pipeline-atom:after{content:"";position:absolute;height:4px;width:4px;border:2px solid #c4cdd6;border-radius:50%;background:#fff !important;top:-5px;left:19px;z-index:2}.bk-pipeline .bk-pipeline-atom.is-intercept{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-intercept:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-error{border-color:#ff5656;color:#ff5656}.bk-pipeline .bk-pipeline-atom.is-error:hover .atom-invalid-icon{display:none}.bk-pipeline .bk-pipeline-atom.is-error .atom-invalid-icon{margin:0 12px}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover{border-color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-name{color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .add-plus-icon.close,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .copy{cursor:pointer;display:block}.bk-pipeline .bk-pipeline-atom .atom-icon{text-align:center;margin:0 14.5px;font-size:18px;width:18px;color:#63656e;fill:currentColor}.bk-pipeline .bk-pipeline-atom .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name:hover{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .pause-button{margin-right:8px;color:#3c96ff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close{position:relative;display:block;width:16px;height:16px;border:1px solid #fff;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;display:none;margin-right:10px;border:none;transform:rotate(45deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{transform:rotate(90deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover{border-color:#ff5656;background-color:#ff5656}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:after{background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{left:7px;top:4px}.bk-pipeline .bk-pipeline-atom .copy{display:none;margin-right:10px;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .copy:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom>.atom-name{flex:1;color:#63656e;display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:188px;margin-right:2px}.bk-pipeline .bk-pipeline-atom>.atom-name span:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom .disabled{cursor:not-allowed;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-execounter{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-operate-area{margin:0 8px 0 0;color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-reviewing-tips[disabled]{cursor:not-allowed;color:#c3cdd7}.bk-pipeline .bk-pipeline-atom .atom-review-diasbled-tips{color:#c3cdd7;margin:0 8px 0 2px}.bk-pipeline .bk-pipeline-atom .atom-canskip-checkbox{margin-right:6px}.bk-pipeline .bk-pipeline-atom.quality-atom{display:flex;justify-content:center;border-color:rgba(0,0,0,0);height:24px;background:rgba(0,0,0,0);border-color:rgba(0,0,0,0) !important;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom:before{height:40px;z-index:8}.bk-pipeline .bk-pipeline-atom.quality-atom:after{display:none}.bk-pipeline .bk-pipeline-atom.quality-atom.last-quality-atom:before{height:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title{display:flex;width:100%;align-items:center;justify-content:center;margin-left:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>span{border-radius:12px;font-weight:bold;border:1px solid #c4cdd6;padding:0 12px;margin:0 4px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>i{height:0;flex:1;border-top:2px dashed #c4cdd6}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list{position:absolute;right:0}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span:first-child{margin-right:5px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job{position:absolute;top:6px;right:42px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job:before{display:inline-block;animation:rotating infinite .6s ease-in-out}.bk-pipeline .bk-pipeline-atom.quality-atom .disabled-review span{color:#c4cdd6;cursor:default}.bk-pipeline .bk-pipeline-atom.readonly{background-color:#fff}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover span{color:#63656e}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover .skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom.readonly.quality-prev-atom:before{height:24px;top:-23px}',""]);const s=a},3300:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.container-atom-list{position:relative;z-index:3}.container-atom-list .sortable-ghost-atom{opacity:.5}.container-atom-list .sortable-chosen-atom{transform:scale(1)}.container-atom-list .add-atom-entry{position:absolute;bottom:-10px;left:111px;background-color:#fff;cursor:pointer;z-index:3}.container-atom-list .add-atom-entry .add-plus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #c4cdd6;background-color:#fff;border-radius:50%;transition:all .3s ease}.container-atom-list .add-atom-entry .add-plus-icon:before,.container-atom-list .add-atom-entry .add-plus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#c4cdd6}.container-atom-list .add-atom-entry .add-plus-icon:after{transform:rotate(90deg)}.container-atom-list .add-atom-entry .add-plus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.container-atom-list .add-atom-entry .add-plus-icon:hover:before,.container-atom-list .add-atom-entry .add-plus-icon:hover:after{background-color:#fff}.container-atom-list .add-atom-entry.block-add-entry{display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;position:static;padding-right:12px;border-style:dashed;color:#ff5656;border-color:#ff5656;border-width:1px}.container-atom-list .add-atom-entry.block-add-entry .add-atom-label{flex:1;color:#dde4eb}.container-atom-list .add-atom-entry.block-add-entry .add-plus-icon{margin:12px 13px}.container-atom-list .add-atom-entry.block-add-entry:before,.container-atom-list .add-atom-entry.block-add-entry:after{display:none}.container-atom-list .add-atom-entry:hover{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow{position:relativecd;display:flex;align-items:center;justify-content:center;position:absolute;height:14px;width:14px;border:1px solid #979ba5;color:#979ba5;border-radius:50%;background:#fff !important;top:-7px;left:17px;z-index:3;font-weight:bold}.container-atom-list .post-action-arrow .toggle-post-action-icon{display:block;transition:all .5s ease}.container-atom-list .post-action-arrow.post-action-arrow-show .toggle-post-action-icon{transform:rotate(180deg)}.container-atom-list .post-action-arrow::after{content:"";position:absolute;width:2px;height:6px;background-color:#979ba5;left:5px;top:-6px}.container-atom-list .post-action-arrow.FAILED{border-color:#ff5656;color:#ff5656}.container-atom-list .post-action-arrow.FAILED::after{background-color:#ff5656}.container-atom-list .post-action-arrow.CANCELED{border-color:#ffb400;color:#ffb400}.container-atom-list .post-action-arrow.CANCELED::after{background-color:#ffb400}.container-atom-list .post-action-arrow.SUCCEED{border-color:#34d97b;color:#34d97b}.container-atom-list .post-action-arrow.SUCCEED::after{background-color:#34d97b}.container-atom-list .post-action-arrow.RUNNING{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow.RUNNING::after{background-color:#3c96ff}',""]);const s=a},6096:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container .container-title{display:flex;height:42px;background:#33333f;cursor:pointer;color:#fff;font-size:14px;align-items:center;position:relative;margin:0 0 16px 0;z-index:3}.devops-stage-container .container-title>.container-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;padding:0 6px}.devops-stage-container .container-title .atom-canskip-checkbox{margin-right:6px}.devops-stage-container .container-title .atom-canskip-checkbox.is-disabled .bk-checkbox{background-color:rgba(0,0,0,0);border-color:#979ba4}.devops-stage-container .container-title input[type=checkbox]{border-radius:3px}.devops-stage-container .container-title .matrix-flag-icon{position:absolute;top:0px;font-size:16px}.devops-stage-container .container-title .fold-atom-icon{position:absolute;background:#fff;border-radius:50%;bottom:-10px;left:44%;transition:all .3s ease}.devops-stage-container .container-title .fold-atom-icon.open{transform:rotate(-180deg)}.devops-stage-container .container-title .fold-atom-icon.readonly{color:#63656e}.devops-stage-container .container-title .copyJob{display:none;margin-right:10px;color:#c4cdd6;cursor:pointer}.devops-stage-container .container-title .copyJob:hover{color:#3c96ff}.devops-stage-container .container-title .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;border:none;display:none;margin-right:10px;transform:rotate(45deg);cursor:pointer}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.devops-stage-container .container-title .close:after{transform:rotate(90deg)}.devops-stage-container .container-title .close:hover{border-color:#ff5656;background-color:#ff5656}.devops-stage-container .container-title .close:hover:before,.devops-stage-container .container-title .close:hover:after{background-color:#fff}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{left:7px;top:4px}.devops-stage-container .container-title .debug-btn{position:absolute;height:100%;right:0}.devops-stage-container .container-title .container-locate-icon{position:absolute;left:-30px;top:13px;color:#3c96ff}.devops-stage-container .container-title:hover .copyJob,.devops-stage-container .container-title:hover .close{display:block}.devops-stage-container .container-title:hover .hover-hide{display:none}',""]);const s=a},6399:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline-matrix-group{border:1px solid #b5c0d5;padding:10px;background:#fff}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header{display:flex;align-items:center;cursor:pointer;justify-content:space-between;height:20px}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name{display:flex;align-items:center;font-size:14px;color:#222;min-width:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon{display:block;margin-right:10px;transition:all .3s ease;flex-shrink:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon.open{transform:rotate(-180deg)}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name>span{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status{color:#3c96ff;display:flex;align-items:center}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status .status-desc{font-size:12px;display:inline-block;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .matrix-body{margin-top:12px}.bk-pipeline-matrix-group .matrix-body>div{margin-bottom:34px}',""]);const s=a},4944:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.pipeline-drag{cursor:grab,default}.pipeline-stage{position:relative;width:280px;border-radius:2px;padding:0;background:#f5f5f5;margin:0 80px 0 0}.pipeline-stage .pipeline-stage-entry{position:relative;cursor:pointer;display:flex;width:100%;height:50px;align-items:center;min-width:0;font-size:14px;background-color:#eff5ff;border:1px solid #d4e8ff;color:#3c96ff}.pipeline-stage .pipeline-stage-entry:hover{border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage .pipeline-stage-entry .check-in-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon{position:absolute;left:-14px;top:11px}.pipeline-stage .pipeline-stage-entry .check-in-icon.check-out-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon.check-out-icon{left:auto;right:-14px}.pipeline-stage .pipeline-stage-entry .stage-entry-name{flex:1;display:flex;align-items:center;justify-content:center;margin:0 80px;overflow:hidden}.pipeline-stage .pipeline-stage-entry .stage-entry-name .stage-title-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-left:6px}.pipeline-stage .pipeline-stage-entry .stage-single-retry{cursor:pointer;position:absolute;right:6%;color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage{position:absolute;right:27px}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon.stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage.stage-entry-error-icon{top:16px;right:8px;color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns{position:absolute;right:0;top:16px;display:none;width:80px;align-items:center;justify-content:flex-end;color:#fff;fill:#fff;z-index:2}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .copy-stage:hover{color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#fff;border-radius:50%;transition:all .3s ease;border:none;margin:0 10px 0 8px;transform:rotate(45deg);cursor:pointer}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{transform:rotate(90deg)}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover{border-color:#ff5656;background-color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:after{background-color:#fff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{left:7px;top:4px}.pipeline-stage.editable:not(.readonly) .pipeline-stage-entry:hover{color:#000;border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-btns{display:flex}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-error-icon{display:none}.pipeline-stage.readonly.SKIP .pipeline-stage-entry{color:#c3cdd7;fill:#c3cdd7}.pipeline-stage.readonly.RUNNING .pipeline-stage-entry{background-color:#eff5ff;border-color:#d4e8ff;color:#3c96ff}.pipeline-stage.readonly.REVIEWING .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly.FAILED .pipeline-stage-entry{border-color:#ffd4d4;background-color:#fff9f9;color:#000}.pipeline-stage.readonly.SUCCEED .pipeline-stage-entry{background-color:#f3fff6;border-color:#bbefc9;color:#000}.pipeline-stage.readonly .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly .pipeline-stage-entry .skip-icon{vertical-align:middle}.pipeline-stage .add-connector{stroke-dasharray:4,4;top:7px;left:10px}.pipeline-stage .append-stage{position:absolute;top:16px;right:-44px;z-index:3}.pipeline-stage .append-stage .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .append-stage .line-add{top:-46px;left:-16px}.pipeline-stage .append-stage .add-plus-connector{position:absolute;width:40px;height:2px;left:-26px;top:8px;background-color:#3c96ff}.pipeline-stage .add-menu{position:absolute;top:16px;left:-53px;cursor:pointer;z-index:3}.pipeline-stage .add-menu .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .add-menu .minus-icon{z-index:4}.pipeline-stage .add-menu .line-add{top:-46px;left:-16px}.pipeline-stage .add-menu .parallel-add{left:50px}.pipeline-stage:first-child .stage-connector{display:none}.pipeline-stage.is-final-stage .stage-connector{width:80px}.pipeline-stage .stage-connector{position:absolute;width:66px;height:2px;left:-80px;top:24px;color:#3c96ff;background-color:#3c96ff}.pipeline-stage .stage-connector:before{content:"";width:8px;height:8px;position:absolute;left:-4px;top:-3px;background-color:#3c96ff;border-radius:50%}.pipeline-stage .stage-connector .connector-angle{position:absolute;right:-3px;top:-6px}.pipeline-stage .insert-stage{position:absolute;display:block;width:160px;background-color:#fff;border:1px solid #dcdee5}.pipeline-stage .insert-stage .click-item{padding:0 15px;font-size:12px;line-height:32px}.pipeline-stage .insert-stage .click-item:hover,.pipeline-stage .insert-stage .click-item :hover{color:#3c96ff;background-color:#eaf3ff}.pipeline-stage .insert-stage .disabled-item{cursor:not-allowed;color:#c4cdd6}.pipeline-stage .insert-stage .disabled-item:hover,.pipeline-stage .insert-stage .disabled-item :hover{color:#c4cdd6;background-color:#fff}.stage-retry-dialog .bk-form-radio{display:block;margin-top:15px}.stage-retry-dialog .bk-form-radio .bk-radio-text{font-size:14px}',""]);const s=a},7849:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-check-icon{border-radius:100px;border:1px solid #d0d8ea;display:flex;align-items:center;background:#fff;font-size:12px;z-index:3;color:#63656e}.stage-check-icon.reviewing{color:#3c96ff;border-color:#3c96ff}.stage-check-icon.quality-check{color:#34d97b;border-color:#34d97b}.stage-check-icon.quality-check-error,.stage-check-icon.review-error{color:#ff5656;border-color:#ff5656}.stage-check-icon .stage-check-txt{padding-right:10px}',""]);const s=a},2718:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container{text-align:left;margin:16px 20px 24px 20px;position:relative}.devops-stage-container:not(.last-stage-container):after{content:"";width:6px;height:6px;position:absolute;right:-3px;top:19px;border-radius:50%}.devops-stage-container:not(.last-stage-container):after:not(.readonly){background:#3c96ff}.devops-stage-container .container-connect-triangle{position:absolute;color:#3c96ff;left:-9px;top:15.5px;z-index:2}.devops-stage-container .connect-line{position:absolute;top:1px;stroke:#3c96ff;stroke-width:1;fill:none;z-index:0}.devops-stage-container .connect-line.left{left:-54px}.devops-stage-container .connect-line.right{right:-46px}.devops-stage-container .connect-line.first-connect-line{height:76px;width:58px;top:-43px}.devops-stage-container .connect-line.first-connect-line.left{left:-63px}.devops-stage-container .connect-line.first-connect-line.right{left:auto;right:-55px}',""]);const s=a},6190:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-status{position:relative;text-align:center;overflow:hidden;font-size:14px;width:42px;height:42px;box-sizing:border-box}.stage-status>span{position:absolute;width:100%;height:100%;display:flex;align-items:center;justify-content:center;left:0;top:0;transition:all .3s cubic-bezier(1, 0.5, 0.8, 1)}.stage-status.matrix{width:20px;height:20px}.stage-status .status-logo{position:absolute;left:15px;top:15px}.stage-status .slide-top-enter,.stage-status .slide-top-leave-to{transform:translateY(42px)}.stage-status .slide-down-enter,.stage-status .slide-down-leave-to{transform:translateY(-42px)}.stage-status .slide-left-enter,.stage-status .slide-left-leave-to{transform:translateX(42px)}.stage-status .slide-right-enter,.stage-status .slide-right-leave-to{transform:translateX(-42px)}.stage-status.readonly{font-size:12px;font-weight:normal;background-color:rgba(0,0,0,0)}.stage-status.readonly.container{color:#fff}',""]);const s=a},812:(t,e,n)=>{"use strict";n.d(e,{Z:()=>s});var i=n(8081),o=n.n(i),r=n(3645),a=n.n(r)()(o());a.push([t.id,".bk-pipeline{display:flex;padding-right:120px;width:fit-content;position:relative;align-items:flex-start}.bk-pipeline ul,.bk-pipeline li{margin:0;padding:0}.list-item{transition:transform .2s ease-out}.list-enter,.list-leave-to{opacity:0;transform:translateY(36px) scale(0, 1)}.list-leave-active{position:absolute !important}",""]);const s=a},3645:t=>{"use strict";t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n="",i=void 0!==e[5];return e[4]&&(n+="@supports (".concat(e[4],") {")),e[2]&&(n+="@media ".concat(e[2]," {")),i&&(n+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),n+=t(e),i&&(n+="}"),e[2]&&(n+="}"),e[4]&&(n+="}"),n})).join("")},e.i=function(t,n,i,o,r){"string"==typeof t&&(t=[[null,t,void 0]]);var a={};if(i)for(var s=0;s0?" ".concat(p[5]):""," {").concat(p[1],"}")),p[5]=r),n&&(p[2]?(p[1]="@media ".concat(p[2]," {").concat(p[1],"}"),p[2]=n):p[2]=n),o&&(p[4]?(p[1]="@supports (".concat(p[4],") {").concat(p[1],"}"),p[4]=o):p[4]="".concat(o)),e.push(p))}},e}},3626:t=>{"use strict";t.exports=function(t,e){return e||(e={}),t?(t=String(t.__esModule?t.default:t),/^['"].*['"]$/.test(t)&&(t=t.slice(1,-1)),e.hash&&(t+=e.hash),/["'() \t\n]|(%20)/.test(t)||e.needQuotes?'"'.concat(t.replace(/"/g,'\\"').replace(/\n/g,"\\n"),'"'):t):t}},8081:t=>{"use strict";t.exports=function(t){return t[1]}},1474:(t,e,n)=>{"use strict";function i(t){return i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}function o(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function r(){return r=Object.assign||function(t){for(var e=1;e=0||(o[n]=t[n]);return o}(t,e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function c(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}n.r(e),n.d(e,{MultiDrag:()=>ge,Sortable:()=>Ht,Swap:()=>ae,default:()=>xe});var l=c(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),p=c(/Edge/i),d=c(/firefox/i),u=c(/safari/i)&&!c(/chrome/i)&&!c(/android/i),f=c(/iP(ad|od|hone)/i),m=c(/chrome/i)&&c(/android/i),h={capture:!1,passive:!1};function b(t,e,n){t.addEventListener(e,n,!l&&h)}function g(t,e,n){t.removeEventListener(e,n,!l&&h)}function v(t,e){if(e){if(">"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}}function y(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function x(t,e,n,i){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&v(t,e):v(t,e))||i&&t===n)return t;if(t===n)break}while(t=y(t))}return null}var w,k=/\s+/g;function E(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var i=(" "+t.className+" ").replace(k," ").replace(" "+e+" "," ");t.className=(i+(n?" "+e:"")).replace(k," ")}}function T(t,e,n){var i=t&&t.style;if(i){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in i||-1!==e.indexOf("webkit")||(e="-webkit-"+e),i[e]=n+("string"==typeof n?"":"px")}}function I(t,e){var n="";if("string"==typeof t)n=t;else do{var i=T(t,"transform");i&&"none"!==i&&(n=i+" "+n)}while(!e&&(t=t.parentNode));var o=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return o&&new o(n)}function S(t,e,n){if(t){var i=t.getElementsByTagName(e),o=0,r=i.length;if(n)for(;o=r:o<=r))return i;if(i===C())break;i=R(i,!1)}return!1}function _(t,e,n){for(var i=0,o=0,r=t.children;o2&&void 0!==arguments[2]?arguments[2]:{},i=n.evt,o=s(n,["evt"]);Y.pluginEvent.bind(Ht)(t,e,a({dragEl:X,parentEl:q,ghostEl:K,rootEl:Z,nextEl:Q,lastDownEl:J,cloneEl:tt,cloneHidden:et,dragStarted:mt,putSortable:st,activeSortable:Ht.active,originalEvent:i,oldIndex:nt,oldDraggableIndex:ot,newIndex:it,newDraggableIndex:rt,hideGhostForTarget:Rt,unhideGhostForTarget:Dt,cloneNowHidden:function(){et=!0},cloneNowShown:function(){et=!1},dispatchSortableEvent:function(t){G({sortable:e,name:t,originalEvent:i})}},o))};function G(t){W(a({putSortable:st,cloneEl:tt,targetEl:X,rootEl:Z,oldIndex:nt,oldDraggableIndex:ot,newIndex:it,newDraggableIndex:rt},t))}var X,q,K,Z,Q,J,tt,et,nt,it,ot,rt,at,st,ct,lt,pt,dt,ut,ft,mt,ht,bt,gt,vt,yt=!1,xt=!1,wt=[],kt=!1,Et=!1,Tt=[],It=!1,St=[],Ct="undefined"!=typeof document,Ot=f,At=p||l?"cssFloat":"float",_t=Ct&&!m&&!f&&"draggable"in document.createElement("div"),Lt=function(){if(Ct){if(l)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),Mt=function(t,e){var n=T(t),i=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),o=_(t,0,e),r=_(t,1,e),a=o&&T(o),s=r&&T(r),c=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+O(o).width,l=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+O(r).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&a.float&&"none"!==a.float){var p="left"===a.float?"left":"right";return!r||"both"!==s.clear&&s.clear!==p?"horizontal":"vertical"}return o&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||c>=i&&"none"===n[At]||r&&"none"===n[At]&&c+l>i)?"vertical":"horizontal"},Nt=function(t){function e(t,n){return function(i,o,r,a){var s=i.options.group.name&&o.options.group.name&&i.options.group.name===o.options.group.name;if(null==t&&(n||s))return!0;if(null==t||!1===t)return!1;if(n&&"clone"===t)return t;if("function"==typeof t)return e(t(i,o,r,a),n)(i,o,r,a);var c=(n?i:o).options.group.name;return!0===t||"string"==typeof t&&t===c||t.join&&t.indexOf(c)>-1}}var n={},o=t.group;o&&"object"==i(o)||(o={name:o}),n.name=o.name,n.checkPull=e(o.pull,!0),n.checkPut=e(o.put),n.revertClone=o.revertClone,t.group=n},Rt=function(){!Lt&&K&&T(K,"display","none")},Dt=function(){!Lt&&K&&T(K,"display","")};Ct&&document.addEventListener("click",(function(t){if(xt)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),xt=!1,!1}),!0);var zt=function(t){if(X){t=t.touches?t.touches[0]:t;var e=(o=t.clientX,r=t.clientY,wt.some((function(t){if(!L(t)){var e=O(t),n=t[B].options.emptyInsertThreshold,i=o>=e.left-n&&o<=e.right+n,s=r>=e.top-n&&r<=e.bottom+n;return n&&i&&s?a=t:void 0}})),a);if(e){var n={};for(var i in t)t.hasOwnProperty(i)&&(n[i]=t[i]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[B]._onDragOver(n)}}var o,r,a},Pt=function(t){X&&X.parentNode[B]._isOutsideThisEl(t.target)};function Ht(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=r({},e),t[B]=this;var n,i,o={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Mt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Ht.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var s in Y.initializePlugins(this,t,o),o)!(s in e)&&(e[s]=o[s]);for(var c in Nt(e),this)"_"===c.charAt(0)&&"function"==typeof this[c]&&(this[c]=this[c].bind(this));this.nativeDraggable=!e.forceFallback&&_t,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?b(t,"pointerdown",this._onTapStart):(b(t,"mousedown",this._onTapStart),b(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(b(t,"dragover",this),b(t,"dragenter",this)),wt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),r(this,(i=[],{captureAnimationState:function(){i=[],this.options.animation&&[].slice.call(this.el.children).forEach((function(t){if("none"!==T(t,"display")&&t!==Ht.ghost){i.push({target:t,rect:O(t)});var e=a({},i[i.length-1].rect);if(t.thisAnimationDuration){var n=I(t,!0);n&&(e.top-=n.f,e.left-=n.e)}t.fromRect=e}}))},addAnimationState:function(t){i.push(t)},removeAnimationState:function(t){i.splice(function(t,e){for(var n in t)if(t.hasOwnProperty(n))for(var i in e)if(e.hasOwnProperty(i)&&e[i]===t[n][i])return Number(n);return-1}(i,{target:t}),1)},animateAll:function(t){var e=this;if(!this.options.animation)return clearTimeout(n),void("function"==typeof t&&t());var o=!1,r=0;i.forEach((function(t){var n=0,i=t.target,a=i.fromRect,s=O(i),c=i.prevFromRect,l=i.prevToRect,p=t.rect,d=I(i,!0);d&&(s.top-=d.f,s.left-=d.e),i.toRect=s,i.thisAnimationDuration&&D(c,s)&&!D(a,s)&&(p.top-s.top)/(p.left-s.left)==(a.top-s.top)/(a.left-s.left)&&(n=function(t,e,n,i){return Math.sqrt(Math.pow(e.top-t.top,2)+Math.pow(e.left-t.left,2))/Math.sqrt(Math.pow(e.top-n.top,2)+Math.pow(e.left-n.left,2))*i.animation}(p,c,l,e.options)),D(s,a)||(i.prevFromRect=a,i.prevToRect=s,n||(n=e.options.animation),e.animate(i,p,s,n)),n&&(o=!0,r=Math.max(r,n),clearTimeout(i.animationResetTimer),i.animationResetTimer=setTimeout((function(){i.animationTime=0,i.prevFromRect=null,i.fromRect=null,i.prevToRect=null,i.thisAnimationDuration=null}),n),i.thisAnimationDuration=n)})),clearTimeout(n),o?n=setTimeout((function(){"function"==typeof t&&t()}),r):"function"==typeof t&&t(),i=[]},animate:function(t,e,n,i){if(i){T(t,"transition",""),T(t,"transform","");var o=I(this.el),r=o&&o.a,a=o&&o.d,s=(e.left-n.left)/(r||1),c=(e.top-n.top)/(a||1);t.animatingX=!!s,t.animatingY=!!c,T(t,"transform","translate3d("+s+"px,"+c+"px,0)"),function(t){t.offsetWidth}(t),T(t,"transition","transform "+i+"ms"+(this.options.easing?" "+this.options.easing:"")),T(t,"transform","translate3d(0,0,0)"),"number"==typeof t.animated&&clearTimeout(t.animated),t.animated=setTimeout((function(){T(t,"transition",""),T(t,"transform",""),t.animated=!1,t.animatingX=!1,t.animatingY=!1}),i)}}}))}function Ft(t,e,n,i,o,r,a,s){var c,d,u=t[B],f=u.options.onMove;return!window.CustomEvent||l||p?(c=document.createEvent("Event")).initEvent("move",!0,!0):c=new CustomEvent("move",{bubbles:!0,cancelable:!0}),c.to=e,c.from=t,c.dragged=n,c.draggedRect=i,c.related=o||e,c.relatedRect=r||O(e),c.willInsertAfter=s,c.originalEvent=a,t.dispatchEvent(c),f&&(d=f.call(u,c,a)),d}function jt(t){t.draggable=!1}function Bt(){It=!1}function Ut(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,i=0;n--;)i+=e.charCodeAt(n);return i.toString(36)}function Vt(t){return setTimeout(t,0)}function Yt(t){return clearTimeout(t)}Ht.prototype={constructor:Ht,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(ht=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,X):this.options.direction},_onTapStart:function(t){if(t.cancelable){var e=this,n=this.el,i=this.options,o=i.preventOnFilter,r=t.type,a=t.touches&&t.touches[0]||t.pointerType&&"touch"===t.pointerType&&t,s=(a||t).target,c=t.target.shadowRoot&&(t.path&&t.path[0]||t.composedPath&&t.composedPath()[0])||s,l=i.filter;if(function(t){St.length=0;for(var e=t.getElementsByTagName("input"),n=e.length;n--;){var i=e[n];i.checked&&St.push(i)}}(n),!X&&!(/mousedown|pointerdown/.test(r)&&0!==t.button||i.disabled||c.isContentEditable||(s=x(s,i.draggable,n,!1))&&s.animated||J===s)){if(nt=M(s),ot=M(s,i.draggable),"function"==typeof l){if(l.call(this,t,s,this))return G({sortable:e,rootEl:c,name:"filter",targetEl:s,toEl:n,fromEl:n}),$("filter",e,{evt:t}),void(o&&t.cancelable&&t.preventDefault())}else if(l&&(l=l.split(",").some((function(i){if(i=x(c,i.trim(),n,!1))return G({sortable:e,rootEl:i,name:"filter",targetEl:s,fromEl:n,toEl:n}),$("filter",e,{evt:t}),!0}))))return void(o&&t.cancelable&&t.preventDefault());i.handle&&!x(c,i.handle,n,!1)||this._prepareDragStart(t,a,s)}}},_prepareDragStart:function(t,e,n){var i,o=this,r=o.el,a=o.options,s=r.ownerDocument;if(n&&!X&&n.parentNode===r){var c=O(n);if(Z=r,q=(X=n).parentNode,Q=X.nextSibling,J=n,at=a.group,Ht.dragged=X,ct={target:X,clientX:(e||t).clientX,clientY:(e||t).clientY},ut=ct.clientX-c.left,ft=ct.clientY-c.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,X.style["will-change"]="all",i=function(){$("delayEnded",o,{evt:t}),Ht.eventCanceled?o._onDrop():(o._disableDelayedDragEvents(),!d&&o.nativeDraggable&&(X.draggable=!0),o._triggerDragStart(t,e),G({sortable:o,name:"choose",originalEvent:t}),E(X,a.chosenClass,!0))},a.ignore.split(",").forEach((function(t){S(X,t.trim(),jt)})),b(s,"dragover",zt),b(s,"mousemove",zt),b(s,"touchmove",zt),b(s,"mouseup",o._onDrop),b(s,"touchend",o._onDrop),b(s,"touchcancel",o._onDrop),d&&this.nativeDraggable&&(this.options.touchStartThreshold=4,X.draggable=!0),$("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(p||l))i();else{if(Ht.eventCanceled)return void this._onDrop();b(s,"mouseup",o._disableDelayedDrag),b(s,"touchend",o._disableDelayedDrag),b(s,"touchcancel",o._disableDelayedDrag),b(s,"mousemove",o._delayedDragTouchMoveHandler),b(s,"touchmove",o._delayedDragTouchMoveHandler),a.supportPointer&&b(s,"pointermove",o._delayedDragTouchMoveHandler),o._dragStartTimer=setTimeout(i,a.delay)}}},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;Math.max(Math.abs(e.clientX-this._lastX),Math.abs(e.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){X&&jt(X),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;g(t,"mouseup",this._disableDelayedDrag),g(t,"touchend",this._disableDelayedDrag),g(t,"touchcancel",this._disableDelayedDrag),g(t,"mousemove",this._delayedDragTouchMoveHandler),g(t,"touchmove",this._delayedDragTouchMoveHandler),g(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?b(document,"pointermove",this._onTouchMove):b(document,e?"touchmove":"mousemove",this._onTouchMove):(b(X,"dragend",this),b(Z,"dragstart",this._onDragStart));try{document.selection?Vt((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(yt=!1,Z&&X){$("dragStarted",this,{evt:e}),this.nativeDraggable&&b(document,"dragover",Pt);var n=this.options;!t&&E(X,n.dragClass,!1),E(X,n.ghostClass,!0),Ht.active=this,t&&this._appendGhost(),G({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(lt){this._lastX=lt.clientX,this._lastY=lt.clientY,Rt();for(var t=document.elementFromPoint(lt.clientX,lt.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(lt.clientX,lt.clientY))!==e;)e=t;if(X.parentNode[B]._isOutsideThisEl(t),e)do{if(e[B]&&e[B]._onDragOver({clientX:lt.clientX,clientY:lt.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break;t=e}while(e=e.parentNode);Dt()}},_onTouchMove:function(t){if(ct){var e=this.options,n=e.fallbackTolerance,i=e.fallbackOffset,o=t.touches?t.touches[0]:t,r=K&&I(K,!0),a=K&&r&&r.a,s=K&&r&&r.d,c=Ot&&vt&&N(vt),l=(o.clientX-ct.clientX+i.x)/(a||1)+(c?c[0]-Tt[0]:0)/(a||1),p=(o.clientY-ct.clientY+i.y)/(s||1)+(c?c[1]-Tt[1]:0)/(s||1);if(!Ht.active&&!yt){if(n&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))i.right+10||t.clientX<=i.right&&t.clientY>i.bottom&&t.clientX>=i.left:t.clientX>i.right&&t.clientY>i.top||t.clientX<=i.right&&t.clientY>i.bottom+10}(t,o,this)&&!b.animated){if(b===X)return F(!1);if(b&&r===t.target&&(s=b),s&&(n=O(s)),!1!==Ft(Z,r,X,e,s,n,t,!!s))return H(),r.appendChild(X),q=r,j(),F(!0)}else if(s.parentNode===r){n=O(s);var g,v,y,w=X.parentNode!==r,k=!function(t,e,n){var i=n?t.left:t.top,o=n?t.right:t.bottom,r=n?t.width:t.height,a=n?e.left:e.top,s=n?e.right:e.bottom,c=n?e.width:e.height;return i===a||o===s||i+r/2===a+c/2}(X.animated&&X.toRect||e,s.animated&&s.toRect||n,o),I=o?"top":"left",S=A(s,"top","top")||A(X,"top","top"),C=S?S.scrollTop:void 0;if(ht!==s&&(v=n[I],kt=!1,Et=!k&&c.invertSwap||w),g=function(t,e,n,i,o,r,a,s){var c=i?t.clientY:t.clientX,l=i?n.height:n.width,p=i?n.top:n.left,d=i?n.bottom:n.right,u=!1;if(!a)if(s&>p+l*r/2:cd-gt)return-bt}else if(c>p+l*(1-o)/2&&cd-l*r/2)?c>p+l/2?1:-1:0}(t,s,n,o,k?1:c.swapThreshold,null==c.invertedSwapThreshold?c.swapThreshold:c.invertedSwapThreshold,Et,ht===s),0!==g){var _=M(X);do{_-=g,y=q.children[_]}while(y&&("none"===T(y,"display")||y===K))}if(0===g||y===s)return F(!1);ht=s,bt=g;var N=s.nextElementSibling,R=!1,D=Ft(Z,r,X,e,s,n,t,R=1===g);if(!1!==D)return 1!==D&&-1!==D||(R=1===D),It=!0,setTimeout(Bt,30),H(),R&&!N?r.appendChild(X):s.parentNode.insertBefore(X,R?N:s),S&&P(S,0,C-S.scrollTop),q=X.parentNode,void 0===v||Et||(gt=Math.abs(v-O(s)[I])),j(),F(!0)}if(r.contains(X))return F(!1)}return!1}function z(c,l){$(c,m,a({evt:t,isOwner:d,axis:o?"vertical":"horizontal",revert:i,dragRect:e,targetRect:n,canSort:u,fromSortable:f,target:s,completed:F,onMove:function(n,i){return Ft(Z,r,X,e,n,O(n),t,i)},changed:j},l))}function H(){z("dragOverAnimationCapture"),m.captureAnimationState(),m!==f&&f.captureAnimationState()}function F(e){return z("dragOverCompleted",{insertion:e}),e&&(d?p._hideClone():p._showClone(m),m!==f&&(E(X,st?st.options.ghostClass:p.options.ghostClass,!1),E(X,c.ghostClass,!0)),st!==m&&m!==Ht.active?st=m:m===Ht.active&&st&&(st=null),f===m&&(m._ignoreWhileAnimating=s),m.animateAll((function(){z("dragOverAnimationComplete"),m._ignoreWhileAnimating=null})),m!==f&&(f.animateAll(),f._ignoreWhileAnimating=null)),(s===X&&!X.animated||s===r&&!s.animated)&&(ht=null),c.dragoverBubble||t.rootEl||s===document||(X.parentNode[B]._isOutsideThisEl(t.target),!e&&zt(t)),!c.dragoverBubble&&t.stopPropagation&&t.stopPropagation(),h=!0}function j(){it=M(X),rt=M(X,c.draggable),G({sortable:m,name:"change",toEl:r,newIndex:it,newDraggableIndex:rt,originalEvent:t})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){g(document,"mousemove",this._onTouchMove),g(document,"touchmove",this._onTouchMove),g(document,"pointermove",this._onTouchMove),g(document,"dragover",zt),g(document,"mousemove",zt),g(document,"touchmove",zt)},_offUpEvents:function(){var t=this.el.ownerDocument;g(t,"mouseup",this._onDrop),g(t,"touchend",this._onDrop),g(t,"pointerup",this._onDrop),g(t,"touchcancel",this._onDrop),g(document,"selectstart",this)},_onDrop:function(t){var e=this.el,n=this.options;it=M(X),rt=M(X,n.draggable),$("drop",this,{evt:t}),q=X&&X.parentNode,it=M(X),rt=M(X,n.draggable),Ht.eventCanceled||(yt=!1,Et=!1,kt=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),Yt(this.cloneId),Yt(this._dragStartId),this.nativeDraggable&&(g(document,"drop",this),g(e,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),u&&T(document.body,"user-select",""),T(X,"transform",""),t&&(mt&&(t.cancelable&&t.preventDefault(),!n.dropBubble&&t.stopPropagation()),K&&K.parentNode&&K.parentNode.removeChild(K),(Z===q||st&&"clone"!==st.lastPutMode)&&tt&&tt.parentNode&&tt.parentNode.removeChild(tt),X&&(this.nativeDraggable&&g(X,"dragend",this),jt(X),X.style["will-change"]="",mt&&!yt&&E(X,st?st.options.ghostClass:this.options.ghostClass,!1),E(X,this.options.chosenClass,!1),G({sortable:this,name:"unchoose",toEl:q,newIndex:null,newDraggableIndex:null,originalEvent:t}),Z!==q?(it>=0&&(G({rootEl:q,name:"add",toEl:q,fromEl:Z,originalEvent:t}),G({sortable:this,name:"remove",toEl:q,originalEvent:t}),G({rootEl:q,name:"sort",toEl:q,fromEl:Z,originalEvent:t}),G({sortable:this,name:"sort",toEl:q,originalEvent:t})),st&&st.save()):it!==nt&&it>=0&&(G({sortable:this,name:"update",toEl:q,originalEvent:t}),G({sortable:this,name:"sort",toEl:q,originalEvent:t})),Ht.active&&(null!=it&&-1!==it||(it=nt,rt=ot),G({sortable:this,name:"end",toEl:q,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){$("nulling",this),Z=X=q=K=Q=tt=J=et=ct=lt=mt=it=rt=nt=ot=ht=bt=st=at=Ht.dragged=Ht.ghost=Ht.clone=Ht.active=null,St.forEach((function(t){t.checked=!0})),St.length=pt=dt=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":X&&(this._onDragOver(t),function(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move"),t.cancelable&&t.preventDefault()}(t));break;case"selectstart":t.preventDefault()}},toArray:function(){for(var t,e=[],n=this.el.children,i=0,o=n.length,r=this.options;i1&&(ue.forEach((function(t){i.addAnimationState({target:t,rect:he?O(t):o}),j(t),t.fromRect=o,e.removeAnimationState(t)})),he=!1,function(t,e){ue.forEach((function(n,i){var o=e.children[n.sortableIndex+(t?Number(i):0)];o?e.insertBefore(n,o):e.appendChild(n)}))}(!this.options.removeCloneOnHide,n))},dragOverCompleted:function(t){var e=t.sortable,n=t.isOwner,i=t.insertion,o=t.activeSortable,r=t.parentEl,a=t.putSortable,s=this.options;if(i){if(n&&o._hideClone(),me=!1,s.animation&&ue.length>1&&(he||!n&&!o.options.sort&&!a)){var c=O(le,!1,!0,!0);ue.forEach((function(t){t!==le&&(F(t,c),r.appendChild(t))})),he=!0}if(!n)if(he||ye(),ue.length>1){var l=de;o._showClone(e),o.options.animation&&!de&&l&&fe.forEach((function(t){o.addAnimationState({target:t,rect:pe}),t.fromRect=pe,t.thisAnimationDuration=null}))}else o._showClone(e)}},dragOverAnimationCapture:function(t){var e=t.dragRect,n=t.isOwner,i=t.activeSortable;if(ue.forEach((function(t){t.thisAnimationDuration=null})),i.options.animation&&!n&&i.multiDrag.isMultiDrag){pe=r({},e);var o=I(le,!0);pe.top-=o.f,pe.left-=o.e}},dragOverAnimationComplete:function(){he&&(he=!1,ye())},drop:function(t){var e=t.originalEvent,n=t.rootEl,i=t.parentEl,o=t.sortable,r=t.dispatchSortableEvent,a=t.oldIndex,s=t.putSortable,c=s||this.sortable;if(e){var l=this.options,p=i.children;if(!be)if(l.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),E(le,l.selectedClass,!~ue.indexOf(le)),~ue.indexOf(le))ue.splice(ue.indexOf(le),1),se=null,W({sortable:o,rootEl:n,name:"deselect",targetEl:le,originalEvt:e});else{if(ue.push(le),W({sortable:o,rootEl:n,name:"select",targetEl:le,originalEvt:e}),e.shiftKey&&se&&o.el.contains(se)){var d,u,f=M(se),m=M(le);if(~f&&~m&&f!==m)for(m>f?(u=f,d=m):(u=m,d=f+1);u1){var h=O(le),b=M(le,":not(."+this.options.selectedClass+")");if(!me&&l.animation&&(le.thisAnimationDuration=null),c.captureAnimationState(),!me&&(l.animation&&(le.fromRect=h,ue.forEach((function(t){if(t.thisAnimationDuration=null,t!==le){var e=he?O(t):h;t.fromRect=e,c.addAnimationState({target:t,rect:e})}}))),ye(),ue.forEach((function(t){p[b]?i.insertBefore(t,p[b]):i.appendChild(t),b++})),a===M(le))){var g=!1;ue.forEach((function(t){t.sortableIndex===M(t)||(g=!0)})),g&&r("update")}ue.forEach((function(t){j(t)})),c.animateAll()}ce=c}(n===i||s&&"clone"!==s.lastPutMode)&&fe.forEach((function(t){t.parentNode&&t.parentNode.removeChild(t)}))}},nullingGlobal:function(){this.isMultiDrag=be=!1,fe.length=0},destroyGlobal:function(){this._deselectMultiDrag(),g(document,"pointerup",this._deselectMultiDrag),g(document,"mouseup",this._deselectMultiDrag),g(document,"touchend",this._deselectMultiDrag),g(document,"keydown",this._checkKeyDown),g(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(t){if(!(void 0!==be&&be||ce!==this.sortable||t&&x(t.target,this.options.draggable,this.sortable.el,!1)||t&&0!==t.button))for(;ue.length;){var e=ue[0];E(e,this.options.selectedClass,!1),ue.shift(),W({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:e,originalEvt:t})}},_checkKeyDown:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},r(t,{pluginName:"multiDrag",utils:{select:function(t){var e=t.parentNode[B];e&&e.options.multiDrag&&!~ue.indexOf(t)&&(ce&&ce!==e&&(ce.multiDrag._deselectMultiDrag(),ce=e),E(t,e.options.selectedClass,!0),ue.push(t))},deselect:function(t){var e=t.parentNode[B],n=ue.indexOf(t);e&&e.options.multiDrag&&~n&&(E(t,e.options.selectedClass,!1),ue.splice(n,1))}},eventProperties:function(){var t,e=this,n=[],i=[];return ue.forEach((function(t){var o;n.push({multiDragElement:t,index:t.sortableIndex}),o=he&&t!==le?-1:he?M(t,":not(."+e.options.selectedClass+")"):M(t),i.push({multiDragElement:t,index:o})})),{items:(t=ue,function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e1&&(t=t.charAt(0).toUpperCase()+t.substr(1)),t}}})}function ve(t,e){fe.forEach((function(n,i){var o=e.children[n.sortableIndex+(t?Number(i):0)];o?e.insertBefore(n,o):e.appendChild(n)}))}function ye(){ue.forEach((function(t){t!==le&&t.parentNode&&t.parentNode.removeChild(t)}))}Ht.mount(new function(){function t(){for(var t in this.defaults={scroll:!0,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0},this)"_"===t.charAt(0)&&"function"==typeof this[t]&&(this[t]=this[t].bind(this))}return t.prototype={dragStarted:function(t){var e=t.originalEvent;this.sortable.nativeDraggable?b(document,"dragover",this._handleAutoScroll):this.options.supportPointer?b(document,"pointermove",this._handleFallbackAutoScroll):e.touches?b(document,"touchmove",this._handleFallbackAutoScroll):b(document,"mousemove",this._handleFallbackAutoScroll)},dragOverCompleted:function(t){var e=t.originalEvent;this.options.dragOverBubble||e.rootEl||this._handleAutoScroll(e)},drop:function(){this.sortable.nativeDraggable?g(document,"dragover",this._handleAutoScroll):(g(document,"pointermove",this._handleFallbackAutoScroll),g(document,"touchmove",this._handleFallbackAutoScroll),g(document,"mousemove",this._handleFallbackAutoScroll)),te(),Jt(),clearTimeout(w),w=void 0},nulling:function(){qt=$t=Wt=Qt=Kt=Gt=Xt=null,Zt.length=0},_handleFallbackAutoScroll:function(t){this._handleAutoScroll(t,!0)},_handleAutoScroll:function(t,e){var n=this,i=(t.touches?t.touches[0]:t).clientX,o=(t.touches?t.touches[0]:t).clientY,r=document.elementFromPoint(i,o);if(qt=t,e||p||l||u){ne(t,this.options,r,e);var a=R(r,!0);!Qt||Kt&&i===Gt&&o===Xt||(Kt&&te(),Kt=setInterval((function(){var r=R(document.elementFromPoint(i,o),!0);r!==a&&(a=r,Jt()),ne(t,n.options,r,e)}),10),Gt=i,Xt=o)}else{if(!this.options.bubbleScroll||R(r,!0)===C())return void Jt();ne(t,this.options,R(r,!1),!1)}}},r(t,{pluginName:"scroll",initializeByDefault:!0})}),Ht.mount(re,oe);const xe=Ht},3379:t=>{"use strict";var e=[];function n(t){for(var n=-1,i=0;i{"use strict";var e={};t.exports=function(t,n){var i=function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}e[t]=n}return e[t]}(t);if(!i)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");i.appendChild(n)}},9216:t=>{"use strict";t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e,t.options),e}},3565:(t,e,n)=>{"use strict";t.exports=function(t){var e=n.nc;e&&t.setAttribute("nonce",e)}},7795:t=>{"use strict";t.exports=function(t){var e=t.insertStyleElement(t);return{update:function(n){!function(t,e,n){var i="";n.supports&&(i+="@supports (".concat(n.supports,") {")),n.media&&(i+="@media ".concat(n.media," {"));var o=void 0!==n.layer;o&&(i+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),i+=n.css,o&&(i+="}"),n.media&&(i+="}"),n.supports&&(i+="}");var r=n.sourceMap;r&&"undefined"!=typeof btoa&&(i+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),e.styleTagTransform(i,t,e.options)}(e,t,n)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},4589:t=>{"use strict";t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}},9980:function(t,e,n){var i;"undefined"!=typeof self&&self,i=function(t){return function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s="fb15")}({"01f9":function(t,e,n){"use strict";var i=n("2d00"),o=n("5ca1"),r=n("2aba"),a=n("32e9"),s=n("84f2"),c=n("41a0"),l=n("7f20"),p=n("38fd"),d=n("2b4c")("iterator"),u=!([].keys&&"next"in[].keys()),f="keys",m="values",h=function(){return this};t.exports=function(t,e,n,b,g,v,y){c(n,e,b);var x,w,k,E=function(t){if(!u&&t in C)return C[t];switch(t){case f:case m:return function(){return new n(this,t)}}return function(){return new n(this,t)}},T=e+" Iterator",I=g==m,S=!1,C=t.prototype,O=C[d]||C["@@iterator"]||g&&C[g],A=O||E(g),_=g?I?E("entries"):A:void 0,L="Array"==e&&C.entries||O;if(L&&(k=p(L.call(new t)))!==Object.prototype&&k.next&&(l(k,T,!0),i||"function"==typeof k[d]||a(k,d,h)),I&&O&&O.name!==m&&(S=!0,A=function(){return O.call(this)}),i&&!y||!u&&!S&&C[d]||a(C,d,A),s[e]=A,s[T]=h,g)if(x={values:I?A:E(m),keys:v?A:E(f),entries:_},y)for(w in x)w in C||r(C,w,x[w]);else o(o.P+o.F*(u||S),e,x);return x}},"02f4":function(t,e,n){var i=n("4588"),o=n("be13");t.exports=function(t){return function(e,n){var r,a,s=String(o(e)),c=i(n),l=s.length;return c<0||c>=l?t?"":void 0:(r=s.charCodeAt(c))<55296||r>56319||c+1===l||(a=s.charCodeAt(c+1))<56320||a>57343?t?s.charAt(c):r:t?s.slice(c,c+2):a-56320+(r-55296<<10)+65536}}},"0390":function(t,e,n){"use strict";var i=n("02f4")(!0);t.exports=function(t,e,n){return e+(n?i(t,e).length:1)}},"0bfb":function(t,e,n){"use strict";var i=n("cb7c");t.exports=function(){var t=i(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},"0d58":function(t,e,n){var i=n("ce10"),o=n("e11e");t.exports=Object.keys||function(t){return i(t,o)}},1495:function(t,e,n){var i=n("86cc"),o=n("cb7c"),r=n("0d58");t.exports=n("9e1e")?Object.defineProperties:function(t,e){o(t);for(var n,a=r(e),s=a.length,c=0;s>c;)i.f(t,n=a[c++],e[n]);return t}},"214f":function(t,e,n){"use strict";n("b0c5");var i=n("2aba"),o=n("32e9"),r=n("79e5"),a=n("be13"),s=n("2b4c"),c=n("520a"),l=s("species"),p=!r((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),d=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();t.exports=function(t,e,n){var u=s(t),f=!r((function(){var e={};return e[u]=function(){return 7},7!=""[t](e)})),m=f?!r((function(){var e=!1,n=/a/;return n.exec=function(){return e=!0,null},"split"===t&&(n.constructor={},n.constructor[l]=function(){return n}),n[u](""),!e})):void 0;if(!f||!m||"replace"===t&&!p||"split"===t&&!d){var h=/./[u],b=n(a,u,""[t],(function(t,e,n,i,o){return e.exec===c?f&&!o?{done:!0,value:h.call(e,n,i)}:{done:!0,value:t.call(n,e,i)}:{done:!1}})),g=b[0],v=b[1];i(String.prototype,t,g),o(RegExp.prototype,u,2==e?function(t,e){return v.call(t,this,e)}:function(t){return v.call(t,this)})}}},"230e":function(t,e,n){var i=n("d3f4"),o=n("7726").document,r=i(o)&&i(o.createElement);t.exports=function(t){return r?o.createElement(t):{}}},"23c6":function(t,e,n){var i=n("2d95"),o=n("2b4c")("toStringTag"),r="Arguments"==i(function(){return arguments}());t.exports=function(t){var e,n,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(t){}}(e=Object(t),o))?n:r?i(e):"Object"==(a=i(e))&&"function"==typeof e.callee?"Arguments":a}},2621:function(t,e){e.f=Object.getOwnPropertySymbols},"2aba":function(t,e,n){var i=n("7726"),o=n("32e9"),r=n("69a8"),a=n("ca5a")("src"),s=n("fa5b"),c="toString",l=(""+s).split(c);n("8378").inspectSource=function(t){return s.call(t)},(t.exports=function(t,e,n,s){var c="function"==typeof n;c&&(r(n,"name")||o(n,"name",e)),t[e]!==n&&(c&&(r(n,a)||o(n,a,t[e]?""+t[e]:l.join(String(e)))),t===i?t[e]=n:s?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,c,(function(){return"function"==typeof this&&this[a]||s.call(this)}))},"2aeb":function(t,e,n){var i=n("cb7c"),o=n("1495"),r=n("e11e"),a=n("613b")("IE_PROTO"),s=function(){},c="prototype",l=function(){var t,e=n("230e")("iframe"),i=r.length;for(e.style.display="none",n("fab2").appendChild(e),e.src="javascript:",(t=e.contentWindow.document).open(),t.write(" diff --git a/src/frontend/devops-pipeline/src/components/Outputs/OutputQrcode.vue b/src/frontend/devops-pipeline/src/components/Outputs/OutputQrcode.vue new file mode 100644 index 00000000000..c3f7ec893b8 --- /dev/null +++ b/src/frontend/devops-pipeline/src/components/Outputs/OutputQrcode.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/frontend/devops-pipeline/src/components/Outputs/index.vue b/src/frontend/devops-pipeline/src/components/Outputs/index.vue index d72d383f39f..00452745324 100644 --- a/src/frontend/devops-pipeline/src/components/Outputs/index.vue +++ b/src/frontend/devops-pipeline/src/components/Outputs/index.vue @@ -1,45 +1,72 @@
@@ -113,8 +138,8 @@ import Logo from '@/components/Logo' import CopyToCustomRepoDialog from '@/components/Outputs/CopyToCustomRepoDialog' import IframeReport from '@/components/Outputs/IframeReport' + import OutputQrcode from '@/components/Outputs/OutputQrcode' import ThirdPartyReport from '@/components/Outputs/ThirdPartyReport' - import qrcode from '@/components/devops/qrcode' import ExtMenu from '@/components/pipelineList/extMenu' import { extForFile, repoTypeMap, repoTypeNameMap } from '@/utils/pipelineConst' import { convertFileSize, convertTime } from '@/utils/util' @@ -125,12 +150,13 @@ Logo, ThirdPartyReport, IframeReport, - qrcode, ExtMenu, - CopyToCustomRepoDialog + CopyToCustomRepoDialog, + OutputQrcode }, data () { return { + keyWord: '', isCopyDialogShow: false, isCopying: false, currentTab: 'all', @@ -138,7 +164,8 @@ activeOutput: '', activeOutputDetail: null, hasPermission: false, - isLoading: false + isLoading: false, + asideCollpased: false } }, computed: { @@ -183,17 +210,19 @@ } ] : [] + let visibleOutputs = [ + ...this.outputs.filter((output) => !this.isThirdReport(output.reportType)), + ...thirdReportList + ] switch (this.currentTab) { case 'artifact': - return this.artifacts + visibleOutputs = this.artifacts + break case 'report': - return [...this.reports, ...thirdReportList] - default: - return [ - ...this.outputs.filter((output) => !this.isThirdReport(output.reportType)), - ...thirdReportList - ] + visibleOutputs = [...this.reports, ...thirdReportList] + break } + return visibleOutputs.filter(output => output.name.toLowerCase().includes(this.keyWord.toLowerCase())) }, isActiveThirdReport () { return this.isThirdReport(this.activeOutput?.reportType) @@ -301,12 +330,16 @@ 'requestExternalUrl', 'requestDownloadUrl' ]), + toggleCollapseAside () { + console.log(this.asideCollpased) + this.asideCollpased = !this.asideCollpased + }, async init () { const { projectId, pipelineId, buildNo: buildId } = this.$route.params try { this.isLoading = true - const [, res] = await Promise.all([ + const [hasPermission, res] = await Promise.all([ this.requestHasPermission(), this.requestOutputs({ projectId, @@ -325,7 +358,8 @@ ...item, id, icon, - isApp: ['ipafile', 'apkfile'].includes(icon) + isApp: ['ipafile', 'apkfile'].includes(icon), + downloadable: hasPermission && this.isArtifact(item.artifactoryType) && item.artifactoryType !== 'IMAGE' } }) } catch (err) { @@ -345,6 +379,7 @@ }) this.hasPermission = res + return res } catch (err) { const message = err.message ? err.message : err const theme = 'error' @@ -355,26 +390,6 @@ }) } }, - async getQrcodeUrl (params) { - try { - const external = await this.requestExternalUrl(params) - return external?.url - } catch (err) { - this.handleError(err, [ - { - actionId: this.$permissionActionMap.download, - resourceId: this.$permissionResourceMap.pipeline, - instanceId: [ - { - id: this.$route.params.pipelineId, - name: this.$route.params.pipelineId - } - ], - projectId: this.$route.params.projectId - } - ]) - } - }, async showDetail (output) { const { projectId } = this.$route.params try { @@ -382,12 +397,9 @@ const params = { projectId, type: output.artifactoryType, - path: `${output.fullPath}` + path: output.fullPath } - const [res, qrcodeUrl] = await Promise.all([ - this.requestFileInfo(params), - ...(output.isApp ? [this.getQrcodeUrl(params)] : []) - ]) + const res = await this.requestFileInfo(params) this.activeOutputDetail = { ...output, ...res, @@ -395,8 +407,7 @@ size: res.size > 0 ? convertFileSize(res.size, 'B') : '--', createdTime: convertTime(res.createdTime * 1000), modifiedTime: convertTime(res.modifiedTime * 1000), - icon: extForFile(res.name), - qrcodeUrl + icon: extForFile(res.name) } this.isLoading = false } catch (err) { @@ -429,6 +440,19 @@ }, isThirdReport (reportType) { return ['THIRDPARTY'].includes(reportType) + }, + async downloadArtifact (output) { + try { + const res = await this.requestDownloadUrl({ + projectId: this.$route.params.projectId, + artifactoryType: output.artifactoryType, + path: output.fullPath + }) + + window.open(res.url2, '_blank') + } catch (error) { + console.error(error) + } } } } @@ -453,12 +477,46 @@ } } .pipeline-exec-outputs-aside { - width: 295px; + position: relative; + width: 30vw; flex-shrink: 0; padding: 16px 11px; border-right: 1px solid #dcdee5; display: flex; flex-direction: column; + transition: all 0.3s; + + &.pipeline-exec-outputs-aside-collapse { + width: 0; + padding: 0; + .pipeline-exec-output-classify-tab { + display: none; + } + .aside-collapse-icon { + transform: rotate(180deg); + } + } + .collapse-handler { + position: absolute; + right: -16px; + top: 50%; + display: flex; + cursor: pointer; + align-items: center; + width: 16px; + height: 100px; + background: #DCDEE5; + border-radius: 0 4px 4px 0; + transform: translateY(-50px); + color: white; + font-size: 12px; + justify-content: center; + font-weight: 700; + z-index: 2; + .aside-collapse-icon { + transition: all 0.3s; + } + } .pipeline-exec-output-classify-tab { display: flex; align-items: center; @@ -493,6 +551,9 @@ } } } + .pipeline-exec-outputs-filter-input { + margin: 12px 0; + } .pipeline-exec-outputs-filter { position: relative; margin: 16px 0 21px 0; @@ -525,33 +586,44 @@ border-radius: 2px; font-size: 12px; margin-bottom: 10px; - > .devops-icon { - display: inline-block; + > .devops-icon, + .output-hover-icon { + display: inline-flex; font-size: 16px; margin-right: 4px; flex-shrink: 0; } + .output-hover-icon { + font-size: 12px; + display: none; + } > span { flex: 1; @include ellipsis(); } &.active, &:hover { - color: $primaryColor; - background: #f5f7fa; + color: $primaryColor; + background: #f5f7fa; + .output-hover-icon { + display: inline-flex; + } } } } } .pipeline-exec-outputs-section { flex: 1; - overflow: auto; + display: flex; + flex-direction: column; + overflow: hidden; .pipeline-exec-output-header { display: flex; align-items: center; height: 48px; background: #fafbfd; padding: 0 24px; + flex-shrink: 0; &-name { display: flex; align-items: center; @@ -571,6 +643,10 @@ margin-left: auto; } } + .pipeline-exec-output-artifact { + flex: 1; + overflow: auto; + } .pipeline-exec-output-block { padding: 16px 24px; .pipeline-exec-output-block-title { diff --git a/src/frontend/devops-pipeline/src/components/PipelineHeader/BuildNumSwitcher.vue b/src/frontend/devops-pipeline/src/components/PipelineHeader/BuildNumSwitcher.vue index c4c446e4b41..61d9e983c89 100644 --- a/src/frontend/devops-pipeline/src/components/PipelineHeader/BuildNumSwitcher.vue +++ b/src/frontend/devops-pipeline/src/components/PipelineHeader/BuildNumSwitcher.vue @@ -58,6 +58,7 @@ this.$router.push({ name: 'pipelinesDetail', params: { + type: 'executeDetail', ...this.$route.params, buildNo: response.data.id, executeCount: undefined diff --git a/src/frontend/devops-pipeline/src/components/PipelineHeader/DetailHeader.vue b/src/frontend/devops-pipeline/src/components/PipelineHeader/DetailHeader.vue index 0a4ba54a3e9..9cc969cb10f 100644 --- a/src/frontend/devops-pipeline/src/components/PipelineHeader/DetailHeader.vue +++ b/src/frontend/devops-pipeline/src/components/PipelineHeader/DetailHeader.vue @@ -8,7 +8,7 @@